implement module unloading, although module_destroy is currently never called
authorTero Marttila <terom@fixme.fi>
Mon, 16 Mar 2009 23:34:05 +0200
changeset 70 a9a4c5e6aa30
parent 69 6f298b6e0d5f
child 71 0a13030f795d
implement module unloading, although module_destroy is currently never called
src/irc_log.c
src/module.c
src/module.h
src/nexus.c
src/nexus.h
src/signals.h
--- a/src/irc_log.c	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/irc_log.c	Mon Mar 16 23:34:05 2009 +0200
@@ -18,6 +18,14 @@
 
     /** The database connection */
     struct evsql *db;
+
+    /**
+     * Our logged channels
+     */
+    TAILQ_HEAD(irc_log_ctx_channels, irc_log_chan) channels;
+    
+    /** Are we currently unloading ourself (via irc_log_unload) ? */
+    bool unloading;
 };
 
 /**
@@ -29,36 +37,80 @@
 
     /** The target channel */
     struct irc_chan *chan;
+
+    /** Are we stopping (irc_log_chan_stop called)? */
+    bool stopping;
+
+    /** We are part of the irc_log_ctx.channels list */
+    TAILQ_ENTRY(irc_log_chan) ctx_channels;
 };
 
+/*
+ * Forward-declare
+ */
+static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx);
+
+/**
+ * The irc_log_ctx has completed stopping all the channels, and should now destroy itself
+ */
+static void irc_log_stopped (struct irc_log_ctx *ctx)
+{
+    // schedule an evsql_destroy
+    if (evsql_destroy_next(ctx->db))
+        log_fatal("evsql_destroy_next failed");
+    
+    // free ourself
+    free(ctx);
+}
+
+/**
+ * The irc_log_chan has completed shutdown
+ */
+static void irc_log_chan_stopped (struct irc_log_chan *chan_ctx)
+{
+    struct irc_log_ctx *ctx = chan_ctx->ctx;
+
+    // destroy the channel
+    irc_log_chan_destroy(chan_ctx);
+
+    // was it the final channel for unloading?
+    if (ctx->unloading && TAILQ_EMPTY(&ctx->channels))
+        irc_log_stopped(ctx);
+}
+
 /**
  * Our evsql result handler for irc_log_event INSERTs.
  */
 static void irc_log_on_sql_result (struct evsql_result *res, void *arg)
 {
-    struct irc_log_ctx *ctx = arg;
+    struct irc_log_chan *chan_ctx = arg;
     err_t err;
 
-    (void) ctx;
-
+    // check errors
     if ((err = evsql_result_check(res)))
         log_error("irc_log_event: %s", evsql_result_error(res));
 
-    // ok
+    // ok, don't need the result anymore
     evsql_result_free(res);
+
+    // if stopping, then handle that
+    if (chan_ctx->stopping)
+        irc_log_chan_stopped(chan_ctx);
 }
 
 /**
  * Log the event into our database.
  *
- * Any of source, target, message can be NULL to insert NULLs
+ * Any of chan_ctx, source, target, message can be NULL to insert NULLs
  */
-static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_chan *chan, const struct irc_nm *source,
+static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_log_chan *chan_ctx, const struct irc_nm *source,
         const char *type, const char *target, const char *message)
 {
     struct evsql_query *query;
     
     // unpack the nick/user/hostname args, as source can be NULL
+    const char *network = chan_ctx ? chan_ctx->chan->net->info.network : NULL;
+    const char *channel = chan_ctx ? irc_chan_name(chan_ctx->chan) : NULL;
     const char *nickname = source ? source->nickname : NULL;
     const char *username = source ? source->username : NULL;
     const char *hostname = source ? source->hostname : NULL;
@@ -90,16 +142,12 @@
     }
 
     // run the INSERT
-    if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, ctx,
-        chan->net->info.network, irc_chan_name(chan),
-        nickname, username, hostname,
-        type, target, message
+    if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, chan_ctx,
+        network, channel, nickname, username, hostname, type, target, message
     )) == NULL) {
         // XXX: get evsql_query error info somehow...
         log_error("evsql_query_exec failed for %s:%s - %s!%s@%s - %s -> %s - %s", 
-            chan->net->info.network, irc_chan_name(chan),
-            nickname, username, hostname,
-            type, target, message
+            network, channel, nickname, username, hostname, type, target, message
         );
 
         return ERR_EVSQL_QUERY_EXEC;
@@ -107,9 +155,7 @@
 
     // ok
     log_debug("%s:%s - %s!%s@%s - %s -> %s - %s", 
-        chan->net->info.network, irc_chan_name(chan),
-        nickname, username, hostname,
-        type, target, message
+        network, channel, nickname, username, hostname, type, target, message
     );
     
     return SUCCESS;
@@ -118,7 +164,7 @@
 /**
  * Parse the prefix into a nickmask and pass on to irc_log_event
  */
-static err_t irc_log_event_prefix (struct irc_log_ctx *ctx, struct irc_chan *chan, const char *prefix,
+static err_t irc_log_event_prefix (struct irc_log_ctx *ctx, struct irc_log_chan *chan_ctx, const char *prefix,
         const char *type, const char *target, const char *message)
 {
     char prefix_buf[IRC_PREFIX_MAX];
@@ -130,7 +176,7 @@
         return err;
 
     // log
-    return irc_log_event(ctx, chan, &nm, type, target, message);
+    return irc_log_event(ctx, chan_ctx, &nm, type, target, message);
 }
 
 /**
@@ -144,7 +190,7 @@
     
     const char *msg = line->args[1];
 
-    irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, msg);
+    irc_log_event_prefix(chan_ctx->ctx, chan_ctx, line->prefix, line->command, NULL, msg);
 }
 
 /**
@@ -175,7 +221,7 @@
     }
     
     // log
-    irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, message);
+    irc_log_event_prefix(chan_ctx->ctx, chan_ctx, line->prefix, line->command, NULL, message);
 }
 
 /**
@@ -190,7 +236,7 @@
     const char *target = line->args[1];    
     const char *msg = line->args[2];
 
-    irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, target, msg);
+    irc_log_event_prefix(chan_ctx->ctx, chan_ctx, line->prefix, line->command, target, msg);
 }
 
 /**
@@ -228,6 +274,9 @@
     irc_cmd_remove(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx);
     irc_chan_remove_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx);
 
+    // remove from channels list
+    TAILQ_REMOVE(&chan_ctx->ctx->channels, chan_ctx, ctx_channels);
+
     // free ourselves
     free(chan_ctx);
 }
@@ -247,6 +296,9 @@
     chan_ctx->ctx = ctx;
     chan_ctx->chan = chan;
 
+    // add to channels list
+    TAILQ_INSERT_TAIL(&ctx->channels, chan_ctx, ctx_channels);
+
     // add low-level handlers
     if ((ERROR_CODE(err) = irc_cmd_add(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx)))
         goto error;
@@ -257,7 +309,7 @@
     
     // log an OPEN message
     // XXX: move this to when we first JOIN the channel
-    if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx->chan, NULL, "OPEN", NULL, NULL)))
+    if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx, NULL, "OPEN", NULL, NULL)))
         goto error;
 
     // ok
@@ -273,6 +325,32 @@
 }
 
 /**
+ * Stop logging the given channel, shutting it down nicely and then releasing its resources.
+ *
+ * If shutting it down nicely fails, this just destroys it.
+ */
+static err_t irc_log_chan_stop (struct irc_log_chan *chan_ctx)
+{
+    err_t err;
+
+    // mark it as stopping, so the result callback knows to destroy it
+    chan_ctx->stopping = true;
+
+    // log the CLOSE event
+    if ((err = irc_log_event(chan_ctx->ctx, chan_ctx, NULL, "CLOSE", NULL, NULL)))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    // destroy it uncleanly
+    irc_log_chan_destroy(chan_ctx);
+
+    return err;
+}
+
+/**
  * Allocate and initialize an irc_log_ctx. This doesn't do very much, the real magic happens in irc_log_conf.
  */
 static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err)
@@ -286,6 +364,9 @@
     // store
     ctx->nexus = nexus;
 
+    // initialize
+    TAILQ_INIT(&ctx->channels);
+
     log_info("module initialized");
 
     // ok
@@ -360,10 +441,15 @@
 /**
  * Set a config option
  */
-static err_t irc_log_conf (void *mod_ctx, const char *name, char *value, struct error_info *err)
+static err_t irc_log_conf (void *_ctx, const char *name, char *value, struct error_info *err)
 {
-    struct irc_log_ctx *ctx = mod_ctx;
+    struct irc_log_ctx *ctx = _ctx;
 
+    // wrong state
+    if (ctx->unloading)
+        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module is being unloaded");
+    
+    // apply the config setting
     if (strcmp(name, "db_info") == 0) {
         return irc_log_conf_db_info(ctx, value, err);
 
@@ -379,10 +465,31 @@
 }
 
 /**
+ * Deinitialize, logging CLOSE events for all channels, and removing any hooks we've added
+ */
+static err_t irc_log_unload (void *_ctx)
+{
+    struct irc_log_ctx *ctx = _ctx;
+    struct irc_log_chan *chan_ctx;
+
+    // update our state to mark ourselves as unloading
+    ctx->unloading = true;
+
+    // stop logging each channel
+    TAILQ_FOREACH(chan_ctx, &ctx->channels, ctx_channels) {
+        irc_log_chan_stop(chan_ctx);
+    }
+    
+    // wait for all the channels to be stopped, which will call irc_log_stopped
+    return SUCCESS;
+}
+
+/**
  * The module function table
  */
 struct module_funcs irc_log_funcs = {
     .init       = &irc_log_init,
     .conf       = &irc_log_conf,
+    .unload     = &irc_log_unload,
 };
 
--- a/src/module.c	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/module.c	Mon Mar 16 23:34:05 2009 +0200
@@ -62,6 +62,7 @@
     
     // store
     module->info = *info;
+    module->modules = modules;
 
     // clear dlerrors
     (void) dlerror();
@@ -114,3 +115,44 @@
     // call the conf func
     return module->funcs->conf(module->ctx, name, value, err);
 }
+
+err_t module_unload (struct module *module)
+{
+    err_t err;
+    
+    // invoke the unload func
+    if ((err = module->funcs->unload(module->ctx)))
+        return err;
+
+    // ok
+    return SUCCESS;
+}
+
+void module_destroy (struct module *module)
+{
+    // remove from modules list
+    TAILQ_REMOVE(&module->modules->list, module, modules_list);
+
+    // unload the dl handle
+    if (dlclose(module->handle))
+        log_error("dlclose(%s): %s", module->info.name, dlerror());
+    
+    // free the module info
+    free(module);
+}
+
+err_t modules_unload (struct modules *modules)
+{
+    struct module *module;
+    err_t err;
+
+    // unload each module in turn
+    TAILQ_FOREACH(module, &modules->list, modules_list) {
+        if ((err = module_unload(module)))
+            log_warn("module_unload(%s) failed: %s", module->info.name, error_name(err));
+    }
+
+    // ok
+    return SUCCESS;
+}
+
--- a/src/module.h	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/module.h	Mon Mar 16 23:34:05 2009 +0200
@@ -55,6 +55,16 @@
      * @param err returned error info
      */
     err_t (*conf) (void *ctx, const char *name, char *value, struct error_info *err);
+
+    /**
+     * Unload the module, removing all handlers/callbacks added to the nexus' irc_client. This does not have to act
+     * immediately, and will still have access to all irc_* resources it had earlier, and may perform I/O to unload
+     * cleanly. But the unloading should complete reasonably quickly, after which all event handlers added by the
+     * module to the nexus' ev_base should have been removed, and resources released.
+     *
+     * @param ctx the module's context pointer as returned by init
+     */
+    err_t (*unload) (void *ctx);
 };
 
 /**
@@ -73,6 +83,9 @@
     /** The module context object */
     void *ctx;
 
+    /** Reference back to modules struct used to load this module */
+    struct modules *modules;
+
     /** Our entry in the list of modules */
     TAILQ_ENTRY(module) modules_list;
 };
@@ -147,10 +160,23 @@
 err_t module_conf (struct module *module, const char *name, char *value, struct error_info *err);
 
 /**
- * Unload a module
+ * Unload a module. This means calling its 'unload' func, which will then go about shutting down the module. This might
+ * not happen right away, though, so the module is not destroyed yet.
  *
- * XXX: not implemented
+ * XXX: currently the module is never destroyed, there needs to be a "module unload done" callback...
  */
 err_t module_unload (struct module *module);
 
+/**
+ * Destroy a module, releasing as many resources as possible
+ */
+void module_destroy (struct module *module);
+
+/**
+ * Unload all modules, this just calls module_unload for each module, logging errors as warnings.
+ *
+ * XXX: currently, this does not destroy any modules, or the modules state itself.
+ */
+err_t modules_unload (struct modules *modules);
+
 #endif
--- a/src/nexus.c	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/nexus.c	Mon Mar 16 23:34:05 2009 +0200
@@ -261,33 +261,23 @@
     (void) sig;
     (void) what;
     
-    if (ctx->client && !ctx->client->quitting) {
-        log_info("Quitting...");
-
-        // quit it
-        irc_client_quit(ctx->client, "Goodbye, cruel world ;(");
+    log_info("Quitting...");
 
-    } else {
-        log_error("Aborting");
-        
-        // die
-        if (ctx->client) {
-            irc_client_destroy(ctx->client);
-            ctx->client = NULL;
-        }
+    // unload the modules
+    modules_unload(ctx->modules);
 
-        // exit
-        event_base_loopexit(ctx->ev_base, NULL);
-    }
+    // quit the irc client
+    irc_client_quit(ctx->client, "Goodbye, cruel world ;(");
+    
+    // remove the signal handlers (ourself)
+    signals_free(ctx->signals);
 }
 
 int main (int argc, char **argv) 
 {
-    struct signals *signals;
+    struct nexus _nexus, *nexus = &_nexus;
     struct error_info err;
 
-    struct nexus _nexus, *nexus = &_nexus;
-
     // zero nexus
     memset(nexus, 0, sizeof(*nexus));
 
@@ -296,13 +286,13 @@
         FATAL("event_base_new");
     
     // initialize signal handlers
-    if ((ERROR_CODE(&err) = signals_create(&signals, nexus->ev_base)))
+    if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
         FATAL("signals_create");
 
     // add our signal handlers
     if (
-            (ERROR_CODE(&err) = signals_add(signals, SIGPIPE, &signals_ignore, signals))
-        ||  (ERROR_CODE(&err) = signals_add(signals, SIGINT, &on_sigint, nexus))
+            (ERROR_CODE(&err) = signals_add(nexus->signals, SIGPIPE, &signals_ignore, nexus->signals))
+        ||  (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, &on_sigint, nexus))
     )
         FATAL_ERROR(&err, "signals_add");
  
--- a/src/nexus.h	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/nexus.h	Mon Mar 16 23:34:05 2009 +0200
@@ -8,6 +8,7 @@
 struct nexus;
 
 #include <event2/event.h>
+#include "signals.h"
 #include "module.h"
 #include "irc_client.h"
 
@@ -18,6 +19,9 @@
     /** The libevent base */
     struct event_base *ev_base;
 
+    /** Our signal handlers */
+    struct signals *signals;
+
     /** Our loaded modules */
     struct modules *modules;
 
--- a/src/signals.h	Mon Mar 16 22:06:39 2009 +0200
+++ b/src/signals.h	Mon Mar 16 23:34:05 2009 +0200
@@ -49,7 +49,7 @@
 struct signals *signals_default (struct event_base *ev_base);
 
 /*
- * Free the resources/handlers associated with the given signal handler
+ * Free the resources/handlers associated with the given signal handler set
  */
 void signals_free (struct signals *signals);