# HG changeset patch # User Tero Marttila # Date 1237239245 -7200 # Node ID a9a4c5e6aa303b9910209cfa9abb4d6f40334211 # Parent 6f298b6e0d5f163ae4a33d1195d9178581291963 implement module unloading, although module_destroy is currently never called diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/irc_log.c --- 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, }; diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/module.c --- 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; +} + diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/module.h --- 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 diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/nexus.c --- 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"); diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/nexus.h --- 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 +#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; diff -r 6f298b6e0d5f -r a9a4c5e6aa30 src/signals.h --- 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);