]>
Commit | Line | Data |
---|---|---|
3ed497fc | 1 | /* |
fba6bd1d | 2 | * Copyright (c) 2008, 2009, 2010, 2012, 2013 Nicira, Inc. |
3ed497fc BP |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include "reconnect.h" | |
19 | ||
3ed497fc BP |
20 | #include <stdlib.h> |
21 | ||
22 | #include "poll-loop.h" | |
ee89ea7b | 23 | #include "util.h" |
e6211adc | 24 | #include "openvswitch/vlog.h" |
3ed497fc | 25 | |
d98e6007 | 26 | VLOG_DEFINE_THIS_MODULE(reconnect); |
5136ce49 | 27 | |
3ed497fc BP |
28 | #define STATES \ |
29 | STATE(VOID, 1 << 0) \ | |
30 | STATE(BACKOFF, 1 << 1) \ | |
c36cf65e | 31 | STATE(CONNECTING, 1 << 3) \ |
3603f8da BP |
32 | STATE(ACTIVE, 1 << 4) \ |
33 | STATE(IDLE, 1 << 5) \ | |
19df7f51 BP |
34 | STATE(RECONNECT, 1 << 6) \ |
35 | STATE(LISTENING, 1 << 7) | |
3ed497fc BP |
36 | enum state { |
37 | #define STATE(NAME, VALUE) S_##NAME = VALUE, | |
38 | STATES | |
39 | #undef STATE | |
40 | }; | |
41 | ||
42 | static bool | |
43 | is_connected_state(enum state state) | |
44 | { | |
45 | return (state & (S_ACTIVE | S_IDLE)) != 0; | |
46 | } | |
47 | ||
48 | struct reconnect { | |
49 | /* Configuration. */ | |
50 | char *name; | |
51 | int min_backoff; | |
52 | int max_backoff; | |
53 | int probe_interval; | |
19df7f51 | 54 | bool passive; |
36a7b32d | 55 | enum vlog_level info; /* Used for informational messages. */ |
3ed497fc BP |
56 | |
57 | /* State. */ | |
58 | enum state state; | |
59 | long long int state_entered; | |
60 | int backoff; | |
a6f639f8 | 61 | long long int last_activity; |
3ed497fc | 62 | long long int last_connected; |
eba18f00 | 63 | long long int last_disconnected; |
a85c0bbc | 64 | unsigned int max_tries; |
3ed497fc BP |
65 | |
66 | /* These values are simply for statistics reporting, not otherwise used | |
67 | * directly by anything internal. */ | |
68 | long long int creation_time; | |
69 | unsigned int n_attempted_connections, n_successful_connections; | |
70 | unsigned int total_connected_duration; | |
71 | unsigned int seqno; | |
72 | }; | |
73 | ||
74 | static void reconnect_transition__(struct reconnect *, long long int now, | |
75 | enum state state); | |
76 | static long long int reconnect_deadline__(const struct reconnect *); | |
a85c0bbc | 77 | static bool reconnect_may_retry(struct reconnect *); |
3ed497fc BP |
78 | |
79 | static const char * | |
80 | reconnect_state_name__(enum state state) | |
81 | { | |
82 | switch (state) { | |
83 | #define STATE(NAME, VALUE) case S_##NAME: return #NAME; | |
84 | STATES | |
85 | #undef STATE | |
86 | } | |
87 | return "***ERROR***"; | |
88 | } | |
89 | ||
90 | /* Creates and returns a new reconnect FSM with default settings. The FSM is | |
91 | * initially disabled. The caller will likely want to call reconnect_enable() | |
92 | * and reconnect_set_name() on the returned object. */ | |
93 | struct reconnect * | |
94 | reconnect_create(long long int now) | |
95 | { | |
96 | struct reconnect *fsm = xzalloc(sizeof *fsm); | |
97 | ||
98 | fsm->name = xstrdup("void"); | |
f71fb704 BP |
99 | fsm->min_backoff = RECONNECT_DEFAULT_MIN_BACKOFF; |
100 | fsm->max_backoff = RECONNECT_DEFAULT_MAX_BACKOFF; | |
101 | fsm->probe_interval = RECONNECT_DEFAULT_PROBE_INTERVAL; | |
19df7f51 | 102 | fsm->passive = false; |
36a7b32d | 103 | fsm->info = VLL_INFO; |
3ed497fc BP |
104 | |
105 | fsm->state = S_VOID; | |
106 | fsm->state_entered = now; | |
107 | fsm->backoff = 0; | |
a6f639f8 | 108 | fsm->last_activity = now; |
5eda645e AE |
109 | fsm->last_connected = LLONG_MAX; |
110 | fsm->last_disconnected = LLONG_MAX; | |
a85c0bbc | 111 | fsm->max_tries = UINT_MAX; |
3ed497fc BP |
112 | fsm->creation_time = now; |
113 | ||
114 | return fsm; | |
115 | } | |
116 | ||
117 | /* Frees 'fsm'. */ | |
118 | void | |
119 | reconnect_destroy(struct reconnect *fsm) | |
120 | { | |
121 | if (fsm) { | |
122 | free(fsm->name); | |
123 | free(fsm); | |
124 | } | |
125 | } | |
126 | ||
36a7b32d BP |
127 | /* If 'quiet' is true, 'fsm' will log informational messages at level VLL_DBG, |
128 | * by default keeping them out of log files. This is appropriate if the | |
129 | * connection is one that is expected to be short-lived, so that the log | |
130 | * messages are merely distracting. | |
131 | * | |
132 | * If 'quiet' is false, 'fsm' logs informational messages at level VLL_INFO. | |
133 | * This is the default. | |
134 | * | |
135 | * This setting has no effect on the log level of debugging, warning, or error | |
136 | * messages. */ | |
137 | void | |
138 | reconnect_set_quiet(struct reconnect *fsm, bool quiet) | |
139 | { | |
140 | fsm->info = quiet ? VLL_DBG : VLL_INFO; | |
141 | } | |
142 | ||
3ed497fc BP |
143 | /* Returns 'fsm''s name. */ |
144 | const char * | |
145 | reconnect_get_name(const struct reconnect *fsm) | |
146 | { | |
147 | return fsm->name; | |
148 | } | |
149 | ||
150 | /* Sets 'fsm''s name to 'name'. If 'name' is null, then "void" is used | |
151 | * instead. | |
152 | * | |
153 | * The name set for 'fsm' is used in log messages. */ | |
154 | void | |
155 | reconnect_set_name(struct reconnect *fsm, const char *name) | |
156 | { | |
157 | free(fsm->name); | |
158 | fsm->name = xstrdup(name ? name : "void"); | |
159 | } | |
160 | ||
161 | /* Return the minimum number of milliseconds to back off between consecutive | |
f71fb704 | 162 | * connection attempts. The default is RECONNECT_DEFAULT_MIN_BACKOFF. */ |
3ed497fc BP |
163 | int |
164 | reconnect_get_min_backoff(const struct reconnect *fsm) | |
165 | { | |
166 | return fsm->min_backoff; | |
167 | } | |
168 | ||
169 | /* Return the maximum number of milliseconds to back off between consecutive | |
f71fb704 | 170 | * connection attempts. The default is RECONNECT_DEFAULT_MAX_BACKOFF. */ |
3ed497fc BP |
171 | int |
172 | reconnect_get_max_backoff(const struct reconnect *fsm) | |
173 | { | |
174 | return fsm->max_backoff; | |
175 | } | |
176 | ||
177 | /* Returns the "probe interval" for 'fsm' in milliseconds. If this is zero, it | |
178 | * disables the connection keepalive feature. If it is nonzero, then if the | |
a6f639f8 | 179 | * interval passes while 'fsm' is connected and without reconnect_activity() |
3ed497fc | 180 | * being called for 'fsm', reconnect_run() returns RECONNECT_PROBE. If the |
a6f639f8 | 181 | * interval passes again without reconnect_activity() being called, |
3ed497fc BP |
182 | * reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'. */ |
183 | int | |
184 | reconnect_get_probe_interval(const struct reconnect *fsm) | |
185 | { | |
186 | return fsm->probe_interval; | |
187 | } | |
188 | ||
a85c0bbc BP |
189 | /* Limits the maximum number of times that 'fsm' will ask the client to try to |
190 | * reconnect to 'max_tries'. UINT_MAX (the default) means an unlimited number | |
191 | * of tries. | |
192 | * | |
193 | * After the number of tries has expired, the 'fsm' will disable itself | |
194 | * instead of backing off and retrying. */ | |
195 | void | |
196 | reconnect_set_max_tries(struct reconnect *fsm, unsigned int max_tries) | |
197 | { | |
198 | fsm->max_tries = max_tries; | |
199 | } | |
200 | ||
201 | /* Returns the current remaining number of connection attempts, UINT_MAX if | |
202 | * the number is unlimited. */ | |
203 | unsigned int | |
204 | reconnect_get_max_tries(struct reconnect *fsm) | |
205 | { | |
206 | return fsm->max_tries; | |
207 | } | |
208 | ||
3ed497fc BP |
209 | /* Configures the backoff parameters for 'fsm'. 'min_backoff' is the minimum |
210 | * number of milliseconds, and 'max_backoff' is the maximum, between connection | |
fba6bd1d BP |
211 | * attempts. The current backoff is also the duration that 'fsm' is willing to |
212 | * wait for a given connection to succeed or fail. | |
3ed497fc BP |
213 | * |
214 | * 'min_backoff' must be at least 1000, and 'max_backoff' must be greater than | |
f71fb704 BP |
215 | * or equal to 'min_backoff'. |
216 | * | |
217 | * Pass 0 for 'min_backoff' or 'max_backoff' or both to use the defaults. */ | |
3ed497fc BP |
218 | void |
219 | reconnect_set_backoff(struct reconnect *fsm, int min_backoff, int max_backoff) | |
220 | { | |
221 | fsm->min_backoff = MAX(min_backoff, 1000); | |
f71fb704 BP |
222 | fsm->max_backoff = (max_backoff |
223 | ? MAX(max_backoff, 1000) | |
224 | : RECONNECT_DEFAULT_MAX_BACKOFF); | |
3ed497fc BP |
225 | if (fsm->min_backoff > fsm->max_backoff) { |
226 | fsm->max_backoff = fsm->min_backoff; | |
227 | } | |
228 | ||
229 | if (fsm->state == S_BACKOFF && fsm->backoff > max_backoff) { | |
230 | fsm->backoff = max_backoff; | |
231 | } | |
232 | } | |
233 | ||
234 | /* Sets the "probe interval" for 'fsm' to 'probe_interval', in milliseconds. | |
235 | * If this is zero, it disables the connection keepalive feature. If it is | |
236 | * nonzero, then if the interval passes while 'fsm' is connected and without | |
a6f639f8 BP |
237 | * reconnect_activity() being called for 'fsm', reconnect_run() returns |
238 | * RECONNECT_PROBE. If the interval passes again without reconnect_activity() | |
3ed497fc BP |
239 | * being called, reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'. |
240 | * | |
241 | * If 'probe_interval' is nonzero, then it will be forced to a value of at | |
242 | * least 1000 ms. */ | |
243 | void | |
244 | reconnect_set_probe_interval(struct reconnect *fsm, int probe_interval) | |
245 | { | |
246 | fsm->probe_interval = probe_interval ? MAX(1000, probe_interval) : 0; | |
247 | } | |
248 | ||
19df7f51 BP |
249 | /* Returns true if 'fsm' is in passive mode, false if 'fsm' is in active mode |
250 | * (the default). */ | |
251 | bool | |
252 | reconnect_is_passive(const struct reconnect *fsm) | |
253 | { | |
254 | return fsm->passive; | |
255 | } | |
256 | ||
257 | /* Configures 'fsm' for active or passive mode. In active mode (the default), | |
258 | * the FSM is attempting to connect to a remote host. In passive mode, the FSM | |
259 | * is listening for connections from a remote host. */ | |
260 | void | |
261 | reconnect_set_passive(struct reconnect *fsm, bool passive, long long int now) | |
262 | { | |
263 | if (fsm->passive != passive) { | |
264 | fsm->passive = passive; | |
265 | ||
266 | if (passive | |
c36cf65e | 267 | ? fsm->state & (S_CONNECTING | S_RECONNECT) |
19df7f51 BP |
268 | : fsm->state == S_LISTENING && reconnect_may_retry(fsm)) { |
269 | reconnect_transition__(fsm, now, S_BACKOFF); | |
270 | fsm->backoff = 0; | |
271 | } | |
272 | } | |
273 | } | |
274 | ||
3ed497fc BP |
275 | /* Returns true if 'fsm' has been enabled with reconnect_enable(). Calling |
276 | * another function that indicates a change in connection state, such as | |
277 | * reconnect_disconnected() or reconnect_force_reconnect(), will also enable | |
278 | * a reconnect FSM. */ | |
279 | bool | |
280 | reconnect_is_enabled(const struct reconnect *fsm) | |
281 | { | |
282 | return fsm->state != S_VOID; | |
283 | } | |
284 | ||
285 | /* If 'fsm' is disabled (the default for newly created FSMs), enables it, so | |
286 | * that the next call to reconnect_run() for 'fsm' will return | |
287 | * RECONNECT_CONNECT. | |
288 | * | |
289 | * If 'fsm' is not disabled, this function has no effect. */ | |
290 | void | |
291 | reconnect_enable(struct reconnect *fsm, long long int now) | |
292 | { | |
a85c0bbc | 293 | if (fsm->state == S_VOID && reconnect_may_retry(fsm)) { |
3ed497fc BP |
294 | reconnect_transition__(fsm, now, S_BACKOFF); |
295 | fsm->backoff = 0; | |
296 | } | |
297 | } | |
298 | ||
299 | /* Disables 'fsm'. Until 'fsm' is enabled again, reconnect_run() will always | |
300 | * return 0. */ | |
301 | void | |
302 | reconnect_disable(struct reconnect *fsm, long long int now) | |
303 | { | |
304 | if (fsm->state != S_VOID) { | |
305 | reconnect_transition__(fsm, now, S_VOID); | |
306 | } | |
307 | } | |
308 | ||
309 | /* If 'fsm' is enabled and currently connected (or attempting to connect), | |
310 | * forces reconnect_run() for 'fsm' to return RECONNECT_DISCONNECT the next | |
311 | * time it is called, which should cause the client to drop the connection (or | |
312 | * attempt), back off, and then reconnect. */ | |
313 | void | |
314 | reconnect_force_reconnect(struct reconnect *fsm, long long int now) | |
315 | { | |
c36cf65e | 316 | if (fsm->state & (S_CONNECTING | S_ACTIVE | S_IDLE)) { |
3ed497fc BP |
317 | reconnect_transition__(fsm, now, S_RECONNECT); |
318 | } | |
319 | } | |
320 | ||
321 | /* Tell 'fsm' that the connection dropped or that a connection attempt failed. | |
322 | * 'error' specifies the reason: a positive value represents an errno value, | |
323 | * EOF indicates that the connection was closed by the peer (e.g. read() | |
324 | * returned 0), and 0 indicates no specific error. | |
325 | * | |
326 | * The FSM will back off, then reconnect. */ | |
327 | void | |
328 | reconnect_disconnected(struct reconnect *fsm, long long int now, int error) | |
329 | { | |
a85c0bbc | 330 | if (!(fsm->state & (S_BACKOFF | S_VOID))) { |
3ed497fc BP |
331 | /* Report what happened. */ |
332 | if (fsm->state & (S_ACTIVE | S_IDLE)) { | |
333 | if (error > 0) { | |
334 | VLOG_WARN("%s: connection dropped (%s)", | |
10a89ef0 | 335 | fsm->name, ovs_strerror(error)); |
3ed497fc | 336 | } else if (error == EOF) { |
36a7b32d | 337 | VLOG(fsm->info, "%s: connection closed by peer", fsm->name); |
3ed497fc | 338 | } else { |
36a7b32d | 339 | VLOG(fsm->info, "%s: connection dropped", fsm->name); |
3ed497fc | 340 | } |
19df7f51 | 341 | } else if (fsm->state == S_LISTENING) { |
3ed497fc | 342 | if (error > 0) { |
19df7f51 | 343 | VLOG_WARN("%s: error listening for connections (%s)", |
10a89ef0 | 344 | fsm->name, ovs_strerror(error)); |
3ed497fc | 345 | } else { |
36a7b32d BP |
346 | VLOG(fsm->info, "%s: error listening for connections", |
347 | fsm->name); | |
19df7f51 BP |
348 | } |
349 | } else { | |
350 | const char *type = fsm->passive ? "listen" : "connection"; | |
351 | if (error > 0) { | |
c0c4982f | 352 | VLOG_INFO("%s: %s attempt failed (%s)", |
10a89ef0 | 353 | fsm->name, type, ovs_strerror(error)); |
19df7f51 | 354 | } else { |
36a7b32d | 355 | VLOG(fsm->info, "%s: %s attempt timed out", fsm->name, type); |
3ed497fc BP |
356 | } |
357 | } | |
358 | ||
eba18f00 AE |
359 | if (fsm->state & (S_ACTIVE | S_IDLE)) { |
360 | fsm->last_disconnected = now; | |
361 | } | |
3ed497fc BP |
362 | /* Back off. */ |
363 | if (fsm->state & (S_ACTIVE | S_IDLE) | |
a6f639f8 | 364 | && (fsm->last_activity - fsm->last_connected >= fsm->backoff |
19df7f51 BP |
365 | || fsm->passive)) { |
366 | fsm->backoff = fsm->passive ? 0 : fsm->min_backoff; | |
3ed497fc BP |
367 | } else { |
368 | if (fsm->backoff < fsm->min_backoff) { | |
369 | fsm->backoff = fsm->min_backoff; | |
370 | } else if (fsm->backoff >= fsm->max_backoff / 2) { | |
371 | fsm->backoff = fsm->max_backoff; | |
372 | } else { | |
373 | fsm->backoff *= 2; | |
374 | } | |
19df7f51 | 375 | if (fsm->passive) { |
36a7b32d | 376 | VLOG(fsm->info, "%s: waiting %.3g seconds before trying to " |
19df7f51 BP |
377 | "listen again", fsm->name, fsm->backoff / 1000.0); |
378 | } else { | |
36a7b32d | 379 | VLOG(fsm->info, "%s: waiting %.3g seconds before reconnect", |
19df7f51 BP |
380 | fsm->name, fsm->backoff / 1000.0); |
381 | } | |
3ed497fc | 382 | } |
a85c0bbc BP |
383 | |
384 | reconnect_transition__(fsm, now, | |
385 | reconnect_may_retry(fsm) ? S_BACKOFF : S_VOID); | |
3ed497fc BP |
386 | } |
387 | } | |
388 | ||
19df7f51 | 389 | /* Tell 'fsm' that a connection or listening attempt is in progress. |
3ed497fc | 390 | * |
19df7f51 | 391 | * The FSM will start a timer, after which the connection or listening attempt |
528b8cc4 BP |
392 | * will be aborted (by returning RECONNECT_DISCONNECT from |
393 | * reconnect_run()). */ | |
3ed497fc BP |
394 | void |
395 | reconnect_connecting(struct reconnect *fsm, long long int now) | |
396 | { | |
c36cf65e | 397 | if (fsm->state != S_CONNECTING) { |
19df7f51 | 398 | if (fsm->passive) { |
36a7b32d | 399 | VLOG(fsm->info, "%s: listening...", fsm->name); |
19df7f51 | 400 | } else { |
36a7b32d | 401 | VLOG(fsm->info, "%s: connecting...", fsm->name); |
19df7f51 | 402 | } |
c36cf65e | 403 | reconnect_transition__(fsm, now, S_CONNECTING); |
3ed497fc BP |
404 | } |
405 | } | |
406 | ||
19df7f51 BP |
407 | /* Tell 'fsm' that the client is listening for connection attempts. This state |
408 | * last indefinitely until the client reports some change. | |
409 | * | |
410 | * The natural progression from this state is for the client to report that a | |
411 | * connection has been accepted or is in progress of being accepted, by calling | |
412 | * reconnect_connecting() or reconnect_connected(). | |
413 | * | |
414 | * The client may also report that listening failed (e.g. accept() returned an | |
415 | * unexpected error such as ENOMEM) by calling reconnect_listen_error(), in | |
416 | * which case the FSM will back off and eventually return RECONNECT_CONNECT | |
417 | * from reconnect_run() to tell the client to try listening again. */ | |
418 | void | |
419 | reconnect_listening(struct reconnect *fsm, long long int now) | |
420 | { | |
421 | if (fsm->state != S_LISTENING) { | |
36a7b32d | 422 | VLOG(fsm->info, "%s: listening...", fsm->name); |
19df7f51 BP |
423 | reconnect_transition__(fsm, now, S_LISTENING); |
424 | } | |
425 | } | |
426 | ||
427 | /* Tell 'fsm' that the client's attempt to accept a connection failed | |
428 | * (e.g. accept() returned an unexpected error such as ENOMEM). | |
429 | * | |
430 | * If the FSM is currently listening (reconnect_listening() was called), it | |
431 | * will back off and eventually return RECONNECT_CONNECT from reconnect_run() | |
432 | * to tell the client to try listening again. If there is an active | |
433 | * connection, this will be delayed until that connection drops. */ | |
434 | void | |
435 | reconnect_listen_error(struct reconnect *fsm, long long int now, int error) | |
436 | { | |
437 | if (fsm->state == S_LISTENING) { | |
438 | reconnect_disconnected(fsm, now, error); | |
439 | } | |
440 | } | |
441 | ||
3ed497fc BP |
442 | /* Tell 'fsm' that the connection was successful. |
443 | * | |
444 | * The FSM will start the probe interval timer, which is reset by | |
a6f639f8 | 445 | * reconnect_activity(). If the timer expires, a probe will be sent (by |
3ed497fc BP |
446 | * returning RECONNECT_PROBE from reconnect_run()). If the timer expires |
447 | * again without being reset, the connection will be aborted (by returning | |
448 | * RECONNECT_DISCONNECT from reconnect_run()). */ | |
449 | void | |
450 | reconnect_connected(struct reconnect *fsm, long long int now) | |
451 | { | |
452 | if (!is_connected_state(fsm->state)) { | |
453 | reconnect_connecting(fsm, now); | |
454 | ||
36a7b32d | 455 | VLOG(fsm->info, "%s: connected", fsm->name); |
3ed497fc BP |
456 | reconnect_transition__(fsm, now, S_ACTIVE); |
457 | fsm->last_connected = now; | |
458 | } | |
459 | } | |
460 | ||
461 | /* Tell 'fsm' that the connection attempt failed. | |
462 | * | |
463 | * The FSM will back off and attempt to reconnect. */ | |
464 | void | |
465 | reconnect_connect_failed(struct reconnect *fsm, long long int now, int error) | |
466 | { | |
467 | reconnect_connecting(fsm, now); | |
468 | reconnect_disconnected(fsm, now, error); | |
469 | } | |
470 | ||
a6f639f8 BP |
471 | /* Tell 'fsm' that some activity has occurred on the connection. This resets |
472 | * the probe interval timer, so that the connection is known not to be idle. */ | |
3ed497fc | 473 | void |
a6f639f8 | 474 | reconnect_activity(struct reconnect *fsm, long long int now) |
3ed497fc BP |
475 | { |
476 | if (fsm->state != S_ACTIVE) { | |
477 | reconnect_transition__(fsm, now, S_ACTIVE); | |
478 | } | |
a6f639f8 | 479 | fsm->last_activity = now; |
3ed497fc BP |
480 | } |
481 | ||
482 | static void | |
483 | reconnect_transition__(struct reconnect *fsm, long long int now, | |
484 | enum state state) | |
485 | { | |
c36cf65e | 486 | if (fsm->state == S_CONNECTING) { |
3ed497fc BP |
487 | fsm->n_attempted_connections++; |
488 | if (state == S_ACTIVE) { | |
489 | fsm->n_successful_connections++; | |
490 | } | |
491 | } | |
492 | if (is_connected_state(fsm->state) != is_connected_state(state)) { | |
493 | if (is_connected_state(fsm->state)) { | |
494 | fsm->total_connected_duration += now - fsm->last_connected; | |
495 | } | |
496 | fsm->seqno++; | |
497 | } | |
498 | ||
499 | VLOG_DBG("%s: entering %s", fsm->name, reconnect_state_name__(state)); | |
500 | fsm->state = state; | |
501 | fsm->state_entered = now; | |
502 | } | |
503 | ||
504 | static long long int | |
505 | reconnect_deadline__(const struct reconnect *fsm) | |
506 | { | |
cb22974d | 507 | ovs_assert(fsm->state_entered != LLONG_MIN); |
3ed497fc BP |
508 | switch (fsm->state) { |
509 | case S_VOID: | |
19df7f51 | 510 | case S_LISTENING: |
3ed497fc BP |
511 | return LLONG_MAX; |
512 | ||
513 | case S_BACKOFF: | |
514 | return fsm->state_entered + fsm->backoff; | |
515 | ||
c36cf65e | 516 | case S_CONNECTING: |
3ed497fc BP |
517 | return fsm->state_entered + MAX(1000, fsm->backoff); |
518 | ||
519 | case S_ACTIVE: | |
520 | if (fsm->probe_interval) { | |
a6f639f8 | 521 | long long int base = MAX(fsm->last_activity, fsm->state_entered); |
3ed497fc BP |
522 | return base + fsm->probe_interval; |
523 | } | |
524 | return LLONG_MAX; | |
525 | ||
526 | case S_IDLE: | |
bceb55c8 EJ |
527 | if (fsm->probe_interval) { |
528 | return fsm->state_entered + fsm->probe_interval; | |
529 | } | |
530 | return LLONG_MAX; | |
3ed497fc BP |
531 | |
532 | case S_RECONNECT: | |
533 | return fsm->state_entered; | |
534 | } | |
535 | ||
428b2edd | 536 | OVS_NOT_REACHED(); |
3ed497fc BP |
537 | } |
538 | ||
539 | /* Assesses whether any action should be taken on 'fsm'. The return value is | |
540 | * one of: | |
541 | * | |
542 | * - 0: The client need not take any action. | |
543 | * | |
19df7f51 BP |
544 | * - Active client, RECONNECT_CONNECT: The client should start a connection |
545 | * attempt and indicate this by calling reconnect_connecting(). If the | |
546 | * connection attempt has definitely succeeded, it should call | |
3ed497fc BP |
547 | * reconnect_connected(). If the connection attempt has definitely |
548 | * failed, it should call reconnect_connect_failed(). | |
549 | * | |
550 | * The FSM is smart enough to back off correctly after successful | |
551 | * connections that quickly abort, so it is OK to call | |
552 | * reconnect_connected() after a low-level successful connection | |
553 | * (e.g. connect()) even if the connection might soon abort due to a | |
554 | * failure at a high-level (e.g. SSL negotiation failure). | |
555 | * | |
19df7f51 BP |
556 | * - Passive client, RECONNECT_CONNECT: The client should try to listen for |
557 | * a connection, if it is not already listening. It should call | |
558 | * reconnect_listening() if successful, otherwise reconnect_connecting() | |
559 | * or reconnected_connect_failed() if the attempt is in progress or | |
560 | * definitely failed, respectively. | |
561 | * | |
562 | * A listening passive client should constantly attempt to accept a new | |
563 | * connection and report an accepted connection with | |
564 | * reconnect_connected(). | |
565 | * | |
3ed497fc | 566 | * - RECONNECT_DISCONNECT: The client should abort the current connection |
19df7f51 BP |
567 | * or connection attempt or listen attempt and call |
568 | * reconnect_disconnected() or reconnect_connect_failed() to indicate it. | |
3ed497fc BP |
569 | * |
570 | * - RECONNECT_PROBE: The client should send some kind of request to the | |
571 | * peer that will elicit a response, to ensure that the connection is | |
572 | * indeed in working order. (This will only be returned if the "probe | |
573 | * interval" is nonzero--see reconnect_set_probe_interval()). | |
574 | */ | |
575 | enum reconnect_action | |
576 | reconnect_run(struct reconnect *fsm, long long int now) | |
577 | { | |
578 | if (now >= reconnect_deadline__(fsm)) { | |
579 | switch (fsm->state) { | |
580 | case S_VOID: | |
581 | return 0; | |
582 | ||
583 | case S_BACKOFF: | |
584 | return RECONNECT_CONNECT; | |
585 | ||
c36cf65e | 586 | case S_CONNECTING: |
3ed497fc BP |
587 | return RECONNECT_DISCONNECT; |
588 | ||
589 | case S_ACTIVE: | |
590 | VLOG_DBG("%s: idle %lld ms, sending inactivity probe", fsm->name, | |
a6f639f8 | 591 | now - MAX(fsm->last_activity, fsm->state_entered)); |
3ed497fc BP |
592 | reconnect_transition__(fsm, now, S_IDLE); |
593 | return RECONNECT_PROBE; | |
594 | ||
595 | case S_IDLE: | |
596 | VLOG_ERR("%s: no response to inactivity probe after %.3g " | |
597 | "seconds, disconnecting", | |
598 | fsm->name, (now - fsm->state_entered) / 1000.0); | |
599 | return RECONNECT_DISCONNECT; | |
600 | ||
601 | case S_RECONNECT: | |
602 | return RECONNECT_DISCONNECT; | |
19df7f51 BP |
603 | |
604 | case S_LISTENING: | |
605 | return 0; | |
3ed497fc BP |
606 | } |
607 | ||
428b2edd | 608 | OVS_NOT_REACHED(); |
3ed497fc | 609 | } else { |
024b7728 | 610 | return 0; |
3ed497fc BP |
611 | } |
612 | } | |
613 | ||
614 | /* Causes the next call to poll_block() to wake up when reconnect_run() should | |
615 | * be called on 'fsm'. */ | |
616 | void | |
617 | reconnect_wait(struct reconnect *fsm, long long int now) | |
618 | { | |
619 | int timeout = reconnect_timeout(fsm, now); | |
620 | if (timeout >= 0) { | |
621 | poll_timer_wait(timeout); | |
622 | } | |
623 | } | |
624 | ||
625 | /* Returns the number of milliseconds after which reconnect_run() should be | |
626 | * called on 'fsm' if nothing else notable happens in the meantime, or a | |
627 | * negative number if this is currently unnecessary. */ | |
628 | int | |
629 | reconnect_timeout(struct reconnect *fsm, long long int now) | |
630 | { | |
631 | long long int deadline = reconnect_deadline__(fsm); | |
632 | if (deadline != LLONG_MAX) { | |
633 | long long int remaining = deadline - now; | |
634 | return MAX(0, MIN(INT_MAX, remaining)); | |
635 | } | |
636 | return -1; | |
637 | } | |
638 | ||
639 | /* Returns true if 'fsm' is currently believed to be connected, that is, if | |
640 | * reconnect_connected() was called more recently than any call to | |
641 | * reconnect_connect_failed() or reconnect_disconnected() or | |
642 | * reconnect_disable(), and false otherwise. */ | |
643 | bool | |
644 | reconnect_is_connected(const struct reconnect *fsm) | |
645 | { | |
646 | return is_connected_state(fsm->state); | |
647 | } | |
648 | ||
5eda645e AE |
649 | /* Returns the number of milliseconds since 'fsm' last successfully connected |
650 | * to its peer (even if it has since disconnected). Returns UINT_MAX if never | |
651 | * connected. */ | |
3ed497fc | 652 | unsigned int |
5eda645e AE |
653 | reconnect_get_last_connect_elapsed(const struct reconnect *fsm, |
654 | long long int now) | |
3ed497fc | 655 | { |
5eda645e AE |
656 | return fsm->last_connected == LLONG_MAX ? UINT_MAX |
657 | : now - fsm->last_connected; | |
3ed497fc BP |
658 | } |
659 | ||
5eda645e AE |
660 | /* Returns the number of milliseconds since 'fsm' last disconnected |
661 | * from its peer (even if it has since reconnected). Returns UINT_MAX if never | |
662 | * disconnected. */ | |
eba18f00 | 663 | unsigned int |
5eda645e AE |
664 | reconnect_get_last_disconnect_elapsed(const struct reconnect *fsm, |
665 | long long int now) | |
eba18f00 | 666 | { |
5eda645e AE |
667 | return fsm->last_disconnected == LLONG_MAX ? UINT_MAX |
668 | : now - fsm->last_disconnected; | |
eba18f00 AE |
669 | } |
670 | ||
3ed497fc BP |
671 | /* Copies various statistics for 'fsm' into '*stats'. */ |
672 | void | |
673 | reconnect_get_stats(const struct reconnect *fsm, long long int now, | |
674 | struct reconnect_stats *stats) | |
675 | { | |
676 | stats->creation_time = fsm->creation_time; | |
a6f639f8 | 677 | stats->last_activity = fsm->last_activity; |
3ed497fc | 678 | stats->last_connected = fsm->last_connected; |
eba18f00 | 679 | stats->last_disconnected = fsm->last_disconnected; |
3ed497fc BP |
680 | stats->backoff = fsm->backoff; |
681 | stats->seqno = fsm->seqno; | |
682 | stats->is_connected = reconnect_is_connected(fsm); | |
5eda645e AE |
683 | stats->msec_since_connect |
684 | = reconnect_get_last_connect_elapsed(fsm, now); | |
685 | stats->msec_since_disconnect | |
686 | = reconnect_get_last_disconnect_elapsed(fsm, now); | |
687 | stats->total_connected_duration = fsm->total_connected_duration | |
688 | + (is_connected_state(fsm->state) | |
689 | ? reconnect_get_last_connect_elapsed(fsm, now) : 0); | |
3ed497fc BP |
690 | stats->n_attempted_connections = fsm->n_attempted_connections; |
691 | stats->n_successful_connections = fsm->n_successful_connections; | |
692 | stats->state = reconnect_state_name__(fsm->state); | |
693 | stats->state_elapsed = now - fsm->state_entered; | |
694 | } | |
a85c0bbc BP |
695 | |
696 | static bool | |
697 | reconnect_may_retry(struct reconnect *fsm) | |
698 | { | |
699 | bool may_retry = fsm->max_tries > 0; | |
700 | if (may_retry && fsm->max_tries != UINT_MAX) { | |
701 | fsm->max_tries--; | |
702 | } | |
703 | return may_retry; | |
704 | } |