diff options
author | Will Thompson <will.thompson@collabora.co.uk> | 2012-10-30 15:52:09 +0000 |
---|---|---|
committer | Will Thompson <will.thompson@collabora.co.uk> | 2012-10-30 16:02:57 +0000 |
commit | eb437c2f0bbc7061570889a825b131969982cc9a (patch) | |
tree | 55ac4034da4d112c06cf2376bf358a1c8e48cdf2 /src | |
parent | 5fc2d07d025fca9a19abd09caa8b7de2ab8911ed (diff) |
Connection: disconnect if we get no PONG for our PING
Previously we'd just send out a PING into the æther every n seconds, and
pay no attention to whether we ever got an answer. So it was perfectly
possible for the connection to just sit there until the TCP timeout
kicks in, which I think is a matter of hours by default.
With this patch, if we haven't heard a PONG 3 keepalive-intervals after
sending a PING, Idle throws its toys out of the pram.
This has been tested as follows:
* Put my laptop on a wired and wireless network simultaneously.
* Connect to an IRC server. (The wired network is used.)
* Pull the network cable out. Idle is too stupid to realise the link it
was using is gone, and because we're still ostensibly online, nothing
tells it to disconnect.
* Wait keepalive-interval * 4, and watch the connection get
disconnected.
It works both with a direct connection to Freenode, and with a
connection over an SSH tunnel to irssi-proxy.
Diffstat (limited to 'src')
-rw-r--r-- | src/idle-connection.c | 60 | ||||
-rw-r--r-- | src/idle-parser.c | 1 | ||||
-rw-r--r-- | src/idle-parser.h | 1 |
3 files changed, 59 insertions, 3 deletions
diff --git a/src/idle-connection.c b/src/idle-connection.c index 3fd8905..f4cf816 100644 --- a/src/idle-connection.c +++ b/src/idle-connection.c @@ -52,6 +52,7 @@ #include "extensions/extensions.h" /* Renaming */ #define DEFAULT_KEEPALIVE_INTERVAL 30 /* sec */ +#define MISSED_KEEPALIVES_BEFORE_DISCONNECTING 3 /* From RFC 2813 : * This in essence means that the client may send one (1) message every @@ -146,6 +147,11 @@ struct _IdleConnectionPrivate { IdleServerConnection *conn; guint sconn_status; + /* When we sent a PING to the server which it hasn't PONGed for yet, or 0 if + * there isn't a PING outstanding. + */ + gint64 ping_time; + /* IRC connection properties */ char *nickname; char *server; @@ -207,6 +213,7 @@ static IdleParserHandlerResult _erroneous_nickname_handler(IdleParser *parser, I static IdleParserHandlerResult _nick_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); static IdleParserHandlerResult _nickname_in_use_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); static IdleParserHandlerResult _ping_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); +static IdleParserHandlerResult _pong_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); static IdleParserHandlerResult _version_privmsg_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); static IdleParserHandlerResult _welcome_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); static IdleParserHandlerResult _whois_user_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data); @@ -703,6 +710,7 @@ static void _connection_connect_ready(GObject *source_object, GAsyncResult *res, idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISUSER, _whois_user_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_CMD_PING, _ping_handler, conn); + idle_parser_add_handler(conn->parser, IDLE_PARSER_PREFIXCMD_PONG, _pong_handler, conn); idle_parser_add_handler_with_priority(conn->parser, IDLE_PARSER_PREFIXCMD_NICK, _nick_handler, conn, IDLE_PARSER_HANDLER_PRIORITY_FIRST); idle_parser_add_handler(conn->parser, IDLE_PARSER_PREFIXCMD_PRIVMSG_USER, _version_privmsg_handler, conn); @@ -808,15 +816,49 @@ static gboolean keepalive_timeout_cb(gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); IdleConnectionPrivate *priv = conn->priv; gchar cmd[IRC_MSG_MAXLEN + 1]; - gint64 ping_time; + gint64 now; if (priv->sconn_status != SERVER_CONNECTION_STATE_CONNECTED) { priv->keepalive_timeout = 0; return FALSE; } - ping_time = g_get_real_time(); - g_snprintf(cmd, IRC_MSG_MAXLEN + 1, "PING %" G_GINT64_FORMAT, ping_time); + now = g_get_real_time(); + + if (priv->ping_time != 0) { + gint64 seconds_since_ping = (now - priv->ping_time) / G_USEC_PER_SEC; + + if (seconds_since_ping > priv->keepalive_interval * MISSED_KEEPALIVES_BEFORE_DISCONNECTING) { + GCancellable *kill_me_now = g_cancellable_new(); + + IDLE_DEBUG("haven't heard from the server in %" G_GINT64_FORMAT " seconds " + "(more than %u keepalive intervals)", + seconds_since_ping, MISSED_KEEPALIVES_BEFORE_DISCONNECTING); + + /* Passing a cancelled cancellable to g_io_stream_close_async() stops us + * waiting for a TCP-level reply from the server which we already know + * has gone away. Quoth the docs for g_io_stream_close(): + * + * Cancelling a close will still leave the stream closed, but some + * streams can use a faster close that doesn't block to e.g. check + * errors. + */ + g_cancellable_cancel(kill_me_now); + idle_server_connection_disconnect_full_async(priv->conn, SERVER_CONNECTION_STATE_REASON_ERROR, kill_me_now, NULL, NULL); + g_object_unref(kill_me_now); + return FALSE; + } + + return TRUE; + } + + if (priv->msg_queue->length > 0) { + /* No point in sending a PING if we're sending data anyway. */ + return TRUE; + } + + priv->ping_time = now; + g_snprintf(cmd, IRC_MSG_MAXLEN + 1, "PING %" G_GINT64_FORMAT, priv->ping_time); _send_with_priority(conn, cmd, SERVER_CMD_MIN_PRIORITY); return TRUE; @@ -1060,6 +1102,18 @@ static IdleParserHandlerResult _ping_handler(IdleParser *parser, IdleParserMessa return IDLE_PARSER_HANDLER_RESULT_HANDLED; } +static IdleParserHandlerResult _pong_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = conn->priv; + + /* We could compare ping_time to g_get_real_time() and give some indication + * of lag, if we were feeling enthusiastic. + */ + priv->ping_time = 0; + + return IDLE_PARSER_HANDLER_RESULT_HANDLED; +} + static IdleParserHandlerResult _version_privmsg_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); const gchar *msg = g_value_get_string(g_value_array_get_nth(args, 2)); diff --git a/src/idle-parser.c b/src/idle-parser.c index 170f299..47ac0e5 100644 --- a/src/idle-parser.c +++ b/src/idle-parser.c @@ -85,6 +85,7 @@ static const MessageSpec message_specs[] = { {"NOTICE", "cIr:", IDLE_PARSER_PREFIXCMD_NOTICE_CHANNEL}, {"NOTICE", "cIc:", IDLE_PARSER_PREFIXCMD_NOTICE_USER}, {"PART", "cIr.", IDLE_PARSER_PREFIXCMD_PART}, + {"PONG", "IIs:", IDLE_PARSER_PREFIXCMD_PONG}, {"PRIVMSG", "cIr:", IDLE_PARSER_PREFIXCMD_PRIVMSG_CHANNEL}, {"PRIVMSG", "cIc:", IDLE_PARSER_PREFIXCMD_PRIVMSG_USER}, {"QUIT", "cI.", IDLE_PARSER_PREFIXCMD_QUIT}, diff --git a/src/idle-parser.h b/src/idle-parser.h index a900b00..1216054 100644 --- a/src/idle-parser.h +++ b/src/idle-parser.h @@ -60,6 +60,7 @@ typedef enum { IDLE_PARSER_PREFIXCMD_NOTICE_CHANNEL, IDLE_PARSER_PREFIXCMD_NOTICE_USER, IDLE_PARSER_PREFIXCMD_PART, + IDLE_PARSER_PREFIXCMD_PONG, IDLE_PARSER_PREFIXCMD_PRIVMSG_CHANNEL, IDLE_PARSER_PREFIXCMD_PRIVMSG_USER, IDLE_PARSER_PREFIXCMD_QUIT, |