From 6eb5b6095d379c433c40a529737a392ca4908aa3 Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Thu, 20 Aug 2026 15:47:26 -0400 Subject: [PATCH 1/8] mpm_prefork: give clean_child_exit() a from_signal variant --- server/mpm/prefork/prefork.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index b5adb57bea1..56365041a04 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -215,19 +215,18 @@ static void prefork_note_child_started(int slot, pid_t pid) } /* a clean exit from a child with proper cleanup */ -static void clean_child_exit(int code) __attribute__ ((noreturn)); -static void clean_child_exit(int code) +static void clean_child_exit_ex(int code, int from_signal) __attribute__ ((noreturn)); +static void clean_child_exit_ex(int code, int from_signal) { retained->mpm->mpm_state = AP_MPMQ_STOPPING; apr_signal(SIGHUP, SIG_IGN); apr_signal(SIGTERM, SIG_IGN); - if (code == 0) { - ap_run_child_stopping(pchild, 0); - } - if (pchild) { + if (!code && !from_signal) { + ap_run_child_stopping(pchild, !retained->mpm->is_ungraceful); + } apr_pool_destroy(pchild); } @@ -240,6 +239,12 @@ static void clean_child_exit(int code) exit(code); } +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + clean_child_exit_ex(code, 0); +} + static apr_status_t accept_mutex_on(void) { apr_status_t rv = apr_proc_mutex_lock(my_bucket->mutex); @@ -356,7 +361,7 @@ static const char *prefork_get_name(void) static void just_die(int sig) { - clean_child_exit(0); + clean_child_exit_ex(0, 1); } /* volatile because it's updated from a signal handler */ From d3b53fc08a6ed90d774ff1ca1684268520fd40bf Mon Sep 17 00:00:00 2001 From: t4r3k <142579274+machine-moon@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:46:12 -0400 Subject: [PATCH 2/8] mpm_event update --- include/mpm_common.h | 15 +++++++++++++++ server/mpm/event/event.c | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/mpm_common.h b/include/mpm_common.h index 6b3d1f536fb..154df9ae6cb 100644 --- a/include/mpm_common.h +++ b/include/mpm_common.h @@ -40,6 +40,7 @@ #include "ap_config.h" #include "ap_mpm.h" #include "scoreboard.h" +#include "apr_optional.h" #if APR_HAVE_NETINET_TCP_H #include /* for TCP_NODELAY */ @@ -479,6 +480,20 @@ AP_DECLARE_HOOK(void, child_stopping, */ void mpm_common_pre_config(apr_pool_t *pconf); +/** + * Hooks for modules to report connections the MPM did not accept itself. + * + * MPMs that wait for their connection count to drain before stopping a child + * need this so externally accepted connections keep the child alive until + * they finish. + * + * Call ap_mpm_note_extra_connection_added() when such a connection starts, + * and ap_mpm_note_extra_connection_removed() when it ends. These functions + * may be NULL if the active MPM does not implement them. + */ +APR_DECLARE_OPTIONAL_FN(void, ap_mpm_note_extra_connection_added, (void)); +APR_DECLARE_OPTIONAL_FN(void, ap_mpm_note_extra_connection_removed, (void)); + #ifdef __cplusplus } #endif diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c index 050d823809b..48119caedb7 100644 --- a/server/mpm/event/event.c +++ b/server/mpm/event/event.c @@ -828,6 +828,21 @@ static apr_status_t decrement_connection_count(void *cs_) return APR_SUCCESS; } +static void ap_mpm_note_extra_connection_added(void) +{ + apr_atomic_inc32(&connection_count); +} + +static void ap_mpm_note_extra_connection_removed(void) +{ + int is_last_connection = !apr_atomic_dec32(&connection_count); + + /* Wake a listener blocked waiting for connection_count to drain. */ + if (listener_is_wakeable && is_last_connection && listener_may_exit) { + apr_pollset_wakeup(event_pollset); + } +} + static void notify_suspend(event_conn_state_t *cs) { ap_run_suspend_connection(cs->c, cs->r); @@ -3466,6 +3481,10 @@ static void setup_slave_conn(conn_rec *c, void *csd) event_conn_state_t *cs; mcs = ap_get_module_config(c->master->conn_config, &mpm_event_module); + if (!mcs) { + /* Master connection is not managed by this MPM; nothing to inherit. */ + return; + } cs = apr_pcalloc(c->pool, sizeof(*cs)); cs->c = c; @@ -3607,6 +3626,9 @@ static int event_pre_config(apr_pool_t * pconf, apr_pool_t * plog, const char *userdata_key = "mpm_event_module"; int test_atomics = 0; + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_added); + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_removed); + debug = ap_exists_config_define("DEBUG"); if (debug) { From 36bb75dd15c468bb367387d94e3790017b754e94 Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Wed, 15 Jul 2026 14:12:56 -0400 Subject: [PATCH 3/8] mpm prefork and worker update --- server/mpm/prefork/prefork.c | 20 ++++++++++++++++++++ server/mpm/worker/worker.c | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index 56365041a04..8cb842e4ab0 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -18,6 +18,7 @@ #include "apr_portable.h" #include "apr_strings.h" #include "apr_thread_proc.h" +#include "apr_atomic.h" #include "apr_signal.h" #define APR_WANT_STDIO @@ -88,6 +89,7 @@ /* config globals */ +static apr_uint32_t connection_count = 0; /* Number of open connections */ static int ap_daemons_to_start=0; static int ap_daemons_min_free=0; static int ap_daemons_max_free=0; @@ -226,6 +228,11 @@ static void clean_child_exit_ex(int code, int from_signal) if (pchild) { if (!code && !from_signal) { ap_run_child_stopping(pchild, !retained->mpm->is_ungraceful); + if (!retained->mpm->is_ungraceful) { + while (apr_atomic_read32(&connection_count) > 0) { + apr_sleep(apr_time_from_msec(100)); + } + } } apr_pool_destroy(pchild); } @@ -367,6 +374,16 @@ static void just_die(int sig) /* volatile because it's updated from a signal handler */ static int volatile die_now = 0; +static void ap_mpm_note_extra_connection_added(void) +{ + apr_atomic_inc32(&connection_count); +} + +static void ap_mpm_note_extra_connection_removed(void) +{ + apr_atomic_dec32(&connection_count); +} + static void stop_listening(int sig) { retained->mpm->mpm_state = AP_MPMQ_STOPPING; @@ -1291,6 +1308,9 @@ static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp apr_status_t rv; const char *userdata_key = "mpm_prefork_module"; + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_added); + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_removed); + debug = ap_exists_config_define("DEBUG"); if (debug) { diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 315371de121..0d0a3d0c209 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -30,6 +30,7 @@ #include "apr_thread_mutex.h" #include "apr_proc_mutex.h" #include "apr_poll.h" +#include "apr_atomic.h" #include @@ -116,6 +117,7 @@ * Actual definitions of config globals */ +static apr_uint32_t connection_count = 0; /* Number of open connections */ static int threads_per_child = 0; /* Worker threads per child */ static int ap_daemons_to_start = 0; static int min_spare_threads = 0; @@ -506,6 +508,16 @@ static void check_infinite_requests(void) } } +static void ap_mpm_note_extra_connection_added(void) +{ + apr_atomic_inc32(&connection_count); +} + +static void ap_mpm_note_extra_connection_removed(void) +{ + apr_atomic_dec32(&connection_count); +} + static void unblock_signal(int sig) { sigset_t sig_mask; @@ -1301,6 +1313,12 @@ static void child_main(int child_num_arg, int child_bucket) rv == AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); } + if (terminate_mode == ST_GRACEFUL) { + while (apr_atomic_read32(&connection_count) > 0) { + apr_sleep(apr_time_from_msec(100)); + } + } + free(threads); clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); @@ -2059,6 +2077,9 @@ static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_status_t rv; const char *userdata_key = "mpm_worker_module"; + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_added); + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_removed); + debug = ap_exists_config_define("DEBUG"); if (debug) { From 5cc8e156b9f28a45a83bf37ac287010dd040ea3b Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Sun, 9 Aug 2026 03:38:13 -0400 Subject: [PATCH 4/8] mpm_winnt update --- server/mpm/winnt/child.c | 18 ++++++++++++++++++ server/mpm/winnt/mpm_winnt.c | 3 +++ server/mpm/winnt/mpm_winnt.h | 2 ++ 3 files changed, 23 insertions(+) diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c index 05151a885ea..bde5184650b 100644 --- a/server/mpm/winnt/child.c +++ b/server/mpm/winnt/child.c @@ -123,9 +123,21 @@ static winnt_conn_ctx_t *qhead = NULL; static winnt_conn_ctx_t *qtail = NULL; static apr_uint32_t num_completion_contexts = 0; static apr_uint32_t max_num_completion_contexts = 0; +static apr_uint32_t extra_connection_count = 0; static HANDLE ThreadDispatchIOCP = NULL; static HANDLE qwait_event = NULL; +/* Connections a module accepted itself, which the worker threads do not serve. */ +void ap_mpm_note_extra_connection_added(void) +{ + apr_atomic_inc32(&extra_connection_count); +} + +void ap_mpm_note_extra_connection_removed(void) +{ + apr_atomic_dec32(&extra_connection_count); +} + static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) { /* Recycle the completion context. @@ -1272,6 +1284,12 @@ void child_main(apr_pool_t *pconf, DWORD parent_pid) } } + /* Drain externally accepted connections within what is left of the deadline. */ + while (apr_atomic_read32(&extra_connection_count) > 0 && time_remains >= 0) { + Sleep(100); + time_remains -= 100; + } + /* Kill remaining threads off the hard way */ if (threads_created) { ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c index 1b8962e7457..8c782f1a309 100644 --- a/server/mpm/winnt/mpm_winnt.c +++ b/server/mpm/winnt/mpm_winnt.c @@ -1369,6 +1369,9 @@ static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *pt * -k runservice [WinNT errors logged from rewrite_args] */ + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_added); + APR_REGISTER_OPTIONAL_FN(ap_mpm_note_extra_connection_removed); + /* Initialize shared static objects. * TODO: Put config related statics into an sconf structure. */ diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h index 22ba001407e..93d46dc2a5e 100644 --- a/server/mpm/winnt/mpm_winnt.h +++ b/server/mpm/winnt/mpm_winnt.h @@ -91,6 +91,8 @@ void hold_console_open_on_error(void); /* From child.c: */ void child_main(apr_pool_t *pconf, DWORD parent_pid); +void ap_mpm_note_extra_connection_added(void); +void ap_mpm_note_extra_connection_removed(void); #endif /* APACHE_MPM_WINNT_H */ /** @} */ From 55210d42dfd4be4dee1368f695113659583fed3a Mon Sep 17 00:00:00 2001 From: t4r3k <142579274+machine-moon@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:34:38 -0400 Subject: [PATCH 5/8] update CHANGES --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index b545f955647..6f4141a4cd1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.69 + *) mpm_event, mpm_worker, mpm_winnt, mpm_prefork: Add optional hooks to + account for connections managed outside the MPM accept loop (e.g., UDP). + [Tarek Ibrahim ] + *) mod_md: OpenSSL 4 compatibility. *) pytest_suite: Port of the old PERL test framework to Python From 5e261f540cd9e7571d4ba65d2ee928d9cf0b33fc Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Fri, 14 Aug 2026 11:22:23 -0400 Subject: [PATCH 6/8] bump mmn --- include/ap_mmn.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/ap_mmn.h b/include/ap_mmn.h index d00c6d1ec9c..fdaf8dca71b 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -612,6 +612,7 @@ * 20120211.140 (2.4.64-dev) Add ap_set_time_process_request() to scoreboard.h * 20120211.141 (2.4.64-dev) add ap_stat_check() to httpd.h * 20120211.142 (2.4.64-dev) Add ap_*_timingsafe() to httpd.h + * 20120211.143 (2.4.69-dev) Add optional hooks for MPM to accept UDP conns. */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -619,7 +620,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 142 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 143 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a From 3b3ab0bf95a9057d2e125f1f88cedb62f988970d Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Sat, 15 Aug 2026 06:53:10 -0400 Subject: [PATCH 7/8] bounded graceful shutdown for extra connections --- docs/log-message-tags/next-number | 1 + server/mpm/event/event.c | 38 ++++++++++++++++++++++++---- server/mpm/prefork/prefork.c | 32 +++++++++++++++++++++--- server/mpm/winnt/child.c | 41 +++++++++++++++++++++++++------ server/mpm/worker/worker.c | 32 +++++++++++++++++++++--- 5 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 docs/log-message-tags/next-number diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number new file mode 100644 index 00000000000..29cdba2b98b --- /dev/null +++ b/docs/log-message-tags/next-number @@ -0,0 +1 @@ +10623 diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c index 48119caedb7..ac79a0757f2 100644 --- a/server/mpm/event/event.c +++ b/server/mpm/event/event.c @@ -182,6 +182,7 @@ static int num_listensocks = 0; static apr_int32_t conns_this_child; /* MaxConnectionsPerChild, only access in listener thread */ static apr_uint32_t connection_count = 0; /* Number of open connections */ +static apr_uint32_t extra_connection_count = 0; /* Number of open connections that the MPM does not own */ static apr_uint32_t lingering_count = 0; /* Number of connections in lingering close */ static apr_uint32_t suspended_count = 0; /* Number of suspended connections */ static apr_uint32_t clogged_count = 0; /* Number of threads processing ssl conns */ @@ -830,16 +831,39 @@ static apr_status_t decrement_connection_count(void *cs_) static void ap_mpm_note_extra_connection_added(void) { - apr_atomic_inc32(&connection_count); + apr_atomic_inc32(&extra_connection_count); } static void ap_mpm_note_extra_connection_removed(void) { - int is_last_connection = !apr_atomic_dec32(&connection_count); + apr_atomic_dec32(&extra_connection_count); +} - /* Wake a listener blocked waiting for connection_count to drain. */ - if (listener_is_wakeable && is_last_connection && listener_may_exit) { - apr_pollset_wakeup(event_pollset); +static void wait_for_extra_connections(void) +{ + apr_uint32_t count = apr_atomic_read32(&extra_connection_count); + apr_time_t graceful, timeout, deadline; + + if (count == 0) { + return; + } + + graceful = apr_time_from_sec(ap_graceful_shutdown_timeout); + timeout = (graceful > ap_server_conf->timeout) ? graceful : ap_server_conf->timeout; + deadline = apr_time_now() + timeout; + + do { + apr_sleep(apr_time_from_msec(100)); + count = apr_atomic_read32(&extra_connection_count); + } while (count > 0 && apr_time_now() < deadline); + + if (count > 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(10619) + "Child: %u connection(s) noted by modules did not " + "finish within %" APR_TIME_T_FMT " seconds, " + "exiting anyway", + count, apr_time_sec(timeout)); } } @@ -2820,6 +2844,10 @@ static void child_main(int child_num_arg, int child_bucket) rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful"); } + if (terminate_mode == ST_GRACEFUL) { + wait_for_extra_connections(); + } + free(threads); clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index 8cb842e4ab0..550ac11a422 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -216,6 +216,34 @@ static void prefork_note_child_started(int slot, pid_t pid) ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); } +static void wait_for_extra_connections(void) +{ + apr_uint32_t count = apr_atomic_read32(&connection_count); + apr_time_t graceful, timeout, deadline; + + if (count == 0) { + return; + } + + graceful = apr_time_from_sec(ap_graceful_shutdown_timeout); + timeout = (graceful > ap_server_conf->timeout) ? graceful : ap_server_conf->timeout; + deadline = apr_time_now() + timeout; + + do { + apr_sleep(apr_time_from_msec(100)); + count = apr_atomic_read32(&connection_count); + } while (count > 0 && apr_time_now() < deadline); + + if (count > 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(10620) + "Child: %u connection(s) noted by modules did not " + "finish within %" APR_TIME_T_FMT " seconds, " + "exiting anyway", + count, apr_time_sec(timeout)); + } +} + /* a clean exit from a child with proper cleanup */ static void clean_child_exit_ex(int code, int from_signal) __attribute__ ((noreturn)); static void clean_child_exit_ex(int code, int from_signal) @@ -229,9 +257,7 @@ static void clean_child_exit_ex(int code, int from_signal) if (!code && !from_signal) { ap_run_child_stopping(pchild, !retained->mpm->is_ungraceful); if (!retained->mpm->is_ungraceful) { - while (apr_atomic_read32(&connection_count) > 0) { - apr_sleep(apr_time_from_msec(100)); - } + wait_for_extra_connections(); } } apr_pool_destroy(pchild); diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c index bde5184650b..50f2efab934 100644 --- a/server/mpm/winnt/child.c +++ b/server/mpm/winnt/child.c @@ -138,6 +138,36 @@ void ap_mpm_note_extra_connection_removed(void) apr_atomic_dec32(&extra_connection_count); } +static void wait_for_extra_connections(void) +{ + apr_uint32_t count = apr_atomic_read32(&extra_connection_count); + apr_time_t graceful, timeout; + int time_remains; + + if (count == 0) { + return; + } + + graceful = apr_time_from_sec(ap_graceful_shutdown_timeout); + timeout = (graceful > ap_server_conf->timeout) ? graceful : ap_server_conf->timeout; + time_remains = (int)(timeout / APR_TIME_C(1000)); + + do { + Sleep(100); + time_remains -= 100; + count = apr_atomic_read32(&extra_connection_count); + } while (count > 0 && time_remains > 0); + + if (count > 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, ap_server_conf, + APLOGNO(10622) + "Child: %u connection(s) noted by modules did not " + "finish within %" APR_TIME_T_FMT " seconds, " + "exiting anyway", + count, apr_time_sec(timeout)); + } +} + static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) { /* Recycle the completion context. @@ -1284,13 +1314,6 @@ void child_main(apr_pool_t *pconf, DWORD parent_pid) } } - /* Drain externally accepted connections within what is left of the deadline. */ - while (apr_atomic_read32(&extra_connection_count) > 0 && time_remains >= 0) { - Sleep(100); - time_remains -= 100; - } - - /* Kill remaining threads off the hard way */ if (threads_created) { ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) "Child: Terminating %d threads that failed to exit.", @@ -1309,6 +1332,10 @@ void child_main(apr_pool_t *pconf, DWORD parent_pid) ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364) "Child: All worker threads have exited."); + if (graceful_shutdown) { + wait_for_extra_connections(); + } + apr_thread_mutex_destroy(child_lock); apr_thread_mutex_destroy(qlock); CloseHandle(qwait_event); diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 0d0a3d0c209..872124ee819 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -518,6 +518,34 @@ static void ap_mpm_note_extra_connection_removed(void) apr_atomic_dec32(&connection_count); } +static void wait_for_extra_connections(void) +{ + apr_uint32_t count = apr_atomic_read32(&connection_count); + apr_time_t graceful, timeout, deadline; + + if (count == 0) { + return; + } + + graceful = apr_time_from_sec(ap_graceful_shutdown_timeout); + timeout = (graceful > ap_server_conf->timeout) ? graceful : ap_server_conf->timeout; + deadline = apr_time_now() + timeout; + + do { + apr_sleep(apr_time_from_msec(100)); + count = apr_atomic_read32(&connection_count); + } while (count > 0 && apr_time_now() < deadline); + + if (count > 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(10621) + "Child: %u connection(s) noted by modules did not " + "finish within %" APR_TIME_T_FMT " seconds, " + "exiting anyway", + count, apr_time_sec(timeout)); + } +} + static void unblock_signal(int sig) { sigset_t sig_mask; @@ -1314,9 +1342,7 @@ static void child_main(int child_num_arg, int child_bucket) } if (terminate_mode == ST_GRACEFUL) { - while (apr_atomic_read32(&connection_count) > 0) { - apr_sleep(apr_time_from_msec(100)); - } + wait_for_extra_connections(); } free(threads); From 97868915a9188e637397976602532d0488ee5067 Mon Sep 17 00:00:00 2001 From: Tarek Ibrahim Date: Sat, 15 Aug 2026 07:00:04 -0400 Subject: [PATCH 8/8] consistency sweep --- CHANGES | 8 ++++++-- include/ap_mmn.h | 4 +++- include/mpm_common.h | 6 ++++++ server/mpm/prefork/prefork.c | 26 +++++++++++++------------- server/mpm/winnt/child.c | 8 +++----- server/mpm/worker/worker.c | 10 +++++----- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index 6f4141a4cd1..d18857bb8ae 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,12 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.69 - *) mpm_event, mpm_worker, mpm_winnt, mpm_prefork: Add optional hooks to - account for connections managed outside the MPM accept loop (e.g., UDP). + *) mpm_event, mpm_worker, mpm_winnt, mpm_prefork: Add the optional functions + ap_mpm_note_extra_connection_added() and + ap_mpm_note_extra_connection_removed() to account for connections managed + outside the MPM accept loop (e.g., UDP). On graceful stop a child waits + for those connections no longer than max(Timeout, + GracefulShutdownTimeout), then warns and exits. [Tarek Ibrahim ] *) mod_md: OpenSSL 4 compatibility. diff --git a/include/ap_mmn.h b/include/ap_mmn.h index fdaf8dca71b..122c1070af1 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -612,7 +612,9 @@ * 20120211.140 (2.4.64-dev) Add ap_set_time_process_request() to scoreboard.h * 20120211.141 (2.4.64-dev) add ap_stat_check() to httpd.h * 20120211.142 (2.4.64-dev) Add ap_*_timingsafe() to httpd.h - * 20120211.143 (2.4.69-dev) Add optional hooks for MPM to accept UDP conns. + * 20120211.143 (2.4.69-dev) Add the optional functions ap_mpm_note_extra_ + * connection_added() and ap_mpm_note_extra_ + * connection_removed() to mpm_common.h */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ diff --git a/include/mpm_common.h b/include/mpm_common.h index 154df9ae6cb..a1fe31af74a 100644 --- a/include/mpm_common.h +++ b/include/mpm_common.h @@ -490,6 +490,12 @@ void mpm_common_pre_config(apr_pool_t *pconf); * Call ap_mpm_note_extra_connection_added() when such a connection starts, * and ap_mpm_note_extra_connection_removed() when it ends. These functions * may be NULL if the active MPM does not implement them. + * + * A module using them is expected to notice that the child is stopping (e.g. + * with the child_stopping hook) and to end the connections it noted in a + * timely manner, gracefully or not. The MPM waits for them no longer than + * max(Timeout, GracefulShutdownTimeout), then logs a warning and exits + * anyway, possibly cutting those connections short. */ APR_DECLARE_OPTIONAL_FN(void, ap_mpm_note_extra_connection_added, (void)); APR_DECLARE_OPTIONAL_FN(void, ap_mpm_note_extra_connection_removed, (void)); diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index 550ac11a422..fd6c6a039ed 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -89,7 +89,7 @@ /* config globals */ -static apr_uint32_t connection_count = 0; /* Number of open connections */ +static apr_uint32_t extra_connection_count = 0; /* Number of open connections that the MPM does not own */ static int ap_daemons_to_start=0; static int ap_daemons_min_free=0; static int ap_daemons_max_free=0; @@ -216,9 +216,19 @@ static void prefork_note_child_started(int slot, pid_t pid) ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); } +static void ap_mpm_note_extra_connection_added(void) +{ + apr_atomic_inc32(&extra_connection_count); +} + +static void ap_mpm_note_extra_connection_removed(void) +{ + apr_atomic_dec32(&extra_connection_count); +} + static void wait_for_extra_connections(void) { - apr_uint32_t count = apr_atomic_read32(&connection_count); + apr_uint32_t count = apr_atomic_read32(&extra_connection_count); apr_time_t graceful, timeout, deadline; if (count == 0) { @@ -231,7 +241,7 @@ static void wait_for_extra_connections(void) do { apr_sleep(apr_time_from_msec(100)); - count = apr_atomic_read32(&connection_count); + count = apr_atomic_read32(&extra_connection_count); } while (count > 0 && apr_time_now() < deadline); if (count > 0) { @@ -400,16 +410,6 @@ static void just_die(int sig) /* volatile because it's updated from a signal handler */ static int volatile die_now = 0; -static void ap_mpm_note_extra_connection_added(void) -{ - apr_atomic_inc32(&connection_count); -} - -static void ap_mpm_note_extra_connection_removed(void) -{ - apr_atomic_dec32(&connection_count); -} - static void stop_listening(int sig) { retained->mpm->mpm_state = AP_MPMQ_STOPPING; diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c index 50f2efab934..d0ea325196b 100644 --- a/server/mpm/winnt/child.c +++ b/server/mpm/winnt/child.c @@ -123,11 +123,10 @@ static winnt_conn_ctx_t *qhead = NULL; static winnt_conn_ctx_t *qtail = NULL; static apr_uint32_t num_completion_contexts = 0; static apr_uint32_t max_num_completion_contexts = 0; -static apr_uint32_t extra_connection_count = 0; +static apr_uint32_t extra_connection_count = 0; /* Number of open connections that the MPM does not own */ static HANDLE ThreadDispatchIOCP = NULL; static HANDLE qwait_event = NULL; -/* Connections a module accepted itself, which the worker threads do not serve. */ void ap_mpm_note_extra_connection_added(void) { apr_atomic_inc32(&extra_connection_count); @@ -141,8 +140,7 @@ void ap_mpm_note_extra_connection_removed(void) static void wait_for_extra_connections(void) { apr_uint32_t count = apr_atomic_read32(&extra_connection_count); - apr_time_t graceful, timeout; - int time_remains; + apr_time_t graceful, timeout, time_remains; if (count == 0) { return; @@ -150,7 +148,7 @@ static void wait_for_extra_connections(void) graceful = apr_time_from_sec(ap_graceful_shutdown_timeout); timeout = (graceful > ap_server_conf->timeout) ? graceful : ap_server_conf->timeout; - time_remains = (int)(timeout / APR_TIME_C(1000)); + time_remains = timeout / APR_TIME_C(1000); do { Sleep(100); diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 872124ee819..00df8221bfc 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -117,7 +117,7 @@ * Actual definitions of config globals */ -static apr_uint32_t connection_count = 0; /* Number of open connections */ +static apr_uint32_t extra_connection_count = 0; /* Number of open connections that the MPM does not own */ static int threads_per_child = 0; /* Worker threads per child */ static int ap_daemons_to_start = 0; static int min_spare_threads = 0; @@ -510,17 +510,17 @@ static void check_infinite_requests(void) static void ap_mpm_note_extra_connection_added(void) { - apr_atomic_inc32(&connection_count); + apr_atomic_inc32(&extra_connection_count); } static void ap_mpm_note_extra_connection_removed(void) { - apr_atomic_dec32(&connection_count); + apr_atomic_dec32(&extra_connection_count); } static void wait_for_extra_connections(void) { - apr_uint32_t count = apr_atomic_read32(&connection_count); + apr_uint32_t count = apr_atomic_read32(&extra_connection_count); apr_time_t graceful, timeout, deadline; if (count == 0) { @@ -533,7 +533,7 @@ static void wait_for_extra_connections(void) do { apr_sleep(apr_time_from_msec(100)); - count = apr_atomic_read32(&connection_count); + count = apr_atomic_read32(&extra_connection_count); } while (count > 0 && apr_time_now() < deadline); if (count > 0) {