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 "util_mutex.h"
25 #define AP_WATCHODG_PGROUP "watchdog"
26 #define AP_WATCHODG_PVERSION "parent"
27 #define AP_WATCHODG_CVERSION "child"
29 typedef struct watchdog_list_t watchdog_list_t;
31 struct watchdog_list_t
33 struct watchdog_list_t *next;
36 apr_interval_time_t interval;
37 apr_interval_time_t step;
39 ap_watchdog_callback_fn_t *callback_fn;
44 apr_thread_mutex_t *startup;
45 apr_proc_mutex_t *mutex;
47 watchdog_list_t *callbacks;
51 apr_interval_time_t step;
56 typedef struct wd_server_conf_t wd_server_conf_t;
57 struct wd_server_conf_t
65 static wd_server_conf_t *wd_server_conf = NULL;
66 static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL;
67 static int wd_interval_set = 0;
68 static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED;
69 static const char *wd_proc_mutex_type = "watchdog-callback";
71 static apr_status_t wd_worker_cleanup(void *data)
74 ap_watchdog_t *w = (ap_watchdog_t *)data;
77 watchdog_list_t *wl = w->callbacks;
79 if (wl->status == APR_SUCCESS) {
80 /* Execute watchdog callback with STOPPING state */
81 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
82 (void *)wl->data, w->pool);
89 apr_thread_join(&rv, w->thread);
93 /*--------------------------------------------------------------------------*/
95 /* Main watchdog worker thread. */
96 /* For singleton workers child thread that first obtains the process */
97 /* mutex is running. Threads in other child's are locked on mutex. */
99 /*--------------------------------------------------------------------------*/
100 static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
102 ap_watchdog_t *w = (ap_watchdog_t *)data;
109 w->pool = apr_thread_pool_get(thread);
112 apr_thread_mutex_unlock(w->startup);
114 while (w->is_running) {
115 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
119 if (mpmq_s == AP_MPMQ_STOPPING) {
123 rv = apr_proc_mutex_trylock(w->mutex);
124 if (rv == APR_SUCCESS) {
126 /* Sleep after we were locked
127 * up to 1 second. Httpd can be
128 * in the middle of shutdown, and
129 * our child didn't yet received
130 * the shutdown signal.
133 while (w->is_running && probed > 0) {
134 apr_sleep(AP_WD_TM_INTERVAL);
136 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
140 if (mpmq_s == AP_MPMQ_STOPPING) {
150 apr_sleep(AP_WD_TM_SLICE);
154 watchdog_list_t *wl = w->callbacks;
155 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
156 "%sWatchdog (%s) running (%" APR_PID_T_FMT ")",
157 w->singleton ? "Singleton" : "",
159 apr_time_clock_hires(w->pool);
161 apr_pool_t *ctx = NULL;
162 apr_pool_create(&ctx, w->pool);
163 while (wl && w->is_running) {
164 /* Execute watchdog callback */
165 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING,
166 (void *)wl->data, ctx);
169 apr_pool_destroy(ctx);
172 ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
177 /* Main execution loop */
178 while (w->is_running) {
179 apr_pool_t *ctx = NULL;
181 watchdog_list_t *wl = w->callbacks;
183 apr_sleep(AP_WD_TM_SLICE);
184 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
187 if (mpmq_s == AP_MPMQ_STOPPING) {
190 if (!w->is_running) {
193 curr = apr_time_now() - AP_WD_TM_SLICE;
194 while (wl && w->is_running) {
195 if (wl->status == APR_SUCCESS) {
196 wl->step += (apr_time_now() - curr);
197 if (wl->step >= wl->interval) {
199 apr_pool_create(&ctx, w->pool);
201 /* Execute watchdog callback */
202 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING,
203 (void *)wl->data, ctx);
204 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
207 if (mpmq_s == AP_MPMQ_STOPPING) {
214 if (w->is_running && w->callbacks == NULL) {
215 /* This is hook mode watchdog
216 * running on WatchogInterval
218 w->step += (apr_time_now() - curr);
219 if (w->step >= wd_interval) {
221 apr_pool_create(&ctx, w->pool);
223 /* Run watchdog step hook */
224 ap_run_watchdog_step(wd_server_conf->s, w->name, ctx);
228 apr_pool_destroy(ctx);
229 if (!w->is_running) {
234 /* Run the watchdog exit hooks.
235 * If this was singleton watchdog the init hook
236 * might never been called, so skip the exit hook
237 * in that case as well.
239 ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool);
242 watchdog_list_t *wl = w->callbacks;
244 if (wl->status == APR_SUCCESS) {
245 /* Execute watchdog callback with STOPPING state */
246 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
247 (void *)wl->data, w->pool);
252 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
253 "%sWatchdog (%s) stopping (%" APR_PID_T_FMT ")",
254 w->singleton ? "Singleton" : "",
258 apr_proc_mutex_unlock(w->mutex);
259 apr_thread_exit(w->thread, APR_SUCCESS);
264 static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
268 /* Create thread startup mutex */
269 rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p);
270 if (rc != APR_SUCCESS)
274 /* Initialize singleton mutex in child */
275 rc = apr_proc_mutex_child_init(&w->mutex,
276 apr_proc_mutex_lockfile(w->mutex), p);
277 if (rc != APR_SUCCESS)
281 /* This mutex fixes problems with a fast start/fast end, where the pool
282 * cleanup was being invoked before the thread completely spawned.
284 apr_thread_mutex_lock(w->startup);
285 apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
287 /* Start the newly created watchdog */
288 rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p);
290 apr_pool_cleanup_kill(p, w, wd_worker_cleanup);
293 apr_thread_mutex_lock(w->startup);
294 apr_thread_mutex_unlock(w->startup);
295 apr_thread_mutex_destroy(w->startup);
300 static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog,
307 const char *pver = parent ? AP_WATCHODG_PVERSION : AP_WATCHODG_CVERSION;
309 if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) {
310 /* Parent threads are not supported for
316 w = ap_lookup_provider(AP_WATCHODG_PGROUP, name, pver);
321 w = apr_pcalloc(p, sizeof(ap_watchdog_t));
324 w->singleton = parent ? 0 : singleton;
326 return ap_register_provider(p, AP_WATCHODG_PGROUP, name,
330 static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w,
331 apr_interval_time_t interval,
333 ap_watchdog_callback_fn_t *callback)
335 watchdog_list_t *c = w->callbacks;
336 apr_status_t rv = APR_EOF;
339 if (c->data == data && c->callback_fn == callback) {
340 /* We have existing callback.
341 * Update the interval and reset status, so the
342 * callback and continue execution if stopped earlier.
344 c->interval = interval;
346 c->status = APR_SUCCESS;
355 static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w,
356 apr_interval_time_t interval,
358 ap_watchdog_callback_fn_t *callback)
360 watchdog_list_t *c = w->callbacks;
363 if (c->data == data && c->callback_fn == callback) {
364 /* We have already registered callback.
365 * Do not allow callbacks that have the same
366 * function and data pointers.
372 c = apr_palloc(w->pool, sizeof(watchdog_list_t));
374 c->callback_fn = callback;
375 c->interval = interval;
377 c->status = APR_EINIT;
380 c->next = w->callbacks;
387 /*--------------------------------------------------------------------------*/
389 /* Pre config hook. */
390 /* Create default watchdogs for parent and child */
391 /* Parent watchdog executes inside parent proces so it doesn't need the */
392 /* singleton mutex */
394 /*--------------------------------------------------------------------------*/
395 static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
401 ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_is_forked);
402 if ((rv = ap_watchdog_get_instance(&w,
403 AP_WATCHDOG_SINGLETON, 0, 1, pconf)) != APR_SUCCESS) {
406 if ((rv = ap_watchdog_get_instance(&w,
407 AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) {
410 if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) {
411 /* Create parent process watchdog for
412 * non forked mpm's only.
414 if ((rv = ap_watchdog_get_instance(&w,
415 AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) {
420 if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL,
421 APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) {
428 /*--------------------------------------------------------------------------*/
430 /* Post config hook. */
431 /* Create watchdog thread in parent and initializes Watchdog module */
433 /*--------------------------------------------------------------------------*/
434 static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
435 apr_pool_t *ptemp, server_rec *s)
438 const char *pk = "watchdog_init_module_tag";
439 apr_pool_t *pproc = s->process->pool;
440 const apr_array_header_t *wl;
442 apr_pool_userdata_get((void *)&wd_server_conf, pk, pproc);
443 if (!wd_server_conf) {
444 if (!(wd_server_conf = apr_pcalloc(pproc, sizeof(wd_server_conf_t))))
446 apr_pool_create(&wd_server_conf->pool, pproc);
447 wd_server_conf->s = s;
448 apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, pproc);
449 /* First time config phase -- skip. */
454 const char *ppid = getenv("AP_PARENT_PID");
456 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
457 "[%" APR_PID_T_FMT " - %s] "
458 "child second stage post config hook",
464 wd_server_conf->s = s;
465 if ((wl = ap_list_provider_names(pconf, AP_WATCHODG_PGROUP,
466 AP_WATCHODG_PVERSION))) {
467 const ap_list_provider_names_t *wn;
470 wn = (ap_list_provider_names_t *)wl->elts;
471 for (i = 0; i < wl->nelts; i++) {
472 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
474 AP_WATCHODG_PVERSION);
477 int status = ap_run_watchdog_need(s, w->name, 1,
480 /* One of the modules returned OK to this watchog.
487 /* We have active watchdog.
488 * Create the watchdog thread
490 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
491 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
492 "Watchdog: Failed to create parent worker thread.");
495 wd_server_conf->parent_workers++;
500 if (wd_server_conf->parent_workers) {
501 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
502 "Spawned %d parent worker threads.",
503 wd_server_conf->parent_workers);
505 if ((wl = ap_list_provider_names(pconf, AP_WATCHODG_PGROUP,
506 AP_WATCHODG_CVERSION))) {
507 const ap_list_provider_names_t *wn;
510 wn = (ap_list_provider_names_t *)wl->elts;
511 for (i = 0; i < wl->nelts; i++) {
512 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
514 AP_WATCHODG_CVERSION);
517 int status = ap_run_watchdog_need(s, w->name, 0,
520 /* One of the modules returned OK to this watchog.
527 /* We have some callbacks registered.
528 * Create mutexes for singleton watchdogs
531 rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type,
533 wd_server_conf->pool, 0);
534 if (rv != APR_SUCCESS) {
538 wd_server_conf->child_workers++;
546 /*--------------------------------------------------------------------------*/
548 /* Child init hook. */
549 /* Create watchdog threads and initializes Mutexes in child */
551 /*--------------------------------------------------------------------------*/
552 static void wd_child_init_hook(apr_pool_t *p, server_rec *s)
555 const apr_array_header_t *wl;
557 if (!wd_server_conf->child_workers) {
558 /* We don't have anything configured, bail out.
562 if ((wl = ap_list_provider_names(p, AP_WATCHODG_PGROUP,
563 AP_WATCHODG_CVERSION))) {
564 const ap_list_provider_names_t *wn;
566 wn = (ap_list_provider_names_t *)wl->elts;
567 for (i = 0; i < wl->nelts; i++) {
568 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHODG_PGROUP,
570 AP_WATCHODG_CVERSION);
571 if (w && w->active) {
572 /* We have some callbacks registered.
573 * Kick of the watchdog
575 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
576 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
577 "Watchdog: Failed to create worker thread.");
578 /* No point to continue */
586 /*--------------------------------------------------------------------------*/
588 /* WatchdogInterval directive */
590 /*--------------------------------------------------------------------------*/
591 static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy,
595 const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY);
600 return "Duplicate WatchdogInterval directives are not allowed";
601 if ((i = atoi(arg)) < 1)
602 return "Invalid WatchdogInterval value";
604 wd_interval = apr_time_from_sec(i);
609 /*--------------------------------------------------------------------------*/
611 /* List of directives specific to our module. */
613 /*--------------------------------------------------------------------------*/
614 static const command_rec wd_directives[] =
617 "WatchdogInterval", /* directive name */
618 wd_cmd_watchdog_int, /* config action routine */
619 NULL, /* argument to include in call */
620 RSRC_CONF, /* where available */
621 "Watchdog interval in seconds"
626 /*--------------------------------------------------------------------------*/
628 /* Which functions are responsible for which hooks in the server. */
630 /*--------------------------------------------------------------------------*/
631 static void wd_register_hooks(apr_pool_t *p)
634 /* Only the mpm_winnt has child init hook handler.
635 * Make sure that we are called after the mpm child init handler
638 static const char *const after_mpm[] = { "mpm_winnt.c", NULL};
640 /* Pre config handling
642 ap_hook_pre_config(wd_pre_config_hook,
647 /* Post config handling
649 ap_hook_post_config(wd_post_config_hook,
656 ap_hook_child_init(wd_child_init_hook,
661 APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance);
662 APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback);
663 APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval);
666 /*--------------------------------------------------------------------------*/
668 /* The list of callback routines and data structures that provide */
669 /* the static hooks into our module from the other parts of the server. */
671 /*--------------------------------------------------------------------------*/
672 AP_DECLARE_MODULE(watchdog) = {
673 STANDARD20_MODULE_STUFF,
674 NULL, /* create per-directory config structure */
675 NULL, /* merge per-directory config structures */
676 NULL, /* create per-server config structure */
677 NULL, /* merge per-server config structures */
678 wd_directives, /* command apr_table_t */
679 wd_register_hooks /* register hooks */
682 /*--------------------------------------------------------------------------*/
684 /* The list of optional hooks that we provide */
686 /*--------------------------------------------------------------------------*/
688 APR_HOOK_LINK(watchdog_need)
689 APR_HOOK_LINK(watchdog_init)
690 APR_HOOK_LINK(watchdog_exit)
691 APR_HOOK_LINK(watchdog_step)
694 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need,
695 (server_rec *s, const char *name,
696 int parent, int singleton),
697 (s, name, parent, singleton),
699 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init,
700 (server_rec *s, const char *name,
704 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit,
705 (server_rec *s, const char *name,
709 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step,
710 (server_rec *s, const char *name,