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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17/* Watchdog module.
18 */
19
20#include "apr.h"
21#include "mod_watchdog.h"
22#include "ap_provider.h"
23#include "ap_mpm.h"
24#include "http_core.h"
25#include "util_mutex.h"
26
27#define AP_WATCHDOG_PGROUP    "watchdog"
28#define AP_WATCHDOG_PVERSION  "parent"
29#define AP_WATCHDOG_CVERSION  "child"
30
31typedef struct watchdog_list_t watchdog_list_t;
32
33struct watchdog_list_t
34{
35    struct watchdog_list_t *next;
36    ap_watchdog_t *wd;
37    apr_status_t status;
38    apr_interval_time_t interval;
39    apr_interval_time_t step;
40    const void *data;
41    ap_watchdog_callback_fn_t *callback_fn;
42};
43
44struct ap_watchdog_t
45{
46    apr_thread_mutex_t   *startup;
47    apr_proc_mutex_t     *mutex;
48    const char           *name;
49    watchdog_list_t      *callbacks;
50    int                   is_running;
51    int                   singleton;
52    int                   active;
53    apr_interval_time_t   step;
54    apr_thread_t         *thread;
55    apr_pool_t           *pool;
56};
57
58typedef struct wd_server_conf_t wd_server_conf_t;
59struct wd_server_conf_t
60{
61    int child_workers;
62    int parent_workers;
63    apr_pool_t *pool;
64    server_rec *s;
65};
66
67static wd_server_conf_t *wd_server_conf = NULL;
68static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL;
69static int wd_interval_set = 0;
70static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED;
71static const char *wd_proc_mutex_type = "watchdog-callback";
72
73static apr_status_t wd_worker_cleanup(void *data)
74{
75    apr_status_t rv;
76    ap_watchdog_t *w = (ap_watchdog_t *)data;
77
78    if (w->is_running) {
79        watchdog_list_t *wl = w->callbacks;
80        while (wl) {
81            if (wl->status == APR_SUCCESS) {
82                /* Execute watchdog callback with STOPPING state */
83                (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
84                                    (void *)wl->data, w->pool);
85                wl->status = APR_EOF;
86            }
87            wl = wl->next;
88        }
89    }
90    w->is_running = 0;
91    apr_thread_join(&rv, w->thread);
92    return rv;
93}
94
95/*--------------------------------------------------------------------------*/
96/*                                                                          */
97/* Main watchdog worker thread.                                             */
98/* For singleton workers child thread that first obtains the process       */
99/* mutex is running. Threads in other child's are locked on mutex.          */
100/*                                                                          */
101/*--------------------------------------------------------------------------*/
102static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
103{
104    ap_watchdog_t *w = (ap_watchdog_t *)data;
105    apr_status_t rv;
106    int locked = 0;
107    int probed = 0;
108    int inited = 0;
109    int mpmq_s = 0;
110
111    w->pool = apr_thread_pool_get(thread);
112    w->is_running = 1;
113
114    apr_thread_mutex_unlock(w->startup);
115    if (w->mutex) {
116        while (w->is_running) {
117            if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
118                w->is_running = 0;
119                break;
120            }
121            if (mpmq_s == AP_MPMQ_STOPPING) {
122                w->is_running = 0;
123                break;
124            }
125            rv = apr_proc_mutex_trylock(w->mutex);
126            if (rv == APR_SUCCESS) {
127                if (probed) {
128                    /* Sleep after we were locked
129                     * up to 1 second. Httpd can be
130                     * in the middle of shutdown, and
131                     * our child didn't yet received
132                     * the shutdown signal.
133                     */
134                    probed = 10;
135                    while (w->is_running && probed > 0) {
136                        apr_sleep(AP_WD_TM_INTERVAL);
137                        probed--;
138                        if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
139                            w->is_running = 0;
140                            break;
141                        }
142                        if (mpmq_s == AP_MPMQ_STOPPING) {
143                            w->is_running = 0;
144                            break;
145                        }
146                    }
147                }
148                locked = 1;
149                break;
150            }
151            probed = 1;
152            apr_sleep(AP_WD_TM_SLICE);
153        }
154    }
155    if (w->is_running) {
156        watchdog_list_t *wl = w->callbacks;
157        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
158                     "%sWatchdog (%s) running",
159                     w->singleton ? "Singleton" : "", w->name);
160        apr_time_clock_hires(w->pool);
161        if (wl) {
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);
168                wl = wl->next;
169            }
170            apr_pool_destroy(ctx);
171        }
172        else {
173            ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
174            inited = 1;
175        }
176    }
177
178    /* Main execution loop */
179    while (w->is_running) {
180        apr_pool_t *ctx = NULL;
181        apr_time_t curr;
182        watchdog_list_t *wl = w->callbacks;
183
184        apr_sleep(AP_WD_TM_SLICE);
185        if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
186            w->is_running = 0;
187        }
188        if (mpmq_s == AP_MPMQ_STOPPING) {
189            w->is_running = 0;
190        }
191        if (!w->is_running) {
192            break;
193        }
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) {
199                    if (!ctx)
200                        apr_pool_create(&ctx, w->pool);
201                    wl->step = 0;
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) {
206                        w->is_running = 0;
207                    }
208                    if (mpmq_s == AP_MPMQ_STOPPING) {
209                        w->is_running = 0;
210                    }
211                }
212            }
213            wl = wl->next;
214        }
215        if (w->is_running && w->callbacks == NULL) {
216            /* This is hook mode watchdog
217             * running on WatchogInterval
218             */
219            w->step += (apr_time_now() - curr);
220            if (w->step >= wd_interval) {
221                if (!ctx)
222                    apr_pool_create(&ctx, w->pool);
223                w->step = 0;
224                /* Run watchdog step hook */
225                ap_run_watchdog_step(wd_server_conf->s, w->name, ctx);
226            }
227        }
228        if (ctx)
229            apr_pool_destroy(ctx);
230        if (!w->is_running) {
231            break;
232        }
233    }
234    if (inited) {
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.
239         */
240        ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool);
241    }
242    else {
243        watchdog_list_t *wl = w->callbacks;
244        while (wl) {
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);
249            }
250            wl = wl->next;
251        }
252    }
253    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
254                 "%sWatchdog (%s) stopping",
255                 w->singleton ? "Singleton" : "", w->name);
256
257    if (locked)
258        apr_proc_mutex_unlock(w->mutex);
259    apr_thread_exit(w->thread, APR_SUCCESS);
260
261    return NULL;
262}
263
264static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
265{
266    apr_status_t rc;
267
268    /* Create thread startup mutex */
269    rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p);
270    if (rc != APR_SUCCESS)
271        return rc;
272
273    if (w->singleton) {
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)
278            return rc;
279    }
280
281    /* This mutex fixes problems with a fast start/fast end, where the pool
282     * cleanup was being invoked before the thread completely spawned.
283     */
284    apr_thread_mutex_lock(w->startup);
285    apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
286
287    /* Start the newly created watchdog */
288    rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p);
289    if (rc) {
290        apr_pool_cleanup_kill(p, w, wd_worker_cleanup);
291    }
292
293    apr_thread_mutex_lock(w->startup);
294    apr_thread_mutex_unlock(w->startup);
295    apr_thread_mutex_destroy(w->startup);
296
297    return rc;
298}
299
300static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog,
301                                             const char *name,
302                                             int parent,
303                                             int singleton,
304                                             apr_pool_t *p)
305{
306    ap_watchdog_t *w;
307    const char *pver = parent ? AP_WATCHDOG_PVERSION : AP_WATCHDOG_CVERSION;
308
309    if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) {
310        /* Parent threads are not supported for
311         * forked mpm's
312         */
313        *watchdog = NULL;
314        return APR_ENOTIMPL;
315    }
316    w = ap_lookup_provider(AP_WATCHDOG_PGROUP, name, pver);
317    if (w) {
318        *watchdog = w;
319        return APR_SUCCESS;
320    }
321    w = apr_pcalloc(p, sizeof(ap_watchdog_t));
322    w->name      = name;
323    w->pool      = p;
324    w->singleton = parent ? 0 : singleton;
325    *watchdog    = w;
326    return ap_register_provider(p, AP_WATCHDOG_PGROUP, name,
327                                pver, *watchdog);
328}
329
330static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w,
331                                                      apr_interval_time_t interval,
332                                                      const void *data,
333                                                      ap_watchdog_callback_fn_t *callback)
334{
335    watchdog_list_t *c = w->callbacks;
336    apr_status_t rv = APR_EOF;
337
338    while (c) {
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.
343             */
344            c->interval = interval;
345            c->step     = 0;
346            c->status   = APR_SUCCESS;
347            rv          = APR_SUCCESS;
348            break;
349        }
350        c = c->next;
351    }
352    return rv;
353}
354
355static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w,
356                                                  apr_interval_time_t interval,
357                                                  const void *data,
358                                                  ap_watchdog_callback_fn_t *callback)
359{
360    watchdog_list_t *c = w->callbacks;
361
362    while (c) {
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.
367             */
368            return APR_EEXIST;
369        }
370        c = c->next;
371    }
372    c = apr_palloc(w->pool, sizeof(watchdog_list_t));
373    c->data        = data;
374    c->callback_fn = callback;
375    c->interval    = interval;
376    c->step        = 0;
377    c->status      = APR_EINIT;
378
379    c->wd          = w;
380    c->next        = w->callbacks;
381    w->callbacks   = c;
382    w->active++;
383
384    return APR_SUCCESS;
385}
386
387/*--------------------------------------------------------------------------*/
388/*                                                                          */
389/* Pre config hook.                                                         */
390/* Create default watchdogs for parent and child                            */
391/* Parent watchdog executes inside parent process so it doesn't need the    */
392/* singleton mutex                                                          */
393/*                                                                          */
394/*--------------------------------------------------------------------------*/
395static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
396                              apr_pool_t *ptemp)
397{
398    apr_status_t rv;
399    ap_watchdog_t *w;
400
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) {
404        return rv;
405    }
406    if ((rv = ap_watchdog_get_instance(&w,
407                AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) {
408        return rv;
409    }
410    if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) {
411        /* Create parent process watchdog for
412         * non forked mpm's only.
413         */
414        if ((rv = ap_watchdog_get_instance(&w,
415                    AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) {
416            return rv;
417        }
418    }
419
420    if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL,
421                                APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) {
422        return rv;
423    }
424
425    return OK;
426}
427
428/*--------------------------------------------------------------------------*/
429/*                                                                          */
430/* Post config hook.                                                        */
431/* Create watchdog thread in parent and initializes Watchdog module         */
432/*                                                                          */
433/*--------------------------------------------------------------------------*/
434static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
435                               apr_pool_t *ptemp, server_rec *s)
436{
437    apr_status_t rv;
438    const char *pk = "watchdog_init_module_tag";
439    apr_pool_t *pproc = s->process->pool;
440    const apr_array_header_t *wl;
441
442    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
443        /* First time config phase -- skip. */
444        return OK;
445
446    apr_pool_userdata_get((void *)&wd_server_conf, pk, pproc);
447    if (!wd_server_conf) {
448        if (!(wd_server_conf = apr_pcalloc(pproc, sizeof(wd_server_conf_t))))
449            return APR_ENOMEM;
450        apr_pool_create(&wd_server_conf->pool, pproc);
451        apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, pproc);
452    }
453    wd_server_conf->s = s;
454    if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
455                                            AP_WATCHDOG_PVERSION))) {
456        const ap_list_provider_names_t *wn;
457        int i;
458
459        wn = (ap_list_provider_names_t *)wl->elts;
460        for (i = 0; i < wl->nelts; i++) {
461            ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
462                                                  wn[i].provider_name,
463                                                  AP_WATCHDOG_PVERSION);
464            if (w) {
465                if (!w->active) {
466                    int status = ap_run_watchdog_need(s, w->name, 1,
467                                                      w->singleton);
468                    if (status == OK) {
469                        /* One of the modules returned OK to this watchog.
470                         * Mark it as active
471                         */
472                        w->active = 1;
473                    }
474                }
475                if (w->active) {
476                    /* We have active watchdog.
477                     * Create the watchdog thread
478                     */
479                    if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
480                        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01571)
481                                "Watchdog: Failed to create parent worker thread.");
482                        return rv;
483                    }
484                    wd_server_conf->parent_workers++;
485                }
486            }
487        }
488    }
489    if (wd_server_conf->parent_workers) {
490        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01572)
491                     "Spawned %d parent worker threads.",
492                     wd_server_conf->parent_workers);
493    }
494    if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
495                                            AP_WATCHDOG_CVERSION))) {
496        const ap_list_provider_names_t *wn;
497        int i;
498
499        wn = (ap_list_provider_names_t *)wl->elts;
500        for (i = 0; i < wl->nelts; i++) {
501            ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
502                                                  wn[i].provider_name,
503                                                  AP_WATCHDOG_CVERSION);
504            if (w) {
505                if (!w->active) {
506                    int status = ap_run_watchdog_need(s, w->name, 0,
507                                                      w->singleton);
508                    if (status == OK) {
509                        /* One of the modules returned OK to this watchog.
510                         * Mark it as active
511                         */
512                        w->active = 1;
513                    }
514                }
515                if (w->active) {
516                    /* We have some callbacks registered.
517                     * Create mutexes for singleton watchdogs
518                     */
519                    if (w->singleton) {
520                        rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type,
521                                                  w->name, s,
522                                                  wd_server_conf->pool, 0);
523                        if (rv != APR_SUCCESS) {
524                            return rv;
525                        }
526                    }
527                    wd_server_conf->child_workers++;
528                }
529            }
530        }
531    }
532    return OK;
533}
534
535/*--------------------------------------------------------------------------*/
536/*                                                                          */
537/* Child init hook.                                                         */
538/* Create watchdog threads and initializes Mutexes in child                 */
539/*                                                                          */
540/*--------------------------------------------------------------------------*/
541static void wd_child_init_hook(apr_pool_t *p, server_rec *s)
542{
543    apr_status_t rv;
544    const apr_array_header_t *wl;
545
546    if (!wd_server_conf->child_workers) {
547        /* We don't have anything configured, bail out.
548         */
549        return;
550    }
551    if ((wl = ap_list_provider_names(p, AP_WATCHDOG_PGROUP,
552                                        AP_WATCHDOG_CVERSION))) {
553        const ap_list_provider_names_t *wn;
554        int i;
555        wn = (ap_list_provider_names_t *)wl->elts;
556        for (i = 0; i < wl->nelts; i++) {
557            ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
558                                                  wn[i].provider_name,
559                                                  AP_WATCHDOG_CVERSION);
560            if (w && w->active) {
561                /* We have some callbacks registered.
562                 * Kick of the watchdog
563                 */
564                if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
565                    ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01573)
566                                 "Watchdog: Failed to create worker thread.");
567                    /* No point to continue */
568                    return;
569                }
570            }
571        }
572    }
573}
574
575/*--------------------------------------------------------------------------*/
576/*                                                                          */
577/* WatchdogInterval directive                                               */
578/*                                                                          */
579/*--------------------------------------------------------------------------*/
580static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy,
581                                       const char *arg)
582{
583    int i;
584    const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY);
585
586    if (errs != NULL)
587        return errs;
588    if (wd_interval_set)
589       return "Duplicate WatchdogInterval directives are not allowed";
590    if ((i = atoi(arg)) < 1)
591        return "Invalid WatchdogInterval value";
592
593    wd_interval = apr_time_from_sec(i);
594    wd_interval_set = 1;
595    return NULL;
596}
597
598/*--------------------------------------------------------------------------*/
599/*                                                                          */
600/* List of directives specific to our module.                               */
601/*                                                                          */
602/*--------------------------------------------------------------------------*/
603static const command_rec wd_directives[] =
604{
605    AP_INIT_TAKE1(
606        "WatchdogInterval",                 /* directive name               */
607        wd_cmd_watchdog_int,                /* config action routine        */
608        NULL,                               /* argument to include in call  */
609        RSRC_CONF,                          /* where available              */
610        "Watchdog interval in seconds"
611    ),
612    {NULL}
613};
614
615/*--------------------------------------------------------------------------*/
616/*                                                                          */
617/* Which functions are responsible for which hooks in the server.           */
618/*                                                                          */
619/*--------------------------------------------------------------------------*/
620static void wd_register_hooks(apr_pool_t *p)
621{
622
623    /* Only the mpm_winnt has child init hook handler.
624     * Make sure that we are called after the mpm child init handler
625     * initializes.
626     */
627    static const char *const after_mpm[]      = { "mpm_winnt.c", NULL};
628
629    /* Pre config handling
630     */
631    ap_hook_pre_config(wd_pre_config_hook,
632                       NULL,
633                       NULL,
634                       APR_HOOK_FIRST);
635
636    /* Post config handling
637     */
638    ap_hook_post_config(wd_post_config_hook,
639                        NULL,
640                        NULL,
641                        APR_HOOK_LAST);
642
643    /* Child init hook
644     */
645    ap_hook_child_init(wd_child_init_hook,
646                       after_mpm,
647                       NULL,
648                       APR_HOOK_MIDDLE);
649
650    APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance);
651    APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback);
652    APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval);
653}
654
655/*--------------------------------------------------------------------------*/
656/*                                                                          */
657/* The list of callback routines and data structures that provide           */
658/* the static hooks into our module from the other parts of the server.     */
659/*                                                                          */
660/*--------------------------------------------------------------------------*/
661AP_DECLARE_MODULE(watchdog) = {
662    STANDARD20_MODULE_STUFF,
663    NULL,                       /* create per-directory config structure    */
664    NULL,                       /* merge per-directory config structures    */
665    NULL,                       /* create per-server config structure       */
666    NULL,                       /* merge per-server config structures       */
667    wd_directives,              /* command apr_table_t                      */
668    wd_register_hooks           /* register hooks                           */
669};
670
671/*--------------------------------------------------------------------------*/
672/*                                                                          */
673/* The list of optional hooks that we provide                               */
674/*                                                                          */
675/*--------------------------------------------------------------------------*/
676APR_HOOK_STRUCT(
677    APR_HOOK_LINK(watchdog_need)
678    APR_HOOK_LINK(watchdog_init)
679    APR_HOOK_LINK(watchdog_exit)
680    APR_HOOK_LINK(watchdog_step)
681)
682
683APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need,
684                                      (server_rec *s, const char *name,
685                                       int parent, int singleton),
686                                      (s, name, parent, singleton),
687                                      DECLINED)
688APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init,
689                                    (server_rec *s, const char *name,
690                                     apr_pool_t *pool),
691                                    (s, name, pool),
692                                    OK, DECLINED)
693APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit,
694                                    (server_rec *s, const char *name,
695                                     apr_pool_t *pool),
696                                    (s, name, pool),
697                                    OK, DECLINED)
698APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step,
699                                    (server_rec *s, const char *name,
700                                     apr_pool_t *pool),
701                                    (s, name, pool),
702                                    OK, DECLINED)
703