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