1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 #include "mod_watchdog.h"
21 #include "ap_provider.h"
23 #include "http_core.h"
24 #include "util_mutex.h"
26 #define AP_WATCHODG_PGROUP "watchdog"
27 #define AP_WATCHODG_PVERSION "parent"
28 #define AP_WATCHODG_CVERSION "child"
30 typedef struct watchdog_list_t watchdog_list_t;
32 struct watchdog_list_t
34 struct watchdog_list_t *next;
37 apr_interval_time_t interval;
38 apr_interval_time_t step;
40 ap_watchdog_callback_fn_t *callback_fn;
45 apr_thread_mutex_t *startup;
46 apr_proc_mutex_t *mutex;
48 watchdog_list_t *callbacks;
52 apr_interval_time_t step;
57 typedef struct wd_server_conf_t wd_server_conf_t;
58 struct wd_server_conf_t
66 static wd_server_conf_t *wd_server_conf = NULL;
67 static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL;
68 static int wd_interval_set = 0;
69 static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED;
70 static const char *wd_proc_mutex_type = "watchdog-callback";
72 static apr_status_t wd_worker_cleanup(void *data)
75 ap_watchdog_t *w = (ap_watchdog_t *)data;
78 watchdog_list_t *wl = w->callbacks;
80 if (wl->status == APR_SUCCESS) {
81 /* Execute watchdog callback with STOPPING state */
82 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
83 (void *)wl->data, w->pool);
90 apr_thread_join(&rv, w->thread);
94 /*--------------------------------------------------------------------------*/
96 /* Main watchdog worker thread. */
97 /* For singleton workers child thread that first obtains the process */
98 /* mutex is running. Threads in other child's are locked on mutex. */
100 /*--------------------------------------------------------------------------*/
101 static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
103 ap_watchdog_t *w = (ap_watchdog_t *)data;
110 w->pool = apr_thread_pool_get(thread);
113 apr_thread_mutex_unlock(w->startup);
115 while (w->is_running) {
116 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
120 if (mpmq_s == AP_MPMQ_STOPPING) {
124 rv = apr_proc_mutex_trylock(w->mutex);
125 if (rv == APR_SUCCESS) {
127 /* Sleep after we were locked
128 * up to 1 second. Httpd can be
129 * in the middle of shutdown, and
130 * our child didn't yet received
131 * the shutdown signal.
134 while (w->is_running && probed > 0) {
135 apr_sleep(AP_WD_TM_INTERVAL);
137 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
141 if (mpmq_s == AP_MPMQ_STOPPING) {
151 apr_sleep(AP_WD_TM_SLICE);
155 watchdog_list_t *wl = w->callbacks;
156 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
157 "%sWatchdog (%s) running (%" APR_PID_T_FMT ")",
158 w->singleton ? "Singleton" : "",
160 apr_time_clock_hires(w->pool);
162 apr_pool_t *ctx = NULL;
163 apr_pool_create(&ctx, w->pool);
164 while (wl && w->is_running) {
165 /* Execute watchdog callback */
166 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING,
167 (void *)wl->data, ctx);
170 apr_pool_destroy(ctx);
173 ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
178 /* Main execution loop */
179 while (w->is_running) {
180 apr_pool_t *ctx = NULL;
182 watchdog_list_t *wl = w->callbacks;
184 apr_sleep(AP_WD_TM_SLICE);
185 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
188 if (mpmq_s == AP_MPMQ_STOPPING) {
191 if (!w->is_running) {
194 curr = apr_time_now() - AP_WD_TM_SLICE;
195 while (wl && w->is_running) {
196 if (wl->status == APR_SUCCESS) {
197 wl->step += (apr_time_now() - curr);
198 if (wl->step >= wl->interval) {
200 apr_pool_create(&ctx, w->pool);
202 /* Execute watchdog callback */
203 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING,
204 (void *)wl->data, ctx);
205 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
208 if (mpmq_s == AP_MPMQ_STOPPING) {
215 if (w->is_running && w->callbacks == NULL) {
216 /* This is hook mode watchdog
217 * running on WatchogInterval
219 w->step += (apr_time_now() - curr);
220 if (w->step >= wd_interval) {
222 apr_pool_create(&ctx, w->pool);
224 /* Run watchdog step hook */
225 ap_run_watchdog_step(wd_server_conf->s, w->name, ctx);
229 apr_pool_destroy(ctx);
230 if (!w->is_running) {
235 /* Run the watchdog exit hooks.
236 * If this was singleton watchdog the init hook
237 * might never been called, so skip the exit hook
238 * in that case as well.
240 ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool);
243 watchdog_list_t *wl = w->callbacks;
245 if (wl->status == APR_SUCCESS) {
246 /* Execute watchdog callback with STOPPING state */
247 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
248 (void *)wl->data, w->pool);
253 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
254 "%sWatchdog (%s) stopping (%" APR_PID_T_FMT ")",
255 w->singleton ? "Singleton" : "",
259 apr_proc_mutex_unlock(w->mutex);
260 apr_thread_exit(w->thread, APR_SUCCESS);
265 static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
269 /* Create thread startup mutex */
270 rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p);
271 if (rc != APR_SUCCESS)
275 /* Initialize singleton mutex in child */
276 rc = apr_proc_mutex_child_init(&w->mutex,
277 apr_proc_mutex_lockfile(w->mutex), p);
278 if (rc != APR_SUCCESS)
282 /* This mutex fixes problems with a fast start/fast end, where the pool
283 * cleanup was being invoked before the thread completely spawned.
285 apr_thread_mutex_lock(w->startup);
286 apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
288 /* Start the newly created watchdog */
289 rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p);
291 apr_pool_cleanup_kill(p, w, wd_worker_cleanup);
294 apr_thread_mutex_lock(w->startup);
295 apr_thread_mutex_unlock(w->startup);
296 apr_thread_mutex_destroy(w->startup);
301 static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog,
308 const char *pver = parent ? AP_WATCHODG_PVERSION : AP_WATCHODG_CVERSION;
310 if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) {
311 /* Parent threads are not supported for
317 w = ap_lookup_provider(AP_WATCHODG_PGROUP, name, pver);
322 w = apr_pcalloc(p, sizeof(ap_watchdog_t));
325 w->singleton = parent ? 0 : singleton;
327 return ap_register_provider(p, AP_WATCHODG_PGROUP, name,
331 static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w,
332 apr_interval_time_t interval,
334 ap_watchdog_callback_fn_t *callback)
336 watchdog_list_t *c = w->callbacks;
337 apr_status_t rv = APR_EOF;
340 if (c->data == data && c->callback_fn == callback) {
341 /* We have existing callback.
342 * Update the interval and reset status, so the
343 * callback and continue execution if stopped earlier.
345 c->interval = interval;
347 c->status = APR_SUCCESS;
356 static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w,
357 apr_interval_time_t interval,
359 ap_watchdog_callback_fn_t *callback)
361 watchdog_list_t *c = w->callbacks;
364 if (c->data == data && c->callback_fn == callback) {
365 /* We have already registered callback.
366 * Do not allow callbacks that have the same
367 * function and data pointers.
373 c = apr_palloc(w->pool, sizeof(watchdog_list_t));
375 c->callback_fn = callback;
376 c->interval = interval;
378 c->status = APR_EINIT;
381 c->next = w->callbacks;
388 /*--------------------------------------------------------------------------*/
390 /* Pre config hook. */
391 /* Create default watchdogs for parent and child */
392 /* Parent watchdog executes inside parent proces so it doesn't need the */
393 /* singleton mutex */
395 /*--------------------------------------------------------------------------*/
396 static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
402 ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_is_forked);
403 if ((rv = ap_watchdog_get_instance(&w,
404 AP_WATCHDOG_SINGLETON, 0, 1, pconf)) != APR_SUCCESS) {
407 if ((rv = ap_watchdog_get_instance(&w,
408 AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) {
411 if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) {
412 /* Create parent process watchdog for
413 * non forked mpm's only.
415 if ((rv = ap_watchdog_get_instance(&w,
416 AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) {
421 if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL,
422 APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) {
429 /*--------------------------------------------------------------------------*/
431 /* Post config hook. */
432 /* Create watchdog thread in parent and initializes Watchdog module */
434 /*--------------------------------------------------------------------------*/
435 static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
436 apr_pool_t *ptemp, server_rec *s)
439 const char *pk = "watchdog_init_module_tag";
440 apr_pool_t *pproc = s->process->pool;
441 const apr_array_header_t *wl;
443 apr_pool_userdata_get((void *)&wd_server_conf, pk, pproc);
444 if (!wd_server_conf) {
445 if (!(wd_server_conf = apr_pcalloc(pproc, sizeof(wd_server_conf_t))))
447 apr_pool_create(&wd_server_conf->pool, pproc);
450 if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
451 /* First time config phase -- skip. */
456 const char *ppid = getenv("AP_PARENT_PID");
458 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
459 "[%" APR_PID_T_FMT " - %s] "
460 "child second stage post config hook",
466 wd_server_conf->s = s;
467 if ((wl = ap_list_provider_names(pconf, AP_WATCHODG_PGROUP,
468 AP_WATCHODG_PVERSION))) {
469 const ap_list_provider_names_t *wn;
472 wn = (ap_list_provider_names_t *)wl->elts;
473 for (i = 0; i < wl->nelts; i++) {
474 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
476 AP_WATCHODG_PVERSION);
479 int status = ap_run_watchdog_need(s, w->name, 1,
482 /* One of the modules returned OK to this watchog.
489 /* We have active watchdog.
490 * Create the watchdog thread
492 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
493 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
494 "Watchdog: Failed to create parent worker thread.");
497 wd_server_conf->parent_workers++;
502 if (wd_server_conf->parent_workers) {
503 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
504 "Spawned %d parent worker threads.",
505 wd_server_conf->parent_workers);
507 if ((wl = ap_list_provider_names(pconf, AP_WATCHODG_PGROUP,
508 AP_WATCHODG_CVERSION))) {
509 const ap_list_provider_names_t *wn;
512 wn = (ap_list_provider_names_t *)wl->elts;
513 for (i = 0; i < wl->nelts; i++) {
514 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
516 AP_WATCHODG_CVERSION);
519 int status = ap_run_watchdog_need(s, w->name, 0,
522 /* One of the modules returned OK to this watchog.
529 /* We have some callbacks registered.
530 * Create mutexes for singleton watchdogs
533 rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type,
535 wd_server_conf->pool, 0);
536 if (rv != APR_SUCCESS) {
540 wd_server_conf->child_workers++;
548 /*--------------------------------------------------------------------------*/
550 /* Child init hook. */
551 /* Create watchdog threads and initializes Mutexes in child */
553 /*--------------------------------------------------------------------------*/
554 static void wd_child_init_hook(apr_pool_t *p, server_rec *s)
557 const apr_array_header_t *wl;
559 if (!wd_server_conf->child_workers) {
560 /* We don't have anything configured, bail out.
564 if ((wl = ap_list_provider_names(p, AP_WATCHODG_PGROUP,
565 AP_WATCHODG_CVERSION))) {
566 const ap_list_provider_names_t *wn;
568 wn = (ap_list_provider_names_t *)wl->elts;
569 for (i = 0; i < wl->nelts; i++) {
570 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
572 AP_WATCHODG_CVERSION);
573 if (w && w->active) {
574 /* We have some callbacks registered.
575 * Kick of the watchdog
577 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
578 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
579 "Watchdog: Failed to create worker thread.");
580 /* No point to continue */
588 /*--------------------------------------------------------------------------*/
590 /* WatchdogInterval directive */
592 /*--------------------------------------------------------------------------*/
593 static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy,
597 const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY);
602 return "Duplicate WatchdogInterval directives are not allowed";
603 if ((i = atoi(arg)) < 1)
604 return "Invalid WatchdogInterval value";
606 wd_interval = apr_time_from_sec(i);
611 /*--------------------------------------------------------------------------*/
613 /* List of directives specific to our module. */
615 /*--------------------------------------------------------------------------*/
616 static const command_rec wd_directives[] =
619 "WatchdogInterval", /* directive name */
620 wd_cmd_watchdog_int, /* config action routine */
621 NULL, /* argument to include in call */
622 RSRC_CONF, /* where available */
623 "Watchdog interval in seconds"
628 /*--------------------------------------------------------------------------*/
630 /* Which functions are responsible for which hooks in the server. */
632 /*--------------------------------------------------------------------------*/
633 static void wd_register_hooks(apr_pool_t *p)
636 /* Only the mpm_winnt has child init hook handler.
637 * Make sure that we are called after the mpm child init handler
640 static const char *const after_mpm[] = { "mpm_winnt.c", NULL};
642 /* Pre config handling
644 ap_hook_pre_config(wd_pre_config_hook,
649 /* Post config handling
651 ap_hook_post_config(wd_post_config_hook,
658 ap_hook_child_init(wd_child_init_hook,
663 APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance);
664 APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback);
665 APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval);
668 /*--------------------------------------------------------------------------*/
670 /* The list of callback routines and data structures that provide */
671 /* the static hooks into our module from the other parts of the server. */
673 /*--------------------------------------------------------------------------*/
674 AP_DECLARE_MODULE(watchdog) = {
675 STANDARD20_MODULE_STUFF,
676 NULL, /* create per-directory config structure */
677 NULL, /* merge per-directory config structures */
678 NULL, /* create per-server config structure */
679 NULL, /* merge per-server config structures */
680 wd_directives, /* command apr_table_t */
681 wd_register_hooks /* register hooks */
684 /*--------------------------------------------------------------------------*/
686 /* The list of optional hooks that we provide */
688 /*--------------------------------------------------------------------------*/
690 APR_HOOK_LINK(watchdog_need)
691 APR_HOOK_LINK(watchdog_init)
692 APR_HOOK_LINK(watchdog_exit)
693 APR_HOOK_LINK(watchdog_step)
696 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need,
697 (server_rec *s, const char *name,
698 int parent, int singleton),
699 (s, name, parent, singleton),
701 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init,
702 (server_rec *s, const char *name,
706 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit,
707 (server_rec *s, const char *name,
711 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step,
712 (server_rec *s, const char *name,