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.
21 #if APR_HAVE_PROCESS_H
22 #include <process.h> /* for getpid() on Win32 */
25 #include "mod_watchdog.h"
26 #include "ap_provider.h"
28 #include "http_core.h"
29 #include "util_mutex.h"
31 #define AP_WATCHDOG_PGROUP "watchdog"
32 #define AP_WATCHDOG_PVERSION "parent"
33 #define AP_WATCHDOG_CVERSION "child"
35 typedef struct watchdog_list_t watchdog_list_t;
37 struct watchdog_list_t
39 struct watchdog_list_t *next;
42 apr_interval_time_t interval;
43 apr_interval_time_t step;
45 ap_watchdog_callback_fn_t *callback_fn;
50 apr_thread_mutex_t *startup;
51 apr_proc_mutex_t *mutex;
53 watchdog_list_t *callbacks;
57 apr_interval_time_t step;
62 typedef struct wd_server_conf_t wd_server_conf_t;
63 struct wd_server_conf_t
71 static wd_server_conf_t *wd_server_conf = NULL;
72 static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL;
73 static int wd_interval_set = 0;
74 static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED;
75 static const char *wd_proc_mutex_type = "watchdog-callback";
77 static apr_status_t wd_worker_cleanup(void *data)
80 ap_watchdog_t *w = (ap_watchdog_t *)data;
83 watchdog_list_t *wl = w->callbacks;
85 if (wl->status == APR_SUCCESS) {
86 /* Execute watchdog callback with STOPPING state */
87 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
88 (void *)wl->data, w->pool);
95 apr_thread_join(&rv, w->thread);
99 /*--------------------------------------------------------------------------*/
101 /* Main watchdog worker thread. */
102 /* For singleton workers child thread that first obtains the process */
103 /* mutex is running. Threads in other child's are locked on mutex. */
105 /*--------------------------------------------------------------------------*/
106 static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
108 ap_watchdog_t *w = (ap_watchdog_t *)data;
115 w->pool = apr_thread_pool_get(thread);
118 apr_thread_mutex_unlock(w->startup);
120 while (w->is_running) {
121 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
125 if (mpmq_s == AP_MPMQ_STOPPING) {
129 rv = apr_proc_mutex_trylock(w->mutex);
130 if (rv == APR_SUCCESS) {
132 /* Sleep after we were locked
133 * up to 1 second. Httpd can be
134 * in the middle of shutdown, and
135 * our child didn't yet received
136 * the shutdown signal.
139 while (w->is_running && probed > 0) {
140 apr_sleep(AP_WD_TM_INTERVAL);
142 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
146 if (mpmq_s == AP_MPMQ_STOPPING) {
156 apr_sleep(AP_WD_TM_SLICE);
160 watchdog_list_t *wl = w->callbacks;
161 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
162 "%sWatchdog (%s) running",
163 w->singleton ? "Singleton" : "", w->name);
164 apr_time_clock_hires(w->pool);
166 apr_pool_t *ctx = NULL;
167 apr_pool_create(&ctx, w->pool);
168 while (wl && w->is_running) {
169 /* Execute watchdog callback */
170 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING,
171 (void *)wl->data, ctx);
174 apr_pool_destroy(ctx);
177 ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
182 /* Main execution loop */
183 while (w->is_running) {
184 apr_pool_t *ctx = NULL;
186 watchdog_list_t *wl = w->callbacks;
188 apr_sleep(AP_WD_TM_SLICE);
189 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
192 if (mpmq_s == AP_MPMQ_STOPPING) {
195 if (!w->is_running) {
198 curr = apr_time_now() - AP_WD_TM_SLICE;
199 while (wl && w->is_running) {
200 if (wl->status == APR_SUCCESS) {
201 wl->step += (apr_time_now() - curr);
202 if (wl->step >= wl->interval) {
204 apr_pool_create(&ctx, w->pool);
206 /* Execute watchdog callback */
207 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING,
208 (void *)wl->data, ctx);
209 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
212 if (mpmq_s == AP_MPMQ_STOPPING) {
219 if (w->is_running && w->callbacks == NULL) {
220 /* This is hook mode watchdog
221 * running on WatchogInterval
223 w->step += (apr_time_now() - curr);
224 if (w->step >= wd_interval) {
226 apr_pool_create(&ctx, w->pool);
228 /* Run watchdog step hook */
229 ap_run_watchdog_step(wd_server_conf->s, w->name, ctx);
233 apr_pool_destroy(ctx);
234 if (!w->is_running) {
239 /* Run the watchdog exit hooks.
240 * If this was singleton watchdog the init hook
241 * might never been called, so skip the exit hook
242 * in that case as well.
244 ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool);
247 watchdog_list_t *wl = w->callbacks;
249 if (wl->status == APR_SUCCESS) {
250 /* Execute watchdog callback with STOPPING state */
251 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
252 (void *)wl->data, w->pool);
257 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
258 "%sWatchdog (%s) stopping",
259 w->singleton ? "Singleton" : "", w->name);
262 apr_proc_mutex_unlock(w->mutex);
263 apr_thread_exit(w->thread, APR_SUCCESS);
268 static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
272 /* Create thread startup mutex */
273 rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p);
274 if (rc != APR_SUCCESS)
278 /* Initialize singleton mutex in child */
279 rc = apr_proc_mutex_child_init(&w->mutex,
280 apr_proc_mutex_lockfile(w->mutex), p);
281 if (rc != APR_SUCCESS)
285 /* This mutex fixes problems with a fast start/fast end, where the pool
286 * cleanup was being invoked before the thread completely spawned.
288 apr_thread_mutex_lock(w->startup);
289 apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
291 /* Start the newly created watchdog */
292 rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p);
294 apr_pool_cleanup_kill(p, w, wd_worker_cleanup);
297 apr_thread_mutex_lock(w->startup);
298 apr_thread_mutex_unlock(w->startup);
299 apr_thread_mutex_destroy(w->startup);
304 static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog,
311 const char *pver = parent ? AP_WATCHDOG_PVERSION : AP_WATCHDOG_CVERSION;
313 if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) {
314 /* Parent threads are not supported for
320 w = ap_lookup_provider(AP_WATCHDOG_PGROUP, name, pver);
325 w = apr_pcalloc(p, sizeof(ap_watchdog_t));
328 w->singleton = parent ? 0 : singleton;
330 return ap_register_provider(p, AP_WATCHDOG_PGROUP, name,
334 static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w,
335 apr_interval_time_t interval,
337 ap_watchdog_callback_fn_t *callback)
339 watchdog_list_t *c = w->callbacks;
340 apr_status_t rv = APR_EOF;
343 if (c->data == data && c->callback_fn == callback) {
344 /* We have existing callback.
345 * Update the interval and reset status, so the
346 * callback and continue execution if stopped earlier.
348 c->interval = interval;
350 c->status = APR_SUCCESS;
359 static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w,
360 apr_interval_time_t interval,
362 ap_watchdog_callback_fn_t *callback)
364 watchdog_list_t *c = w->callbacks;
367 if (c->data == data && c->callback_fn == callback) {
368 /* We have already registered callback.
369 * Do not allow callbacks that have the same
370 * function and data pointers.
376 c = apr_palloc(w->pool, sizeof(watchdog_list_t));
378 c->callback_fn = callback;
379 c->interval = interval;
381 c->status = APR_EINIT;
384 c->next = w->callbacks;
391 /*--------------------------------------------------------------------------*/
393 /* Pre config hook. */
394 /* Create default watchdogs for parent and child */
395 /* Parent watchdog executes inside parent proces so it doesn't need the */
396 /* singleton mutex */
398 /*--------------------------------------------------------------------------*/
399 static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
405 ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_is_forked);
406 if ((rv = ap_watchdog_get_instance(&w,
407 AP_WATCHDOG_SINGLETON, 0, 1, pconf)) != APR_SUCCESS) {
410 if ((rv = ap_watchdog_get_instance(&w,
411 AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) {
414 if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) {
415 /* Create parent process watchdog for
416 * non forked mpm's only.
418 if ((rv = ap_watchdog_get_instance(&w,
419 AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) {
424 if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL,
425 APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) {
432 /*--------------------------------------------------------------------------*/
434 /* Post config hook. */
435 /* Create watchdog thread in parent and initializes Watchdog module */
437 /*--------------------------------------------------------------------------*/
438 static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
439 apr_pool_t *ptemp, server_rec *s)
442 const char *pk = "watchdog_init_module_tag";
443 apr_pool_t *pproc = s->process->pool;
444 const apr_array_header_t *wl;
446 if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
447 /* First time config phase -- skip. */
452 const char *ppid = getenv("AP_PARENT_PID");
454 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
455 "[%" APR_PID_T_FMT " - %s] "
456 "child second stage post config hook",
463 apr_pool_userdata_get((void *)&wd_server_conf, pk, pproc);
464 if (!wd_server_conf) {
465 if (!(wd_server_conf = apr_pcalloc(pproc, sizeof(wd_server_conf_t))))
467 apr_pool_create(&wd_server_conf->pool, pproc);
468 apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, pproc);
470 wd_server_conf->s = s;
471 if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
472 AP_WATCHDOG_PVERSION))) {
473 const ap_list_provider_names_t *wn;
476 wn = (ap_list_provider_names_t *)wl->elts;
477 for (i = 0; i < wl->nelts; i++) {
478 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
480 AP_WATCHDOG_PVERSION);
483 int status = ap_run_watchdog_need(s, w->name, 1,
486 /* One of the modules returned OK to this watchog.
493 /* We have active watchdog.
494 * Create the watchdog thread
496 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
497 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
498 "Watchdog: Failed to create parent worker thread.");
501 wd_server_conf->parent_workers++;
506 if (wd_server_conf->parent_workers) {
507 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
508 "Spawned %d parent worker threads.",
509 wd_server_conf->parent_workers);
511 if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
512 AP_WATCHDOG_CVERSION))) {
513 const ap_list_provider_names_t *wn;
516 wn = (ap_list_provider_names_t *)wl->elts;
517 for (i = 0; i < wl->nelts; i++) {
518 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
520 AP_WATCHDOG_CVERSION);
523 int status = ap_run_watchdog_need(s, w->name, 0,
526 /* One of the modules returned OK to this watchog.
533 /* We have some callbacks registered.
534 * Create mutexes for singleton watchdogs
537 rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type,
539 wd_server_conf->pool, 0);
540 if (rv != APR_SUCCESS) {
544 wd_server_conf->child_workers++;
552 /*--------------------------------------------------------------------------*/
554 /* Child init hook. */
555 /* Create watchdog threads and initializes Mutexes in child */
557 /*--------------------------------------------------------------------------*/
558 static void wd_child_init_hook(apr_pool_t *p, server_rec *s)
561 const apr_array_header_t *wl;
563 if (!wd_server_conf->child_workers) {
564 /* We don't have anything configured, bail out.
568 if ((wl = ap_list_provider_names(p, AP_WATCHDOG_PGROUP,
569 AP_WATCHDOG_CVERSION))) {
570 const ap_list_provider_names_t *wn;
572 wn = (ap_list_provider_names_t *)wl->elts;
573 for (i = 0; i < wl->nelts; i++) {
574 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
576 AP_WATCHDOG_CVERSION);
577 if (w && w->active) {
578 /* We have some callbacks registered.
579 * Kick of the watchdog
581 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
582 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
583 "Watchdog: Failed to create worker thread.");
584 /* No point to continue */
592 /*--------------------------------------------------------------------------*/
594 /* WatchdogInterval directive */
596 /*--------------------------------------------------------------------------*/
597 static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy,
601 const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY);
606 return "Duplicate WatchdogInterval directives are not allowed";
607 if ((i = atoi(arg)) < 1)
608 return "Invalid WatchdogInterval value";
610 wd_interval = apr_time_from_sec(i);
615 /*--------------------------------------------------------------------------*/
617 /* List of directives specific to our module. */
619 /*--------------------------------------------------------------------------*/
620 static const command_rec wd_directives[] =
623 "WatchdogInterval", /* directive name */
624 wd_cmd_watchdog_int, /* config action routine */
625 NULL, /* argument to include in call */
626 RSRC_CONF, /* where available */
627 "Watchdog interval in seconds"
632 /*--------------------------------------------------------------------------*/
634 /* Which functions are responsible for which hooks in the server. */
636 /*--------------------------------------------------------------------------*/
637 static void wd_register_hooks(apr_pool_t *p)
640 /* Only the mpm_winnt has child init hook handler.
641 * Make sure that we are called after the mpm child init handler
644 static const char *const after_mpm[] = { "mpm_winnt.c", NULL};
646 /* Pre config handling
648 ap_hook_pre_config(wd_pre_config_hook,
653 /* Post config handling
655 ap_hook_post_config(wd_post_config_hook,
662 ap_hook_child_init(wd_child_init_hook,
667 APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance);
668 APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback);
669 APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval);
672 /*--------------------------------------------------------------------------*/
674 /* The list of callback routines and data structures that provide */
675 /* the static hooks into our module from the other parts of the server. */
677 /*--------------------------------------------------------------------------*/
678 AP_DECLARE_MODULE(watchdog) = {
679 STANDARD20_MODULE_STUFF,
680 NULL, /* create per-directory config structure */
681 NULL, /* merge per-directory config structures */
682 NULL, /* create per-server config structure */
683 NULL, /* merge per-server config structures */
684 wd_directives, /* command apr_table_t */
685 wd_register_hooks /* register hooks */
688 /*--------------------------------------------------------------------------*/
690 /* The list of optional hooks that we provide */
692 /*--------------------------------------------------------------------------*/
694 APR_HOOK_LINK(watchdog_need)
695 APR_HOOK_LINK(watchdog_init)
696 APR_HOOK_LINK(watchdog_exit)
697 APR_HOOK_LINK(watchdog_step)
700 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need,
701 (server_rec *s, const char *name,
702 int parent, int singleton),
703 (s, name, parent, singleton),
705 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init,
706 (server_rec *s, const char *name,
710 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit,
711 (server_rec *s, const char *name,
715 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step,
716 (server_rec *s, const char *name,