From 19df7f512c50e453c8eaa97675e213b4d6e9735d Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 24 Mar 2010 13:09:38 -0700 Subject: [PATCH] reconnect: Implement "passive mode". This allows the reconnect library to support clients that want to listen for an incoming connection. --- lib/reconnect.c | 130 +++++++++++++++++++++++++++++++++++------ lib/reconnect.h | 8 ++- tests/reconnect.at | 120 +++++++++++++++++++++++++++++++++++++ tests/test-reconnect.c | 21 +++++++ 4 files changed, 261 insertions(+), 18 deletions(-) diff --git a/lib/reconnect.c b/lib/reconnect.c index 40e87b0e..96917bec 100644 --- a/lib/reconnect.c +++ b/lib/reconnect.c @@ -31,7 +31,8 @@ STATE(CONNECT_IN_PROGRESS, 1 << 3) \ STATE(ACTIVE, 1 << 4) \ STATE(IDLE, 1 << 5) \ - STATE(RECONNECT, 1 << 6) + STATE(RECONNECT, 1 << 6) \ + STATE(LISTENING, 1 << 7) enum state { #define STATE(NAME, VALUE) S_##NAME = VALUE, STATES @@ -50,6 +51,7 @@ struct reconnect { int min_backoff; int max_backoff; int probe_interval; + bool passive; /* State. */ enum state state; @@ -95,6 +97,7 @@ reconnect_create(long long int now) fsm->min_backoff = 1000; fsm->max_backoff = 8000; fsm->probe_interval = 5000; + fsm->passive = false; fsm->state = S_VOID; fsm->state_entered = now; @@ -218,6 +221,32 @@ reconnect_set_probe_interval(struct reconnect *fsm, int probe_interval) fsm->probe_interval = probe_interval ? MAX(1000, probe_interval) : 0; } +/* Returns true if 'fsm' is in passive mode, false if 'fsm' is in active mode + * (the default). */ +bool +reconnect_is_passive(const struct reconnect *fsm) +{ + return fsm->passive; +} + +/* Configures 'fsm' for active or passive mode. In active mode (the default), + * the FSM is attempting to connect to a remote host. In passive mode, the FSM + * is listening for connections from a remote host. */ +void +reconnect_set_passive(struct reconnect *fsm, bool passive, long long int now) +{ + if (fsm->passive != passive) { + fsm->passive = passive; + + if (passive + ? fsm->state & (S_CONNECT_IN_PROGRESS | S_RECONNECT) + : fsm->state == S_LISTENING && reconnect_may_retry(fsm)) { + reconnect_transition__(fsm, now, S_BACKOFF); + fsm->backoff = 0; + } + } +} + /* Returns true if 'fsm' has been enabled with reconnect_enable(). Calling * another function that indicates a change in connection state, such as * reconnect_disconnected() or reconnect_force_reconnect(), will also enable @@ -284,19 +313,28 @@ reconnect_disconnected(struct reconnect *fsm, long long int now, int error) } else { VLOG_INFO("%s: connection dropped", fsm->name); } - } else { + } else if (fsm->state == S_LISTENING) { if (error > 0) { - VLOG_WARN("%s: connection attempt failed (%s)", + VLOG_WARN("%s: error listening for connections (%s)", fsm->name, strerror(error)); } else { - VLOG_INFO("%s: connection attempt timed out", fsm->name); + VLOG_INFO("%s: error listening for connections", fsm->name); + } + } else { + const char *type = fsm->passive ? "listen" : "connection"; + if (error > 0) { + VLOG_WARN("%s: %s attempt failed (%s)", + fsm->name, type, strerror(error)); + } else { + VLOG_INFO("%s: %s attempt timed out", fsm->name, type); } } /* Back off. */ if (fsm->state & (S_ACTIVE | S_IDLE) - && fsm->last_received - fsm->last_connected >= fsm->backoff) { - fsm->backoff = fsm->min_backoff; + && (fsm->last_received - fsm->last_connected >= fsm->backoff + || fsm->passive)) { + fsm->backoff = fsm->passive ? 0 : fsm->min_backoff; } else { if (fsm->backoff < fsm->min_backoff) { fsm->backoff = fsm->min_backoff; @@ -305,8 +343,13 @@ reconnect_disconnected(struct reconnect *fsm, long long int now, int error) } else { fsm->backoff *= 2; } - VLOG_INFO("%s: waiting %.3g seconds before reconnect\n", - fsm->name, fsm->backoff / 1000.0); + if (fsm->passive) { + VLOG_INFO("%s: waiting %.3g seconds before trying to " + "listen again", fsm->name, fsm->backoff / 1000.0); + } else { + VLOG_INFO("%s: waiting %.3g seconds before reconnect", + fsm->name, fsm->backoff / 1000.0); + } } reconnect_transition__(fsm, now, @@ -314,19 +357,58 @@ reconnect_disconnected(struct reconnect *fsm, long long int now, int error) } } -/* Tell 'fsm' that a connection attempt is in progress. +/* Tell 'fsm' that a connection or listening attempt is in progress. * - * The FSM will start a timer, after which the connection attempt will be - * aborted (by returning RECONNECT_DISCONNECT from reconect_run()). */ + * The FSM will start a timer, after which the connection or listening attempt + * will be aborted (by returning RECONNECT_DISCONNECT from reconect_run()). */ void reconnect_connecting(struct reconnect *fsm, long long int now) { if (fsm->state != S_CONNECT_IN_PROGRESS) { - VLOG_INFO("%s: connecting...", fsm->name); + if (fsm->passive) { + VLOG_INFO("%s: listening...", fsm->name); + } else { + VLOG_INFO("%s: connecting...", fsm->name); + } reconnect_transition__(fsm, now, S_CONNECT_IN_PROGRESS); } } +/* Tell 'fsm' that the client is listening for connection attempts. This state + * last indefinitely until the client reports some change. + * + * The natural progression from this state is for the client to report that a + * connection has been accepted or is in progress of being accepted, by calling + * reconnect_connecting() or reconnect_connected(). + * + * The client may also report that listening failed (e.g. accept() returned an + * unexpected error such as ENOMEM) by calling reconnect_listen_error(), in + * which case the FSM will back off and eventually return RECONNECT_CONNECT + * from reconnect_run() to tell the client to try listening again. */ +void +reconnect_listening(struct reconnect *fsm, long long int now) +{ + if (fsm->state != S_LISTENING) { + VLOG_INFO("%s: listening...", fsm->name); + reconnect_transition__(fsm, now, S_LISTENING); + } +} + +/* Tell 'fsm' that the client's attempt to accept a connection failed + * (e.g. accept() returned an unexpected error such as ENOMEM). + * + * If the FSM is currently listening (reconnect_listening() was called), it + * will back off and eventually return RECONNECT_CONNECT from reconnect_run() + * to tell the client to try listening again. If there is an active + * connection, this will be delayed until that connection drops. */ +void +reconnect_listen_error(struct reconnect *fsm, long long int now, int error) +{ + if (fsm->state == S_LISTENING) { + reconnect_disconnected(fsm, now, error); + } +} + /* Tell 'fsm' that the connection was successful. * * The FSM will start the probe interval timer, which is reset by @@ -395,6 +477,7 @@ reconnect_deadline__(const struct reconnect *fsm) assert(fsm->state_entered != LLONG_MIN); switch (fsm->state) { case S_VOID: + case S_LISTENING: return LLONG_MAX; case S_BACKOFF: @@ -425,9 +508,9 @@ reconnect_deadline__(const struct reconnect *fsm) * * - 0: The client need not take any action. * - * - RECONNECT_CONNECT: The client should start a connection attempt and - * indicate this by calling reconnect_connecting(). If the connection - * attempt has definitely succeeded, it should call + * - Active client, RECONNECT_CONNECT: The client should start a connection + * attempt and indicate this by calling reconnect_connecting(). If the + * connection attempt has definitely succeeded, it should call * reconnect_connected(). If the connection attempt has definitely * failed, it should call reconnect_connect_failed(). * @@ -437,9 +520,19 @@ reconnect_deadline__(const struct reconnect *fsm) * (e.g. connect()) even if the connection might soon abort due to a * failure at a high-level (e.g. SSL negotiation failure). * + * - Passive client, RECONNECT_CONNECT: The client should try to listen for + * a connection, if it is not already listening. It should call + * reconnect_listening() if successful, otherwise reconnect_connecting() + * or reconnected_connect_failed() if the attempt is in progress or + * definitely failed, respectively. + * + * A listening passive client should constantly attempt to accept a new + * connection and report an accepted connection with + * reconnect_connected(). + * * - RECONNECT_DISCONNECT: The client should abort the current connection - * or connection attempt and call reconnect_disconnected() or - * reconnect_connect_failed() to indicate it. + * or connection attempt or listen attempt and call + * reconnect_disconnected() or reconnect_connect_failed() to indicate it. * * - RECONNECT_PROBE: The client should send some kind of request to the * peer that will elicit a response, to ensure that the connection is @@ -474,6 +567,9 @@ reconnect_run(struct reconnect *fsm, long long int now) case S_RECONNECT: return RECONNECT_DISCONNECT; + + case S_LISTENING: + return 0; } NOT_REACHED(); diff --git a/lib/reconnect.h b/lib/reconnect.h index 76c7f78e..d0790ec6 100644 --- a/lib/reconnect.h +++ b/lib/reconnect.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 Nicira Networks. + * Copyright (c) 2009, 2010 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,10 @@ void reconnect_set_backoff(struct reconnect *, int min_backoff, int max_backoff); void reconnect_set_probe_interval(struct reconnect *, int probe_interval); +bool reconnect_is_passive(const struct reconnect *); +void reconnect_set_passive(struct reconnect *, bool passive, + long long int now); + bool reconnect_is_enabled(const struct reconnect *); void reconnect_enable(struct reconnect *, long long int now); void reconnect_disable(struct reconnect *, long long int now); @@ -61,6 +65,8 @@ unsigned int reconnect_get_connection_duration(const struct reconnect *, void reconnect_disconnected(struct reconnect *, long long int now, int error); void reconnect_connecting(struct reconnect *, long long int now); +void reconnect_listening(struct reconnect *, long long int now); +void reconnect_listen_error(struct reconnect *, long long int now, int error); void reconnect_connected(struct reconnect *, long long int now); void reconnect_connect_failed(struct reconnect *, long long int now, int error); diff --git a/tests/reconnect.at b/tests/reconnect.at index 7c1cc6ab..24ad838f 100644 --- a/tests/reconnect.at +++ b/tests/reconnect.at @@ -1109,3 +1109,123 @@ timeout no timeout ]) AT_CLEANUP + +###################################################################### +AT_SETUP([passive mode]) +AT_KEYWORDS([reconnect]) +AT_DATA([input], [passive +enable + +# Start listening. +timeout +run +listening + +# Listening never times out. +timeout +run + +# Listening failed (accept() returned funny error?). Back off and try again. +listen-error 0 +timeout +run +listening + +# Connection accepted. +connected +received +advance 1000 +received + +# Connection times out. +timeout +run +timeout +run +disconnected + +# Start listening again. +timeout +run +listening +]) +AT_CHECK([test-reconnect < input], [0], + [### t=1000 ### +passive +enable + in BACKOFF for 0 ms (0 ms backoff) + +# Start listening. +timeout + advance 0 ms +run + should connect +listening + in LISTENING for 0 ms (0 ms backoff) + +# Listening never times out. +timeout + no timeout +run + +# Listening failed (accept() returned funny error?). Back off and try again. +listen-error 0 + in BACKOFF for 0 ms (1000 ms backoff) +timeout + advance 1000 ms + +### t=2000 ### + in BACKOFF for 1000 ms (1000 ms backoff) +run + should connect +listening + in LISTENING for 0 ms (1000 ms backoff) + +# Connection accepted. +connected + in ACTIVE for 0 ms (1000 ms backoff) + created 1000, last received 1000, last connected 2000 + 1 successful connections out of 1 attempts, seqno 1 + connected (0 ms), total 0 ms connected +received + created 1000, last received 2000, last connected 2000 +advance 1000 + +### t=3000 ### + in ACTIVE for 1000 ms (1000 ms backoff) + connected (1000 ms), total 1000 ms connected +received + created 1000, last received 3000, last connected 2000 + +# Connection times out. +timeout + advance 5000 ms + +### t=8000 ### + in ACTIVE for 6000 ms (1000 ms backoff) + connected (6000 ms), total 6000 ms connected +run + should send probe + in IDLE for 0 ms (1000 ms backoff) +timeout + advance 5000 ms + +### t=13000 ### + in IDLE for 5000 ms (1000 ms backoff) + connected (11000 ms), total 11000 ms connected +run + should disconnect +disconnected + in BACKOFF for 0 ms (0 ms backoff) + 1 successful connections out of 1 attempts, seqno 2 + not connected (0 ms), total 11000 ms connected + +# Start listening again. +timeout + advance 0 ms +run + should connect +listening + in LISTENING for 0 ms (0 ms backoff) +]) +AT_CLEANUP diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c index 93991ff5..2d0d4414 100644 --- a/tests/test-reconnect.c +++ b/tests/test-reconnect.c @@ -235,6 +235,24 @@ diff_stats(const struct reconnect_stats *old, } } +static void +do_set_passive(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_set_passive(reconnect, true, now); +} + +static void +do_listening(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_listening(reconnect, now); +} + +static void +do_listen_error(int argc OVS_UNUSED, char *argv[]) +{ + reconnect_listen_error(reconnect, now, atoi(argv[1])); +} + static const struct command commands[] = { { "enable", 0, 0, do_enable }, { "disable", 0, 0, do_disable }, @@ -248,6 +266,9 @@ static const struct command commands[] = { { "advance", 1, 1, do_advance }, { "timeout", 0, 0, do_timeout }, { "set-max-tries", 1, 1, do_set_max_tries }, + { "passive", 0, 0, do_set_passive }, + { "listening", 0, 0, do_listening }, + { "listen-error", 1, 1, do_listen_error }, { NULL, 0, 0, NULL }, }; -- 2.30.2