implement irc_net reconnect, requires testing
authorTero Marttila <terom@fixme.fi>
Fri, 24 Apr 2009 23:01:34 +0300
changeset 153 d35e7cb3a489
parent 152 dae7bcf08474
child 154 f4472119de3b
child 169 e6a1ce44aecc
implement irc_net reconnect, requires testing
TODO
src/CMakeLists.txt
src/irc_net.c
src/irc_net.h
src/irc_net_connect.c
src/irc_net_internal.h
--- a/TODO	Thu Apr 23 21:22:15 2009 +0300
+++ b/TODO	Fri Apr 24 23:01:34 2009 +0300
@@ -15,9 +15,6 @@
 irc_net:
  * reconnect, maybe cycling servers?
 
-irc_chan:
- * strdup irc_chan_info strings, currently lua_irc just leaks them
-
 config:
  * user-defined types
  * return values
@@ -29,7 +26,6 @@
  * some kind of remote console
 
 irc_log:
- * log messages sent by ourself
  * recode to valid UTF8
 
 logwatch:
--- a/src/CMakeLists.txt	Thu Apr 23 21:22:15 2009 +0300
+++ b/src/CMakeLists.txt	Fri Apr 24 23:01:34 2009 +0300
@@ -12,7 +12,7 @@
 # define our source code modules
 set (CORE_SOURCES error.c log.c str.c)
 set (SOCK_SOURCES sock.c sock_fd.c sock_tcp.c sock_gnutls.c sock_test.c sock_fifo.c line_proto.c)
-set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c)
+set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c irc_net_connect.c)
 set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c)
 set (CONSOLE_SOURCES console.c lua_console.c)
 
--- a/src/irc_net.c	Thu Apr 23 21:22:15 2009 +0300
+++ b/src/irc_net.c	Fri Apr 24 23:01:34 2009 +0300
@@ -1,9 +1,11 @@
 #include "irc_net.h"
+#include "irc_net_internal.h"
 #include "log.h"
 
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <time.h>
 
 const char* irc_net_name (struct irc_net *net)
 {
@@ -11,28 +13,6 @@
 }
 
 /**
- * Something happaned which caused our irc_conn to fail. Destroy it, and recover. XXX: somehow
- */
-static void irc_net_error (struct irc_net *net, struct error_info *err)
-{
-    struct irc_chan *chan = NULL;
-
-    // log an error
-    log_err_info(err, "irc_conn failed");
-
-    // destroy connection and set NULL
-    irc_conn_destroy(net->conn);
-    net->conn = NULL;
-
-    // update channel state
-    TAILQ_FOREACH(chan, &net->channels, net_channels) {
-        // XXX: notify channel somehow
-    }
-
-    // XXX: reconnect?
-}
-
-/**
  * Do a lookup for an irc_user with the given nickname, and return it it found, else NULL.
  *
  * This does not refcount anything.
@@ -65,9 +45,10 @@
 
     // join our channels
     TAILQ_FOREACH(chan, &net->channels, net_channels) {
-        if ((ERROR_CODE(&err) = irc_chan_join(chan)))
+        if ((ERROR_CODE(&err) = irc_chan_join(chan))) {
             // XXX: this should be some kind of irc_chan_error instead
-            irc_net_error(net, &err);
+            
+        }
     }
 }
 
@@ -80,7 +61,15 @@
 
     (void) conn;
     
-    irc_net_error(net, err);
+    // log an error
+    log_err_info(err, "irc_conn failed");
+    
+    // tear down state
+    irc_net_disconnect(net, err);
+    
+    // reconnect, either right away, or at the five-minute interval
+    if (irc_net_connect(net, (time(NULL) - net->connected_ts > IRC_NET_RECONNECT_INTERVAL), err))
+        log_err_info(err, "unable to reconnect");
 }
 
 /**
@@ -100,7 +89,7 @@
 /**
  * Our irc_conn_callbacks list
  */
-struct irc_conn_callbacks _conn_callbacks = {
+struct irc_conn_callbacks irc_net_conn_callbacks = {
     .on_registered  = &irc_net_conn_registered,
     .on_error       = &irc_net_conn_error,
     .on_quit        = &irc_net_conn_quit,
@@ -239,7 +228,7 @@
 /**
  * Our irc_cmd handler list
  */
-static struct irc_cmd_handler _cmd_handlers[] = {
+struct irc_cmd_handler irc_net_cmd_handlers[] = {
     // propagate certain messages to the appropriate channel
     {   "QUIT",             &irc_net_on_chanuser    },
     {   "JOIN",             &irc_net_on_chan0       },
@@ -260,113 +249,6 @@
     {   NULL,               NULL                    }
 };
 
-/**
- * The given socket is now connected, so create the irc_conn and bind it in to us.
- *
- * If this fails, this will clean up any partial state, including sock.
- */
-static err_t irc_net_connected (struct irc_net *net, struct sock_stream *sock, struct error_info *err)
-{
-    // create the irc connection state
-    if (irc_conn_create(&net->conn, sock, &_conn_callbacks, net, err))
-        goto error;
-
-    // add our command handlers
-    if ((ERROR_CODE(err) = irc_conn_add_cmd_handlers (net->conn, _cmd_handlers, net)))
-        goto error;
-
-    // register
-    if ((ERROR_CODE(err) = irc_conn_register(net->conn, &net->info.register_info)))
-        goto error;
-
-    // ok
-    return SUCCESS;
-
-error:
-    if (!net->conn) {
-        // cleanup sock ourselves
-        sock_stream_release(sock);
-
-    } else {
-        // cleanup the partial stuff
-        irc_conn_destroy(net->conn);
-
-        net->conn = NULL;
-    }
-
-    return ERROR_CODE(err);
-}
-
-/**
- * Our sock_*_connect_async callback
- */
-static void irc_net_on_connect (struct sock_stream *sock, struct error_info *conn_err, void *arg)
-{
-    struct irc_net *net = arg;
-    struct error_info err;
-
-    // XXX: better error handling
-    
-    // trap errors
-    if (conn_err)
-        FATAL_ERROR(conn_err, "async connect failed");
-    
-    // ok, yay
-    if (irc_net_connected(net, sock, &err))
-        FATAL_ERROR(&err, "irc_net_connected failed");
-
-    // XXX: cleanup
-}
-
-/**
- * Connect and create a new irc_conn based on our irc_net_info.
- */
-static err_t irc_net_connect (struct irc_net *net, struct error_info *err)
-{
-    struct irc_net_info *info = &net->info;
-    struct sock_stream *sock = NULL;
-
-    // sanity check
-    assert(!net->conn);
-
-    // connect based on what's known
-    if (info->raw_sock) {
-        log_debug("connected using raw socket: %p", info->raw_sock);
-
-        // direct sock_stream connection
-        sock = info->raw_sock;
-
-        // then create the conn right away
-        if (irc_net_connected(net, sock, err))
-            goto error;
-
-    } else if (info->ssl_cred) {
-        // aquire a ref
-        // NOTE: before any error handling
-        sock_ssl_client_cred_get(info->ssl_cred);
-        
-        log_debug("connecting to [%s]:%s using SSL", info->hostname, info->service);
-
-        // connect
-        if (sock_ssl_connect_async(&sock, info->hostname, info->service, info->ssl_cred, &irc_net_on_connect, net, err))
-            goto error;
-
-    } else {
-        log_debug("connecting to [%s]:%s", info->hostname, info->service);
-            
-        // begin async connect
-        if (sock_tcp_connect_async(&sock, info->hostname, info->service, &irc_net_on_connect, net, err))
-            goto error;
-        
-    }
-
-    // ok
-    return SUCCESS;
-
-error:
-    return ERROR_CODE(err);    
-}
-
 err_t irc_net_create (struct irc_net **net_ptr, const struct irc_net_info *info, struct error_info *err)
 {
     struct irc_net *net;
@@ -380,9 +262,13 @@
     net->info = *info;
     TAILQ_INIT(&net->channels);
     LIST_INIT(&net->users);
+    
+    // init subsystems
+    if (irc_net_connect_init(net, err))
+        goto error;
 
     // initial connect
-    if (irc_net_connect(net, err))
+    if (irc_net_connect(net, true, err))
         goto error;
 
     // ok
@@ -424,6 +310,9 @@
     // our info
     if (net->info.ssl_cred)
         sock_ssl_client_cred_put(net->info.ssl_cred);
+    
+    // misc
+    irc_net_connect_destroy(net);
 
     // ourselves
     free(net);
--- a/src/irc_net.h	Thu Apr 23 21:22:15 2009 +0300
+++ b/src/irc_net.h	Fri Apr 24 23:01:34 2009 +0300
@@ -57,12 +57,24 @@
     /** Our connection info */
     struct irc_net_info info;
 
+    /** Are we currently connecting? */
+    bool connecting;
+
+    /** Are we connected? */
+    bool connected;
+    
+    /** Succesfull connect time */
+    time_t connected_ts;
+
     /** The list of IRC channel states */
     TAILQ_HEAD(irc_net_chan_list, irc_chan) channels;
 
     /** Our set of valid irc_user items for use with irc_chan */
     LIST_HEAD(irc_net_users_list, irc_user) users;
 
+    /** Reconnect timer */
+    struct event *reconnect_timer;
+
     /** The irc_client list */
     TAILQ_ENTRY(irc_net) client_networks;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_net_connect.c	Fri Apr 24 23:01:34 2009 +0300
@@ -0,0 +1,226 @@
+
+#include "irc_net_internal.h"
+#include "log.h"
+
+#include <time.h>
+#include <assert.h>
+
+void irc_net_disconnect (struct irc_net *net, struct error_info *err)
+{
+    struct irc_chan *chan = NULL;
+
+    (void) err;
+
+    // mark
+    net->connected = false;
+
+    // destroy connection and set NULL
+    irc_conn_destroy(net->conn);
+    net->conn = NULL;
+
+    // update channel state
+    TAILQ_FOREACH(chan, &net->channels, net_channels) {
+        // XXX: notify channel somehow
+
+    }
+
+}
+
+/**
+ * We have succesfully established a connection to our server with the given sock, so create the irc_conn and bind it
+ * to us.
+ *
+ * If this fails, this will clean up any partial state, including sock.
+ */
+static err_t irc_net_connected (struct irc_net *net, struct sock_stream *sock, struct error_info *err)
+{
+    // mark state
+    net->connecting = false;
+
+    // create the irc connection state
+    if (irc_conn_create(&net->conn, sock, &irc_net_conn_callbacks, net, err))
+        goto error;
+
+    // add our command handlers
+    if ((ERROR_CODE(err) = irc_conn_add_cmd_handlers (net->conn, irc_net_cmd_handlers, net)))
+        goto error;
+
+    // register
+    if ((ERROR_CODE(err) = irc_conn_register(net->conn, &net->info.register_info)))
+        goto error;
+
+    // great, we're alive now
+    net->connected = true;
+    net->connected_ts = time(NULL);
+
+    return SUCCESS;
+
+error:
+    if (!net->conn) {
+        // cleanup sock ourselves
+        sock_stream_release(sock);
+
+    } else {
+        // cleanup the partial stuff
+        irc_conn_destroy(net->conn);
+
+        net->conn = NULL;
+    }
+
+    return ERROR_CODE(err);
+}
+
+/**
+ * Our sock_*_connect_async callback. If the connect ended up failing, then try and reconnect later. Otherwise, do
+ * irc_net_connected().
+ */
+static void irc_net_connect_cb (struct sock_stream *sock, struct error_info *conn_err, void *arg)
+{
+    struct irc_net *net = arg;
+    struct error_info err;
+    
+    if (conn_err) {
+        // attempt reconnect later
+        log_err_info(conn_err, "connect failed");
+        
+        if (irc_net_connect(net, false, &err))
+            log_err_info(&err, "unable to reconnect");
+
+    } else {
+        // yay
+        if (irc_net_connected(net, sock, &err))
+            log_err_info(&err, "irc_net_connected");
+
+    }
+}
+
+/**
+ * The low-level connect() implementation, connects based on irc_net::info, calling irc_net_connected/irc_net_reconnect
+ * later if this succeeds.
+ */
+static err_t irc_net_do_connect (struct irc_net *net, struct error_info *err)
+{
+    struct irc_net_info *info = &net->info;
+    struct sock_stream *sock = NULL;
+
+    // sanity check
+    assert(!net->connecting && !net->connected);
+
+    // connect based on what's known
+    if (info->raw_sock) {
+        log_debug("connected using raw socket: %p", info->raw_sock);
+
+        // direct sock_stream connection
+        sock = info->raw_sock;
+
+        // then create the conn right away
+        if (irc_net_connected(net, sock, err))
+            goto error;
+
+    } else if (info->ssl_cred) {
+        // aquire a ref
+        // NOTE: before any error handling
+        sock_ssl_client_cred_get(info->ssl_cred);
+        
+        log_debug("connecting to [%s]:%s using SSL", info->hostname, info->service);
+
+        // connect
+        if (sock_ssl_connect_async(&sock, info->hostname, info->service, info->ssl_cred, &irc_net_connect_cb, net, err))
+            goto error;
+
+        net->connecting = true;
+
+    } else {
+        log_debug("connecting to [%s]:%s", info->hostname, info->service);
+            
+        // begin async connect
+        if (sock_tcp_connect_async(&sock, info->hostname, info->service, &irc_net_connect_cb, net, err))
+            goto error;
+        
+        net->connecting = true;
+
+    }
+
+    return SUCCESS;
+
+error:
+    return ERROR_CODE(err);    
+}
+
+/**
+ * Reconnect timer callback
+ */
+static void irc_net_reconnect_timer_cb (int fd, short what, void *arg)
+{
+    struct irc_net *net = arg;
+    struct error_info err;
+
+    (void) fd;
+    (void) what;
+
+    // execute it?
+    if (irc_net_connect(net, true, &err))
+        log_err_info(&err, "unable to reconnect");
+}
+
+// XXX: to get the ev_base
+#include "sock_internal.h"
+
+/**
+ * Schedule a reconnection attempt in IRC_NET_RECONNECT_INTERVAL.
+ */
+static err_t irc_net_schedule_reconnect (struct irc_net *net, struct error_info *err)
+{   
+    // ...for x seconds
+    struct timeval tv = { IRC_NET_RECONNECT_INTERVAL, 0 };
+    
+    // schedule
+    if (event_add(net->reconnect_timer, &tv))
+        return SET_ERROR(err, ERR_EVENT_ADD);
+
+    // oke
+    return SUCCESS;
+}
+
+err_t irc_net_connect (struct irc_net *net, bool now, struct error_info *err)
+{
+    // attempt to reconnect now?
+    if (now) {
+        if (irc_net_do_connect(net, err))
+            // log error and continue below with schedule_reconnect
+            log_err_info(err, "reconnect failed");
+
+        else
+            // connecting, done
+            return SUCCESS;
+    }
+    
+    // schedule for later
+    if (irc_net_schedule_reconnect(net, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    return ERROR_CODE(err);
+}
+
+err_t irc_net_connect_init (struct irc_net *net, struct error_info *err)
+{
+    // look up the ev_base 
+    struct event_base *ev_base = _sock_stream_ctx.ev_base;
+ 
+    // reconnect timer
+    if ((net->reconnect_timer = evtimer_new(ev_base, irc_net_reconnect_timer_cb, net)) == NULL)
+        return SET_ERROR(err, ERR_EVENT_NEW);
+    
+    // ok
+    return SUCCESS;
+}
+
+void irc_net_connect_destroy (struct irc_net *net)
+{
+    event_free(net->reconnect_timer);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_net_internal.h	Fri Apr 24 23:01:34 2009 +0300
@@ -0,0 +1,44 @@
+#ifndef IRC_NET_INTERNAL_H
+#define IRC_NET_INTERNAL_H
+
+/**
+ * Private irc_net interface
+ */
+#include "irc_net.h"
+
+/**
+ * Our callbacks for irc_conn
+ */
+extern struct irc_conn_callbacks irc_net_conn_callbacks;
+
+/**
+ * Our irc_conn::handlers list
+ */
+extern struct irc_cmd_handler irc_net_cmd_handlers[];
+
+/**
+ * Destroy our irc_conn, and mark ourselves as disconnected.
+ */
+void irc_net_disconnect (struct irc_net *net, struct error_info *err);
+
+/**
+ * Fixed delay between reconnection attempts in seconds
+ */
+#define IRC_NET_RECONNECT_INTERVAL (5 * 60)
+
+/**
+ * Establish a new connection to our server, either right away (if \a now give), or after IRC_NET_RECONNETC_INTERVAL.
+ */
+err_t irc_net_connect (struct irc_net *net, bool now, struct error_info *err);
+
+/**
+ * Initialize the connect state
+ */
+err_t irc_net_connect_init (struct irc_net *net, struct error_info *err);
+
+/**
+ * Destroy the connect state
+ */
+void irc_net_connect_destroy (struct irc_net *net);
+
+#endif