diff --git a/doc/mod_debug.xml b/doc/mod_debug.xml index ddadad6..d897a87 100644 --- a/doc/mod_debug.xml +++ b/doc/mod_debug.xml @@ -25,4 +25,33 @@ + + + shows a plain text list of all events + + this is a very low level debug tool for developers. + + + + if req.path == "/debug/events" { debug.show_events; } + + + + + + time in seconds after start of shutdown to log remaining active events + + timeout after which to display events (default: disabled) + + + + this is a very low level debug tool for developers; it shows which event listeners keep lighttpd2 alive when it should stop. + + + + + setup { debug.show_events_after_shutdown 5; } + + + diff --git a/include/lighttpd/events.h b/include/lighttpd/events.h index 82cc302..b1a7683 100644 --- a/include/lighttpd/events.h +++ b/include/lighttpd/events.h @@ -190,6 +190,7 @@ INLINE liEventPrepare* li_event_prepare_from(liEventBase *base); LI_API void li_event_check_init(liEventLoop *loop, const char *event_name, liEventCheck *check, liEventCallback callback); INLINE liEventCheck* li_event_check_from(liEventBase *base); +LI_API const char* li_event_type_string(liEventType type); /* inline implementations */ diff --git a/src/common/events.c b/src/common/events.c index e0817cb..214c1da 100644 --- a/src/common/events.c +++ b/src/common/events.c @@ -391,3 +391,25 @@ void li_event_check_init(liEventLoop *loop, const char *event_name, liEventCheck if (NULL != loop) li_event_attach(loop, check); li_event_start(check); } + +const char* li_event_type_string(liEventType type) { + switch (type) { + case LI_EVT_NONE: + return "none"; + case LI_EVT_IO: + return "io"; + case LI_EVT_TIMER: + return "timer"; + case LI_EVT_ASYNC: + return "async"; + case LI_EVT_CHILD: + return "child"; + case LI_EVT_SIGNAL: + return "signal"; + case LI_EVT_PREPARE: + return "prepare"; + case LI_EVT_CHECK: + return "check"; + } + return "INVALID"; +} diff --git a/src/modules/mod_debug.c b/src/modules/mod_debug.c index e728bd4..fa0c3d5 100644 --- a/src/modules/mod_debug.c +++ b/src/modules/mod_debug.c @@ -63,6 +63,21 @@ struct mod_debug_job_t { }; typedef struct mod_debug_job_t mod_debug_job_t; +struct plugin_debug_worker_data { + liWorker *wrk; + struct plugin_debug_data *pd; + liEventAsync stop_listen; + liEventTimer stop_listen_timeout; +}; +typedef struct plugin_debug_worker_data plugin_debug_worker_data; + +struct plugin_debug_data { + int stop_listen_timeout_seconds; + plugin_debug_worker_data *worker_data; +}; +typedef struct plugin_debug_data plugin_debug_data; + + /* the CollectFunc */ static gpointer debug_collect_func(liWorker *wrk, gpointer fdata) { GArray *cons; @@ -353,6 +368,178 @@ static liAction* debug_profiler_dump_create(liServer *srv, liWorker *wrk, liPlug } #endif +/* if show_all is FALSE only active events that keep the loop alive are shown */ +static void log_events(liWorker *wrk, liLogContext *context, gboolean show_all) { + GList *lnk; + + for (lnk = wrk->loop.watchers.head; NULL != lnk; lnk = lnk->next) { + liEventBase *base = LI_CONTAINER_OF(lnk, liEventBase, link_watchers); + gboolean active = li_event_active_(base); + + if (show_all || (active && base->keep_loop_alive)) { + _ERROR(wrk->srv, wrk, context, + "Event listener for worker %i: '%s' (%s %s)%s", + wrk->ndx, + base->event_name, + active ? "active" : "inactive", + li_event_type_string(base->type), + base->keep_loop_alive + ? (active + ? "" + : " [doesn't keep loop alive]") + : " [does never keep loop alive]"); + } + } +} + +/* if show_all is FALSE only active events that keep the loop alive are shown */ +static void format_events(GString *out, liWorker *wrk, gboolean show_all) { + GList *lnk; + g_string_truncate(out, 0); + + for (lnk = wrk->loop.watchers.head; NULL != lnk; lnk = lnk->next) { + liEventBase *base = LI_CONTAINER_OF(lnk, liEventBase, link_watchers); + gboolean active = li_event_active_(base); + + if (show_all || (active && base->keep_loop_alive)) { + g_string_append_printf(out, + "Event listener for worker %i: '%s' (%s %s)%s\n", + wrk->ndx, + base->event_name, + active ? "active" : "inactive", + li_event_type_string(base->type), + base->keep_loop_alive + ? (active + ? "" + : " [doesn't keep loop alive]") + : " [does never keep loop alive]"); + } + } +} + +struct collect_events_job { + liVRequest *vr; + gpointer *context; + gboolean show_all; +}; +typedef struct collect_events_job collect_events_job; + +/* the CollectFunc */ +static gpointer debug_show_events_func(liWorker *wrk, gpointer fdata) { + collect_events_job *job = fdata; + GString *out = g_string_sized_new(0); + + format_events(out, wrk, job->show_all); + + return out; +} + +/* the CollectCallback */ +static void debug_show_events_cb(gpointer cbdata, gpointer fdata, GPtrArray *result, gboolean complete) { + collect_events_job *job = fdata; + liVRequest *vr; + UNUSED(cbdata); + + if (complete) { + vr = job->vr; + /* clear context so it doesn't get cleaned up anymore */ + *(job->context) = NULL; + + if (li_vrequest_handle_direct(vr)) { + guint i; + + li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain; charset=utf-8")); + vr->response.http_status = 200; + + for (i = 0; i < result->len; i++) { + GString *str = g_ptr_array_index(result, i); + g_ptr_array_index(result, i) = NULL; + li_chunkqueue_append_string(vr->direct_out, str); + } + + li_vrequest_joblist_append(vr); + } + } + + { + guint i; + + for (i = 0; i < result->len; i++) { + GString *str = g_ptr_array_index(result, i); + if (NULL != str) g_string_free(str, TRUE); + } + + g_slice_free(collect_events_job, job); + } +} + +static liHandlerResult debug_show_events_cleanup(liVRequest *vr, gpointer param, gpointer context) { + liCollectInfo *ci = (liCollectInfo*) context; + + UNUSED(vr); + UNUSED(param); + + li_collect_break(ci); + + return LI_HANDLER_GO_ON; +} + +static liHandlerResult debug_show_events(liVRequest *vr, gpointer param, gpointer *context) { + UNUSED(param); + + switch (vr->request.http_method) { + case LI_HTTP_METHOD_GET: + case LI_HTTP_METHOD_HEAD: + break; + default: + return LI_HANDLER_GO_ON; + } + + if (NULL != *context) + return LI_HANDLER_WAIT_FOR_EVENT; + + if (!li_vrequest_is_handled(vr)) { + liCollectInfo *ci; + collect_events_job *job = g_slice_new0(collect_events_job); + job->vr = vr; + job->context = context; + job->show_all = TRUE; + + VR_DEBUG(vr, "%s", "collecting events info..."); + + ci = li_collect_start(vr->wrk, debug_show_events_func, job, debug_show_events_cb, NULL); + *context = ci; /* may be NULL */ + } + + return (*context) ? LI_HANDLER_WAIT_FOR_EVENT : LI_HANDLER_GO_ON; +} + +static liAction* debug_show_events_create(liServer *srv, liWorker *wrk, liPlugin* p, liValue *val, gpointer userdata) { + UNUSED(wrk); UNUSED(userdata); UNUSED(p); + + if (!li_value_is_nothing(val)) { + ERROR(srv, "%s", "debug.show_events doesn't expect any parameters"); + return NULL; + } + + return li_action_new_function(debug_show_events, debug_show_events_cleanup, NULL, NULL); +} + +static gboolean debug_show_events_after_shutdown(liServer *srv, liPlugin* p, liValue *val, gpointer userdata) { + plugin_debug_data *pd = p->data; + UNUSED(userdata); + + val = li_value_get_single_argument(val); + + if (LI_VALUE_NUMBER != li_value_type(val)) { + ERROR(srv, "debug.show_events_after_shutdown expected number, got %s", li_value_type_string(val)); + return FALSE; + } + + pd->stop_listen_timeout_seconds = val->data.number; + + return TRUE; +} static const liPluginOption options[] = { { NULL, 0, 0, NULL } @@ -363,21 +550,104 @@ static const liPluginAction actions[] = { #ifdef WITH_PROFILER { "debug.profiler_dump", debug_profiler_dump_create, NULL }, #endif + { "debug.show_events", debug_show_events_create, NULL }, { NULL, NULL, NULL } }; static const liPluginSetup setups[] = { + { "debug.show_events_after_shutdown", debug_show_events_after_shutdown, NULL }, + { NULL, NULL, NULL } }; +static void plugin_debug_stop_listen_timeout(liEventBase *watcher, int events) { + plugin_debug_worker_data *pwd = LI_CONTAINER_OF(li_event_timer_from(watcher), plugin_debug_worker_data, stop_listen_timeout); + UNUSED(events); + + ERROR(pwd->wrk->srv, "Couldn't suspend yet, checking events for worker %i:", pwd->wrk->ndx); + log_events(pwd->wrk, NULL, FALSE); +} + +static void plugin_debug_worker_stop_listen(liEventBase *watcher, int events) { + plugin_debug_worker_data *pwd = LI_CONTAINER_OF(li_event_async_from(watcher), plugin_debug_worker_data, stop_listen); + plugin_debug_data *pd = pwd->pd; + UNUSED(events); + + if (!li_event_attached(&pwd->stop_listen_timeout)) { + li_event_attach(&pwd->wrk->loop, &pwd->stop_listen_timeout); + li_event_timer_once(&pwd->stop_listen_timeout, pd->stop_listen_timeout_seconds); + } +} + +static void plugin_debug_prepare_worker(liServer *srv, liPlugin *p, liWorker *wrk) { + plugin_debug_data *pd = p->data; + plugin_debug_worker_data *pwd; + UNUSED(srv); + + if (pd->stop_listen_timeout_seconds < 0) return; + + pwd = &pd->worker_data[wrk->ndx]; + pwd->pd = pd; + pwd->wrk = wrk; + + li_event_async_init(&wrk->loop, "mod_debug stop_listen", &pwd->stop_listen, plugin_debug_worker_stop_listen); + li_event_timer_init(NULL, "mod_debug stop_listen_timeout", &pwd->stop_listen_timeout, plugin_debug_stop_listen_timeout); + li_event_set_keep_loop_alive(&pwd->stop_listen_timeout, FALSE); +} + +static void plugin_debug_prepare(liServer *srv, liPlugin *p) { + plugin_debug_data *pd = p->data; + + if (pd->stop_listen_timeout_seconds >= 0) { + pd->worker_data = g_new0(plugin_debug_worker_data, srv->worker_count); + } +} + +static void plugin_debug_stop_listen(liServer *srv, liPlugin *p) { + plugin_debug_data *pd = p->data; + + if (NULL != pd->worker_data) { + unsigned int i; + for (i = 0; i < srv->worker_count; ++i) { + plugin_debug_worker_data *pwd = &pd->worker_data[i]; + if (NULL == pwd->wrk) continue; + li_event_async_send(&pwd->stop_listen); + } + } +} + +static void plugin_debug_free(liServer *srv, liPlugin *p) { + plugin_debug_data *pd = p->data; + + if (NULL != pd->worker_data) { + unsigned int i; + for (i = 0; i < srv->worker_count; ++i) { + plugin_debug_worker_data *pwd = &pd->worker_data[i]; + li_event_clear(&pwd->stop_listen); + li_event_clear(&pwd->stop_listen_timeout); + } + g_free(pd->worker_data); + } + g_free(pd); +} static void plugin_debug_init(liServer *srv, liPlugin *p, gpointer userdata) { + plugin_debug_data *pd = g_malloc0(sizeof(plugin_debug_data)); UNUSED(srv); UNUSED(userdata); p->options = options; p->actions = actions; p->setups = setups; + + p->data = pd; + + pd->stop_listen_timeout_seconds = -1; /* disabled by default */ + + p->free = plugin_debug_free; + p->handle_stop_listen = plugin_debug_stop_listen; + p->handle_prepare = plugin_debug_prepare; + p->handle_prepare_worker = plugin_debug_prepare_worker; }