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