{"vulnerability": "cve-2026-3405", "sightings": [{"uuid": "67680ed4-e395-4fdd-ad3c-e08d7d7a5077", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34055", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mhy4ge6foy2u", "content": "", "creation_timestamp": "2026-03-26T17:20:09.704612Z"}, {"uuid": "6cb6c553-b049-4235-a5b5-94fffed5339d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-3405", "type": "seen", "source": "https://bsky.app/profile/cve.skyfleet.blue/post/3mg2ep73k472z", "content": "", "creation_timestamp": "2026-03-02T04:03:12.243821Z"}, {"uuid": "2c36423d-acbc-428c-a535-a2b4682d91ef", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-3405", "type": "seen", "source": "https://infosec.exchange/users/vuldb/statuses/116163037793945583", "content": "", "creation_timestamp": "2026-03-03T03:08:11.421256Z"}, {"uuid": "a8ed3fc3-473e-4862-bf9d-783f3e322ba0", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34055", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3mhwfolx6lc2p", "content": "", "creation_timestamp": "2026-03-26T01:00:31.117983Z"}, {"uuid": "ebe7b832-de81-4fc9-b61f-6b45b441e597", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34056", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3mhwfosxhev2j", "content": "", "creation_timestamp": "2026-03-26T01:00:37.970851Z"}, {"uuid": "661016fc-b641-439e-b621-3d7cf20ffce0", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34055", "type": "seen", "source": "https://bsky.app/profile/cve.skyfleet.blue/post/3mhwfxipqup2d", "content": "", "creation_timestamp": "2026-03-26T01:05:28.781149Z"}, {"uuid": "e9f61dfd-b513-498f-92fb-bc78ec4fe31a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34056", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mhy7rnoiql2k", "content": "", "creation_timestamp": "2026-03-26T18:20:09.504910Z"}, {"uuid": "64dc912e-2a25-4207-98b2-f6b16f152666", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34053", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mhyyeg5wn62c", "content": "", "creation_timestamp": "2026-03-27T01:40:09.790047Z"}, {"uuid": "af965a54-8cf5-47f4-9680-89382f811630", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://infosec.exchange/users/vuldb/statuses/116518129197995419", "content": "Our CTI team identified a lot of activities targeting Apache HTTP Server (CVE-2026-34059) https://vuldb.com/vuln/360955/cti", "creation_timestamp": "2026-05-04T20:12:36.531872Z"}, {"uuid": "addef0a8-3726-40b0-bd4b-ea4c2eca206e", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34054", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3midc3x3gyw2p", "content": "", "creation_timestamp": "2026-03-31T04:01:00.176735Z"}, {"uuid": "17ead550-1f9e-48d2-a5ce-13a91f7517ca", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34054", "type": "seen", "source": "Telegram/62pZ0M7EEU9QzqUpq3hJFXhFPR-ebrkyPJ1cBwC7xQpoTU0", "content": "", "creation_timestamp": "2026-03-31T05:17:42.000000Z"}, {"uuid": "3c0d51de-31dc-4086-8973-27c2106b54de", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/slackers.it/post/3ml2v2iz7we2x", "content": "6/11\n\nFor more information, see:\n    https://downloads.apache.org/httpd/CHANGES_2.4.67\n    https://www.cve.org/CVERecord?id=CVE-2026-34059\n    https://www.cve.org/CVERecord?id=CVE-2026-34032\n    https://www.cve.org/CVERecord?id=CVE-2026-33857", "creation_timestamp": "2026-05-05T00:01:49.907937Z"}, {"uuid": "10a25e04-b595-4078-982b-0ab089afcabc", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/cve.skyfleet.blue/post/3mkzwbqp6w52h", "content": "CVE-2026-34059 - Apache HTTP Server: mod_proxy_ajp: Heap Over-Read and memory disclosure in ajp_parse_data()\nCVE ID : CVE-2026-34059\n \n Published : May 4, 2026, 1:16 p.m. | 1\u00a0hour, 4\u00a0minutes ago\n \n Description : Buffer Over-read vulnerability in Apache HTTP Server.\n\nThis issue...", "creation_timestamp": "2026-05-04T14:51:06.701291Z"}, {"uuid": "e83ff810-56c1-4779-b521-834b29490e14", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/thehackerwire.bsky.social/post/3mkzyjg7ozk2z", "content": "\ud83d\udfe0 CVE-2026-34059 - High (7.5)\n\nBuffer Over-read vulnerability in Apache HTTP Server.\n\nThis issue affects Apache HTTP Server: thr...\n\nhttps://www.thehackerwire.com/vulnerability/CVE-2026-34059/\n\n#infosec #cybersecurity #CVE #vulnerability #security #patchstack", "creation_timestamp": "2026-05-04T15:31:12.153463Z"}, {"uuid": "32926063-38ce-43fe-8b4f-a54c975a618c", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/infosec.skyfleet.blue/post/3ml2a3ar7on22", "content": "CVE-2026-34059: Apache HTTP Server: mod_proxy_ajp: Heap Over-Read and memory disclosure in ajp_parse_data()", "creation_timestamp": "2026-05-04T17:46:26.163408Z"}, {"uuid": "79fefe88-1cef-4343-8043-bcbf4ff1afba", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "86ecb4e1-bb32-44d5-9f39-8a4673af8385", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://www.hkcert.org/security-bulletin/apache-http-server-multiple-vulnerabilities_20260506", "content": "", "creation_timestamp": "2026-05-05T18:00:00.000000Z"}, {"uuid": "bba62e02-085a-4b32-ad5f-a16d63ca2b0c", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/o2cloud.bsky.social/post/3ml4fuvyyd22w", "content": "\ud83d\udd17 CVE : CVE-2026-23918, CVE-2026-24072, CVE-2026-28780, CVE-2026-29168, CVE-2026-29169, CVE-2026-33006, CVE-2026-33007, CVE-2026-33523, CVE-2026-33857, CVE-2026-34032, CVE-2026-34059", "creation_timestamp": "2026-05-05T14:35:37.971382Z"}, {"uuid": "adad3313-0f9f-4cf5-b6fb-22e7cdb0759b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "86ecb4e1-bb32-44d5-9f39-8a4673af8385", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://ccb.belgium.be/advisories/warning-multiple-vulnerabilities-apache-http-server-can-lead-remote-code-execution-patch", "content": "", "creation_timestamp": "2026-05-06T09:37:53.000000Z"}, {"uuid": "42af8009-f065-46eb-9c78-896d6a00ac33", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://gist.github.com/jimjag/a93acf3e1a90689c2d1aa0c178a7dabf", "content": "diff --git a/CHANGES b/CHANGES\nindex 443e05f539..c13c09dd55 100644\n--- a/CHANGES\n+++ b/CHANGES\n@@ -1,6 +1,13 @@\n                                                          -*- coding: utf-8 -*-\n Changes with Apache 2.4.68\n \n+  *) mpm_motorz, mod_http2: Rework the MotorZ MPM with\n+     multi-poller scale-out (new PollersPerChild directive) and async\n+     keep-alive/HTTP/2 handoff, plus concurrency hardening and bug\n+     fixes. Includes a required mod_http2 fix so a client's graceful\n+     GOAWAY does not drop an in-flight response under async MPMs, and\n+     a self-contained motorz test suite.  [Jim Jagielski]\n+\n Changes with Apache 2.4.67\n \n   *) SECURITY: CVE-2026-34059: Apache HTTP Server: mod_proxy_ajp:\ndiff --git a/docs/manual/mod/allmodules.xml b/docs/manual/mod/allmodules.xml\nindex 8fdd483eaa..e7a5517191 100644\n--- a/docs/manual/mod/allmodules.xml\n+++ b/docs/manual/mod/allmodules.xml\n@@ -129,6 +129,7 @@\n   mod_xml2enc.xml\n   mpm_common.xml\n   event.xml\n+  motorz.xml\n   mpm_netware.xml\n   mpmt_os2.xml\n   prefork.xml\ndiff --git a/docs/manual/mod/motorz.xml b/docs/manual/mod/motorz.xml\nnew file mode 100644\nindex 0000000000..8950e47673\n--- /dev/null\n+++ b/docs/manual/mod/motorz.xml\n@@ -0,0 +1,258 @@\n+\n+\n+\n+\n+\n+\n+\n+\n+motorz\n+A lean, fast, self-contained event-driven Multi-Processing Module\n+built on the APR pollset and thread pool especially suited as a reverse proxy\n+MPM\n+motorz.c\n+mpm_motorz_module\n+\n+\n+    \nThe motorz Multi-Processing Module (MPM) is an\n+    asynchronous, event-driven implementation. It combines a\n+    prefork-style fixed pool of child processes with an event core built on\n+    APR's pollset and a shared thread pool. Each child\n+    runs one or more dedicated poller threads that watch sockets\n+    and timers, dispatching ready I/O events and expired timers to a pool of\n+    worker threads. The workers never poll; they only process the\n+    connection/request work pushed to them.\n+\n+    \nThe design goal is a fast, efficient, single, compact MPM that runs on modern\n+    Unix platforms by leaning on APR as much as possible, while still supporting\n+    the asynchronous connection handling needed for efficient keep-alive and\n+    HTTP/2.\n+\n+    \nTo use the motorz MPM, add\n+      --with-mpm=motorz to the configure\n+      script's arguments when building the httpd, or build\n+      it as a loadable module with\n+      --enable-mpms-shared=motorz.\n+\n+\n+\n+The event MPM\n+The worker MPM\n+The prefork MPM\n+Setting which addresses and ports Apache HTTP Server uses\n+\n+\nHow it Works\n+    \nmotorz uses prefork as the framework for process\n+    management and an event core for connection handling. A single control\n+    process (the parent) launches a fixed number of child processes, as set\n+    by the StartServers directive.\n+    Unlike worker and event, the number of\n+    children does not float with load: motorz maintains a\n+    static pool, replacing children one-for-one as they exit. Concurrency\n+    within a host is scaled by adding worker threads\n+    (ThreadsPerChild) and, where\n+    the poll/dispatch path is the bottleneck, poller threads\n+    (PollersPerChild), rather than by spawning more\n+    processes.\n+\n+    \nEach child process runs:\n+    \n\n+        \nOne or more poller threads. Each poller owns its\n+        own pollset, timer ring (with a guarding mutex) and lock-free\n+        transaction-pool recycle list, so pollers never contend with one\n+        another. A poller polls, dispatches ready I/O events and expired\n+        timers to the worker pool, and (for the poller that owns the listening\n+        sockets) accepts new connections. The number of pollers is controlled\n+        by PollersPerChild.\n+\n+        \nA shared pool of worker threads\n+        (ThreadsPerChild) that run\n+        the actual connection and request processing pushed to them. Workers\n+        never poll.\n+\n+        \nA supervisor (the child's main thread) that\n+        watches MaxConnectionsPerChild\n+        and the pipe-of-death / generation, signals the pollers to wind down,\n+        and then joins them on exit.\n+    \n+\n+    \nA connection is sharded to one poller at accept time (round-robin) and\n+    bound to it for its whole lifetime: it re-arms in, and times out on, that\n+    poller's pollset and timer ring. Using multiple pollers lifts the\n+    single-poll-thread throughput ceiling, so accept, event dispatch and timer\n+    expiry scale with PollersPerChild instead of being\n+    serialized on one thread.\n+\n+    \nWhile the parent process is usually started as root under\n+    Unix in order to bind to port 80, the child processes and threads are\n+    launched by the server as a less-privileged user. The\n+    User and\n+    Group directives are used to set\n+    the privileges of the Apache HTTP Server child processes. The child\n+    processes must be able to read all the content that will be served, but\n+    should have as few privileges beyond that as possible.\n+\n+    \nMaxConnectionsPerChild\n+    controls how frequently the server recycles processes by retiring old ones\n+    and launching new ones.\n+\n+\n+\nAsynchronous connection handling\n+    \nmotorz reports itself as an asynchronous MPM. When a\n+    worker finishes the active phase of a connection (for example, an\n+    HTTP keep-alive connection between requests, or a connection waiting on\n+    further I/O), it hands the socket back to its poller rather than holding a\n+    worker thread idle. The poller waits for the next event on that socket,\n+    bounded by the configured Timeout,\n+    and re-dispatches the connection to a worker only when there is work to do.\n+    This frees worker threads from idle keep-alive connections and is what\n+    allows efficient HTTP/2 handling, where the master connection is handed\n+    back to the MPM between requests.\n+\n+    \nLingering close is also non-blocking: instead of blocking a worker for\n+    the duration of the lingering-close timeout, the draining socket is handed\n+    back to the poll loop with a bounded linger timeout, so the worker is\n+    returned to the pool immediately.\n+\n+    \nModules that take a connection fully asynchronous (suspending it and\n+    resuming it later) are supported; a suspended connection is parked and\n+    re-armed on its owning poller when resumed.\n+\n+\n+\nAdmission control\n+    \nTo keep a child safe under overload, motorz applies\n+    listener backpressure. When the worker pool saturates, the poller that\n+    owns the listening sockets removes them from its pollset and stops\n+    accepting; it re-adds them once the backlog drains. This keeps the work\n+    queue and per-connection memory bounded rather than growing without limit.\n+    The decision is based on the worker pool's idle, pending and active-thread\n+    counts, with hysteresis to avoid flapping the listeners on and off.\n+\n+    ThreadsPerChild and admission control\n+    \nBecause the admission-control low-water mark is a fraction of\n+    ThreadsPerChild, very small\n+    values (in particular ThreadsPerChild 1) cause the listeners\n+    to re-enable only when the work queue is completely empty, which severely\n+    degrades throughput. A value of ThreadsPerChild of at least 4\n+    is strongly recommended; the server emits a warning otherwise.\n+    \n+\n+\n+\nRelationship to other MPMs\n+    \nmotorz uses prefork for process management and an APR\n+    thread pool for workers, with pollers dispatching work to that pool. This\n+    is distinct from event's listener/worker/fdqueue design,\n+    in which the worker threads themselves re-arm a shared, thread-safe\n+    pollset.\n+\n+    \nWhether additional pollers help depends on the workload. If the worker\n+    threads are the CPU bottleneck&#8212;typical for real request\n+    processing&#8212;the poller threads are not the limiting factor, and a\n+    PollersPerChild beyond one or two yields little. The\n+    multiple-poller design removes motorz's\n+    structural single-thread ceiling, but per-host throughput is still\n+    governed by worker CPU.\n+\n+    No ServerLimit / dynamic process scaling\n+    \nUnlike worker and event,\n+    motorz does not scale the number of child processes with\n+    load and does not provide a separate\n+    ServerLimit ceiling. The process\n+    pool is fixed at StartServers,\n+    which therefore acts as the hard daemon limit, and there are no\n+    MinSpareThreads /\n+    MaxSpareThreads /\n+    MaxRequestWorkers controls.\n+    Scale concurrency with\n+    ThreadsPerChild (and, if the\n+    poll path saturates, PollersPerChild).\n+    \n+\n+\n+CoreDumpDirectory\n+\n+EnableExceptionHook\n+\n+Group\n+\n+Listen\n+\n+ListenBacklog\n+\n+MaxConnectionsPerChild\n+\n+MaxMemFree\n+\n+PidFile\n+\n+ScoreBoardFile\n+\n+SendBufferSize\n+\n+StartServers\n+\n+ThreadLimit\n+\n+ThreadsPerChild\n+\n+ThreadStackSize\n+\n+User\n+\n+\n+\n+PollersPerChild\n+Number of poll threads per child process\n+PollersPerChild number\n+PollersPerChild 0\n+server config\n+motorz\n+\n+\n+    \nThe PollersPerChild directive sets the number of\n+    poller threads created in each child process. Each poller owns its own\n+    pollset, timer ring and connection-recycle list, and handles a shard of\n+    the child's connections, so adding pollers raises the rate at which a\n+    single child can accept connections and dispatch I/O events and timer\n+    expiries.\n+\n+    \nA value of 0 (the default) means auto: the number\n+    of pollers is derived from the number of online CPUs, capped at a built-in\n+    maximum. In all cases the number of pollers is clamped so that it never\n+    exceeds ThreadsPerChild and is\n+    never less than one.\n+\n+    \nBecause event dispatch is rarely the bottleneck for real request\n+    processing&#8212;worker CPU usually is&#8212;values beyond one or two\n+    seldom improve throughput. Raising PollersPerChild\n+    is mainly useful for workloads dominated by very high connection churn or\n+    large numbers of idle, event-driven connections, where the poll/accept\n+    path itself becomes the limit.\n+\n+    Example\n+    \n+StartServers       2\n+ThreadsPerChild   64\n+ThreadLimit       64\n+PollersPerChild    2\n+    \n+    \n+\n+\n+\n+\ndiff --git a/docs/manual/mod/motorz.xml.meta b/docs/manual/mod/motorz.xml.meta\nnew file mode 100644\nindex 0000000000..0d8012243a\n--- /dev/null\n+++ b/docs/manual/mod/motorz.xml.meta\n@@ -0,0 +1,12 @@\n+\n+\n+\n+\n+  motorz\n+  /mod/\n+  ..\n+\n+  \n+    en\n+  \n+\ndiff --git a/modules/arch/unix/config5.m4 b/modules/arch/unix/config5.m4\nindex 7ee06663b6..49343d03a1 100644\n--- a/modules/arch/unix/config5.m4\n+++ b/modules/arch/unix/config5.m4\n@@ -3,7 +3,8 @@ APACHE_MODPATH_INIT(arch/unix)\n \n if ap_mpm_is_enabled \"worker\" \\\n    || ap_mpm_is_enabled \"event\" \\\n-   || ap_mpm_is_enabled \"prefork\"; then\n+   || ap_mpm_is_enabled \"prefork\" \\\n+   || ap_mpm_is_enabled \"motorz\"; then\n     unixd_mods_enable=yes\n else\n     unixd_mods_enable=no\ndiff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c\nindex dda6c77178..a3019456b0 100644\n--- a/modules/http2/h2_session.c\n+++ b/modules/http2/h2_session.c\n@@ -1536,7 +1536,42 @@ static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char\n         session-&gt;remote.accepting = 0;\n         session-&gt;remote.shutdown = 1;\n         cleanup_unprocessed_streams(session);\n-        transit(session, \"remote goaway\", H2_SESSION_ST_DONE);\n+        if (arg == 0 &amp;&amp; session-&gt;open_streams &gt; 0) {\n+            /* Graceful client GOAWAY while we are still processing streams it\n+             * sent us. Do NOT go to DONE here: that makes h2_c1_run() put the\n+             * connection into CONN_STATE_LINGER and the MPM close it, which\n+             * runs h2_mplx_c1_destroy() and h2_c2_abort()s any stream whose\n+             * secondary connection (c2) has not yet flushed its response onto\n+             * c1 -- silently dropping that response. The window between a c2\n+             * finishing its output and signalling done is small, but an async\n+             * MPM that hands c1 back to a fresh worker between events (e.g.\n+             * mpm_motorz) drives the close into it far more often than\n+             * mpm_event, whose scheduling happens to let c2 drain first.\n+             *\n+             * Instead keep the session running. The remaining streams complete\n+             * normally, their output is written, and once open_streams reaches\n+             * 0 we finish from the IDLE state below (or via NO_MORE_STREAMS),\n+             * i.e. only after every c2 is done and flushed. This also honors\n+             * RFC 9113: a peer's GOAWAY does not abort streams at or below its\n+             * last-stream-id, it just stops new ones.\n+             *\n+             * Liveness: keeping the session running here does NOT risk pinning\n+             * c1 open indefinitely on a slow or wedged c2. While draining we\n+             * return to ST_BUSY/ST_WAIT and poll the mplx with session-&gt;s-&gt;timeout\n+             * (see h2_session_process()), and each c2 runs its request under the\n+             * same server Timeout. A c2 that stops making progress is aborted by\n+             * its own timeout, which drops open_streams and lets us reach IDLE\n+             * and finish. The new dependency this introduces -- relative to the\n+             * old straight-to-DONE behaviour -- is exactly that c1 teardown now\n+             * waits on c2 progress/timeout instead of racing ahead of it; that\n+             * is the point of the fix, and it is bounded by Timeout. */\n+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session-&gt;c1,\n+                          H2_SSSN_MSG(session,\n+                          \"remote goaway, draining open streams\"));\n+        }\n+        else {\n+            transit(session, \"remote goaway\", H2_SESSION_ST_DONE);\n+        }\n     }\n }\n \n@@ -1944,6 +1979,24 @@ apr_status_t h2_session_process(h2_session *session, int async,\n \n         case H2_SESSION_ST_IDLE:\n             ap_assert(session-&gt;open_streams == 0);\n+            if (session-&gt;remote.shutdown) {\n+                /* The client sent a GOAWAY and all streams it sent us have now\n+                 * been processed and their output written (open_streams == 0).\n+                 * It will not open new streams, so there is nothing to wait\n+                 * for: send our GOAWAY and finish. Reaching DONE only now -- as\n+                 * opposed to when the client's GOAWAY arrived -- guarantees the\n+                 * connection is closed only after every c2 is done and flushed,\n+                 * which is what keeps async handoff (e.g. mpm_motorz) from\n+                 * dropping the last response under HTTP/2 connection churn.\n+                 * (Checked before the want_read assert below: after receiving a\n+                 * GOAWAY nghttp2 may no longer want to read.) */\n+                if (!session-&gt;local.shutdown) {\n+                    h2_session_shutdown(session, 0, \"done\", 0);\n+                }\n+                transit(session, \"remote goaway, streams drained\",\n+                        H2_SESSION_ST_DONE);\n+                break;\n+            }\n             ap_assert(nghttp2_session_want_read(session-&gt;ngh2));\n             if (!h2_session_want_send(session)) {\n                 /* Give any new incoming request a short grace period to\ndiff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h\nindex 497f7e1936..fab2001a99 100644\n--- a/modules/http2/h2_session.h\n+++ b/modules/http2/h2_session.h\n@@ -91,7 +91,20 @@ typedef struct h2_session {\n     struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */\n     \n     struct h2_stream_monitor *monitor;/* monitor callbacks for streams */\n-    unsigned int open_streams;      /* number of streams processing */\n+    unsigned int open_streams;      /* number of streams processing.\n+                                     * c1-thread only: written via\n+                                     * h2_mplx_c1_stream_cleanup() on H2_SS_CLEANUP\n+                                     * and read throughout h2_session_process(),\n+                                     * all on the c1 connection thread. An async\n+                                     * MPM (e.g. motorz) re-dispatches c1 to a\n+                                     * fresh worker between events -- successive,\n+                                     * never concurrent -- so no atomics/volatile\n+                                     * are needed; the MPM pollset handoff\n+                                     * establishes the happens-before. Decrements\n+                                     * to 0 only after each stream's c2 has\n+                                     * finished and flushed; the graceful-GOAWAY\n+                                     * drain in h2_session_process() relies on\n+                                     * this (see h2_session_ev_remote_goaway). */\n \n     unsigned int streams_done;      /* number of http/2 streams handled */\n     unsigned int responses_submitted; /* number of http/2 responses submitted */\ndiff --git a/server/mpm/config2.m4 b/server/mpm/config2.m4\nindex d7e73ec05e..8f10593044 100644\n--- a/server/mpm/config2.m4\n+++ b/server/mpm/config2.m4\n@@ -8,9 +8,9 @@ APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by\n     default_mpm=$withval\n     AC_MSG_RESULT($withval);\n ],[\n-    dnl Order of preference for default MPM: \n+    dnl Order of preference for default MPM:\n     dnl   The Windows and OS/2 MPMs are used on those platforms.\n-    dnl   Everywhere else: event, worker, prefork\n+    dnl   Everywhere else: event, motorz, worker, prefork\n     if ap_mpm_is_supported \"winnt\"; then\n         default_mpm=winnt\n         AC_MSG_RESULT(winnt)\n@@ -20,12 +20,15 @@ APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by\n     elif ap_mpm_is_supported \"event\"; then\n         default_mpm=event\n         AC_MSG_RESULT(event)\n+    elif ap_mpm_is_supported \"motorz\"; then\n+        default_mpm=motorz\n+        AC_MSG_RESULT(motorz - event is not supported)\n     elif ap_mpm_is_supported \"worker\"; then\n         default_mpm=worker\n-        AC_MSG_RESULT(worker - event is not supported)\n+        AC_MSG_RESULT(worker - event and motorz are not supported)\n     else\n         default_mpm=prefork\n-        AC_MSG_RESULT(prefork - event and worker are not supported)\n+        AC_MSG_RESULT(prefork - event, motorz and worker are not supported)\n     fi\n ])\n \ndiff --git a/server/mpm/motorz/MOTORZ.README b/server/mpm/motorz/MOTORZ.README\nnew file mode 100644\nindex 0000000000..35d2f450d1\n--- /dev/null\n+++ b/server/mpm/motorz/MOTORZ.README\n@@ -0,0 +1,267 @@\n+The MotorZ MPM aims to create a single MPM, that runs on all modern\n+Unix and Win32 platforms, by levering APR as much as possible.\n+\n+The MoteZ MPM uses an APR Poll CB event system, with  timers being\n+built in.  When an event, either an I/O or Timer is ready to run, it\n+is pushed to any available thread in the current process. A simple\n+APR Thread pool is used.\n+\n+MotorZ uses Prefork as the framework and Simple for the actual event\n+structure.\n+\n+\n+Threading model\n+===============\n+\n+Each child process runs:\n+\n+  - N poll threads (\"pollers\", PollersPerChild; default: one per online CPU,\n+    capped). Each poller owns its OWN pollset, timer ring (+ guarding mutex)\n+    and lock-free transaction-pool recycle list, so pollers never contend with\n+    each other. A poller polls, dispatches ready I/O events and expired timers\n+    to the worker pool, and (for the listener-owning poller) accepts.\n+  - A shared APR thread pool of worker threads (ThreadsPerChild) that run the\n+    actual connection/request processing pushed to them; workers never poll.\n+  - A supervisor (the child's main thread) that watches MaxRequestsPerChild and\n+    the pipe-of-death / generation, signalling the pollers to wind down, then\n+    joins them on exit.\n+\n+A connection is sharded to one poller at accept time (round-robin) and bound to\n+it for its whole lifetime: it re-arms in and times out on that poller's\n+pollset/ring. (Its transaction pool is recycled back to the ACCEPTING poller's\n+free-list, which is the single consumer of that list -- recycling is not\n+sharded, only I/O is.) Multiple pollers lift the old single-poll-thread\n+throughput ceiling: accept + event dispatch + timer expiry now scale with\n+PollersPerChild instead of being serialized on one thread.\n+\n+Admission control (listener backpressure) keeps the child safe under overload:\n+when the worker pool saturates, the listener-owning poller removes the\n+listening sockets from its pollset (motorz_update_listeners /\n+motorz_disable_listeners) and re-adds them once the backlog drains, so the work\n+queue and per-connection memory stay bounded rather than growing without limit.\n+\n+Relationship to mpm_event\n+-------------------------\n+\n+MotorZ uses Prefork for process management and an APR thread pool for workers,\n+with pollers dispatching to that pool -- distinct from mpm_event's\n+listener/worker/fdqueue design where workers themselves re-arm a shared\n+thread-safe pollset. In practice, whether more pollers help depends on the\n+workload: if the worker threads are the CPU bottleneck (typical for real\n+request processing), the poll threads are not the limit and PollersPerChild\n+beyond 1-2 yields little. The multiple-poller design removes motorz's\n+*structural* single-thread ceiling, but per-box throughput is still governed by\n+worker CPU. For the broadest, most battle-tested high-concurrency async\n+behavior, mpm_event remains the reference; MotorZ stays a lean, self-contained\n+alternative.\n+\n+\n+HTTP/2 async handoff (ENABLED -- mod_http2 close-ordering fixed)\n+===============================================================\n+\n+Status: motorz reports AP_MPMQ_IS_ASYNC = 1 and AP_MPMQ_CAN_WAITIO = 1\n+(MOTORZ_ENABLE_ASYNC = 1 in motorz.c). Async was previously disabled to work\n+around a real defect in the interaction between motorz's async connection\n+handoff and mod_http2's stream lifecycle that dropped a small fraction of\n+requests under HTTP/2 connection churn. That defect has now been fixed in\n+mod_http2 (see \"The fix\" below), so async is safe to advertise: the c1\n+connection is no longer closed until every secondary connection (c2) has\n+finished and flushed its response. CONN_STATE_ASYNC_WAITIO in\n+motorz_io_process() is now actually exercised by mod_http2 (it only requests\n+WAITIO of an async MPM).\n+\n+The symptom / root cause / diagnosis below are retained as the rationale for\n+the fix and as a guide if a regression ever reappears.\n+\n+Symptom\n+-------\n+Under HTTP/2 with many short-lived, rapidly churning connections (the worst case\n+is one request per connection at high concurrency, e.g. `h2load -n 30000 -c 50\n+-m 1`), motorz intermittently dropped ~0.2-3% of requests. h2load reports them\n+as \"Process Request Failure\"; the responses are lost. All h2 error codes are 0\n+(graceful), there is no TCP RST, no server-side 4xx/5xx, and -- crucially -- no\n+data race on motorz's own per-connection state (the claim/ownership model holds;\n+verified with ThreadSanitizer). mpm_event does NOT exhibit this; motorz does.\n+It is timing-sensitive (a Heisenbug): trace8 logging, lldb, and Guard Malloc all\n+slow the hot path enough to hide it; it reproduces under TSan and at info level.\n+\n+Root cause: a c1/c2 close-ordering race in mod_http2, exposed by async handoff\n+---------------------------------------------------------------------------\n+mod_http2 runs the HTTP/2 master connection (\"c1\") on the MPM-provided thread\n+and runs each request stream on a SECONDARY connection (\"c2\") on its OWN worker\n+thread pool (h2_workers.c). A stream's response is produced by its c2 worker and\n+written out through c1.\n+\n+Two facts combine to make the bug:\n+\n+  1. A stream is considered \"running\" until its c2 worker calls c2_prod_done(),\n+     which sets conn_ctx-&gt;done = 1 (h2_mplx.c). h2_mplx.c:stream_is_running() ==\n+     (started &amp;&amp; !done). There is a WINDOW between the c2 submitting its final\n+     output + EOS to its output beam (which lets c1/nghttp2 see the stream as\n+     CLOSED -&gt; CLEANUP) and the c2 worker actually returning and calling\n+     c2_prod_done(). During that window the stream is CLOSED at the protocol\n+     level but still \"running\".\n+\n+  2. When the c1 session ends (e.g. the client sends GOAWAY after its last\n+     request -- normal for one-request-per-connection churn), h2_c1_run()\n+     (h2_c1.c) sets c-&gt;cs-&gt;state = CONN_STATE_LINGER for the ST_DONE/ST_CLEANUP\n+     session states. The MPM then performs a lingering close, whose pre_close\n+     hook (ap_prep_lingering_close -&gt; ap_run_pre_close_connection -&gt; h2_c1_pre_close\n+     -&gt; h2_session_pre_close) sends GOAWAY and tears the session down.\n+     m_stream_cleanup() (h2_mplx.c), for any stream still \"running\" at that\n+     moment, calls h2_c2_abort() -- which aborts the c2 output beam\n+     (\"(53)Software caused connection abort\"), discarding the in-flight response.\n+\n+So if the c1 close runs DURING the window in (1), the just-finished stream's\n+response is aborted instead of flushed. mpm_event keeps the c1 connection\n+scheduled such that the c2 reaches done=1 first (its trace shows nearly all\n+streams \"c2 is done, move to spurge\", almost none \"c2 is running, abort\").\n+motorz, dispatching c1 work to a fresh pool thread on each async re-entry,\n+drives the close ahead of c2_prod_done far more often (many \"c2 is running,\n+abort\", and GOAWAYs logged with reason='timeout' -- the reason string is just\n+\"state was IDLE at close\", NOT an actual timeout: elapsed times were ~tens of ms\n+against a 10s Timeout).\n+\n+Why disabling async fixes it\n+----------------------------\n+With AP_MPMQ_IS_ASYNC = 0, mod_http2's h2_c1_run() does NOT return the c1\n+connection to the MPM between requests. Instead it LOOPS on h2_session_process(),\n+holding one worker thread and driving mod_http2's own multiplexer pollset\n+(h2_mplx_c1_poll, which polls both the c1 socket and the c2 beam pipes) until the\n+session is genuinely done -- i.e. until every stream's c2 has completed and its\n+output has been written. The early c1 close-vs-c2 race cannot occur because the\n+same thread that would close the connection is the one pumping the c2 output to\n+completion first. Measured: zero dropped requests, and HTTP/2 throughput is\n+unchanged (mod_http2 pins a worker per active c1 connection either way).\n+\n+Cost of the workaround\n+----------------------\n+The trade-off is HTTP/1.1, not HTTP/2. With async on, an idle keep-alive\n+connection is handed back to the MPM and frees its worker; with async off it\n+holds a worker for the connection's lifetime, so the count of concurrent\n+kept-alive HTTP/1.1 connections is bounded by ThreadsPerChild. (No requests are\n+lost -- HTTP/1.1 correctness is unaffected; only keep-alive worker-occupancy\n+scaling regresses.) This is an acceptable trade to be correct under HTTP/2;\n+revisit if/when motorz targets large HTTP/1.1 keep-alive fan-out.\n+\n+The fix (implemented in mod_http2)\n+----------------------------------\n+The fix lives in mod_http2, not motorz, and follows the second candidate\n+approach above: never let the c1 session reach DONE -&gt; LINGER (which is what\n+lets the MPM close the connection) while a stream it is processing still has a\n+c2 whose response has not been written. The invariant established is: \"a c1\n+connection is only closed once every stream's c2 has finished and its output is\n+flushed.\" Two small changes in h2_session.c do this for the path that actually\n+triggered the loss -- the client's graceful GOAWAY:\n+\n+  * h2_session_ev_remote_goaway(): a graceful GOAWAY (error code 0) with streams\n+    still in flight (open_streams &gt; 0) no longer transits straight to\n+    H2_SESSION_ST_DONE. It records the remote shutdown, RSTs only the\n+    unprocessed streams (as before), and keeps the session running so the\n+    in-flight streams complete and their c2 output is written. (An error GOAWAY,\n+    or one with no streams in flight, still goes to DONE immediately.) This also\n+    matches RFC 9113: a peer GOAWAY stops new streams, it does not abort streams\n+    at or below its last-stream-id.\n+\n+  * H2_SESSION_ST_IDLE handling in h2_session_process(): once those streams have\n+    drained (open_streams == 0) and the remote has shut down, the session sends\n+    our GOAWAY and goes to DONE from IDLE instead of parking the connection back\n+    on the MPM to wait for new streams that a departing client will never open.\n+    Reaching DONE only here -- after the c2s are done and flushed -- is what\n+    keeps the close from racing an in-flight c2.\n+\n+With this in place the abort-on-close in m_stream_cleanup()/h2_mplx_c1_destroy()\n+is no longer reached while a c2's response is unsent, so MOTORZ_ENABLE_ASYNC=1\n+is lossless under churn. The churn regression (server/mpm/motorz/test/smoke.sh\n+and run-http2.sh) asserts it at n=.. c=50 m=1, and the async assertions there\n+have been inverted to expect async ON (CONN_STATE_ASYNC_WAITIO arms / \"returning\n+to mpm c1 monitoring\" appears).\n+\n+Measuring the regression correctly (two pitfalls)\n+.................................................\n+The committed tests learned two lessons the hard way; preserve them if you edit:\n+\n+  1. Assert on RESPONSE LOSS, not on h2load's \"failed\" total. h2load's \"failed\"\n+     counts BOTH connection-establishment errors AND dropped responses:\n+       failed = (total - started)  +  (started - succeeded)\n+                \\__ connection setup _/   \\__ THIS bug ___/\n+     The first term is environmental -- ephemeral-port exhaustion and accept-\n+     queue pressure when hammering loopback at high -c (it appears with the fix,\n+     without the fix, and even on mpm_event; on a busy macOS loopback a few\n+     hundred per 30000 is normal). Only the second term, started - succeeded\n+     (responses dropped on connections that DID start), is the close-ordering\n+     bug. The tests compute `lost = started - succeeded` and assert that is 0;\n+     asserting \"failed == 0\" or \"started == total\" gives flaky failures that\n+     have nothing to do with this fix.\n+\n+  2. Measure at LogLevel info, NOT trace8. This is a Heisenbug: trace8 slows the\n+     hot path enough to hide it (the same reason it hides under lldb / Guard\n+     Malloc, noted below). A churn assertion run under trace8 passes even with\n+     the fix deliberately removed -- i.e. it is vacuous. smoke.sh runs at trace8\n+     for its state-machine traces, so its churn check is only a gross sanity\n+     pass; the load-bearing, non-vacuous churn regression is the one in\n+     run-http2.sh, which runs at info. (Verified: defeating the fix and\n+     re-running at info brings response loss back; under trace8 it does not.)\n+\n+How to trigger the bug (step by step)\n+-------------------------------------\n+The bug is masked while async is OFF, so you must first turn it back on:\n+\n+  1. Re-enable async:    set MOTORZ_ENABLE_ASYNC to 1 in motorz.c, rebuild\n+     (`make`). (motorz_query will now report AP_MPMQ_IS_ASYNC=1 and\n+     AP_MPMQ_CAN_WAITIO=1, so mod_http2 takes the async c1 hand-back path.)\n+\n+  2. Build at a NORMAL/fast level. Do NOT use trace8/trace2 logging, lldb, or\n+     Guard Malloc while trying to TRIGGER it -- the bug is a Heisenbug and any\n+     of those slow the hot path enough to hide it. (Use trace2 only afterwards\n+     to observe the close path, see below.)\n+\n+  3. Config that maximizes the race (TLS vhost with `Protocols h2 http/1.1`):\n+       StartServers 1\n+       PollersPerChild 1        ; fewer pollers =&gt; worse (more serialized on\n+                                ;   poller 0), so 1 is the most reliable trigger\n+       ThreadsPerChild 128      ; large, so the failures are NOT worker\n+                                ;   starvation -- rules that out as a cause\n+       ThreadLimit 128\n+       LogLevel info\n+     (Any document works; a tiny static file is fine.)\n+\n+  4. Drive maximum HTTP/2 CONNECTION CHURN with h2load -- the trigger is many\n+     short connections each doing very few streams, NOT many streams per conn:\n+\n+       h2load -n 30000 -c 50 -m 1  https://localhost:PORT/\n+\n+     -m 1 (one stream per connection) is the strongest trigger; -m 25 still\n+     fails sometimes; -m &gt;= 50 essentially hides it. Run it 5-10 times: roughly\n+     1 in 3-5 runs drops a few hundred requests (h2load: \"Process Request\n+     Failure\", \"failed\"/\"errored\" &gt; 0, and \"started\" &lt; \"total\"). Higher -c (e.g.\n+     100) raises the hit rate. The committed test harness encodes this:\n+     `server/mpm/motorz/test/smoke.sh` and `run-http2.sh` (which currently\n+     assert 0 failures *because async is off* -- with async on they will fail,\n+     which IS the reproduction).\n+\n+  5. The failures are graceful: no TCP RST, no 4xx/5xx, no crash -- just lost\n+     responses. (Very rarely the child SIGBUSes; that is the same race hitting\n+     freed memory, not a separate bug.)\n+\n+How it was diagnosed (for whoever picks this up)\n+------------------------------------------------\n+  * Confirm the close path: with async on, LogLevel trace2 (GLOBAL -- a\n+    per-module \"http2:trace2\" spec does NOT emit on this build), small runs\n+    (e.g. n=2000) until a fail, then grep the failing connection id for\n+    \"c2 is running, abort\", \"Software caused connection abort\", and\n+    \"GOAWAY[... reason='timeout'\". Those three together are the signature.\n+  * Confirm it is the async handoff (not anything else): flip\n+    MOTORZ_ENABLE_ASYNC back to 0 -&gt; failures vanish entirely (0/N runs). That\n+    single toggle is the proof, and is the shipped workaround.\n+  * Confirm it is NOT a motorz data race: ThreadSanitizer build\n+    (`make EXTRA_CFLAGS=\"-fsanitize=thread -g -O1\" LDFLAGS=\"-fsanitize=thread\"`,\n+    run with `TSAN_OPTIONS=log_path=...`) shows no race on motorz's scon\n+    fields; the only h2 races are the by-design c2-&gt;aborted signal flag and\n+    benign lock-free free-list idioms.\n+  * Contrast with mpm_event: run the identical h2load churn against event -&gt;\n+    0 failures, and its trace shows nearly all streams \"c2 is done, move to\n+    spurge\" (clean) vs motorz's many \"c2 is running, abort\".\n+  * Earlier dead ends (do NOT re-chase): the scoreboard slot-0 contention and\n+    the c2-&gt;cs conn_state aliasing were both real races but NOT the cause --\n+    fixing either did not stop the dropped requests.\ndiff --git a/server/mpm/motorz/Makefile.in b/server/mpm/motorz/Makefile.in\nnew file mode 100644\nindex 0000000000..f34af9cbd6\n--- /dev/null\n+++ b/server/mpm/motorz/Makefile.in\n@@ -0,0 +1 @@\n+include $(top_srcdir)/build/special.mk\ndiff --git a/server/mpm/motorz/config.m4 b/server/mpm/motorz/config.m4\nnew file mode 100644\nindex 0000000000..efcf548f6a\n--- /dev/null\n+++ b/server/mpm/motorz/config.m4\n@@ -0,0 +1,11 @@\n+AC_MSG_CHECKING(if motorz MPM supports this platform)\n+if test $forking_mpms_supported != yes; then\n+    AC_MSG_RESULT(no - This is not a forking platform)\n+elif test $ac_cv_define_APR_HAS_THREADS != yes; then\n+    AC_MSG_RESULT(no - APR does not support threads)\n+elif test $have_threaded_sig_graceful != yes; then\n+    AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM)\n+else\n+    AC_MSG_RESULT(yes)\n+    APACHE_MPM_SUPPORTED(motorz, yes, yes)\n+fi\ndiff --git a/server/mpm/motorz/config3.m4 b/server/mpm/motorz/config3.m4\nnew file mode 100644\nindex 0000000000..7cf26379bc\n--- /dev/null\n+++ b/server/mpm/motorz/config3.m4\n@@ -0,0 +1,7 @@\n+dnl ## XXX - Need a more thorough check of the proper flags to use\n+\n+APACHE_SUBST(MOD_MPM_MOTORZ_LDADD)\n+\n+APACHE_MPM_MODULE(motorz, $enable_mpm_motorz, motorz.lo,[\n+    AC_CHECK_FUNCS(pthread_kill)\n+], , [\\$(MOD_MPM_MOTORZ_LDADD)])\ndiff --git a/server/mpm/motorz/motorz.c b/server/mpm/motorz/motorz.c\nnew file mode 100644\nindex 0000000000..cd7a429b0f\n--- /dev/null\n+++ b/server/mpm/motorz/motorz.c\n@@ -0,0 +1,2752 @@\n+/* Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  You may obtain a copy of the License at\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include \"motorz.h\"\n+\n+/* Upper bound on the number of transaction pools kept on the recycle\n+ * free-list (motorz_ptrans_get/put). Beyond this, freed pools are destroyed\n+ * outright so a burst of connections does not pin memory forever.\n+ */\n+#define MAX_RECYCLED_POOLS 64\n+\n+/* Lingering close timeouts. Not exported by the core, so mirror the values\n+ * connection.c uses (also mirrored this way by mpm_event). MAX_SECS_TO_LINGER\n+ * bounds the whole non-blocking drain; SECONDS_TO_LINGER is the shortened\n+ * period used when a module requested it (e.g. DoS mitigation).\n+ */\n+#ifndef MAX_SECS_TO_LINGER\n+#define MAX_SECS_TO_LINGER 30\n+#endif\n+#define SECONDS_TO_LINGER  2\n+\n+/**\n+ * config globals\n+ */\n+static motorz_core_t *g_motorz_core;\n+static int threads_per_child = 16;\n+static int ap_num_kids = DEFAULT_START_DAEMON;\n+/* Number of poll threads per child (#2 / scaling). 0 means \"auto\": derive from\n+ * online CPUs in child_main, capped so a small box doesn't over-thread. Each\n+ * poller owns its own pollset/timer-ring/recycle-list and a shard of the\n+ * connections, lifting the single-poll-thread throughput ceiling.\n+ */\n+static int num_pollers = 0;\n+#define MOTORZ_MAX_POLLERS 8\n+\n+/* Async HTTP/2 handoff is ENABLED (MOTORZ_ENABLE_ASYNC 1).\n+ *\n+ * When motorz advertises AP_MPMQ_IS_ASYNC=1, mod_http2 hands the master (c1)\n+ * connection back to the MPM between requests; motorz then re-dispatches it on\n+ * a fresh worker thread when its socket is readable. This previously raced\n+ * mod_http2's stream lifecycle under rapid HTTP/2 connection churn: motorz\n+ * could drive the c1 close/cleanup faster than a just-finished stream's\n+ * secondary (c2) worker called c2_prod_done(), so the stream was still\n+ * \"running\" at cleanup and its in-flight response got aborted -- the client\n+ * saw a dropped request.\n+ *\n+ * That race is now FIXED in mod_http2 (h2_session.c): a graceful client GOAWAY\n+ * with streams still in flight no longer transits the session straight to DONE;\n+ * the session keeps running until those streams' c2s have finished and flushed\n+ * (open_streams == 0), and only then -- from the IDLE state -- sends our GOAWAY\n+ * and closes. The c1 connection is therefore handed to LINGER only after every\n+ * c2 is done, so async handoff is lossless under churn. The full analysis,\n+ * reproduction recipe, and the fix are in MOTORZ.README (\"HTTP/2 async\n+ * handoff\").\n+ *\n+ * This remains a single flip point: set to 0 to fall back to the old workaround\n+ * (report IS_ASYNC=0 so mod_http2 keeps c1 on one worker, driving its own\n+ * multiplexer pollset until every c2 completes) should a regression ever\n+ * reappear. It gates both AP_MPMQ_IS_ASYNC and AP_MPMQ_CAN_WAITIO\n+ * (CONN_STATE_ASYNC_WAITIO is only meaningful when async).\n+ */\n+#define MOTORZ_ENABLE_ASYNC 1\n+/* Upper bound for ThreadsPerChild; matches worker/event in using\n+ * DEFAULT_THREAD_LIMIT rather than an arbitrary fraction of MAX_THREAD_LIMIT.\n+ */\n+static int thread_limit = DEFAULT_THREAD_LIMIT;\n+\n+/* Unique connection ID: child_slot * thread_limit + per-child sequence number.\n+ * Mirrors the formula used by the worker and event MPMs so that c-&gt;id values\n+ * are globally unique across children and connections within a child.\n+ * conn_seq is a per-child atomic counter; thread_limit slots per child ensures\n+ * no overlap between children.\n+ */\n+#define ID_FROM_CHILD_THREAD(c, t) ((long)(c) * (long)thread_limit + (long)(t))\n+static apr_uint32_t conn_seq = 0;\n+\n+/* one_process --- debugging mode variable; can be set from the command line\n+ * with the -X flag.  If set, this gets you the child_main loop running\n+ * in the process which originally started up (no detach, no make_child),\n+ * which is a pretty nice debugging environment.  (You'll get a SIGHUP\n+ * early in standalone_main; just continue through.  This is the server\n+ * trying to kill off any child processes which it might have lying\n+ * around --- Apache doesn't keep track of their pids, it just sends\n+ * SIGHUP to the process group, ignoring it in the root process.\n+ * Continue through and you'll be fine.).\n+ */\n+static int one_process = 0;\n+\n+static apr_pool_t *pconf;               /* Pool for config stuff */\n+static apr_pool_t *pchild;              /* Pool for httpd child stuff */\n+\n+static pid_t ap_my_pid; /* it seems silly to call getpid all the time */\n+static pid_t parent_pid;\n+static int my_child_num;\n+/* Number of connections accepted by this child so far; compared against\n+ * ap_max_requests_per_child. Written by the accepting poller thread\n+ * (motorz_io_accept) and read by the supervisor on the main thread\n+ * (motorz_supervise). volatile ensures neither side caches a stale value;\n+ * a torn read is harmless for a monotone counter used only for a soft cap.\n+ */\n+static volatile int requests_this_child;\n+/* Set to stop the child's main loop. volatile because it's updated from a\n+ * signal handler (stop_listening), from poller threads, and from the\n+ * supervisor. On ARM (Apple Silicon) the poller-&gt;mtx lock/unlock performed\n+ * on every poll-loop iteration provides acquire/release barriers, so the\n+ * practical visibility lag is bounded by the 500ms poll timeout at worst.\n+ */\n+static int volatile die_now = 0;\n+static motorz_child_bucket *all_buckets, /* All listeners buckets */\n+                            *my_bucket;   /* Current child bucket */\n+\n+static void clean_child_exit(int code) __attribute__ ((noreturn));\n+\n+\n+static apr_status_t motorz_io_process(motorz_conn_t *scon);\n+static void motorz_pollset_del(motorz_poller_t *poller, motorz_conn_t *scon);\n+static void motorz_conn_claim(motorz_poller_t *poller, motorz_conn_t *scon);\n+static void motorz_conn_done(motorz_conn_t *scon);\n+static void motorz_start_lingering_close(motorz_conn_t *scon);\n+static apr_status_t motorz_lingering_close(motorz_conn_t *scon);\n+static void motorz_update_listeners(motorz_poller_t *poller);\n+\n+static motorz_core_t *motorz_core_get(void)\n+{\n+    return g_motorz_core;\n+}\n+\n+/* Obtain a transaction pool for a new connection, reusing one from the\n+ * recycle free-list if available, otherwise creating a fresh one with its\n+ * own allocator (so per-connection memory is released as a unit and the\n+ * allocator's free blocks can be reused).\n+ *\n+ * SINGLE-CONSUMER: the lock-free CAS pop below is only safe with one popper,\n+ * because it dereferences first-&gt;next without atomicity (see mpm_fdqueue.c's\n+ * ap_queue_info_pop_pool and its PR caveat). This MUST be called only from the\n+ * owning poller's thread (its sole caller is motorz_io_accept, which runs on\n+ * that poller). Each poller has its own free-list, so \"one popper\" holds.\n+ * Concurrent lock-free pushes (motorz_ptrans_put, from any worker) are fine.\n+ */\n+static apr_pool_t *motorz_ptrans_get(motorz_poller_t *poller)\n+{\n+    apr_pool_t *ptrans;\n+\n+    for (;;) {\n+        motorz_recycled_pool *first = poller-&gt;recycled_pools;\n+        if (first == NULL) {\n+            break;\n+        }\n+        if (apr_atomic_casptr((void *)&amp;poller-&gt;recycled_pools,\n+                              first-&gt;next, first) == first) {\n+            apr_atomic_dec32(&amp;poller-&gt;num_recycled);\n+            /* The node lived inside the pool it describes; the pool is now\n+             * ours to hand out (it will be cleared again on next reuse).\n+             */\n+            return first-&gt;pool;\n+        }\n+        /* CAS lost a race with another pop... but there is only one popper, so\n+         * this only happens transiently vs. a push changing the head; retry.\n+         */\n+    }\n+\n+    {\n+        apr_allocator_t *allocator;\n+        apr_allocator_create(&amp;allocator);\n+        apr_allocator_max_free_set(allocator, ap_max_mem_free);\n+        apr_pool_create_ex(&amp;ptrans, pconf, NULL, allocator);\n+        apr_allocator_owner_set(allocator, ptrans);\n+        apr_pool_tag(ptrans, \"transaction\");\n+    }\n+    return ptrans;\n+}\n+\n+/* Return a finished connection's transaction pool to the recycle free-list,\n+ * or destroy it if the list is already at MAX_RECYCLED_POOLS. Clearing the\n+ * pool runs all its cleanups (closing the socket, de-registering timers) and\n+ * resets it for reuse.\n+ *\n+ * MULTI-PRODUCER: the lock-free CAS push is safe from any thread (workers and\n+ * the poll thread), concurrently with each other and with a single popper.\n+ */\n+static void motorz_ptrans_put(motorz_poller_t *poller, apr_pool_t *ptrans)\n+{\n+    motorz_recycled_pool *node;\n+\n+    /* Bound the free-list. apr_atomic_read32 + inc is not a strict CAS, so the\n+     * count may momentarily overshoot MAX_RECYCLED_POOLS under concurrency;\n+     * that is harmless (it just caps roughly).\n+     */\n+    if (apr_atomic_read32(&amp;poller-&gt;num_recycled) &gt;= MAX_RECYCLED_POOLS) {\n+        apr_pool_destroy(ptrans);\n+        return;\n+    }\n+    apr_atomic_inc32(&amp;poller-&gt;num_recycled);\n+\n+    /* Clear (don't destroy) to keep the allocator and its free blocks; this\n+     * also runs the pool's cleanups (closing the socket, de-registering any\n+     * timer). Then carve the list node out of the now-empty pool.\n+     */\n+    apr_pool_clear(ptrans);\n+    apr_pool_tag(ptrans, \"transaction\");\n+    node = apr_palloc(ptrans, sizeof(*node));\n+    node-&gt;pool = ptrans;\n+\n+    for (;;) {\n+        /* Save the current head in a local before the CAS: node-&gt;next must not\n+         * be re-read after a successful CAS, as a concurrent pusher may have\n+         * already changed it (see mpm_fdqueue.c push_pool, PR 44402).\n+         */\n+        motorz_recycled_pool *next = poller-&gt;recycled_pools;\n+        node-&gt;next = next;\n+        if (apr_atomic_casptr((void *)&amp;poller-&gt;recycled_pools, node, next) == next) {\n+            break;\n+        }\n+    }\n+}\n+\n+static int timer_comp(void *a, void *b)\n+{\n+    motorz_timer_t *ta = (motorz_timer_t *) a;\n+    motorz_timer_t *tb = (motorz_timer_t *) b;\n+    apr_time_t t1 = ta-&gt;expires;\n+    apr_time_t t2 = tb-&gt;expires;\n+    AP_DEBUG_ASSERT(t1);\n+    AP_DEBUG_ASSERT(t2);\n+    /* Identity match: required so that apr_skiplist_remove() (which relies on\n+     * the compare function returning 0) can locate the exact timer node. We\n+     * must never return 0 for two *distinct* timers, otherwise\n+     * apr_skiplist_insert() would drop duplicates (timers created within the\n+     * same microsecond) and remove() could delete the wrong connection's\n+     * timer. Equal expiry on distinct timers therefore falls back to a stable\n+     * total order on the timer address.\n+     */\n+    if (ta == tb) {\n+        return 0;\n+    }\n+    if (t1 &lt; t2) {\n+        return -1;\n+    }\n+    if (t1 &gt; t2) {\n+        return 1;\n+    }\n+    return (ta &lt; tb) ? -1 : 1;\n+}\n+\n+static apr_status_t motorz_conn_pool_cleanup(void *baton)\n+{\n+    motorz_conn_t *scon = (motorz_conn_t *)baton;\n+\n+    if (scon-&gt;timer.expires) {\n+        motorz_poller_t *poller = scon-&gt;poller;\n+\n+        apr_thread_mutex_lock(poller-&gt;mtx);\n+        apr_skiplist_remove(poller-&gt;timeout_ring, &amp;scon-&gt;timer, NULL);\n+        apr_thread_mutex_unlock(poller-&gt;mtx);\n+    }\n+\n+    return APR_SUCCESS;\n+}\n+\n+static APR_INLINE apr_interval_time_t\n+motorz_get_timeout(motorz_conn_t *scon)\n+{\n+    if (scon-&gt;c-&gt;base_server) {\n+        return scon-&gt;c-&gt;base_server-&gt;timeout;\n+    }\n+    else {\n+        return ap_server_conf-&gt;timeout;\n+    }\n+}\n+\n+static APR_INLINE apr_interval_time_t\n+motorz_get_keep_alive_timeout(motorz_conn_t *scon)\n+{\n+    if (scon-&gt;c-&gt;base_server) {\n+        return scon-&gt;c-&gt;base_server-&gt;keep_alive_timeout;\n+    }\n+    else {\n+        return ap_server_conf-&gt;keep_alive_timeout;\n+    }\n+}\n+\n+static void motorz_io_timeout_cb(motorz_core_t *mz, void *baton)\n+{\n+\n+    motorz_conn_t *scon = (motorz_conn_t *) baton;\n+    conn_rec *c = scon-&gt;c;\n+\n+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02842)\n+                 \"io timeout hit scon: %pp, c: %pp\", scon, c);\n+\n+    /* The keep-alive/write timeout expired. Begin a non-blocking lingering\n+     * close rather than blocking this worker; scon is handed to the poll loop\n+     * or torn down inside, and is invalid afterwards. The timer has already\n+     * been popped from the ring by the caller.\n+     */\n+    motorz_start_lingering_close(scon);\n+}\n+\n+static void *motorz_io_setup_conn(apr_thread_t *thread, void *baton)\n+{\n+    apr_status_t status;\n+    ap_sb_handle_t *sbh;\n+    long conn_id;\n+    motorz_sb_t *sb;\n+    motorz_conn_t *scon = (motorz_conn_t *) baton;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03316)\n+                         \"motorz_io_setup_conn(): entered\");\n+\n+    /* Derive a unique connection ID matching worker/event's formula.\n+     * apr_atomic_inc32 returns the value BEFORE increment, so add 1 to get\n+     * the sequence number for this connection (sequence starts at 1).\n+     * my_child_num is set once at child startup and read-only from here.\n+     */\n+    conn_id = ID_FROM_CHILD_THREAD(my_child_num,\n+                                   (apr_uint32_t)apr_atomic_inc32(&amp;conn_seq) + 1);\n+    ap_create_sb_handle(&amp;sbh, scon-&gt;pool, my_child_num, 0);\n+    scon-&gt;sbh = sbh;\n+    scon-&gt;ba = apr_bucket_alloc_create(scon-&gt;pool);\n+\n+    scon-&gt;c = ap_run_create_connection(scon-&gt;pool, ap_server_conf, scon-&gt;sock,\n+                                       conn_id, sbh, scon-&gt;ba);\n+    if (scon-&gt;c == NULL) {\n+        /* create_connection failed (e.g. a module declined or hit a resource\n+         * limit). There is no conn_rec to process or linger-close; just\n+         * release the transaction pool, which closes the accepted socket via\n+         * its pool cleanup.\n+         */\n+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10547)\n+                     \"motorz_io_setup_conn: ap_run_create_connection failed\");\n+        motorz_conn_done(scon);\n+        return NULL;\n+    }\n+\n+    scon-&gt;c-&gt;cs = &amp;scon-&gt;cs;\n+    sb = apr_pcalloc(scon-&gt;pool, sizeof(motorz_sb_t));\n+\n+    scon-&gt;c-&gt;current_thread = thread;\n+\n+    scon-&gt;pfd.p = scon-&gt;pool;\n+    scon-&gt;pfd.desc_type = APR_POLL_SOCKET;\n+    scon-&gt;pfd.desc.s = scon-&gt;sock;\n+    scon-&gt;pfd.reqevents = APR_POLLIN;\n+\n+    sb-&gt;type = PT_CSD;\n+    sb-&gt;baton = scon;\n+    scon-&gt;pfd.client_data = sb;\n+\n+    ap_update_vhost_given_ip(scon-&gt;c);\n+\n+    status = ap_pre_connection(scon-&gt;c, scon-&gt;sock);\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03317)\n+                         \"motorz_io_setup_conn(): did pre-conn\");\n+    if (status != OK &amp;&amp; status != DONE) {\n+        ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(02843)\n+                     \"motorz_io_setup_conn: connection aborted\");\n+    }\n+\n+    /* pfd is initialized here to ensure reqevents == 0, so the defensive\n+     * pollset_remove guard in motorz_io_process is a no-op on this first call.\n+     */\n+    scon-&gt;pfd.reqevents = 0;\n+    scon-&gt;cs.state = CONN_STATE_PROCESSING;\n+    scon-&gt;cs.sense = CONN_SENSE_DEFAULT;\n+\n+    status = motorz_io_process(scon);\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, status, ap_server_conf, APLOGNO(02844)\n+                 \"motorz_io_setup_conn: motorz_io_process status: %d\", (int)status);\n+    return NULL;\n+}\n+\n+static apr_status_t motorz_io_user(motorz_poller_t *poller, motorz_sb_t *sb)\n+{\n+    /* PT_USER poll events are not implemented yet. Nothing currently\n+     * registers a PT_USER descriptor in the pollset, so reaching here means\n+     * an unexpected event; log it rather than silently dropping it.\n+     */\n+    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(10548)\n+                 \"motorz_io_user: PT_USER poll events are not implemented\");\n+    return APR_SUCCESS;\n+}\n+\n+static apr_status_t motorz_io_accept(motorz_poller_t *poller, motorz_sb_t *sb)\n+{\n+    motorz_core_t *mz = poller-&gt;mz;\n+    apr_status_t rv;\n+    apr_pool_t *ptrans;\n+    apr_socket_t *socket = NULL;\n+    ap_listen_rec *lr = (ap_listen_rec *) sb-&gt;baton;\n+    motorz_conn_t *scon;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03318)\n+                 \"motorz_io_accept(): entered\");\n+\n+    /* Drain the kernel accept queue in one poll wakeup instead of returning\n+     * to apr_pollset_poll() for each connection. Without this, N queued\n+     * connections require N round-trips through the poll loop, costing O(N)\n+     * wakeups under burst. The loop stops when accept() returns EAGAIN (queue\n+     * empty), on a fatal error, when admission control disables the listener,\n+     * or when the child is shutting down.\n+     *\n+     * ap_unixd_accept() outcome buckets:\n+     *   - APR_SUCCESS + socket set: a connection was accepted;\n+     *   - APR_EGENERAL: fatal/resource condition (E[MN]FILE, ENETDOWN, etc.) --\n+     *     stop gracefully rather than spin;\n+     *   - any other non-success (EAGAIN, EINTR, ECONNABORTED, ...): transient,\n+     *     log and stop draining.\n+     * socket == NULL on every non-SUCCESS path.\n+     */\n+    do {\n+        ptrans = motorz_ptrans_get(poller);\n+        socket = NULL;\n+        rv = lr-&gt;accept_func((void *)&amp;socket, lr, ptrans);\n+\n+        if (rv == APR_SUCCESS &amp;&amp; socket != NULL) {\n+            static apr_uint32_t rr;\n+            motorz_poller_t *target;\n+\n+            scon = apr_pcalloc(ptrans, sizeof(motorz_conn_t));\n+            scon-&gt;pool = ptrans;\n+            scon-&gt;sock = socket;\n+            scon-&gt;mz = mz;\n+\n+            /* Shard I/O across pollers round-robin. The accepting poller is\n+             * always poller 0, so this counter needs no atomics.\n+             */\n+            target = mz-&gt;pollers[rr % (apr_uint32_t)mz-&gt;num_pollers];\n+            rr++;\n+            scon-&gt;poller = target;\n+\n+            /* Recycling is NOT sharded: the ptrans came from THIS poller's\n+             * free-list (its single-consumer pop home). Return it here.\n+             */\n+            scon-&gt;pool_poller = poller;\n+\n+            requests_this_child++;\n+\n+            apr_pool_cleanup_register(scon-&gt;pool, scon, motorz_conn_pool_cleanup,\n+                                      apr_pool_cleanup_null);\n+\n+            rv = apr_thread_pool_push(mz-&gt;workers,\n+                                      motorz_io_setup_conn,\n+                                      scon,\n+                                      APR_THREAD_TASK_PRIORITY_HIGHEST, NULL);\n+            if (rv != APR_SUCCESS) {\n+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,\n+                             APLOGNO(03319)\n+                             \"motorz_io_accept: could not queue connection to \"\n+                             \"worker pool\");\n+                motorz_ptrans_put(poller, ptrans);\n+            }\n+\n+            /* Re-check admission after each accept: if the worker pool has\n+             * become saturated, motorz_update_listeners() will remove the\n+             * listener from the pollset and set listeners_disabled, which\n+             * terminates the drain loop below.\n+             */\n+            motorz_update_listeners(poller);\n+        }\n+        else {\n+            /* Nothing accepted (EAGAIN/EINTR/error): recycle the pool. */\n+            motorz_ptrans_put(poller, ptrans);\n+\n+            if (rv == APR_EGENERAL) {\n+                ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,\n+                             APLOGNO(02845)\n+                             \"motorz_io_accept: accept failed, shutting down \"\n+                             \"child gracefully\");\n+                mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+                die_now = 1;\n+            }\n+            else if (APR_STATUS_IS_ECONNREFUSED(rv)\n+                     || APR_STATUS_IS_ECONNABORTED(rv)\n+                     || APR_STATUS_IS_ECONNRESET(rv)) {\n+                /* 2.4.x has no ap_accept_error_is_nonfatal(); inline the same\n+                 * set of non-fatal accept errors it checks for. */\n+                ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf,\n+                             APLOGNO(10549)\n+                             \"accept() on client socket failed\");\n+            }\n+\n+            break;\n+        }\n+    } while (!poller-&gt;listeners_disabled &amp;&amp; !die_now);\n+\n+    return APR_SUCCESS;\n+}\n+\n+static void *motorz_timer_invoke(apr_thread_t *thread, void *baton)\n+{\n+    motorz_timer_t *ep = (motorz_timer_t *)baton;\n+    motorz_conn_t *scon = (motorz_conn_t *)ep-&gt;baton;\n+\n+    scon-&gt;c-&gt;current_thread = thread;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03320)\n+                         \"motorz_timer_invoke(): entered\");\n+\n+    ep-&gt;cb(ep-&gt;mz, ep-&gt;baton);\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03321)\n+                         \"motorz_timer_invoke(): exited\");\n+\n+    return NULL;\n+}\n+\n+static apr_status_t motorz_timer_event_process(motorz_poller_t *poller, motorz_timer_t *te)\n+{\n+    motorz_conn_t *scon = (motorz_conn_t *)te-&gt;baton;\n+    scon-&gt;timer.expires = 0;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03322)\n+                         \"motorz_timer_event_process(): entered\");\n+\n+    /* Claim the connection on the poll thread before dispatching the timeout\n+     * (fix #5). The timer has already been popped from the ring by the caller\n+     * (so there is nothing to remove there -- and we must not take poller-&gt;mtx\n+     * here as the caller holds it), but the connection's descriptor may still\n+     * be armed in the pollset; disarm it so a concurrent/subsequent poll\n+     * cannot dispatch the same scon while the timeout worker is closing it.\n+     * apr_pollset_remove() takes only the (leaf) pollset lock, so calling it\n+     * under poller-&gt;mtx introduces no lock-ordering inversion.\n+     */\n+    motorz_pollset_del(poller, scon);\n+\n+    return apr_thread_pool_push(poller-&gt;mz-&gt;workers,\n+                                motorz_timer_invoke,\n+                                te, APR_THREAD_TASK_PRIORITY_NORMAL, NULL);\n+}\n+\n+static void *motorz_io_invoke(apr_thread_t *thread, void *baton)\n+{\n+    motorz_sb_t *sb = (motorz_sb_t *) baton;\n+    motorz_conn_t *scon = (motorz_conn_t *) sb-&gt;baton;\n+    apr_status_t rv;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03323)\n+                         \"motorz_io_invoke(): entered\");\n+    scon-&gt;c-&gt;current_thread = thread;\n+\n+    rv = motorz_io_process(scon);\n+\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_TRACE8, rv, ap_server_conf, APLOGNO(02846)\n+                     \"motorz_io_invoke: motorz_io_process failed (?)\");\n+    }\n+    return NULL;\n+}\n+\n+static apr_status_t motorz_io_event_process(motorz_poller_t *poller, motorz_sb_t *sb)\n+{\n+    motorz_conn_t *scon = (motorz_conn_t *) sb-&gt;baton;\n+\n+    /* Take ownership of this connection on the poll thread before handing it\n+     * to a worker: disarm its pollset entry and cancel any pending timeout\n+     * (fix #5). This guarantees the poll thread cannot dispatch the same scon\n+     * again -- neither re-reported by the pollset nor via timer expiry --\n+     * until the worker re-arms it at the end of motorz_io_process(). Without\n+     * this, two workers could race on one scon and, now that the transaction\n+     * pool is freed on teardown, that race is a use-after-free.\n+     *\n+     * The identity-correct timer_comp (fix #3) is what makes the targeted\n+     * skiplist removal reliable.\n+     */\n+    motorz_conn_claim(poller, scon);\n+\n+    return apr_thread_pool_push(poller-&gt;mz-&gt;workers,\n+                                motorz_io_invoke,\n+                                sb, APR_THREAD_TASK_PRIORITY_NORMAL, NULL);\n+}\n+\n+static apr_status_t motorz_io_callback(void *baton, const apr_pollfd_t *pfd)\n+{\n+    apr_status_t status = APR_SUCCESS;\n+    motorz_poller_t *poller = (motorz_poller_t *) baton;\n+    motorz_sb_t *sb = pfd-&gt;client_data;\n+\n+\n+    if (sb-&gt;type == PT_ACCEPT) {\n+        status = motorz_io_accept(poller, sb);\n+    }\n+    else if (sb-&gt;type == PT_CSD) {\n+        status = motorz_io_event_process(poller, sb);\n+    }\n+    else if (sb-&gt;type == PT_USER) {\n+        status = motorz_io_user(poller, sb);\n+    }\n+    return status;\n+}\n+\n+/* Insert/refresh scon's timer in the ring. CALLER MUST HOLD mz-&gt;mtx.\n+ *\n+ * Everything that touches the sort key (expires) and the ring must happen\n+ * under mz-&gt;mtx. In particular:\n+ *\n+ *  - If this connection's timer is still linked in the ring from an earlier\n+ *    registration (expires != 0 is, under the lock, exactly the \"in ring\"\n+ *    predicate), remove it first -- using its *current* expiry as the key,\n+ *    before we overwrite it.\n+ *  - Only then mutate expires and re-insert.\n+ *\n+ * Re-inserting the same node, or mutating a linked node's sort key in place,\n+ * corrupts the skiplist and sends apr_skiplist_insert()'s insert_compare()\n+ * into an infinite loop *while holding mz-&gt;mtx*, which deadlocks the entire\n+ * child. (Found by a load test with StartServers 1 and MaxRequestsPerChild\n+ * churn.)\n+ */\n+static void motorz_register_timeout_locked(motorz_conn_t *scon,\n+                                           motorz_timer_cb cb,\n+                                           apr_interval_time_t relative_time)\n+{\n+    apr_time_t t = apr_time_now() + relative_time;\n+    motorz_timer_t *elem = &amp;scon-&gt;timer;\n+    motorz_poller_t *poller = scon-&gt;poller;\n+\n+    if (elem-&gt;expires) {\n+        apr_skiplist_remove(poller-&gt;timeout_ring, elem, NULL);\n+    }\n+\n+    elem-&gt;expires = t;\n+    elem-&gt;cb = cb;\n+    elem-&gt;baton = scon;\n+    elem-&gt;pool = scon-&gt;pool;\n+    elem-&gt;mz = poller-&gt;mz;\n+    elem-&gt;poller = poller;\n+\n+#ifdef AP_DEBUG\n+    ap_assert(apr_skiplist_insert(poller-&gt;timeout_ring, elem));\n+#else\n+    apr_skiplist_insert(poller-&gt;timeout_ring, elem);\n+#endif\n+}\n+\n+/* Hand a connection back to the poll thread: arm its pollset entry for\n+ * 'reqevents' AND register its timeout, atomically under mz-&gt;mtx. This is the\n+ * ONLY safe way for a worker to release a connection it still holds a pointer\n+ * to: once either the timer or the pollset entry is armed, the poll thread may\n+ * fire the timeout (or a readable event) and tear the connection down --\n+ * freeing scon. Doing both under one lock, and touching scon nowhere after\n+ * this returns, closes the use-after-free window. MUST be the worker's last\n+ * action on scon; returns the pollset_add status (scon may already be freed\n+ * on a concurrent timeout by the time we look at the return, so the caller\n+ * must not deref scon regardless of it).\n+ */\n+static apr_status_t motorz_conn_register(motorz_conn_t *scon,\n+                                         apr_int16_t reqevents,\n+                                         motorz_timer_cb cb,\n+                                         apr_interval_time_t timeout)\n+{\n+    motorz_poller_t *poller = scon-&gt;poller;\n+    apr_status_t rv;\n+\n+    apr_thread_mutex_lock(poller-&gt;mtx);\n+    scon-&gt;pfd.reqevents = reqevents;\n+    scon-&gt;cs.sense = CONN_SENSE_DEFAULT;\n+    motorz_register_timeout_locked(scon, cb, timeout);\n+    rv = apr_pollset_add(poller-&gt;pollset, &amp;scon-&gt;pfd);\n+    if (rv != APR_SUCCESS) {\n+        /* Roll back the timer so the half-armed connection isn't left\n+         * reachable via the ring with no pollset entry; the caller will tear\n+         * it down.\n+         */\n+        if (scon-&gt;pfd.reqevents != 0) {\n+            scon-&gt;pfd.reqevents = 0;\n+        }\n+        apr_skiplist_remove(poller-&gt;timeout_ring, &amp;scon-&gt;timer, NULL);\n+        scon-&gt;timer.expires = 0;\n+    }\n+    apr_thread_mutex_unlock(poller-&gt;mtx);\n+    return rv;\n+}\n+\n+/* Remove scon's descriptor from the pollset if it is currently armed, and\n+ * mark it disarmed. Does NOT touch the timer ring. Safe to call without\n+ * holding mz-&gt;mtx: the pollset is created APR_POLLSET_THREADSAFE, and APR's\n+ * pollset lock is never held while acquiring mz-&gt;mtx (or vice versa), so no\n+ * lock-ordering inversion is possible.\n+ *\n+ * Some pollset backends (kqueue, epoll) automatically drop a descriptor when\n+ * its socket is closed, so APR_NOTFOUND is an acceptable, non-error result.\n+ */\n+static void motorz_pollset_del(motorz_poller_t *poller, motorz_conn_t *scon)\n+{\n+    if (scon-&gt;pfd.reqevents != 0) {\n+        apr_status_t rv = apr_pollset_remove(poller-&gt;pollset, &amp;scon-&gt;pfd);\n+        if (rv != APR_SUCCESS &amp;&amp; !APR_STATUS_IS_NOTFOUND(rv)) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, ap_server_conf,\n+                         \"motorz_pollset_del: apr_pollset_remove failure\");\n+        }\n+        scon-&gt;pfd.reqevents = 0;\n+    }\n+}\n+\n+/* Claim a connection on behalf of a worker, on the poll/main thread, before\n+ * dispatching it. This is the heart of the per-connection ownership model\n+ * (fix #5): it makes the connection invisible to the poll thread for as long\n+ * as a worker owns it, so the same scon can never be dispatched twice (once\n+ * for an I/O event and again for a timeout, or re-reported by a level-\n+ * triggered pollset before the worker has run).\n+ *\n+ * It removes scon's descriptor from the pollset and cancels any pending\n+ * timeout. The worker re-arms the connection (pollset_add + register_timeout)\n+ * only at the very end of motorz_io_process(), at which point ownership\n+ * returns to the poll thread. MUST be called on the poll thread only.\n+ */\n+static void motorz_conn_claim(motorz_poller_t *poller, motorz_conn_t *scon)\n+{\n+    motorz_pollset_del(poller, scon);\n+\n+    if (scon-&gt;timer.expires) {\n+        apr_thread_mutex_lock(poller-&gt;mtx);\n+        apr_skiplist_remove(poller-&gt;timeout_ring, &amp;scon-&gt;timer, NULL);\n+        scon-&gt;timer.expires = 0;\n+        apr_thread_mutex_unlock(poller-&gt;mtx);\n+    }\n+}\n+\n+/* Terminal teardown for a connection: remove it from the pollset (if still\n+ * registered) and recycle its transaction pool. Clearing the pool (inside\n+ * motorz_ptrans_put) releases the conn_rec, bucket allocator and scoreboard\n+ * handle allocated within it, and fires motorz_conn_pool_cleanup(), which\n+ * de-registers any pending timer from the ring under mz-&gt;mtx.\n+ *\n+ * This MUST be called exactly once per connection, on every path that ends\n+ * it (lingering close, abort, or fired timeout). It runs on a worker-pool\n+ * thread; removing from the pollset concurrently with the polling thread is\n+ * safe because the pollset is created APR_POLLSET_THREADSAFE. By the time a\n+ * worker reaches a terminal state the connection has already been claimed\n+ * (disarmed) by the poll thread, so motorz_pollset_del() is normally a no-op\n+ * here -- it remains as a defensive backstop.\n+ */\n+static void motorz_conn_done(motorz_conn_t *scon)\n+{\n+    motorz_poller_t *poller = scon-&gt;poller;\n+    motorz_poller_t *pool_poller = scon-&gt;pool_poller;\n+    apr_pool_t *ptrans = scon-&gt;pool;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf,\n+                 \"motorz_conn_done(): scon: %pp\", scon);\n+\n+    /* Disarm on the I/O poller (its pollset), then recycle to the accepting\n+     * poller's free-list (its single-consumer pop home -- not the I/O poller).\n+     */\n+    motorz_pollset_del(poller, scon);\n+\n+    /* scon lives in ptrans, so it (and scon-&gt;pool) are invalid afterwards. */\n+    motorz_ptrans_put(pool_poller, ptrans);\n+}\n+\n+/* Timer callback for a lingering close that ran out of time: force the\n+ * connection closed. Mirrors motorz_io_timeout_cb but for the linger phase.\n+ */\n+static void motorz_linger_timeout_cb(motorz_core_t *mz, void *baton)\n+{\n+    motorz_conn_t *scon = (motorz_conn_t *) baton;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf,\n+                 \"motorz_linger_timeout_cb(): scon: %pp\", scon);\n+\n+    /* The timer has already been popped from the ring; tear down. */\n+    motorz_conn_done(scon);\n+}\n+\n+/* Drain and discard any data the peer is still sending, without blocking.\n+ * Called (on a worker thread) when a lingering socket is readable or its\n+ * linger timer fires. Returns when the peer has closed/erred (-&gt; teardown)\n+ * or there is nothing more to read right now (-&gt; re-arm in the pollset).\n+ */\n+static apr_status_t motorz_lingering_close(motorz_conn_t *scon)\n+{\n+    apr_socket_t *csd = scon-&gt;sock;\n+    char dummybuf[512];\n+    apr_size_t nbytes;\n+    apr_status_t rv;\n+\n+    do {\n+        nbytes = sizeof(dummybuf);\n+        rv = apr_socket_recv(csd, dummybuf, &amp;nbytes);\n+    } while (rv == APR_SUCCESS);\n+\n+    if (!APR_STATUS_IS_EAGAIN(rv)) {\n+        /* Peer closed, reset, or hard error: we are done. */\n+        motorz_conn_done(scon);\n+        return APR_SUCCESS;\n+    }\n+\n+    /* Nothing left to read for now; wait for more readability, bounded by the\n+     * linger timeout. A readable PT_CSD dispatch goes through\n+     * motorz_conn_claim(), which cancels this connection's timer, so we must\n+     * (re)register the linger timeout here alongside (re)arming the pollset.\n+     * This means a peer that keeps dribbling data resets the deadline each\n+     * time -- the same bounded imprecision mpm_event accepts for its linger\n+     * queues, and exactly the slow-drain case the timeout exists to cap.\n+     * Honour a module's request for a shortened linger period.\n+     *\n+     * Arm pollset + timer atomically (motorz_conn_register); after it returns\n+     * scon may already have been freed by a concurrent timeout, so we must not\n+     * touch it again -- including on the error path, where the rollback inside\n+     * motorz_conn_register has disarmed it and we just close.\n+     */\n+    {\n+        apr_interval_time_t linger =\n+            apr_table_get(scon-&gt;c-&gt;notes, \"short-lingering-close\")\n+                ? apr_time_from_sec(SECONDS_TO_LINGER)\n+                : apr_time_from_sec(MAX_SECS_TO_LINGER);\n+        rv = motorz_conn_register(scon,\n+                                  APR_POLLIN | APR_POLLHUP | APR_POLLERR,\n+                                  motorz_linger_timeout_cb, linger);\n+    }\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, ap_server_conf,\n+                     \"motorz_lingering_close: apr_pollset_add failed; closing\");\n+        motorz_conn_done(scon);\n+    }\n+    return APR_SUCCESS;\n+}\n+\n+/* Begin a non-blocking lingering close (fix #3/A3). Runs on a worker thread,\n+ * but unlike the old inline ap_lingering_close() it never blocks the worker\n+ * for up to MAX_SECS_TO_LINGER: it shuts the write side down, then arms a\n+ * linger timeout and hands the socket back to the poll loop, which drives the\n+ * drain via motorz_lingering_close() as data arrives.\n+ *\n+ * Pre-condition: scon has already been claimed (not in the pollset, no timer).\n+ */\n+static void motorz_start_lingering_close(motorz_conn_t *scon)\n+{\n+    conn_rec *c = scon-&gt;c;\n+    apr_socket_t *csd = scon-&gt;sock;\n+\n+    scon-&gt;cs.state = CONN_STATE_LINGER;\n+\n+    /* ap_start_lingering_close() flushes and shuts down the write side. A\n+     * true return means there is nothing to linger over (aborted or no\n+     * half-close needed), so close immediately.\n+     */\n+    if (ap_start_lingering_close(c)) {\n+        motorz_conn_done(scon);\n+        return;\n+    }\n+\n+    scon-&gt;linger_started = 1;\n+\n+    /* All draining from here is non-blocking. */\n+    apr_socket_timeout_set(csd, 0);\n+    apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 0);\n+\n+    /* First drain attempt. If the peer still has data to send,\n+     * motorz_lingering_close() arms both the pollset and the linger timeout;\n+     * otherwise it tears the connection down here. We deliberately do not\n+     * pre-register a timer (the drain owns that), so scon-&gt;timer is inserted\n+     * into the ring at most once at a time.\n+     */\n+    motorz_lingering_close(scon);\n+}\n+\n+/* Park a connection that a process_connection hook left in\n+ * CONN_STATE_SUSPENDED (A4). Ownership passes to the module, which now drives\n+ * the connection itself (via its own threads, registered poll/timed callbacks,\n+ * etc.) and interacts with the MPM only through the suspend/resume_connection\n+ * hooks. The connection is intentionally left out of the pollset and timer ring\n+ * (it has been claimed), and its transaction pool is NOT recycled, so nothing\n+ * here tears it down -- which is what previously leaked. Runs on a worker\n+ * thread. This mirrors mpm_event's notify_suspend() on 2.4.x: there is no\n+ * MPM-level resume-suspended re-injection API in 2.4.x, so a resumed connection\n+ * is handled entirely by the owning module.\n+ */\n+static void motorz_suspend_connection(motorz_conn_t *scon)\n+{\n+    conn_rec *c = scon-&gt;c;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf,\n+                 \"motorz_suspend_connection(): scon: %pp\", scon);\n+\n+    scon-&gt;suspended = 1;\n+    ap_run_suspend_connection(c, scon-&gt;r);\n+    /* sbh is owned by the (now parked) connection; drop our reference like\n+     * mpm_event's notify_suspend() does.\n+     */\n+    c-&gt;sbh = NULL;\n+}\n+\n+static apr_status_t motorz_io_process(motorz_conn_t *scon)\n+{\n+    apr_status_t rv;\n+    conn_rec *c;\n+\n+    ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, APLOGNO(03325)\n+                         \"motorz_io_process(): entered\");\n+\n+    /* A connection already in non-blocking lingering close (its socket became\n+     * readable again, or it was re-dispatched) just continues draining. It\n+     * has been claimed, so its pollset entry/timer were cleared; the drain\n+     * re-arms them or tears down.\n+     */\n+    if (scon-&gt;linger_started) {\n+        return motorz_lingering_close(scon);\n+    }\n+\n+    if (scon-&gt;c-&gt;clogging_input_filters &amp;&amp; !scon-&gt;c-&gt;aborted) {\n+        /* Since we have an input filter which 'clogs' the input stream,\n+         * like mod_ssl used to, lets just do the normal read from input\n+         * filters, like the Worker MPM does. Filters that need to write\n+         * where they would otherwise read, or read where they would\n+         * otherwise write, should set the sense appropriately.\n+         *\n+         * This path bypasses the normal motorz_conn_claim() that precedes\n+         * every other call to motorz_io_process(). Do a full claim now:\n+         * disarm the pollset entry AND cancel any pending timer under the\n+         * poller mutex. Without the timer cancel, a concurrent timer expiry\n+         * can dispatch a timeout worker on the same scon while this worker\n+         * is inside ap_run_process_connection() -- a use-after-free race.\n+         */\n+        motorz_conn_claim(scon-&gt;poller, scon);\n+        ap_run_process_connection(scon-&gt;c);\n+        /* The process_connection hooks set the next connection state on\n+         * return; honor it and let the dispatch below act on it, mirroring\n+         * the event MPM (see event.c:process_socket()). Async modules reach\n+         * this clogging path too: mod_http2's secondary (c2) connections set\n+         * clogging_input_filters unconditionally, and come back wanting either\n+         * to wait for I/O (CONN_STATE_ASYNC_WAITIO), flush\n+         * (CONN_STATE_WRITE_COMPLETION), or suspend -- all of which must be\n+         * preserved rather than force-closed.\n+         *\n+         * A hook-returned CONN_STATE_KEEPALIVE is mapped to\n+         * CONN_STATE_WRITE_COMPLETION (as event does) so it flushes any\n+         * pending output and then waits for the next request: passing bare\n+         * KEEPALIVE through to the dispatch below would hit the\n+         * KEEPALIVE -&gt; PROCESSING entry transition and synchronously re-run\n+         * ap_run_process_connection() instead of returning to the poller.\n+         *\n+         * Anything left unfinished -- still CONN_STATE_PROCESSING because a\n+         * hook returned DECLINED or OK without setting a state, as a non-async\n+         * module would -- gets a lingering close, like the worker MPM. That\n+         * also keeps us out of the CONN_STATE_PROCESSING branch below.\n+         */\n+        if (scon-&gt;cs.state == CONN_STATE_KEEPALIVE) {\n+            scon-&gt;cs.state = CONN_STATE_WRITE_COMPLETION;\n+        }\n+        else if (scon-&gt;cs.state != CONN_STATE_ASYNC_WAITIO\n+                 &amp;&amp; scon-&gt;cs.state != CONN_STATE_WRITE_COMPLETION\n+                 &amp;&amp; scon-&gt;cs.state != CONN_STATE_SUSPENDED) {\n+            scon-&gt;cs.state = CONN_STATE_LINGER;\n+        }\n+    }\n+\n+    c = scon-&gt;c;\n+\n+    if (!c-&gt;aborted) {\n+\n+        /* On the normal dispatch path (from motorz_io_event_process or\n+         * motorz_io_setup_conn), the connection has already been claimed --\n+         * pollset entry removed and reqevents cleared -- before reaching here.\n+         * No redundant apr_pollset_remove() is needed or performed.\n+         */\n+\n+        if (scon-&gt;cs.state == CONN_STATE_KEEPALIVE) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(03327)\n+                         \"motorz_io_process(): keepalive -&gt; processing\");\n+            scon-&gt;cs.state = CONN_STATE_PROCESSING;\n+        }\n+        else if (scon-&gt;cs.state == CONN_STATE_ASYNC_WAITIO) {\n+            /* The socket this connection was waiting on (CONN_STATE_ASYNC_WAITIO,\n+             * armed below) became readable/writable, so we were re-dispatched:\n+             * re-enter the process_connection hooks, mirroring how event's loop\n+             * maps ASYNC_WAITIO back to PROCESSING. (A Timeout expiry does not\n+             * arrive here -- motorz_io_timeout_cb lingers/closes directly.)\n+             */\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(10559)\n+                         \"motorz_io_process(): async waitio -&gt; processing\");\n+            scon-&gt;cs.state = CONN_STATE_PROCESSING;\n+        }\n+\n+read_request:\n+        if (scon-&gt;cs.state == CONN_STATE_PROCESSING) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(03328) \"motorz_io_process(): processing\");\n+            if (!c-&gt;aborted) {\n+                ap_update_child_status(scon-&gt;sbh, SERVER_BUSY_READ, NULL);\n+                ap_run_process_connection(c);\n+                /* state will be updated upon return\n+                 * fall thru to either wait for readability/timeout or\n+                 * do lingering close\n+                 */\n+            }\n+            else {\n+                ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                             APLOGNO(03330)\n+                             \"motorz_io_process(): aborted -&gt; linger\");\n+                scon-&gt;cs.state = CONN_STATE_LINGER;\n+            }\n+        }\n+\n+        if (scon-&gt;cs.state == CONN_STATE_SUSPENDED) {\n+            /* A module has taken the connection asynchronous (A4). Park it;\n+             * ownership returns only via motorz_resume_suspended(). Do not\n+             * re-arm the pollset/timer or tear it down.\n+             */\n+            ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf,\n+                         APLOGNO(10550)\n+                         \"motorz_io_process(): suspended\");\n+            motorz_suspend_connection(scon);\n+            return APR_SUCCESS;\n+        }\n+\n+        if (scon-&gt;cs.state == CONN_STATE_ASYNC_WAITIO) {\n+            /* A process_connection hook wants the MPM to wait for the\n+             * connection to become readable or writable (per c-&gt;cs-&gt;sense,\n+             * defaulting to read) within the configured Timeout, and then\n+             * re-enter the hooks. This is the same wait the WANT_READ\n+             * workaround does through WRITE_COMPLETION, but explicit and\n+             * without first checking ap_run_output_pending() -- the hook has\n+             * told us it is done writing and is now waiting on I/O. Arm the\n+             * pollset + timer atomically and do not touch scon afterwards (a\n+             * concurrent timeout may free it). On failure scon is already\n+             * disarmed by the rollback; close it.\n+             */\n+            apr_int16_t reqevents;\n+\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(10557)\n+                         \"motorz_io_process(): async waitio\");\n+\n+            ap_update_child_status(scon-&gt;sbh, SERVER_BUSY_READ, NULL);\n+\n+            reqevents =\n+                (scon-&gt;cs.sense == CONN_SENSE_WANT_WRITE ? APR_POLLOUT\n+                                                         : APR_POLLIN)\n+                | APR_POLLHUP | APR_POLLERR;\n+            rv = motorz_conn_register(scon, reqevents,\n+                                      motorz_io_timeout_cb,\n+                                      motorz_get_timeout(scon));\n+            if (rv != APR_SUCCESS) {\n+                ap_log_error(APLOG_MARK, APLOG_WARNING, rv,\n+                             ap_server_conf, APLOGNO(10558)\n+                             \"apr_pollset_add: failed in async waitio\");\n+                motorz_conn_done(scon);\n+            }\n+            return APR_SUCCESS;\n+        }\n+\n+        if (scon-&gt;cs.state == CONN_STATE_WRITE_COMPLETION) {\n+            ap_filter_t *output_filter;\n+            apr_status_t flush_rv;\n+\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(03331)\n+                         \"motorz_io_process(): write completion\");\n+\n+            ap_update_child_status(scon-&gt;sbh, SERVER_BUSY_WRITE, NULL);\n+\n+            /* 2.4.x has no ap_run_output_pending()/ap_run_input_pending();\n+             * follow mpm_event's WRITE_COMPLETION handling instead. Flush all\n+             * pending output by passing a NULL brigade down the last output\n+             * filter, then decide based on the conn_rec data_in_*_filters flags\n+             * (and CONN_SENSE_WANT_READ) whether output is still pending.\n+             */\n+            output_filter = c-&gt;output_filters;\n+            while (output_filter-&gt;next != NULL) {\n+                output_filter = output_filter-&gt;next;\n+            }\n+            flush_rv = output_filter-&gt;frec-&gt;filter_func.out_func(output_filter,\n+                                                                 NULL);\n+            if (flush_rv != APR_SUCCESS) {\n+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, flush_rv, c,\n+                              APLOGNO(10551)\n+                              \"network write failure in core output filter\");\n+                scon-&gt;cs.state = CONN_STATE_LINGER;\n+            }\n+            else if (c-&gt;data_in_output_filters\n+                     || scon-&gt;cs.sense == CONN_SENSE_WANT_READ) {\n+                /* Still in WRITE_COMPLETION_STATE: set a read/write timeout and\n+                 * let the poll thread wait for read/writeability. Arm pollset +\n+                 * timer atomically and do not touch scon afterwards (it may be\n+                 * freed by a concurrent timeout). On failure scon is already\n+                 * disarmed by the rollback; close it.\n+                 */\n+                apr_int16_t reqevents =\n+                    (scon-&gt;cs.sense == CONN_SENSE_WANT_READ ? APR_POLLIN\n+                                                            : APR_POLLOUT)\n+                    | APR_POLLHUP | APR_POLLERR;\n+                rv = motorz_conn_register(scon, reqevents,\n+                                          motorz_io_timeout_cb,\n+                                          motorz_get_timeout(scon));\n+                if (rv != APR_SUCCESS) {\n+                    ap_log_error(APLOG_MARK, APLOG_WARNING, rv,\n+                                 ap_server_conf, APLOGNO(02849)\n+                                 \"apr_pollset_add: failed in write completion\");\n+                    motorz_conn_done(scon);\n+                }\n+                return APR_SUCCESS;\n+            }\n+            else if (c-&gt;keepalive != AP_CONN_KEEPALIVE || c-&gt;aborted) {\n+                scon-&gt;cs.state = CONN_STATE_LINGER;\n+            }\n+            else if (c-&gt;data_in_input_filters) {\n+                scon-&gt;cs.state = CONN_STATE_PROCESSING;\n+                goto read_request;\n+            }\n+            else {\n+                scon-&gt;cs.state = CONN_STATE_KEEPALIVE;\n+            }\n+        }\n+\n+        if (scon-&gt;cs.state == CONN_STATE_LINGER) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(03332) \"motorz_io_process(): linger\");\n+            /* Begin a non-blocking lingering close instead of blocking this\n+             * worker for up to MAX_SECS_TO_LINGER (A3). scon may be torn down\n+             * or handed back to the poll loop inside; invalid afterwards.\n+             */\n+            motorz_start_lingering_close(scon);\n+            return APR_SUCCESS;\n+        }\n+\n+        if (scon-&gt;cs.state == CONN_STATE_KEEPALIVE) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf,\n+                         APLOGNO(03333) \"motorz_io_process(): keepalive\");\n+            /* Arm pollset + keep-alive timer atomically; do not touch scon\n+             * afterwards (a concurrent timeout may free it). On failure scon\n+             * is already disarmed by the rollback; close it.\n+             */\n+            rv = motorz_conn_register(scon,\n+                                      APR_POLLIN | APR_POLLHUP | APR_POLLERR,\n+                                      motorz_io_timeout_cb,\n+                                      motorz_get_keep_alive_timeout(scon));\n+            if (rv != APR_SUCCESS) {\n+                ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,\n+                             APLOGNO(02850)\n+                             \"process_socket: apr_pollset_add failure in \"\n+                             \"read request line\");\n+                motorz_conn_done(scon);\n+                return APR_SUCCESS;\n+            }\n+        }\n+    } else {\n+        /* Aborted: begin (non-blocking) lingering close. */\n+        motorz_start_lingering_close(scon);\n+        return APR_SUCCESS;\n+    }\n+    return APR_SUCCESS;\n+}\n+\n+/* NOTE: 2.4.x has no MPM-level resume-suspended hook (ap_hook_mpm_resume_suspended\n+ * / ap_mpm_resume_suspended() and conn_rec.suspended_baton are trunk-only). A\n+ * module that suspends a connection on 2.4.x owns it for the rest of its life\n+ * and drives it via its own threads and the suspend/resume_connection hooks; it\n+ * does not hand the connection back to the MPM. motorz therefore does not\n+ * advertise AP_MPMQ_CAN_SUSPEND and provides no resume-suspended hook, matching\n+ * mpm_event on 2.4.x.\n+ */\n+\n+/* One poll thread per poller drives accept/dispatch/timer work for the\n+ * connections bound to it; workers only process. Each child runs num_pollers\n+ * of these in parallel. See \"Scaling / architecture limits\" in MOTORZ.README.\n+ */\n+static apr_status_t motorz_pollset_cb(motorz_poller_t *poller, apr_interval_time_t timeout)\n+{\n+    apr_status_t rc;\n+    const apr_pollfd_t *out_pfd = NULL;\n+    apr_int32_t num = 0;\n+\n+    rc = apr_pollset_poll(poller-&gt;pollset, timeout, &amp;num, &amp;out_pfd);\n+    if (rc != APR_SUCCESS) {\n+        if (APR_STATUS_IS_EINTR(rc) || APR_STATUS_IS_TIMEUP(rc)) {\n+                return APR_SUCCESS;\n+        } else {\n+            return rc;\n+        }\n+    }\n+    while (num &gt; 0) {\n+        rc = motorz_io_callback(poller, out_pfd);\n+        if (rc != APR_SUCCESS) {\n+            ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL, APLOGNO(03334)\n+                         \"Call to motorz_io_callback() failed\");\n+        }\n+        out_pfd++;\n+        num--;\n+    }\n+    return APR_SUCCESS;\n+}\n+\n+/**\n+ * Create worker thread pool.\n+ */\n+static apr_status_t motorz_setup_workers(motorz_core_t *mz)\n+{\n+    apr_status_t rv;\n+\n+    rv = apr_thread_pool_create(&amp;mz-&gt;workers,\n+                                threads_per_child,\n+                                threads_per_child, mz-&gt;pool);\n+\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(02851)\n+                     \"motorz_setup_workers: apr_thread_pool_create with %d threads failed\",\n+                     threads_per_child);\n+        return rv;\n+    }\n+\n+    return APR_SUCCESS;\n+}\n+\n+static int motorz_setup_pollset(motorz_poller_t *poller)\n+{\n+    int i;\n+    apr_status_t rv;\n+    int good_methods[] = {APR_POLLSET_KQUEUE, APR_POLLSET_PORT, APR_POLLSET_EPOLL};\n+\n+    /* The pollset is mutated (apr_pollset_{add,remove}) from worker-pool\n+     * threads while this poller's thread is blocked in apr_pollset_poll(), so\n+     * it MUST be thread-safe. All the preferred backends below\n+     * (kqueue/port/epoll) support APR_POLLSET_THREADSAFE.\n+     */\n+    for (i = 0; i &lt; sizeof(good_methods) / sizeof(good_methods[0]); i++) {\n+        rv = apr_pollset_create_ex(&amp;poller-&gt;pollset,\n+                                  512,\n+                                  poller-&gt;pool,\n+                                  APR_POLLSET_NODEFAULT | APR_POLLSET_THREADSAFE,\n+                                  good_methods[i]);\n+        if (rv == APR_SUCCESS) {\n+            ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(02852)\n+                         \"motorz_setup_pollset: apr_pollset_create_ex using %s\", apr_pollset_method_name(poller-&gt;pollset));\n+\n+            break;\n+        }\n+    }\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_INFO, rv, ap_server_conf, APLOGNO(02853)\n+                     \"motorz_setup_pollset: apr_pollset_create_ex failed for all possible backends!\");\n+        rv = apr_pollset_create(&amp;poller-&gt;pollset,\n+                                    512,\n+                                    poller-&gt;pool,\n+                                    APR_POLLSET_THREADSAFE);\n+    }\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(02854)\n+                     \"motorz_setup_pollset: apr_pollset_create failed for all possible backends!\");\n+    }\n+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(03335)\n+                 \"motorz_setup_pollset: Using %s\", apr_pollset_method_name(poller-&gt;pollset));\n+    return rv;\n+}\n+\n+static void motorz_note_child_killed(int childnum, pid_t pid,\n+                                      ap_generation_t gen)\n+{\n+    AP_DEBUG_ASSERT(childnum != -1); /* no scoreboard squatting with this MPM */\n+    ap_run_child_status(ap_server_conf,\n+                        ap_scoreboard_image-&gt;parent[childnum].pid,\n+                        ap_scoreboard_image-&gt;parent[childnum].generation,\n+                        childnum, MPM_CHILD_EXITED);\n+    ap_scoreboard_image-&gt;parent[childnum].pid = 0;\n+}\n+\n+static void motorz_note_child_started(motorz_core_t *mz, int slot, pid_t pid)\n+{\n+    ap_generation_t gen = mz-&gt;mpm-&gt;my_generation;\n+    ap_scoreboard_image-&gt;parent[slot].pid = pid;\n+    ap_scoreboard_image-&gt;parent[slot].generation = gen;\n+    ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED);\n+}\n+\n+/* a clean exit from a child with proper cleanup */\n+static void clean_child_exit(int code)\n+{\n+    motorz_core_t *mz = motorz_core_get();\n+\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+\n+    apr_signal(SIGHUP, SIG_IGN);\n+    apr_signal(SIGTERM, SIG_IGN);\n+\n+    /* Join the poller threads before tearing down the worker pool. A poller\n+     * thread, mid motorz_pollset_cb, may be about to hand a ready connection\n+     * to the worker pool via apr_thread_pool_push(mz-&gt;workers, ...); if we\n+     * destroy mz-&gt;workers (or pchild, on which the poller threads were\n+     * created) out from under it, that push dereferences freed memory and\n+     * crashes in APR's add_task. child_main's normal exit already joins the\n+     * pollers before getting here, but the abrupt paths (just_die, and the\n+     * mid-startup error exits below where some pollers may already be\n+     * running) do not -- so quiesce and join them here too. die_now stops the\n+     * poll loops; the join is skipped for any poller running on the *current*\n+     * thread (defensive: clean_child_exit is normally reached on the main\n+     * thread, never a poller thread).\n+     */\n+    die_now = 1;\n+    if (mz-&gt;pollers) {\n+        apr_os_thread_t self = apr_os_thread_current();\n+        int i;\n+        for (i = 0; i &lt; mz-&gt;num_pollers; i++) {\n+            motorz_poller_t *poller = mz-&gt;pollers[i];\n+            if (poller &amp;&amp; poller-&gt;thread) {\n+                apr_os_thread_t *pos = NULL;\n+                if (apr_os_thread_get(&amp;pos, poller-&gt;thread) != APR_SUCCESS\n+                        || pos == NULL\n+                        || !apr_os_thread_equal(*pos, self)) {\n+                    apr_status_t pstatus;\n+                    apr_thread_join(&amp;pstatus, poller-&gt;thread);\n+                }\n+            }\n+        }\n+    }\n+\n+    /* Drain the worker thread pool before tearing down pools. Without this,\n+     * worker threads executing motorz_io_process or motorz_conn_done (which\n+     * call ap_log_error and apr_pool_clear) may still be running when pchild\n+     * and its log state are destroyed, causing use-after-free crashes.\n+     * apr_thread_pool_destroy() joins all worker threads before returning.\n+     * mz-&gt;workers is NULL only if motorz_setup_workers() was never called\n+     * (i.e. we're exiting very early, before child_main set up workers).\n+     */\n+    if (mz-&gt;workers) {\n+        apr_thread_pool_destroy(mz-&gt;workers);\n+        mz-&gt;workers = NULL;\n+    }\n+\n+    if (pchild) {\n+        apr_pool_destroy(pchild);\n+    }\n+\n+    if (one_process) {\n+        motorz_note_child_killed(/* slot */ 0, 0, 0);\n+    }\n+\n+    ap_mpm_pod_close(my_bucket-&gt;pod);\n+    exit(code);\n+}\n+\n+#if 0 /* unused for now */\n+static apr_status_t accept_mutex_on(void)\n+{\n+    motorz_core_t *mz = motorz_core_get();\n+    apr_status_t rv = apr_proc_mutex_lock(my_bucket-&gt;mutex);\n+    if (rv != APR_SUCCESS) {\n+        const char *msg = \"couldn't grab the accept mutex\";\n+\n+        if (mz-&gt;mpm-&gt;my_generation !=\n+            ap_scoreboard_image-&gt;global-&gt;running_generation) {\n+            ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(02855) \"%s\", msg);\n+            clean_child_exit(0);\n+        }\n+        else {\n+            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(02856) \"%s\", msg);\n+            exit(APEXIT_CHILDFATAL);\n+        }\n+    }\n+    return APR_SUCCESS;\n+}\n+\n+static apr_status_t accept_mutex_off(void)\n+{\n+    motorz_core_t *mz = motorz_core_get();\n+    apr_status_t rv = apr_proc_mutex_unlock(my_bucket-&gt;mutex);\n+    if (rv != APR_SUCCESS) {\n+        const char *msg = \"couldn't release the accept mutex\";\n+\n+        if (mz-&gt;mpm-&gt;my_generation !=\n+            ap_scoreboard_image-&gt;global-&gt;running_generation) {\n+            ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(02857) \"%s\", msg);\n+            /* don't exit here... we have a connection to\n+             * process, after which point we'll see that the\n+             * generation changed and we'll exit cleanly\n+             */\n+        }\n+        else {\n+            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(02858) \"%s\", msg);\n+            exit(APEXIT_CHILDFATAL);\n+        }\n+    }\n+    return APR_SUCCESS;\n+}\n+#endif\n+\n+/* On some architectures it's safe to do unserialized accept()s in the single\n+ * Listen case.  But it's never safe to do it in the case where there's\n+ * multiple Listen statements.  Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n+ * when it's safe in the single Listen case.\n+ */\n+#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n+#define SAFE_ACCEPT(stmt) (ap_listeners-&gt;next ? (stmt) : APR_SUCCESS)\n+#else\n+#define SAFE_ACCEPT(stmt) (stmt)\n+#endif\n+\n+static int motorz_query(int query_code, int *result, apr_status_t *rv)\n+{\n+    motorz_core_t *mz = motorz_core_get();\n+    *rv = APR_SUCCESS;\n+    switch(query_code){\n+    case AP_MPMQ_IS_ASYNC:\n+        /* See MOTORZ_ENABLE_ASYNC at the top of this file: async HTTP/2 handoff\n+         * is disabled pending a mod_http2 c1/c2 close-ordering fix. */\n+        *result = MOTORZ_ENABLE_ASYNC;\n+        break;\n+    case AP_MPMQ_CAN_WAITIO:\n+        /* CONN_STATE_ASYNC_WAITIO is only requested by modules when the MPM is\n+         * async; motorz honors it (polls per c-&gt;cs-&gt;sense under Timeout and\n+         * re-enters the process_connection hooks -- see motorz_io_process()),\n+         * but gate it on MOTORZ_ENABLE_ASYNC so it tracks IS_ASYNC. */\n+        *result = MOTORZ_ENABLE_ASYNC;\n+        break;\n+    case AP_MPMQ_MAX_DAEMON_USED:\n+        *result = ap_num_kids;\n+        break;\n+    case AP_MPMQ_IS_THREADED:\n+        *result = AP_MPMQ_STATIC;\n+        break;\n+    case AP_MPMQ_IS_FORKED:\n+        *result = AP_MPMQ_STATIC;\n+        break;\n+    case AP_MPMQ_HARD_LIMIT_DAEMONS:\n+        *result = ap_num_kids;\n+        break;\n+    case AP_MPMQ_HARD_LIMIT_THREADS:\n+        *result = thread_limit;\n+        break;\n+    case AP_MPMQ_MAX_THREADS:\n+        *result = threads_per_child;\n+        break;\n+    case AP_MPMQ_MIN_SPARE_DAEMONS:\n+        *result = 0;\n+        break;\n+    case AP_MPMQ_MIN_SPARE_THREADS:\n+        *result = 0;\n+        break;\n+    case AP_MPMQ_MAX_SPARE_DAEMONS:\n+        *result = ap_num_kids;\n+        break;\n+    case AP_MPMQ_MAX_SPARE_THREADS:\n+        *result = 0;\n+        break;\n+    case AP_MPMQ_MAX_REQUESTS_DAEMON:\n+        *result = 0;\n+        break;\n+    case AP_MPMQ_MAX_DAEMONS:\n+        *result = ap_num_kids;\n+        break;\n+    case AP_MPMQ_MPM_STATE:\n+        *result = mz-&gt;mpm-&gt;mpm_state;\n+        break;\n+    case AP_MPMQ_GENERATION:\n+        *result = mz-&gt;mpm-&gt;my_generation;\n+        break;\n+    default:\n+        *rv = APR_ENOTIMPL;\n+        break;\n+    }\n+    return OK;\n+}\n+\n+static const char *motorz_get_name(void)\n+{\n+    return \"motorz\";\n+}\n+\n+/*****************************************************************\n+ * Connection structures and accounting...\n+ */\n+\n+static void just_die(int sig)\n+{\n+    /* Async-signal context: do the minimum. Setting die_now stops the poller\n+     * loops and breaks motorz_supervise on the main thread, which then joins\n+     * the pollers and calls clean_child_exit -- the one teardown path that\n+     * quiesces the poller threads before destroying mz-&gt;workers and pchild.\n+     * Calling clean_child_exit() directly from here (the old behaviour) tore\n+     * those down while a poller could still be pushing work to the pool, a\n+     * use-after-free that crashed in APR's add_task.\n+     */\n+    die_now = 1;\n+}\n+\n+static void stop_listening(int sig)\n+{\n+    motorz_core_t *mz = motorz_core_get();\n+\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+    ap_close_listeners_ex(my_bucket-&gt;listeners);\n+\n+    /* For a graceful stop, we want the child to exit when done */\n+    die_now = 1;\n+}\n+\n+/*****************************************************************\n+ * Child process main loop.\n+ * The following vars are static to avoid getting clobbered by longjmp();\n+ * they are really private to child_main.\n+ */\n+\n+static int num_listensocks = 0;\n+\n+/* Listener admission control (#1). The listener pollfds live in the poller\n+ * that owns the listeners (poller 0); only that poller toggles them, on its\n+ * own thread, so no locking is needed. The hysteresis band is derived from\n+ * threads_per_child in child_main.\n+ */\n+static apr_size_t motorz_throttle_hi;\n+static apr_size_t motorz_throttle_lo;\n+\n+/* Stop accepting: remove the listener sockets from the poller's pollset so it\n+ * stops dispatching new connections while workers are saturated. Runs on the\n+ * owning poller's thread only; idempotent.\n+ */\n+static void motorz_disable_listeners(motorz_poller_t *poller)\n+{\n+    int i;\n+\n+    if (poller-&gt;listeners_disabled) {\n+        return;\n+    }\n+    for (i = 0; i &lt; poller-&gt;num_listener_pfds; i++) {\n+        apr_status_t rv = apr_pollset_remove(poller-&gt;pollset,\n+                                             poller-&gt;listener_pfds[i]);\n+        if (rv != APR_SUCCESS &amp;&amp; !APR_STATUS_IS_NOTFOUND(rv)) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, ap_server_conf,\n+                         \"motorz_disable_listeners: apr_pollset_remove failed\");\n+        }\n+    }\n+    poller-&gt;listeners_disabled = 1;\n+    if (my_child_num &gt;= 0) {\n+        ap_scoreboard_image-&gt;parent[my_child_num].not_accepting = 1;\n+    }\n+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(10552)\n+                 \"Workers busy, not accepting new connections in this child\");\n+}\n+\n+/* Resume accepting: re-add the listener sockets to the poller's pollset. Runs\n+ * on the owning poller's thread only; idempotent; a no-op while shutting down.\n+ */\n+static void motorz_enable_listeners(motorz_poller_t *poller)\n+{\n+    int i;\n+\n+    if (!poller-&gt;listeners_disabled || die_now) {\n+        return;\n+    }\n+    for (i = 0; i &lt; poller-&gt;num_listener_pfds; i++) {\n+        apr_status_t rv = apr_pollset_add(poller-&gt;pollset,\n+                                          poller-&gt;listener_pfds[i]);\n+        if (rv != APR_SUCCESS) {\n+            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, ap_server_conf,\n+                         \"motorz_enable_listeners: apr_pollset_add failed\");\n+        }\n+    }\n+    poller-&gt;listeners_disabled = 0;\n+    if (my_child_num &gt;= 0) {\n+        ap_scoreboard_image-&gt;parent[my_child_num].not_accepting = 0;\n+    }\n+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(10553)\n+                 \"Accepting new connections again in this child\");\n+}\n+\n+/* Reconsider admission once per poll-loop iteration (owning poller's thread).\n+ * Disable listeners when the worker pool is saturated and re-enable once it has\n+ * drained. Three complementary saturation signals:\n+ *\n+ *  1. idle == 0: no thread is free to pick up a new connection right now.\n+ *  2. pending &gt;= throttle_hi: the push queue has a full wave of unstarted tasks\n+ *     (each accepted connection becomes one task), so we are ahead of the workers.\n+ *  3. active &gt;= threads_per_child: all threads are occupied, including those\n+ *     blocked in I/O waits -- catches the slow-client / keep-alive-heavy case\n+ *     where the task queue looks empty but workers are fully tied up.\n+ *\n+ * The hysteresis band (hi/lo) on the pending count avoids enable/disable\n+ * flapping. A poller that does not own listeners (num_listener_pfds == 0) no-ops.\n+ */\n+static void motorz_update_listeners(motorz_poller_t *poller)\n+{\n+    apr_size_t idle, pending, active;\n+\n+    if (poller-&gt;num_listener_pfds == 0) {\n+        return;\n+    }\n+    /* Read total before idle: if a thread exits between the two reads,\n+     * reading idle first risks unsigned underflow (idle &gt; total -&gt; wrap).\n+     * Clamp the subtraction to zero so a transient race never yields a\n+     * spuriously huge 'active' value that trips the saturation check.\n+     */\n+    {\n+        apr_size_t total;\n+        total   = apr_thread_pool_threads_count(poller-&gt;mz-&gt;workers);\n+        idle    = apr_thread_pool_idle_count(poller-&gt;mz-&gt;workers);\n+        active  = (total &gt; idle) ? (total - idle) : 0;\n+    }\n+    pending = apr_thread_pool_tasks_count(poller-&gt;mz-&gt;workers);\n+\n+    if (!poller-&gt;listeners_disabled) {\n+        if (idle == 0\n+                || pending &gt;= motorz_throttle_hi\n+                || active &gt;= (apr_size_t)threads_per_child) {\n+            motorz_disable_listeners(poller);\n+        }\n+    }\n+    else if (idle &gt; 0\n+                 &amp;&amp; pending &lt;= motorz_throttle_lo\n+                 &amp;&amp; active &lt; (apr_size_t)threads_per_child) {\n+        motorz_enable_listeners(poller);\n+    }\n+}\n+\n+/* Create and initialize one poller context (its own pool, pollset, timer ring\n+ * and ring mutex). The recycle free-list and listener state start zeroed.\n+ * 'owns_listeners' marks the poller that holds the accept sockets.\n+ */\n+static motorz_poller_t *motorz_poller_create(motorz_core_t *mz, int index)\n+{\n+    apr_status_t rv;\n+    motorz_poller_t *poller = apr_pcalloc(mz-&gt;pool, sizeof(*poller));\n+\n+    poller-&gt;mz = mz;\n+    poller-&gt;index = index;\n+    apr_pool_create(&amp;poller-&gt;pool, mz-&gt;pool);\n+    apr_pool_tag(poller-&gt;pool, \"motorz-poller\");\n+\n+    rv = apr_thread_mutex_create(&amp;poller-&gt;mtx, APR_THREAD_MUTEX_DEFAULT,\n+                                 poller-&gt;pool);\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(02966)\n+                     \"motorz_poller_create: apr_thread_mutex_create failed\");\n+        clean_child_exit(APEXIT_CHILDSICK);\n+    }\n+\n+    apr_skiplist_init(&amp;poller-&gt;timeout_ring, poller-&gt;pool);\n+    apr_skiplist_set_compare(poller-&gt;timeout_ring, timer_comp, timer_comp);\n+\n+    rv = motorz_setup_pollset(poller);\n+    if (rv != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(02869)\n+                     \"Couldn't setup pollset in child; check system or user limits\");\n+        clean_child_exit(APEXIT_CHILDSICK); /* assume temporary resource issue */\n+    }\n+\n+    return poller;\n+}\n+\n+/* Add this child's listening sockets to 'poller' and capture them so admission\n+ * control can pause/resume accepting (#1). Only the listener-owning poller\n+ * calls this.\n+ */\n+static void motorz_poller_add_listeners(motorz_poller_t *poller)\n+{\n+    apr_status_t status;\n+    ap_listen_rec *lr;\n+    int i;\n+\n+    poller-&gt;listener_pfds = apr_pcalloc(poller-&gt;pool,\n+                                        num_listensocks * sizeof(apr_pollfd_t *));\n+    poller-&gt;num_listener_pfds = 0;\n+    poller-&gt;listeners_disabled = 0;\n+\n+    for (lr = my_bucket-&gt;listeners, i = num_listensocks; i--; lr = lr-&gt;next) {\n+        apr_pollfd_t *pfd = apr_pcalloc(poller-&gt;pool, sizeof *pfd);\n+        motorz_sb_t *sb = apr_pcalloc(poller-&gt;pool, sizeof(motorz_sb_t));\n+\n+        pfd-&gt;desc_type = APR_POLL_SOCKET;\n+        pfd-&gt;desc.s = lr-&gt;sd;\n+        pfd-&gt;reqevents = APR_POLLIN;\n+        pfd-&gt;p = poller-&gt;pool;\n+        pfd-&gt;client_data = sb;\n+\n+        sb-&gt;type = PT_ACCEPT;\n+        sb-&gt;baton = lr;\n+\n+        poller-&gt;listener_pfds[poller-&gt;num_listener_pfds++] = pfd;\n+\n+        status = apr_socket_opt_set(pfd-&gt;desc.s, APR_SO_NONBLOCK, 1);\n+        if (status != APR_SUCCESS) {\n+            ap_log_error(APLOG_MARK, APLOG_CRIT, status, NULL, APLOGNO(02870)\n+                         \"apr_socket_opt_set(APR_SO_NONBLOCK = 1) failed on %pI\",\n+                         lr-&gt;bind_addr);\n+            clean_child_exit(0);\n+        }\n+\n+        status = apr_pollset_add(poller-&gt;pollset, pfd);\n+        if (status != APR_SUCCESS) {\n+            /* If the child processed a SIGWINCH before setting up the\n+             * pollset, this error path is expected and harmless,\n+             * since the listener fd was already closed; so don't\n+             * pollute the logs in that case.\n+             */\n+            if (!die_now) {\n+                ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(02871)\n+                             \"Couldn't add listener to pollset; check system or user limits\");\n+                clean_child_exit(APEXIT_CHILDSICK);\n+            }\n+            clean_child_exit(0);\n+        }\n+\n+        lr-&gt;accept_func = ap_unixd_accept;\n+    }\n+}\n+\n+/* One poller's poll loop: poll, dispatch ready events to workers, expire\n+ * timers, reconsider admission. Runs until die_now / shutdown / restart. Each\n+ * poller runs this on its own thread; the child's main thread is the\n+ * supervisor (motorz_supervise) that owns the MaxRequestsPerChild / pod /\n+ * generation checks and sets die_now. A fatal poll error sets die_now and\n+ * returns rather than exiting the process, so the other pollers can wind down\n+ * and the supervisor can clean up.\n+ */\n+static void *APR_THREAD_FUNC motorz_poller_main(apr_thread_t *thread, void *baton)\n+{\n+    motorz_poller_t *poller = (motorz_poller_t *) baton;\n+    motorz_core_t *mz = poller-&gt;mz;\n+    apr_status_t status;\n+\n+    while (!die_now\n+           &amp;&amp; !mz-&gt;mpm-&gt;shutdown_pending\n+           &amp;&amp; !mz-&gt;mpm-&gt;restart_pending) {\n+        apr_time_t tnow = apr_time_now();\n+        motorz_timer_t *te;\n+        apr_interval_time_t timeout = apr_time_from_msec(500);\n+\n+        apr_thread_mutex_lock(poller-&gt;mtx);\n+        te = apr_skiplist_peek(poller-&gt;timeout_ring);\n+\n+        if (te) {\n+            if (tnow &lt; te-&gt;expires) {\n+                timeout = (te-&gt;expires - tnow);\n+                if (timeout &gt; apr_time_from_msec(500)) {\n+                    timeout = apr_time_from_msec(500);\n+                }\n+            }\n+            else {\n+                timeout = 0;\n+            }\n+        }\n+        apr_thread_mutex_unlock(poller-&gt;mtx);\n+\n+        status = motorz_pollset_cb(poller, timeout);\n+\n+        tnow = apr_time_now();\n+\n+        if (status != APR_SUCCESS) {\n+            if (!APR_STATUS_IS_EINTR(status) &amp;&amp; !APR_STATUS_IS_TIMEUP(status)) {\n+                ap_log_error(APLOG_MARK, APLOG_CRIT, status, NULL, APLOGNO(03117)\n+                             \"motorz_main_loop: apr_pollcb_poll failed\");\n+                die_now = 1;\n+                break;\n+            }\n+        }\n+\n+        apr_thread_mutex_lock(poller-&gt;mtx);\n+\n+        /* Now iterate any expired timers and push them to the worker\n+         * pool. The loop is driven entirely off a fresh peek taken under\n+         * the lock rather than the 'te' cached before the poll: while the\n+         * lock was dropped for polling, a worker thread may have inserted\n+         * a timer that is now the earliest in the ring. Peeking and\n+         * popping the minimum in lock-step keeps the popped node and the\n+         * processed node consistent.\n+         */\n+        while ((te = apr_skiplist_peek(poller-&gt;timeout_ring))\n+               &amp;&amp; te-&gt;expires &lt; tnow) {\n+            apr_skiplist_pop(poller-&gt;timeout_ring, NULL);\n+            motorz_timer_event_process(poller, te);\n+        }\n+\n+        apr_thread_mutex_unlock(poller-&gt;mtx);\n+\n+        /* Admission control (#1): pause/resume accepting based on worker-pool\n+         * saturation. Done here, on the poll thread and outside poller-&gt;mtx,\n+         * once per iteration. While listeners are disabled the loop still wakes\n+         * via the 500ms timeout floor and timer expiries, bounding resume\n+         * latency. No-op on pollers that do not own the listeners.\n+         */\n+        motorz_update_listeners(poller);\n+    }\n+\n+    return NULL;\n+}\n+\n+/* Child supervisor loop, run on the child's main thread while the poller\n+ * threads do the I/O. Watches MaxRequestsPerChild and the pipe-of-death /\n+ * generation change, setting die_now so the pollers wind down. Returns when\n+ * the child should exit.\n+ */\n+static void motorz_supervise(motorz_core_t *mz, ap_sb_handle_t *sbh)\n+{\n+    while (!die_now\n+           &amp;&amp; !mz-&gt;mpm-&gt;shutdown_pending\n+           &amp;&amp; !mz-&gt;mpm-&gt;restart_pending) {\n+\n+        /* requests_this_child is bumped per accepted connection by the\n+         * listener-owning poller; once the cap is reached, wind down.\n+         */\n+        if (ap_max_requests_per_child &gt; 0\n+            &amp;&amp; requests_this_child &gt;= ap_max_requests_per_child) {\n+            die_now = 1;\n+            break;\n+        }\n+\n+        ap_update_child_status(sbh, SERVER_READY, NULL);\n+\n+        if (ap_mpm_pod_check(my_bucket-&gt;pod) == APR_SUCCESS) { /* idle kill? */\n+            die_now = 1;\n+        }\n+        else if (mz-&gt;mpm-&gt;my_generation !=\n+                 ap_scoreboard_image-&gt;global-&gt;running_generation) { /* restart? */\n+            /* yeah, this could be non-graceful restart, in which case the\n+             * parent will kill us soon enough, but why bother checking?\n+             */\n+            die_now = 1;\n+        }\n+        else {\n+            /* Nothing to do; sleep briefly so we don't spin. The pollers run\n+             * independently, so the supervisor only needs coarse latency.\n+             */\n+            apr_sleep(apr_time_from_msec(100));\n+        }\n+    }\n+}\n+\n+static void child_main(motorz_core_t *mz, int child_num_arg, int child_bucket)\n+{\n+#if APR_HAS_THREADS\n+    apr_thread_t *thd = NULL;\n+    apr_os_thread_t osthd;\n+#endif\n+    apr_status_t status;\n+    int i;\n+    ap_sb_handle_t *sbh;\n+    const char *lockfile;\n+    motorz_poller_t *poller;\n+\n+    /* for benefit of any hooks that run as this child initializes */\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STARTING;\n+\n+    my_child_num = child_num_arg;\n+    ap_my_pid = getpid();\n+    requests_this_child = 0;\n+\n+    ap_fatal_signal_child_setup(ap_server_conf);\n+\n+    /* Get a sub context for global allocations in this child, so that\n+     * we can have cleanups occur when the child exits.\n+     */\n+    apr_pool_create(&amp;pchild, pconf);\n+    apr_pool_tag(pchild, \"pchild\");\n+\n+#if APR_HAS_THREADS\n+    osthd = apr_os_thread_current();\n+    apr_os_thread_put(&amp;thd, &amp;osthd, pchild);\n+#endif\n+\n+    /* close unused listeners and pods */\n+    for (i = 0; i &lt; mz-&gt;mpm-&gt;num_buckets; i++) {\n+        if (i != child_bucket) {\n+            ap_close_listeners_ex(all_buckets[i].listeners);\n+            ap_mpm_pod_close(all_buckets[i].pod);\n+        }\n+    }\n+\n+    /* needs to be done before we switch UIDs so we have permissions */\n+    ap_reopen_scoreboard(pchild, NULL, 0);\n+    status = SAFE_ACCEPT(apr_proc_mutex_child_init(&amp;my_bucket-&gt;mutex,\n+                                    apr_proc_mutex_lockfile(my_bucket-&gt;mutex),\n+                                    pchild));\n+    if (status != APR_SUCCESS) {\n+        lockfile = apr_proc_mutex_lockfile(my_bucket-&gt;mutex);\n+        ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(02867)\n+                     \"Couldn't initialize cross-process lock in child \"\n+                     \"(%s) (%s)\",\n+                     lockfile ? lockfile : \"none\",\n+                     apr_proc_mutex_name(my_bucket-&gt;mutex));\n+        clean_child_exit(APEXIT_CHILDFATAL);\n+    }\n+\n+    if (ap_run_drop_privileges(pchild, ap_server_conf)) {\n+        clean_child_exit(APEXIT_CHILDFATAL);\n+    }\n+\n+    ap_run_child_init(pchild, ap_server_conf);\n+\n+    ap_create_sb_handle(&amp;sbh, pchild, my_child_num, 0);\n+\n+    ap_update_child_status(sbh, SERVER_READY, NULL);\n+\n+    status = motorz_setup_workers(mz);\n+    if (status != APR_SUCCESS) {\n+        ap_log_error(APLOG_MARK, APLOG_CRIT, status, ap_server_conf, APLOGNO(02868)\n+                     \"child_main: motorz_setup_workers failed\");\n+        clean_child_exit(APEXIT_CHILDSICK);\n+    }\n+\n+    /* Admission-control hysteresis band: pause accepting once the pending\n+     * backlog reaches a full wave (threads_per_child) and resume once it\n+     * drains to 75%. The 75% low-water mark (vs. the old 50%) re-enables the\n+     * listener sooner, reducing latency spikes at the cost of slightly more\n+     * frequent enable/disable transitions -- a good trade under variable load.\n+     */\n+    motorz_throttle_hi = threads_per_child;\n+    motorz_throttle_lo = (threads_per_child * 3) / 4;\n+\n+    /* Resolve the poller count: explicit PollersPerChild, else auto from online\n+     * CPUs (capped). Never more pollers than worker threads, and at least 1.\n+     */\n+    mz-&gt;num_pollers = num_pollers;\n+    if (mz-&gt;num_pollers &lt;= 0) {\n+#ifdef _SC_NPROCESSORS_ONLN\n+        long ncpu = sysconf(_SC_NPROCESSORS_ONLN);\n+        mz-&gt;num_pollers = (ncpu &gt; 0) ? (int)ncpu : 1;\n+#else\n+        mz-&gt;num_pollers = 1;\n+#endif\n+        if (mz-&gt;num_pollers &gt; MOTORZ_MAX_POLLERS) {\n+            mz-&gt;num_pollers = MOTORZ_MAX_POLLERS;\n+        }\n+    }\n+    if (mz-&gt;num_pollers &gt; threads_per_child) {\n+        mz-&gt;num_pollers = threads_per_child;\n+    }\n+    if (mz-&gt;num_pollers &lt; 1) {\n+        mz-&gt;num_pollers = 1;\n+    }\n+\n+    /* Create N pollers, each on its own thread, supervised by this thread.\n+     * Poller 0 owns the listening sockets (and thus does the accepting);\n+     * Stage 3 will shard accepted connections across all pollers. With a\n+     * single poller this is behaviourally identical to the old design.\n+     */\n+    mz-&gt;pollers = apr_pcalloc(mz-&gt;pool,\n+                              mz-&gt;num_pollers * sizeof(motorz_poller_t *));\n+    for (i = 0; i &lt; mz-&gt;num_pollers; i++) {\n+        mz-&gt;pollers[i] = motorz_poller_create(mz, i);\n+    }\n+    /* Listeners live in poller 0. */\n+    motorz_poller_add_listeners(mz-&gt;pollers[0]);\n+\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_RUNNING;\n+\n+    /* die_now is set when AP_SIG_GRACEFUL is received in the child;\n+     * {shutdown,restart}_pending are set when a signal is received while\n+     * running in single process mode.\n+     */\n+    for (i = 0; i &lt; mz-&gt;num_pollers; i++) {\n+        poller = mz-&gt;pollers[i];\n+        status = apr_thread_create(&amp;poller-&gt;thread, NULL,\n+                                   motorz_poller_main, poller, pchild);\n+        if (status != APR_SUCCESS) {\n+            ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(10554)\n+                         \"child_main: apr_thread_create failed for poller %d\", i);\n+            die_now = 1;\n+            clean_child_exit(APEXIT_CHILDSICK);\n+        }\n+    }\n+\n+    /* Supervise on this thread; returns when the child should wind down. */\n+    motorz_supervise(mz, sbh);\n+\n+    /* die_now is now set; join the poller threads so their pollsets/rings are\n+     * quiescent before we tear the child down.\n+     */\n+    for (i = 0; i &lt; mz-&gt;num_pollers; i++) {\n+        if (mz-&gt;pollers[i]-&gt;thread) {\n+            apr_status_t pstatus;\n+            apr_thread_join(&amp;pstatus, mz-&gt;pollers[i]-&gt;thread);\n+        }\n+    }\n+\n+    clean_child_exit(0);\n+}\n+\n+static int make_child(motorz_core_t *mz, server_rec *s, int slot)\n+{\n+    int bucket = slot % mz-&gt;mpm-&gt;num_buckets;\n+    int pid;\n+\n+    if (slot + 1 &gt; mz-&gt;max_daemons_limit) {\n+        mz-&gt;max_daemons_limit = slot + 1;\n+    }\n+\n+    if (one_process) {\n+        my_bucket = &amp;all_buckets[0];\n+\n+        motorz_note_child_started(mz, slot, getpid());\n+        child_main(mz, slot, 0);\n+        /* NOTREACHED */\n+        ap_assert(0);\n+        return -1;\n+    }\n+\n+    ap_update_child_status_from_indexes(slot, 0, SERVER_STARTING, NULL);\n+\n+    if ((pid = fork()) == -1) {\n+        ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(02872) \"fork: Unable to fork new process\");\n+\n+        /* fork didn't succeed. Fix the scoreboard or else\n+         * it will say SERVER_STARTING forever and ever\n+         */\n+        ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD, NULL);\n+\n+        /* In case system resources are maxxed out, we don't want\n+         * Apache running away with the CPU trying to fork over and\n+         * over and over again.\n+         */\n+        sleep(10);\n+\n+        return -1;\n+    }\n+\n+    if (!pid) {\n+        my_bucket = &amp;all_buckets[bucket];\n+\n+#ifdef HAVE_BINDPROCESSOR\n+        /* by default AIX binds to a single processor\n+         * this bit unbinds children which will then bind to another cpu\n+         */\n+        int status = bindprocessor(BINDPROCESS, (int)getpid(),\n+                                   PROCESSOR_CLASS_ANY);\n+        if (status != OK) {\n+            ap_log_error(APLOG_MARK, APLOG_DEBUG, errno,\n+                         ap_server_conf, APLOGNO(02873) \"processor unbind failed\");\n+        }\n+#endif\n+        RAISE_SIGSTOP(MAKE_CHILD);\n+        AP_MONCONTROL(1);\n+        /* Disable the parent's signal handlers and set up proper handling in\n+         * the child.\n+         */\n+        apr_signal(SIGHUP, just_die);\n+        apr_signal(SIGTERM, just_die);\n+        /* Ignore SIGINT in child. This fixes race-condition in signals\n+         * handling when httpd is running on foreground and user hits ctrl+c.\n+         * In this case, SIGINT is sent to all children followed by SIGTERM\n+         * from the main process, which interrupts the SIGINT handler and\n+         * leads to inconsistency.\n+         */\n+        apr_signal(SIGINT, SIG_IGN);\n+        /* The child process just closes listeners on AP_SIG_GRACEFUL.\n+         * The pod is used for signalling the graceful restart.\n+         */\n+        apr_signal(AP_SIG_GRACEFUL, stop_listening);\n+        child_main(mz, slot, bucket);\n+    }\n+\n+    motorz_note_child_started(mz, slot, pid);\n+\n+    return 0;\n+}\n+\n+\n+/* start up a bunch of children */\n+static void startup_children(motorz_core_t *mz, int number_to_start)\n+{\n+    int i;\n+\n+    for (i = 0; number_to_start &amp;&amp; i &lt; ap_num_kids; ++i) {\n+        if (ap_scoreboard_image-&gt;servers[i][0].status != SERVER_DEAD) {\n+            continue;\n+        }\n+        if (make_child(mz, ap_server_conf, i) &lt; 0) {\n+            break;\n+        }\n+        --number_to_start;\n+    }\n+}\n+\n+static void perform_idle_server_maintenance(motorz_core_t *mz, apr_pool_t *p)\n+{\n+    int free_length;\n+    int free_slots[1];\n+\n+    int i;\n+    worker_score *ws;\n+\n+    int active = 0;\n+    free_length = 0;\n+    free_slots[0] = 0;\n+\n+    for (i = 0; i &lt; ap_num_kids; ++i) {\n+        int status;\n+        ws = &amp;ap_scoreboard_image-&gt;servers[i][0];\n+        status = ws-&gt;status;\n+        if (status == SERVER_DEAD &amp;&amp; !free_length) {\n+            free_slots[free_length] = i;\n+            free_length++;\n+        }\n+        if (status &gt;= SERVER_READY) {\n+            active++;\n+        }\n+    }\n+    if (active &gt; ap_num_kids) {\n+        static int bucket_kill_child_record = -1;\n+        /* kill off one child... we use the pod because that'll cause it to\n+         * shut down gracefully, in case it happened to pick up a request\n+         * while we were counting\n+         */\n+        bucket_kill_child_record = (bucket_kill_child_record + 1) % mz-&gt;mpm-&gt;num_buckets;\n+        ap_mpm_pod_signal(all_buckets[bucket_kill_child_record].pod);\n+    }\n+    else if (active &lt; ap_num_kids) {\n+        make_child(mz, ap_server_conf, free_slots[0]);\n+    }\n+}\n+\n+/*****************************************************************\n+ * Executive routines.\n+ */\n+\n+static int motorz_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)\n+{\n+    int index;\n+    int remaining_children_to_start;\n+    int i;\n+    motorz_core_t *mz = motorz_core_get();\n+\n+    ap_log_pid(pconf, ap_pid_fname);\n+\n+    if (!mz-&gt;mpm-&gt;was_graceful) {\n+        if (ap_run_pre_mpm(s-&gt;process-&gt;pool, SB_SHARED) != OK) {\n+            mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+            return !OK;\n+        }\n+        /* fix the generation number in the global score; we just got a new,\n+         * cleared scoreboard\n+         */\n+        ap_scoreboard_image-&gt;global-&gt;running_generation = mz-&gt;mpm-&gt;my_generation;\n+    }\n+\n+    ap_unixd_mpm_set_signals(pconf, one_process);\n+\n+    if (one_process) {\n+        AP_MONCONTROL(1);\n+        make_child(mz, ap_server_conf, 0);\n+        /* NOTREACHED */\n+        ap_assert(0);\n+        return !OK;\n+    }\n+\n+    /* Don't thrash since num_buckets depends on the\n+     * system and the number of online CPU cores...\n+     */\n+    if (ap_num_kids &lt; mz-&gt;mpm-&gt;num_buckets)\n+        ap_num_kids = mz-&gt;mpm-&gt;num_buckets;\n+\n+    /* If we're doing a graceful_restart then we're going to see a lot\n+     * of children exiting immediately when we get into the main loop\n+     * below (because we just sent them AP_SIG_GRACEFUL).  This happens pretty\n+     * rapidly... and for each one that exits we'll start a new one until\n+     * we reach at least daemons_min_free.  But we may be permitted to\n+     * start more than that, so we'll just keep track of how many we're\n+     * supposed to start up without the 1 second penalty between each fork.\n+     */\n+    remaining_children_to_start = ap_num_kids;\n+    if (!mz-&gt;mpm-&gt;was_graceful) {\n+        startup_children(mz, remaining_children_to_start);\n+        remaining_children_to_start = 0;\n+    }\n+\n+    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02874)\n+                \"%s configured -- resuming normal operations\",\n+                ap_get_server_description());\n+    ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(02875)\n+                \"Server built: %s\", ap_get_server_built());\n+    ap_log_command_line(plog, s);\n+    ap_log_mpm_common(s);\n+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02876)\n+                \"Accept mutex: %s (default: %s)\",\n+                (all_buckets[0].mutex)\n+                    ? apr_proc_mutex_name(all_buckets[0].mutex)\n+                    : \"none\",\n+                apr_proc_mutex_defname());\n+\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_RUNNING;\n+\n+    while (!mz-&gt;mpm-&gt;restart_pending &amp;&amp; !mz-&gt;mpm-&gt;shutdown_pending) {\n+        int child_slot;\n+        apr_exit_why_e exitwhy;\n+        int status, processed_status;\n+        /* this is a memory leak, but I'll fix it later. */\n+        apr_proc_t pid;\n+\n+        ap_wait_or_timeout(&amp;exitwhy, &amp;status, &amp;pid, pconf, ap_server_conf);\n+\n+        /* XXX: if it takes longer than 1 second for all our children\n+         * to start up and get into IDLE state then we may spawn an\n+         * extra child\n+         */\n+        if (pid.pid != -1) {\n+            processed_status = ap_process_child_status(&amp;pid, exitwhy, status);\n+            child_slot = ap_find_child_by_pid(&amp;pid);\n+            if (processed_status == APEXIT_CHILDFATAL) {\n+                /* fix race condition found in PR 39311\n+                 * A child created at the same time as a graceful happens\n+                 * can find the lock missing and create a fatal error.\n+                 * It is not fatal for the last generation to be in this state.\n+                 */\n+                if (child_slot &lt; 0\n+                    || ap_get_scoreboard_process(child_slot)-&gt;generation\n+                       == mz-&gt;mpm-&gt;my_generation) {\n+                    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+                    return !OK;\n+                }\n+                else {\n+                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(02877)\n+                                 \"Ignoring fatal error in child of previous \"\n+                                 \"generation (pid %ld).\",\n+                                 (long)pid.pid);\n+                }\n+            }\n+\n+            /* non-fatal death... note that it's gone in the scoreboard. */\n+            if (child_slot &gt;= 0) {\n+                ap_update_child_status_from_indexes(child_slot, 0,\n+                                                    SERVER_DEAD, NULL);\n+                motorz_note_child_killed(child_slot, 0, 0);\n+                if (remaining_children_to_start\n+                    &amp;&amp; child_slot &lt; ap_num_kids) {\n+                    /* we're still doing a 1-for-1 replacement of dead\n+                     * children with new children\n+                     */\n+                    make_child(mz, ap_server_conf, child_slot);\n+                    --remaining_children_to_start;\n+                }\n+#if APR_HAS_OTHER_CHILD\n+            }\n+            else if (apr_proc_other_child_alert(&amp;pid, APR_OC_REASON_DEATH, status) == APR_SUCCESS) {\n+                /* handled */\n+#endif\n+            }\n+            else if (mz-&gt;mpm-&gt;was_graceful) {\n+                /* Great, we've probably just lost a slot in the\n+                 * scoreboard.  Somehow we don't know about this\n+                 * child.\n+                 */\n+                ap_log_error(APLOG_MARK, APLOG_WARNING,\n+                            0, ap_server_conf, APLOGNO(02878)\n+                            \"long lost child came home! (pid %ld)\", (long)pid.pid);\n+            }\n+            /* Don't perform idle maintenance when a child dies,\n+             * only do it when there's a timeout.  Remember only a\n+             * finite number of children can die, and it's pretty\n+             * pathological for a lot to die suddenly.\n+             */\n+            continue;\n+        }\n+        else if (remaining_children_to_start) {\n+            /* we hit a 1 second timeout in which none of the previous\n+             * generation of children needed to be reaped... so assume\n+             * they're all done, and pick up the slack if any is left.\n+             */\n+            startup_children(mz, remaining_children_to_start);\n+            remaining_children_to_start = 0;\n+            /* In any event we really shouldn't do the code below because\n+             * few of the servers we just started are in the IDLE state\n+             * yet, so we'd mistakenly create an extra server.\n+             */\n+            continue;\n+        }\n+\n+        perform_idle_server_maintenance(mz, pconf);\n+    }\n+\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STOPPING;\n+\n+    if (mz-&gt;mpm-&gt;shutdown_pending &amp;&amp; mz-&gt;mpm-&gt;is_ungraceful) {\n+        /* Time to shut down:\n+         * Kill child processes, tell them to call child_exit, etc...\n+         */\n+        if (ap_unixd_killpg(getpgrp(), SIGTERM) &lt; 0) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(02879) \"killpg SIGTERM\");\n+        }\n+        ap_reclaim_child_processes(1, /* Start with SIGTERM */\n+                                   motorz_note_child_killed);\n+\n+        /* cleanup pid file on normal shutdown */\n+        ap_remove_pid(pconf, ap_pid_fname);\n+        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02880)\n+                    \"caught SIGTERM, shutting down\");\n+\n+        return DONE;\n+    }\n+\n+    if (mz-&gt;mpm-&gt;shutdown_pending) {\n+        /* Time to perform a graceful shut down:\n+         * Reap the inactive children, and ask the active ones\n+         * to close their listeners, then wait until they are\n+         * all done to exit.\n+         */\n+        int active_children;\n+        apr_time_t cutoff = 0;\n+\n+        /* Stop listening */\n+        ap_close_listeners();\n+\n+        /* kill off the idle ones */\n+        for (i = 0; i &lt; mz-&gt;mpm-&gt;num_buckets; i++) {\n+            ap_mpm_pod_killpg(all_buckets[i].pod, mz-&gt;max_daemons_limit);\n+        }\n+\n+        /* Send SIGUSR1 to the active children */\n+        active_children = 0;\n+        for (index = 0; index &lt; ap_num_kids; ++index) {\n+            if (ap_scoreboard_image-&gt;servers[index][0].status != SERVER_DEAD) {\n+                /* Ask each child to close its listeners. */\n+                ap_mpm_safe_kill(MPM_CHILD_PID(index), AP_SIG_GRACEFUL);\n+                active_children++;\n+            }\n+        }\n+\n+        /* Allow each child which actually finished to exit */\n+        ap_relieve_child_processes(motorz_note_child_killed);\n+\n+        /* cleanup pid file */\n+        ap_remove_pid(pconf, ap_pid_fname);\n+        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02881)\n+           \"caught \" AP_SIG_GRACEFUL_STOP_STRING \", shutting down gracefully\");\n+\n+        if (ap_graceful_shutdown_timeout) {\n+            cutoff = apr_time_now() +\n+                     apr_time_from_sec(ap_graceful_shutdown_timeout);\n+        }\n+\n+        /* Don't really exit until each child has finished */\n+        mz-&gt;mpm-&gt;shutdown_pending = 0;\n+        do {\n+            /* Pause for a second */\n+            sleep(1);\n+\n+            /* Relieve any children which have now exited */\n+            ap_relieve_child_processes(motorz_note_child_killed);\n+\n+            active_children = 0;\n+            for (index = 0; index &lt; ap_num_kids; ++index) {\n+                if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) {\n+                    active_children = 1;\n+                    /* Having just one child is enough to stay around */\n+                    break;\n+                }\n+            }\n+        } while (!mz-&gt;mpm-&gt;shutdown_pending &amp;&amp; active_children &amp;&amp;\n+                 (!ap_graceful_shutdown_timeout || apr_time_now() &lt; cutoff));\n+\n+        /* We might be here because we received SIGTERM, either\n+         * way, try and make sure that all of our processes are\n+         * really dead.\n+         */\n+        ap_unixd_killpg(getpgrp(), SIGTERM);\n+\n+        return DONE;\n+    }\n+\n+    /* we've been told to restart */\n+    if (one_process) {\n+        /* not worth thinking about */\n+        return DONE;\n+    }\n+\n+    /* advance to the next generation */\n+    /* XXX: we really need to make sure this new generation number isn't in\n+     * use by any of the children.\n+     */\n+    ++mz-&gt;mpm-&gt;my_generation;\n+    ap_scoreboard_image-&gt;global-&gt;running_generation = mz-&gt;mpm-&gt;my_generation;\n+\n+    if (!mz-&gt;mpm-&gt;is_ungraceful) {\n+        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02882)\n+                    \"Graceful restart requested, doing restart\");\n+\n+        /* kill off the idle ones */\n+        for (i = 0; i &lt; mz-&gt;mpm-&gt;num_buckets; i++) {\n+            ap_mpm_pod_killpg(all_buckets[i].pod, mz-&gt;max_daemons_limit);\n+        }\n+\n+        /* This is mostly for debugging... so that we know what is still\n+         * gracefully dealing with existing request.  This will break\n+         * in a very nasty way if we ever have the scoreboard totally\n+         * file-based (no shared memory)\n+         */\n+        for (index = 0; index &lt; ap_num_kids; ++index) {\n+            if (ap_scoreboard_image-&gt;servers[index][0].status != SERVER_DEAD) {\n+                ap_scoreboard_image-&gt;servers[index][0].status = SERVER_GRACEFUL;\n+                /* Ask each child to close its listeners.\n+                 *\n+                 * NOTE: we use the scoreboard, because if we send SIGUSR1\n+                 * to every process in the group, this may include CGI's,\n+                 * piped loggers, etc. They almost certainly won't handle\n+                 * it gracefully.\n+                 */\n+                ap_mpm_safe_kill(ap_scoreboard_image-&gt;parent[index].pid, AP_SIG_GRACEFUL);\n+            }\n+        }\n+    }\n+    else {\n+        /* Kill 'em off */\n+        if (ap_unixd_killpg(getpgrp(), SIGHUP) &lt; 0) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(02883) \"killpg SIGHUP\");\n+        }\n+        ap_reclaim_child_processes(0, /* Not when just starting up */\n+                                   motorz_note_child_killed);\n+        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02884)\n+                    \"SIGHUP received.  Attempting to restart\");\n+    }\n+\n+    return OK;\n+}\n+\n+/* This really should be a post_config hook, but the error log is already\n+ * redirected by that point, so we need to do this in the open_logs phase.\n+ */\n+static int motorz_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)\n+{\n+    int startup = 0;\n+    int level_flags = 0;\n+    ap_listen_rec **listen_buckets;\n+    apr_status_t rv;\n+    char id[16];\n+    int i;\n+\n+    motorz_core_t *mz = motorz_core_get();\n+    pconf = p;\n+\n+    /* the reverse of pre_config, we want this only the first time around */\n+    if (mz-&gt;mpm-&gt;module_loads == 1) {\n+        startup = 1;\n+        level_flags |= APLOG_STARTUP;\n+    }\n+\n+    if ((num_listensocks = ap_setup_listeners(ap_server_conf)) &lt; 1) {\n+        ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0,\n+                     (startup ? NULL : s), APLOGNO(03275)\n+                     \"no listening sockets available, shutting down\");\n+        return !OK;\n+    }\n+\n+    if (one_process) {\n+        mz-&gt;mpm-&gt;num_buckets = 1;\n+    }\n+    else if (!mz-&gt;mpm-&gt;was_graceful) {\n+        /* Preserve the number of buckets on graceful restarts. */\n+        mz-&gt;mpm-&gt;num_buckets = 0;\n+    }\n+    if ((rv = ap_duplicate_listeners(pconf, ap_server_conf,\n+                                     &amp;listen_buckets, &amp;mz-&gt;mpm-&gt;num_buckets))) {\n+        ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv,\n+                     (startup ? NULL : s), APLOGNO(03276)\n+                     \"could not duplicate listeners\");\n+        return !OK;\n+    }\n+    all_buckets = apr_pcalloc(pconf, mz-&gt;mpm-&gt;num_buckets *\n+                                     sizeof(motorz_child_bucket));\n+    for (i = 0; i &lt; mz-&gt;mpm-&gt;num_buckets; i++) {\n+        if ((rv = ap_mpm_pod_open(pconf, &amp;all_buckets[i].pod))) {\n+            ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv,\n+                         (startup ? NULL : s), APLOGNO(03277)\n+                         \"could not open pipe-of-death\");\n+            return !OK;\n+        }\n+        /* Initialize cross-process accept lock (safe accept needed only) */\n+        if ((rv = SAFE_ACCEPT((apr_snprintf(id, sizeof id, \"%i\", i),\n+                               ap_proc_mutex_create(&amp;all_buckets[i].mutex,\n+                                                    NULL, AP_ACCEPT_MUTEX_TYPE,\n+                                                    id, s, pconf, 0))))) {\n+            ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv,\n+                         (startup ? NULL : s), APLOGNO(03278)\n+                         \"could not create accept mutex\");\n+            return !OK;\n+        }\n+        all_buckets[i].listeners = listen_buckets[i];\n+    }\n+\n+    return OK;\n+}\n+\n+static int motorz_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)\n+{\n+    int no_detach, debug, foreground;\n+    apr_status_t rv;\n+    const char *userdata_key = \"mpm_motorz_module\";\n+    motorz_core_t *mz;\n+\n+    debug = ap_exists_config_define(\"DEBUG\");\n+\n+    if (debug) {\n+        foreground = one_process = 1;\n+        no_detach = 0;\n+    }\n+    else\n+    {\n+        no_detach = ap_exists_config_define(\"NO_DETACH\");\n+        one_process = ap_exists_config_define(\"ONE_PROCESS\");\n+        foreground = ap_exists_config_define(\"FOREGROUND\");\n+    }\n+\n+    ap_mutex_register(p, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0);\n+\n+    mz = g_motorz_core = ap_retained_data_get(userdata_key);\n+    if (!g_motorz_core) {\n+        mz = g_motorz_core = ap_retained_data_create(userdata_key, sizeof(*g_motorz_core));\n+        mz-&gt;mpm = ap_unixd_mpm_get_retained_data();\n+        mz-&gt;mpm-&gt;baton = mz;\n+        mz-&gt;max_daemons_limit = -1;\n+        /* Pollsets, timer rings and their mutexes are now per-poller and are\n+         * created per child in motorz_poller_create(); nothing to seed here.\n+         */\n+    }\n+    else if (mz-&gt;mpm-&gt;baton != mz) {\n+        /* If the MPM changes on restart, be ungraceful */\n+        mz-&gt;mpm-&gt;baton = mz;\n+        mz-&gt;mpm-&gt;was_graceful = 0;\n+    }\n+    mz-&gt;mpm-&gt;mpm_state = AP_MPMQ_STARTING;\n+    ++mz-&gt;mpm-&gt;module_loads;\n+\n+    /* sigh, want this only the second time around */\n+    if (mz-&gt;mpm-&gt;module_loads == 2) {\n+        if (!one_process &amp;&amp; !foreground) {\n+            /* before we detach, setup crash handlers to log to errorlog */\n+            ap_fatal_signal_setup(ap_server_conf, p /* pconf */);\n+            rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND\n+                                           : APR_PROC_DETACH_DAEMONIZE);\n+            if (rv != APR_SUCCESS) {\n+                ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(02885)\n+                             \"apr_proc_detach failed\");\n+                return HTTP_INTERNAL_SERVER_ERROR;\n+            }\n+        }\n+        apr_pool_create(&amp;mz-&gt;pool, ap_pglobal);\n+        apr_pool_tag(mz-&gt;pool, \"motorz-mpm-core\");\n+        /* Per-poller ring mutexes are created in motorz_poller_create(). */\n+    }\n+\n+    parent_pid = ap_my_pid = getpid();\n+\n+    ap_listen_pre_config();\n+    ap_num_kids = DEFAULT_START_DAEMON;\n+    ap_extended_status = 0;\n+\n+    return OK;\n+}\n+\n+static int motorz_check_config(apr_pool_t *p, apr_pool_t *plog,\n+                                apr_pool_t *ptemp, server_rec *s)\n+{\n+    int startup = 0;\n+    motorz_core_t *mz = motorz_core_get();\n+\n+    /* the reverse of pre_config, we want this only the first time around */\n+    if (mz-&gt;mpm-&gt;module_loads == 1) {\n+        startup = 1;\n+    }\n+\n+    if (ap_num_kids &gt; DEFAULT_SERVER_LIMIT) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(02886)\n+                         \"WARNING: StartServers of %d exceeds compile-time \"\n+                         \"limit of %d servers, decreasing to %d.\",\n+                         ap_num_kids, DEFAULT_SERVER_LIMIT, DEFAULT_SERVER_LIMIT);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02887)\n+                         \"StartServers of %d exceeds compile-time limit \"\n+                         \"of %d, decreasing to match\",\n+                         ap_num_kids, DEFAULT_SERVER_LIMIT);\n+        }\n+        ap_num_kids = DEFAULT_SERVER_LIMIT;\n+    }\n+    else if (ap_num_kids &lt; 1) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(02888)\n+                         \"WARNING: StartServers of %d not allowed, \"\n+                         \"increasing to 1.\", ap_num_kids);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02889)\n+                         \"StartServers of %d not allowed, increasing to 1\",\n+                         ap_num_kids);\n+        }\n+        ap_num_kids = 1;\n+    }\n+\n+    if (thread_limit &gt; MAX_THREAD_LIMIT) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(10015)\n+                         \"WARNING: ThreadLimit of %d exceeds compile-time \"\n+                         \"limit of %d threads, decreasing to %d.\",\n+                         thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10016)\n+                         \"ThreadLimit of %d exceeds compile-time limit \"\n+                         \"of %d, decreasing to match\",\n+                         thread_limit, MAX_THREAD_LIMIT);\n+        }\n+        thread_limit = MAX_THREAD_LIMIT;\n+    }\n+    else if (thread_limit &lt; 1) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(10017)\n+                         \"WARNING: ThreadLimit of %d not allowed, \"\n+                         \"increasing to 1.\", thread_limit);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10018)\n+                         \"ThreadLimit of %d not allowed, increasing to 1\",\n+                         thread_limit);\n+        }\n+        thread_limit = 1;\n+    }\n+\n+    if (threads_per_child &gt; thread_limit) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(03336)\n+                         \"WARNING: ThreadsPerChild of %d exceeds run-time \"\n+                         \"limit of\", threads_per_child);\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(03337)\n+                         \" %d servers, decreasing to %d.\",\n+                         thread_limit, thread_limit);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(03338)\n+                         \"ThreadsPerChild of %d exceeds run-time limit \"\n+                         \"of %d, decreasing to match\",\n+                         threads_per_child, thread_limit);\n+        }\n+        threads_per_child = thread_limit;\n+    }\n+    else if (threads_per_child &lt; 1) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(03339)\n+                         \"WARNING: ThreadsPerChild of %d not allowed, \"\n+                         \"increasing to 1.\", threads_per_child);\n+        } else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(03340)\n+                         \"ThreadsPerChild of %d not allowed, increasing to 1\",\n+                         threads_per_child);\n+        }\n+        threads_per_child = 1;\n+    }\n+\n+    /* Warn about ThreadsPerChild 1: the admission-control low-water mark\n+     * becomes (1*3)/4 = 0, so listeners only re-enable when the task queue\n+     * is completely empty, causing severe throughput degradation under any\n+     * sustained load. ThreadsPerChild &gt;= 4 is strongly recommended.\n+     */\n+    if (threads_per_child == 1) {\n+        if (startup) {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,\n+                         APLOGNO(10555)\n+                         \"WARNING: ThreadsPerChild 1 causes severe throughput \"\n+                         \"degradation in motorz due to admission-control \"\n+                         \"hysteresis. Use ThreadsPerChild &gt;= 4.\");\n+        }\n+        else {\n+            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10556)\n+                         \"ThreadsPerChild 1 causes severe throughput \"\n+                         \"degradation in motorz. Use ThreadsPerChild &gt;= 4.\");\n+        }\n+    }\n+\n+    return OK;\n+}\n+\n+static void motorz_hooks(apr_pool_t *p)\n+{\n+    /* Our open_logs hook function must run before the core's, or stderr\n+     * will be redirected to a file, and the messages won't print to the\n+     * console.\n+     */\n+    static const char *const aszSucc[] = {\"core.c\", NULL};\n+\n+    ap_hook_open_logs(motorz_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST);\n+    /* we need to set the MPM state before other pre-config hooks use MPM query\n+     * to retrieve it, so register as REALLY_FIRST\n+     */\n+    ap_hook_pre_config(motorz_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);\n+    ap_hook_check_config(motorz_check_config, NULL, NULL, APR_HOOK_MIDDLE);\n+    ap_hook_mpm(motorz_run, NULL, NULL, APR_HOOK_MIDDLE);\n+    ap_hook_mpm_query(motorz_query, NULL, NULL, APR_HOOK_MIDDLE);\n+    ap_hook_mpm_get_name(motorz_get_name, NULL, NULL, APR_HOOK_MIDDLE);\n+}\n+\n+static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)\n+{\n+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);\n+    if (err != NULL) {\n+        return err;\n+    }\n+    ap_num_kids = atoi(arg);\n+    return NULL;\n+}\n+\n+static const char *set_threads_per_child(cmd_parms * cmd, void *dummy,\n+                                         const char *arg)\n+{\n+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);\n+    if (err != NULL) {\n+        return err;\n+    }\n+    threads_per_child = atoi(arg);\n+    return NULL;\n+}\n+\n+static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg)\n+{\n+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);\n+    if (err != NULL) {\n+        return err;\n+    }\n+\n+    thread_limit = atoi(arg);\n+    return NULL;\n+}\n+\n+static const char *set_pollers_per_child(cmd_parms *cmd, void *dummy,\n+                                         const char *arg)\n+{\n+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);\n+    if (err != NULL) {\n+        return err;\n+    }\n+    num_pollers = atoi(arg);\n+    return NULL;\n+}\n+\n+static const command_rec motorz_cmds[] = {\n+LISTEN_COMMANDS,\n+AP_INIT_TAKE1(\"StartServers\", set_daemons_to_start, NULL, RSRC_CONF,\n+              \"Number of child processes launched at server startup\"),\n+AP_INIT_TAKE1(\"ThreadsPerChild\", set_threads_per_child, NULL, RSRC_CONF,\n+              \"Number of threads each child creates\"),\n+AP_INIT_TAKE1(\"ThreadLimit\", set_thread_limit, NULL, RSRC_CONF,\n+  \"Maximum number of worker threads per child process for this run of Apache - Upper limit for ThreadsPerChild\"),\n+AP_INIT_TAKE1(\"PollersPerChild\", set_pollers_per_child, NULL, RSRC_CONF,\n+  \"Number of poll threads per child process (0 = auto from online CPUs)\"),\n+AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND,\n+{ NULL }\n+};\n+\n+AP_DECLARE_MODULE(mpm_motorz) = {\n+    MPM20_MODULE_STUFF,\n+    NULL,                       /* hook to run before apache parses args */\n+    NULL,                       /* create per-directory config structure */\n+    NULL,                       /* merge per-directory config structures */\n+    NULL,                       /* create per-server config structure */\n+    NULL,                       /* merge per-server config structures */\n+    motorz_cmds,               /* command apr_table_t */\n+    motorz_hooks,              /* register hooks */\n+};\ndiff --git a/server/mpm/motorz/motorz.h b/server/mpm/motorz/motorz.h\nnew file mode 100644\nindex 0000000000..66d48ef4f6\n--- /dev/null\n+++ b/server/mpm/motorz/motorz.h\n@@ -0,0 +1,247 @@\n+/* Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  You may obtain a copy of the License at\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include \"apr.h\"\n+#include \"apr_atomic.h\"\n+#include \"apr_portable.h\"\n+#include \"apr_strings.h\"\n+#include \"apr_thread_proc.h\"\n+#include \"apr_signal.h\"\n+\n+#define APR_WANT_STDIO\n+#define APR_WANT_STRFUNC\n+#include \"apr_want.h\"\n+\n+#if APR_HAVE_UNISTD_H\n+#include \n+#endif\n+#if APR_HAVE_SYS_TYPES_H\n+#include \n+#endif\n+\n+#include \"ap_config.h\"\n+#include \"httpd.h\"\n+#include \"mpm_default.h\"\n+#include \"http_main.h\"\n+#include \"http_log.h\"\n+#include \"http_config.h\"\n+#include \"http_core.h\"          /* for get_remote_host */\n+#include \"http_connection.h\"\n+#include \"scoreboard.h\"\n+#include \"ap_mpm.h\"\n+#include \"util_mutex.h\"\n+#include \"unixd.h\"\n+#include \"http_vhost.h\"\n+#include \"mpm_common.h\"\n+#include \"ap_listen.h\"\n+#include \"ap_mmn.h\"\n+#include \"apr_poll.h\"\n+#include \"apr_skiplist.h\"\n+#include \"apr_thread_pool.h\"\n+#include \"util_time.h\"\n+\n+#include \n+\n+#ifdef HAVE_TIME_H\n+#include \n+#endif\n+#ifdef HAVE_SYS_PROCESSOR_H\n+#include  /* for bindprocessor() */\n+#endif\n+\n+#include \n+#include \n+\n+/* Limit on the total --- clients will be locked out if more servers than\n+ * this are needed.  It is intended solely to keep the server from crashing\n+ * when things get out of hand.\n+ *\n+ * We keep a hard maximum number of servers, for two reasons --- first off,\n+ * in case something goes seriously wrong, we want to stop the fork bomb\n+ * short of actually crashing the machine we're running on by filling some\n+ * kernel table.  Secondly, it keeps the size of the scoreboard file small\n+ * enough that we can read the whole thing without worrying too much about\n+ * the overhead.\n+ */\n+#ifndef DEFAULT_SERVER_LIMIT\n+#define DEFAULT_SERVER_LIMIT 256\n+#endif\n+\n+/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT.  We want\n+ * some sort of compile-time limit to help catch typos.\n+ */\n+#ifndef MAX_SERVER_LIMIT\n+#define MAX_SERVER_LIMIT 200000\n+#endif\n+\n+/* Limit on the threads per process.  Clients will be locked out if more than\n+ * this are needed.\n+ *\n+ * We keep this for one reason it keeps the size of the scoreboard file small\n+ * enough that we can read the whole thing without worrying too much about\n+ * the overhead.\n+ */\n+#ifndef DEFAULT_THREAD_LIMIT\n+#define DEFAULT_THREAD_LIMIT 64\n+#endif\n+\n+/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT.  We want\n+ * some sort of compile-time limit to help catch typos.\n+ */\n+#ifndef MAX_THREAD_LIMIT\n+#define MAX_THREAD_LIMIT 100000\n+#endif\n+\n+#define MPM_CHILD_PID(i) (ap_scoreboard_image-&gt;parent[i].pid)\n+\n+/**\n+ * typedefs\n+ */\n+/* data retained by prefork across load/unload of the module\n+ * allocated on first call to pre-config hook; located on\n+ * subsequent calls to pre-config hook\n+ */\n+typedef struct motorz_poller_t motorz_poller_t;\n+\n+typedef struct motorz_core_t motorz_core_t;\n+struct motorz_core_t {\n+    ap_unixd_mpm_retained_data *mpm;\n+\n+    int first_server_limit;\n+    int maxclients_reported;\n+    /*\n+     * The max child slot ever assigned, preserved across restarts.  Necessary\n+     * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts.  We\n+     * use this value to optimize routines that have to scan the entire scoreboard.\n+     */\n+    int max_daemons_limit;\n+    apr_pool_t *pool;\n+    /* Worker thread pool, shared by all pollers in this child. */\n+    apr_thread_pool_t *workers;\n+    /* Per-child array of pollers. Each owns its own pollset + timer ring +\n+     * recycle list, so the single-poll-thread throughput ceiling scales with\n+     * num_pollers. A connection is bound to one poller for its lifetime\n+     * (scon-&gt;poller) and always re-arms there. See MOTORZ.README.\n+     */\n+    motorz_poller_t **pollers;\n+    int num_pollers;\n+};\n+\n+typedef struct motorz_recycled_pool motorz_recycled_pool;\n+struct motorz_recycled_pool {\n+    apr_pool_t *pool;\n+    motorz_recycled_pool *next;\n+};\n+\n+/* One poll thread's context. Each poller owns its pollset, timer ring and the\n+ * mutex guarding that ring, plus its own lock-free transaction-pool recycle\n+ * list and listener-admission state -- so pollers do not contend with each\n+ * other. A connection is permanently bound to the poller that accepted it\n+ * (scon-&gt;poller), which is what makes connection sharding across pollers\n+ * correct: it always re-arms in, and recycles to, its own poller.\n+ */\n+struct motorz_poller_t {\n+    motorz_core_t *mz;            /* back-pointer to the shared child core */\n+    int index;                   /* 0 .. num_pollers-1 */\n+    apr_pool_t *pool;            /* subpool of mz-&gt;pool for this poller */\n+    apr_thread_t *thread;        /* the poll thread running motorz_poller_main */\n+    apr_pollset_t *pollset;\n+    apr_skiplist *timeout_ring;\n+    apr_thread_mutex_t *mtx;     /* guards this poller's timeout_ring */\n+    /* Lock-free (CAS) recycle free-list: multi-producer push (any worker), but\n+     * single-consumer pop (THIS poller's thread only) -- the same MPSC\n+     * contract as mpm_fdqueue.c's ap_queue_info_{push,pop}_pool.\n+     */\n+    struct motorz_recycled_pool *volatile recycled_pools;\n+    apr_uint32_t num_recycled;\n+    /* Listener admission control (poller that owns the listeners only). */\n+    apr_pollfd_t **listener_pfds;\n+    int num_listener_pfds;\n+    int listeners_disabled;\n+};\n+\n+typedef struct motorz_child_bucket motorz_child_bucket;\n+struct motorz_child_bucket {\n+    ap_pod_t *pod;\n+    ap_listen_rec *listeners;\n+    apr_proc_mutex_t *mutex;\n+};\n+\n+typedef enum\n+{\n+    PT_CSD,\n+    PT_ACCEPT,\n+    PT_USER\n+} motorz_poll_type_e;\n+\n+typedef struct motorz_sb_t motorz_sb_t;\n+struct motorz_sb_t\n+{\n+    motorz_poll_type_e type;\n+    void *baton;\n+};\n+\n+typedef void (*motorz_timer_cb) (motorz_core_t *mz, void *baton);\n+typedef void (*motorz_io_sock_cb) (motorz_core_t *mz, apr_socket_t *sock,\n+                                   int flags, void *baton);\n+typedef void (*motorz_io_file_cb) (motorz_core_t *mz, apr_socket_t *sock,\n+                                   int flags, void *baton);\n+\n+\n+typedef struct motorz_timer_t motorz_timer_t;\n+struct motorz_timer_t\n+{\n+    apr_time_t expires;\n+    motorz_timer_cb cb;\n+    void *baton;\n+    apr_pool_t *pool;\n+    motorz_core_t *mz;\n+    motorz_poller_t *poller;     /* the poller whose ring this timer is in */\n+};\n+\n+typedef struct motorz_conn_t motorz_conn_t;\n+struct motorz_conn_t\n+{\n+    apr_pool_t *pool;\n+    motorz_core_t *mz;\n+    /* The poller this connection does its I/O on for its whole lifetime: it\n+     * re-arms in, and times out on, this poller's pollset/ring. Sharded across\n+     * pollers at accept time to spread load. */\n+    motorz_poller_t *poller;\n+    /* The poller that owns this connection's transaction-pool recycling -- the\n+     * poller that accepted it (and popped its ptrans). Recycling must return to\n+     * the same single-consumer free-list it came from, which (unlike I/O) is\n+     * NOT sharded: only the accepting poller pops, so only it may be the home.\n+     */\n+    motorz_poller_t *pool_poller;\n+    apr_socket_t *sock;\n+    apr_bucket_alloc_t *ba;\n+    ap_sb_handle_t *sbh;\n+    /** connection record this struct refers to */\n+    conn_rec *c;\n+    /** request record (if any) this struct refers to */\n+    request_rec *r;\n+    /** is the current conn_rec suspended? */\n+    int suspended;\n+    /** has ap_start_lingering_close() been called for this conn? */\n+    int linger_started;\n+    /** poll file descriptor information */\n+    apr_pollfd_t pfd;\n+    /** public parts of the connection state */\n+    conn_state_t cs;\n+    /** timer associated with the connection */\n+    motorz_timer_t timer;\n+};\ndiff --git a/server/mpm/motorz/mpm_default.h b/server/mpm/motorz/mpm_default.h\nnew file mode 100644\nindex 0000000000..9bc59f0ae6\n--- /dev/null\n+++ b/server/mpm/motorz/mpm_default.h\n@@ -0,0 +1,36 @@\n+/* Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  You may obtain a copy of the License at\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+/**\n+ * @file  motorz/mpm_default.h\n+ * @brief MotorZ MPM defaults\n+ *\n+ * @defgroup APACHE_MPM_MOTORZ MotorZ MPM\n+ * @ingroup APACHE_INTERNAL\n+ * @{\n+ */\n+\n+#ifndef APACHE_MPM_DEFAULT_H\n+#define APACHE_MPM_DEFAULT_H\n+\n+/* Number of servers to spawn off by default --- \n+ */\n+#ifndef DEFAULT_START_DAEMON\n+#define DEFAULT_START_DAEMON 2\n+#endif\n+\n+#endif /* AP_MPM_DEFAULT_H */\n+/** @} */\ndiff --git a/server/mpm/motorz/test/README.md b/server/mpm/motorz/test/README.md\nnew file mode 100644\nindex 0000000000..a777cb535f\n--- /dev/null\n+++ b/server/mpm/motorz/test/README.md\n@@ -0,0 +1,187 @@\n+# motorz MPM test harness\n+\n+Self-contained smoke / regression tests for the **motorz** MPM\n+(`server/mpm/motorz/motorz.c`). These drive a real `httpd` built from this tree\n+against a throwaway `ServerRoot` in a temp dir; nothing is installed and no\n+existing config is touched.\n+\n+## Build &amp; configure (do this first)\n+\n+The tests run a real `httpd` built from this tree. The easiest way to get a\n+correctly-configured build is the bootstrap script \u2014 it runs `buildconf` (only\n+if `./configure` is missing), `./configure` with the right flags, `make`, and\n+then verifies every module the tests need is present:\n+\n+```sh\n+server/mpm/motorz/test/setup.sh                # configure (if needed) + build + verify\n+server/mpm/motorz/test/setup.sh --reconfigure  # force a fresh ./configure\n+server/mpm/motorz/test/setup.sh --jobs 8        # control make parallelism\n+```\n+\n+It does **not** install httpd; the tests use the freshly built `./httpd` in\n+place. On success it prints `setup OK` and the run-all command.\n+\n+### Doing it by hand instead\n+\n+```sh\n+# from the build top. (Run ./buildconf first only on a fresh git checkout that\n+# has no ./configure; needs autoconf + libtool + python3.)\n+./configure --with-included-apr \\\n+    --enable-mpms-shared='event motorz' \\\n+    --enable-so --enable-unixd \\\n+    --enable-authz_core --enable-authz_host --enable-log_config \\\n+    --enable-mime --enable-dir \\\n+    --enable-socache_shmcb --enable-ssl --enable-http2\n+make\n+```\n+\n+`--with-included-apr` uses the bundled APR/APR-Util (no system APR needed).\n+`event` is built alongside `motorz` because `bench.sh` compares against it.\n+(`mod_ssl`/`mod_http2` are also part of the default `most` module set, so a\n+plain `./configure --enable-mpms-shared='event motorz' --with-included-apr`\n+usually yields them too; the explicit flags above just make it deterministic.)\n+\n+### What the tests need\n+\n+The harness finds the `httpd` binary at the top of the build tree and locates\n+the shared module `.so` files under `modules/` and `server/mpm/`. Required for\n+the HTTP/1.1 suite + smoke: `mod_mpm_motorz`, `mod_unixd`, `mod_authz_core`,\n+`mod_authz_host`, `mod_log_config`, `mod_mime`, `mod_dir`. The HTTP/2 suite\n+additionally needs `mod_ssl`, `mod_http2`, `mod_socache_shmcb` (building these\n+requires OpenSSL headers and `libnghttp2`), plus an `openssl` CLI and a `curl`\n+built with HTTP/2 \u2014 it **self-skips** (exit 0) with a clear message if any are\n+missing. If [`h2load`](https://nghttp2.org/) (from nghttp2) is on `PATH`, the\n+HTTP/2 suite adds real multiplexed load tests; otherwise those are skipped (not\n+failed). `bench.sh` needs `mod_mpm_event` and `ab`/`h2load`.\n+\n+## Running\n+\n+```sh\n+server/mpm/motorz/test/run-all.sh      # smoke + both suites\n+server/mpm/motorz/test/smoke.sh        # fast change-mapped smoke test only\n+server/mpm/motorz/test/run-http1.sh    # HTTP/1.1 only\n+server/mpm/motorz/test/run-http2.sh    # HTTP/2-over-TLS only\n+server/mpm/motorz/test/bench.sh        # motorz vs event throughput comparison\n+```\n+\n+### bench.sh \u2014 motorz vs event\n+\n+Runs identical `ab` (HTTP/1.1) and `h2load` (HTTP/2) workloads against the same\n+build configured first with the event MPM, then motorz, with matched tunables,\n+and prints a req/s comparison table.\n+\n+**Critical:** worker threads must exceed peak connection concurrency \u2014 under\n+HTTP/2 each connection holds a worker for its lifetime, so too few workers\n+starves the pool and h2load reports spurious failures on *both* MPMs. `bench.sh`\n+defaults `THREADS=128` (\u2265 the c=50 default) and **flags any run with\n+failed/errored requests** (its req/s is then not a valid figure). Env knobs:\n+`REQS CONC H2_REQS H2_CONC H2_STREAMS H2_BIG_REQS THREADS PORT`.\n+\n+Observed (12-core arm64, 1 child, adequate workers): motorz tracks event within\n+~1\u20132% on all four workloads. Note: motorz shows an *intermittent* small h2\n+failure rate under rapid connection churn at high concurrency that event does\n+not \u2014 see the project memory note; not a throughput issue.\n+\n+Environment knobs:\n+\n+- `PORT` / `TLS_PORT` \u2014 listen ports (default 8099 / 8443).\n+- `KEEP=1` \u2014 keep the temp `ServerRoot` after the run for inspection\n+  (path is printed); otherwise it is removed on exit.\n+\n+Exit status is non-zero if any assertion fails.\n+\n+## What is covered\n+\n+**`smoke.sh`** \u2014 fast (~10 s) robust smoke test whose checks map one-to-one to\n+the changes made on this branch, so a failure points straight at what broke:\n+\n+1. **forward-decl of `motorz_update_listeners()`** \u2014 motorz loads, parses\n+   config, and serves (the bug was a C89 implicit-declaration / link error);\n+2. **clogging-input-filters branch honors hook state** \u2014 h2 keep-alive reuses\n+   one connection instead of collapsing to one-shot (h2 c2 connections set\n+   `clogging_input_filters` unconditionally);\n+3. **async HTTP/2 handoff is enabled** (`MOTORZ_ENABLE_ASYNC 1`) \u2014 asserts\n+   motorz *does* return the c1 connection to MPM monitoring (async hand-back)\n+   and that HTTP/2 connection churn (`h2load -n 10000 -c 50 -m 1`) still drops\n+   **0** requests. This is the regression test for the dropped-request bug,\n+   which is now fixed in mod_http2 (c1 is closed only after every c2 is done and\n+   flushed); see MOTORZ.README \"HTTP/2 async handoff\" for the close-ordering\n+   issue and the fix.\n+\n+Runs h2-over-TLS when ssl/http2/openssl/h2-curl are present, else an\n+HTTP/1.1-only subset.\n+\n+**`run-http1.sh`** \u2014 exercises the connection state machine in\n+`motorz_io_process()`:\n+\n+- basic GET / 404 / body correctness\n+- **keep-alive reuse** (5 requests, asserts a single TCP connection) \u2014 the\n+  KEEPALIVE \u2192 WRITE_COMPLETION path\n+- 200KB body (write-completion cycling)\n+- concurrency / load: keep-alive correctness is checked with **curl** (3000\n+  reused requests, all 200) because `ab` miscounts a server-closed keep-alive\n+  connection's final read as a non-2xx failure \u2014 a known `ab` artifact, more\n+  frequent with `MOTORZ_ENABLE_ASYNC=0` since idle keep-alive closes via the\n+  blocking path; `ab` is still used for the completion count and the\n+  non-keep-alive lingering-close path (no `-k`, so no artifact)\n+- slow client: a partial request line completed after a pause (read-wait path)\n+- **lifecycle**: graceful restart under load, and 5\u00d7 restart churn under\n+  continuous load \u2014 the skiplist/worker-drain scenario this branch hardened\n+- error-log scan for crash/crit/emerg/assert/deadlock\n+\n+**`run-http2.sh`** \u2014 mod_http2 + mod_ssl on motorz:\n+\n+- h2 ALPN negotiation (`http_version == 2`)\n+- request multiplexing over a single connection (10 streams, 1 connect)\n+- large multi-frame body\n+- **`h2load` load tests** (when available): 5000 req / 20 clients / 25 streams,\n+  1000 req of the 200KB body / 50 streams (flow control), and a rate-limited\n+  run with idle gaps \u2014 each asserting **zero** failed/errored and all-2xx\n+- inspects the `motorz_io_process()` state traces, asserting PROCESSING is\n+  driven (with async on, an idle c1 is handed back as `CONN_STATE_ASYNC_WAITIO`\n+  rather than via WRITE_COMPLETION; HTTP/1.1 still covers WRITE_COMPLETION)\n+- **asserts async is enabled**: the `CONN_STATE_ASYNC_WAITIO` arm (`AH10557`)\n+  appears and h2 logs \"returning to mpm c1 monitoring\" (see below)\n+- h2 still negotiated after a graceful restart\n+- error-log scan\n+\n+### Two-phase logging (why)\n+\n+The state traces require global `LogLevel trace8`, but the `h2load` phase\n+pushes thousands of requests \u2014 at trace8 the error_log would balloon to\n+**gigabytes** and make the log greps crawl. So the suite runs the load phase at\n+`LogLevel info` (quiet) and only switches to `trace8` (via `set_loglevel()`,\n+which rewrites the marked LogLevel line and gracefully restarts) for the light\n+trace phase. Trace scans read only past a marker line written at the switch,\n+keeping them fast.\n+\n+### Async is enabled; the suite asserts it is on\n+\n+motorz reports `AP_MPMQ_IS_ASYNC = 1` (`MOTORZ_ENABLE_ASYNC 1` in `motorz.c`).\n+The HTTP/2 churn bug it used to expose is fixed in mod_http2 (a graceful client\n+GOAWAY no longer tears the session down while a stream's c2 is still finishing;\n+see MOTORZ.README \"HTTP/2 async handoff\"). Consequences the suite asserts:\n+mod_http2 requests `CONN_STATE_ASYNC_WAITIO` of an async MPM, so the WAITIO arm\n+trace `AH10557` **does** appear, and h2 **does** log \"returning to mpm c1\n+monitoring\" \u2014 while the churn run stays at 0 dropped requests. The suite drives\n+the WAITIO path (an idle raw-h2 `openssl s_client` connection \u2014 preface + empty\n+`SETTINGS`, no request) via `trigger_async_waitio()` in `lib.sh` and confirms it\n+arms \u2265 1 time.\n+\n+This positively exercises the branch added in this work. The suite also\n+confirms (via the `info`-level log) that motorz's `AP_MPMQ_IS_ASYNC = 1` drives\n+mod_http2's async idle-return (\"returning to mpm c1 monitoring\"), rather than\n+the prefork-style blocking poll.\n+\n+## Layout\n+\n+```\n+setup.sh         configure + build httpd with the modules the tests need\n+lib.sh           shared helpers (build-tree discovery, config gen, assert API,\n+                 set_loglevel, trigger_async_waitio, h2load_stat)\n+smoke.sh         fast change-mapped smoke test\n+run-http1.sh     HTTP/1.1 functional + lifecycle suite\n+run-http2.sh     HTTP/2-over-TLS suite (+ h2load load tests)\n+run-all.sh       runs smoke + both suites, aggregates pass/fail\n+bench.sh         motorz vs event throughput comparison (not in run-all)\n+```\ndiff --git a/server/mpm/motorz/test/bench.sh b/server/mpm/motorz/test/bench.sh\nnew file mode 100755\nindex 0000000000..87910122cc\n--- /dev/null\n+++ b/server/mpm/motorz/test/bench.sh\n@@ -0,0 +1,214 @@\n+#!/bin/sh\n+#\n+# motorz vs event -- head-to-head MPM performance comparison.\n+#\n+# Runs identical workloads against the SAME httpd build configured first with\n+# the event MPM, then the motorz MPM, with matched tunables (one child,\n+# ThreadsPerChild 16, quiet logging) so the only variable is the MPM.\n+#\n+# Workloads:\n+#   - HTTP/1.1 keep-alive          (ab -k)        small body\n+#   - HTTP/1.1 no keep-alive       (ab)           small body  (accept/linger cost)\n+#   - HTTP/2 multiplexed           (h2load)       small body\n+#   - HTTP/2 large body            (h2load)       200 KB      (write/flow control)\n+#\n+# Reports req/s and mean latency for each, side by side. NOT a rigorous\n+# microbenchmark -- it's a practical apples-to-apples smoke of relative\n+# throughput on this machine. Run it a couple of times; numbers vary.\n+#\n+# Usage:  server/mpm/motorz/test/bench.sh   [REQS=50000 CONC=50 DUR=...]\n+# Env:    PORT (TLS+plain reuse one port per run), KEEP=1\n+\n+. \"$(dirname \"$0\")/lib.sh\"\n+\n+require_httpd\n+\n+REQS=\"${REQS:-50000}\"      # ab request count (keep-alive run)\n+REQS_NOKA=\"${REQS_NOKA:-20000}\"\n+CONC=\"${CONC:-50}\"\n+H2_REQS=\"${H2_REQS:-50000}\"\n+H2_CONC=\"${H2_CONC:-50}\"\n+H2_STREAMS=\"${H2_STREAMS:-25}\"\n+H2_BIG_REQS=\"${H2_BIG_REQS:-5000}\"\n+# Worker threads MUST exceed the peak concurrent CONNECTION count: under HTTP/2\n+# each connection holds a worker for its lifetime (the h2 c1 connection is\n+# dispatched to a worker), so fewer workers than connections starves the pool\n+# and h2load reports spurious failures/resets on BOTH MPMs -- making any req/s\n+# number meaningless. Size generously above max(CONC, H2_CONC).\n+THREADS=\"${THREADS:-128}\"\n+PORT=\"${PORT:-8551}\"\n+\n+have_ab=0;     command -v ab &gt;/dev/null 2&gt;&amp;1 &amp;&amp; have_ab=1\n+have_h2load=0; command -v h2load &gt;/dev/null 2&gt;&amp;1 &amp;&amp; have_h2load=1\n+have_ssl=1\n+for n in mod_ssl.so mod_http2.so mod_socache_shmcb.so; do\n+    find \"$TOP/modules\" -name \"$n\" -path '*/.libs/*' 2&gt;/dev/null | grep -q . || have_ssl=0\n+done\n+[ \"$have_h2load\" -eq 1 ] &amp;&amp; [ \"$have_ssl\" -eq 1 ] &amp;&amp; do_h2=1 || do_h2=0\n+\n+make_rundir\n+trap cleanup EXIT INT TERM\n+openssl req -x509 -newkey rsa:2048 -nodes -keyout \"$RUNDIR/key.pem\" \\\n+    -out \"$RUNDIR/cert.pem\" -days 2 -subj \"/CN=localhost\" &gt;/dev/null 2&gt;&amp;1\n+\n+# Result accumulators (one column per MPM), kept as text rows we print at the end.\n+RESULTS_FILE=\"$RUNDIR/results.txt\"\n+: &gt; \"$RESULTS_FILE\"\n+record() { printf '%s\\t%s\\t%s\\n' \"$1\" \"$2\" \"$3\" &gt;&gt; \"$RESULTS_FILE\"; }  # workload, mpm, \"val\"\n+\n+# --- write a config for the given MPM ($1 = event|motorz) -------------------\n+write_conf() {\n+    local mpm=\"$1\" mpm_line tune\n+    if [ \"$mpm\" = event ]; then\n+        mpm_line=\"$(load mpm_event_module mod_mpm_event.so)\"\n+        tune=\"StartServers 1\n+ServerLimit 1\n+ThreadLimit $THREADS\n+ThreadsPerChild $THREADS\n+MinSpareThreads $THREADS\n+MaxSpareThreads $THREADS\n+MaxRequestWorkers $THREADS\"\n+    else\n+        mpm_line=\"$(load mpm_motorz_module mod_mpm_motorz.so)\"\n+        tune=\"StartServers 1\n+ThreadLimit $THREADS\n+ThreadsPerChild $THREADS\n+PollersPerChild 2\"\n+    fi\n+    # Both MPMs run ONE child with $THREADS worker threads, sized above the peak\n+    # connection concurrency so neither starves under h2 (see THREADS note).\n+    {\n+        echo \"ServerRoot \\\"$RUNDIR\\\"\"\n+        echo \"ServerName localhost\"\n+        echo \"PidFile \\\"$RUNDIR/httpd.pid\\\"\"\n+        echo \"Listen $PORT\"\n+        echo \"$mpm_line\"\n+        load unixd_module mod_unixd.so\n+        load authz_core_module mod_authz_core.so\n+        load authz_host_module mod_authz_host.so\n+        load log_config_module mod_log_config.so\n+        load mime_module mod_mime.so\n+        load dir_module mod_dir.so\n+        if [ \"$do_h2\" -eq 1 ]; then\n+            load socache_shmcb_module mod_socache_shmcb.so\n+            load ssl_module mod_ssl.so\n+            load http2_module mod_http2.so\n+        fi\n+        echo \"$tune\"\n+        echo \"ErrorLog \\\"$RUNDIR/logs/error_log\\\"\"\n+        echo \"LogLevel error\"          # quiet: logging must not skew the numbers\n+        echo \"TypesConfig /dev/null\"\n+        echo \"AddType text/html .html\"\n+        echo \"AddType text/plain .txt\"\n+        echo \"EnableSendfile On\"\n+        echo \"DocumentRoot \\\"$RUNDIR/htdocs\\\"\"\n+        echo \"DirectoryIndex index.html\"\n+        echo \"\"\n+        echo \"  Require all granted\"\n+        echo \"\"\n+        if [ \"$do_h2\" -eq 1 ]; then\n+            echo \"Protocols h2 http/1.1\"\n+            echo \"SSLSessionCache \\\"shmcb:$RUNDIR/logs/sc(512000)\\\"\"\n+            echo \"\"\n+            echo \"  ServerName localhost\"\n+            echo \"  SSLEngine on\"\n+            echo \"  SSLCertificateFile \\\"$RUNDIR/cert.pem\\\"\"\n+            echo \"  SSLCertificateKeyFile \\\"$RUNDIR/key.pem\\\"\"\n+            echo \"  Protocols h2 http/1.1\"\n+            echo \"\"\n+        fi\n+    } &gt; \"$RUNDIR/httpd.conf\"\n+}\n+\n+# extract \"Requests per second\" and \"Time per request (mean)\" from ab output\n+ab_rps()  { printf '%s\\n' \"$1\" | awk '/Requests per second/{print $4; exit}'; }\n+ab_mean() { printf '%s\\n' \"$1\" | awk '/Time per request/ &amp;&amp; /\\(mean\\)/{print $4; exit}'; }\n+# h2load: \"finished in Xs, NNN.NN req/s, ...\" and the req mean from the table\n+h2_rps()  { printf '%s\\n' \"$1\" | awk -F',' '/req\\/s/{for(i=1;i&lt;=NF;i++) if($i ~ /req\\/s/){gsub(/[^0-9.]/,\"\",$i); print $i; exit}}'; }\n+# Warn loudly if an h2load run had ANY failed/errored requests -- the req/s of\n+# such a run is not a valid throughput figure (it raced a starved pool or a\n+# crash). $1=output $2=workload $3=mpm.\n+h2_check_clean() {\n+    local f e\n+    f=$(printf '%s\\n' \"$1\" | sed -nE 's/.* ([0-9]+) failed.*/\\1/p')\n+    e=$(printf '%s\\n' \"$1\" | sed -nE 's/.* ([0-9]+) errored.*/\\1/p')\n+    if [ \"${f:-0}\" -ne 0 ] || [ \"${e:-0}\" -ne 0 ]; then\n+        echo \"     !! WARNING: $2/$3 had $f failed / $e errored -- req/s is NOT valid\"\n+        record \"${2}-FAILED\" \"$mpm\" \"$f/$e\"\n+    fi\n+}\n+\n+run_mpm() {\n+    local mpm=\"$1\" base out rps mean\n+    echo\n+    echo \"######## $mpm ########\"\n+    write_conf \"$mpm\"\n+    \"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -t &gt;/dev/null 2&gt;&amp;1 \\\n+        || { echo \"  config invalid for $mpm; skipping\"; return; }\n+    start_httpd\n+    # confirm which MPM is actually serving\n+    sleep 0.5\n+\n+    if [ \"$have_ab\" -eq 1 ]; then\n+        base=\"http://127.0.0.1:$PORT\"\n+        echo \"-- HTTP/1.1 keep-alive  ($REQS req, c=$CONC)\"\n+        out=$(ab -n \"$REQS\" -c \"$CONC\" -k -q \"$base/\" 2&gt;&amp;1)\n+        rps=$(ab_rps \"$out\"); mean=$(ab_mean \"$out\")\n+        echo \"     req/s=$rps  mean(ms/req-across-conc)=$mean\"\n+        record \"h1-keepalive\" \"$mpm\" \"$rps\"\n+\n+        echo \"-- HTTP/1.1 no keep-alive  ($REQS_NOKA req, c=$CONC)\"\n+        out=$(ab -n \"$REQS_NOKA\" -c \"$CONC\" -q \"$base/\" 2&gt;&amp;1)\n+        rps=$(ab_rps \"$out\")\n+        echo \"     req/s=$rps\"\n+        record \"h1-no-keepalive\" \"$mpm\" \"$rps\"\n+    else\n+        echo \"-- ab not found; skipping HTTP/1.1 throughput\"\n+    fi\n+\n+    if [ \"$do_h2\" -eq 1 ]; then\n+        base=\"https://localhost:$PORT\"\n+        echo \"-- HTTP/2  ($H2_REQS req, c=$H2_CONC, m=$H2_STREAMS, small body)\"\n+        out=$(h2load -n \"$H2_REQS\" -c \"$H2_CONC\" -m \"$H2_STREAMS\" \"$base/\" 2&gt;&amp;1)\n+        rps=$(h2_rps \"$out\"); h2_check_clean \"$out\" \"h2-small\" \"$mpm\"\n+        echo \"     req/s=$rps  ($(h2load_stat \"$out\" '^requests:'))\"\n+        record \"h2-small\" \"$mpm\" \"$rps\"\n+\n+        echo \"-- HTTP/2 large body  ($H2_BIG_REQS req, c=$H2_CONC, m=$H2_STREAMS, 200KB)\"\n+        out=$(h2load -n \"$H2_BIG_REQS\" -c \"$H2_CONC\" -m \"$H2_STREAMS\" \"$base/big.txt\" 2&gt;&amp;1)\n+        rps=$(h2_rps \"$out\"); h2_check_clean \"$out\" \"h2-large\" \"$mpm\"\n+        echo \"     req/s=$rps\"\n+        record \"h2-large\" \"$mpm\" \"$rps\"\n+    else\n+        echo \"-- h2load/ssl unavailable; skipping HTTP/2 throughput\"\n+    fi\n+\n+    stop_httpd\n+}\n+\n+echo \"==== motorz vs event MPM benchmark ====\"\n+echo \"host: $(uname -sm), cpus: $(sysctl -n hw.ncpu 2&gt;/dev/null || nproc 2&gt;/dev/null)\"\n+echo \"settings: 1 child, ThreadsPerChild=$THREADS, LogLevel error\"\n+echo \"tools: ab=$have_ab h2load=$have_h2load ssl=$have_ssl\"\n+\n+run_mpm event\n+run_mpm motorz\n+\n+# ---- comparison table ------------------------------------------------------\n+echo\n+echo \"==== req/s comparison (higher is better) ====\"\n+awk -F'\\t' '\n+    { v[$1\"|\"$2]=$3; if(!seen[$1]++) order[++n]=$1 }\n+    END {\n+        printf \"%-18s %12s %12s %10s\\n\", \"workload\", \"event\", \"motorz\", \"motorz/event\"\n+        printf \"%-18s %12s %12s %10s\\n\", \"--------\", \"-----\", \"------\", \"-----------\"\n+        for(i=1;i&lt;=n;i++){\n+            w=order[i]; e=v[w\"|event\"]; m=v[w\"|motorz\"]\n+            ratio = (e+0&gt;0 &amp;&amp; m+0&gt;0) ? sprintf(\"%.2fx\", m/e) : \"n/a\"\n+            printf \"%-18s %12s %12s %10s\\n\", w, (e==\"\"?\"-\":e), (m==\"\"?\"-\":m), ratio\n+        }\n+    }\n+' \"$RESULTS_FILE\"\n+echo\n+echo \"(req/s; ratio = motorz relative to event. Re-run a few times -- single\"\n+echo \" runs are noisy. LogLevel was 'error' so logging did not skew results.)\"\ndiff --git a/server/mpm/motorz/test/lib.sh b/server/mpm/motorz/test/lib.sh\nnew file mode 100644\nindex 0000000000..3fd4e54215\n--- /dev/null\n+++ b/server/mpm/motorz/test/lib.sh\n@@ -0,0 +1,177 @@\n+# Shared helpers for the motorz MPM test harness.\n+#\n+# Sourced by run-http1.sh and run-http2.sh. Not executable on its own.\n+#\n+# Resolves the httpd build tree, locates the shared MPM/module .so files,\n+# generates a throwaway ServerRoot under a temp dir, and provides start/stop\n+# plus a tiny assertion API. Everything lives under a per-run temp dir that is\n+# removed on exit unless KEEP=1 is set in the environment.\n+\n+set -u\n+\n+# --- locate the build tree -------------------------------------------------\n+# This script lives in /server/mpm/motorz/test/. The top of the\n+# build tree is four levels up.\n+TEST_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &amp;&amp; pwd)\"\n+TOP=\"$(cd \"$TEST_DIR/../../../..\" &amp;&amp; pwd)\"\n+\n+HTTPD=\"$TOP/httpd\"\n+PORT=\"${PORT:-8099}\"        # http1 default; http2 suite overrides to 8443\n+TLS_PORT=\"${TLS_PORT:-8443}\"\n+\n+PASS=0\n+FAIL=0\n+RUNDIR=\"\"\n+\n+fail() { echo \"  FAIL: $*\"; FAIL=$((FAIL + 1)); }\n+pass() { echo \"  ok:   $*\"; PASS=$((PASS + 1)); }\n+\n+# assert_eq   \n+assert_eq() {\n+    if [ \"$1\" = \"$2\" ]; then pass \"$3 ($2)\"; else fail \"$3: expected '$1', got '$2'\"; fi\n+}\n+\n+# assert_gt    -- passes when actual &gt; floor (integers)\n+assert_gt() {\n+    if [ \"$1\" -gt \"$2\" ] 2&gt;/dev/null; then pass \"$3 ($1 &gt; $2)\";\n+    else fail \"$3: expected &gt; '$2', got '$1'\"; fi\n+}\n+\n+# Locate a built shared module by basename, searching the usual subtrees.\n+# Aborts the run if not found (the suite cannot proceed without it).\n+find_so() {\n+    local name=\"$1\" hit\n+    hit=\"$(find \"$TOP/modules\" \"$TOP/server/mpm\" -name \"$name\" -path '*/.libs/*' 2&gt;/dev/null | head -1)\"\n+    if [ -z \"$hit\" ]; then\n+        echo \"ERROR: required module '$name' not built under $TOP\" &gt;&amp;2\n+        echo \"       (re)build with: ./configure --enable-mpms-shared='event motorz' &amp;&amp; make\" &gt;&amp;2\n+        exit 2\n+    fi\n+    printf '%s' \"$hit\"\n+}\n+\n+# LoadModule   -&gt; emits a LoadModule line\n+load() { printf 'LoadModule %s %s\\n' \"$1\" \"$(find_so \"$2\")\"; }\n+\n+require_httpd() {\n+    if [ ! -x \"$HTTPD\" ]; then\n+        echo \"ERROR: httpd binary not found/executable at $HTTPD\" &gt;&amp;2\n+        echo \"       build it first: make\" &gt;&amp;2\n+        exit 2\n+    fi\n+}\n+\n+# Create the per-run ServerRoot. Sets $RUNDIR.\n+make_rundir() {\n+    RUNDIR=\"$(mktemp -d \"${TMPDIR:-/tmp}/motorz-test.XXXXXX\")\"\n+    mkdir -p \"$RUNDIR/htdocs\" \"$RUNDIR/logs\"\n+    printf 'hello-motorz\\n' &gt; \"$RUNDIR/htdocs/index.html\"\n+    # ~200KB body to force multi-frame / write-completion cycling\n+    head -c 200000 /dev/zero | tr '\\0' 'A' &gt; \"$RUNDIR/htdocs/big.txt\"\n+}\n+\n+start_httpd() {\n+    \"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -k start\n+    local rc=$?\n+    [ $rc -eq 0 ] || { echo \"ERROR: httpd failed to start (rc=$rc)\" &gt;&amp;2; tail -20 \"$RUNDIR/logs/error_log\" 2&gt;/dev/null &gt;&amp;2; exit 3; }\n+    # wait for the listener\n+    local i\n+    for i in $(seq 1 20); do\n+        if lsof -iTCP -sTCP:LISTEN -a -p \"$(cat \"$RUNDIR/httpd.pid\" 2&gt;/dev/null || echo 0)\" &gt;/dev/null 2&gt;&amp;1; then\n+            return 0\n+        fi\n+        sleep 0.3\n+    done\n+    return 0\n+}\n+\n+graceful() { \"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -k graceful 2&gt;/dev/null; sleep 1; }\n+\n+# Switch the running server's log level and gracefully restart. Rewrites the\n+# LogLevel line that immediately follows a \"# @LOGLEVEL@\" marker comment in\n+# httpd.conf (httpd does not accept inline trailing comments, so the marker\n+# lives on its own line above the directive). Used to keep heavy load phases at\n+# a quiet level (so the trace8 error_log does not balloon to gigabytes) while\n+# still capturing state traces for the light trace phase.\n+# $1 = new LogLevel argument (e.g. \"info\" or \"trace8\").\n+set_loglevel() {\n+    local lvl=\"$1\"\n+    awk -v L=\"$lvl\" '\n+        prev ~ /# @LOGLEVEL@$/ { print \"LogLevel \" L; prev=\"\"; next }\n+        { print; prev=$0 }\n+    ' \"$RUNDIR/httpd.conf\" &gt; \"$RUNDIR/httpd.conf.new\" \\\n+        &amp;&amp; mv \"$RUNDIR/httpd.conf.new\" \"$RUNDIR/httpd.conf\"\n+    graceful\n+}\n+\n+stop_httpd() {\n+    [ -n \"$RUNDIR\" ] &amp;&amp; [ -f \"$RUNDIR/httpd.conf\" ] &amp;&amp; \"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -k stop 2&gt;/dev/null\n+    # bounded wait for the parent to exit\n+    local pid i\n+    pid=\"$(cat \"$RUNDIR/httpd.pid\" 2&gt;/dev/null || true)\"\n+    [ -n \"$pid\" ] || return 0\n+    for i in $(seq 1 20); do kill -0 \"$pid\" 2&gt;/dev/null || break; sleep 0.3; done\n+}\n+\n+# Drive the CONN_STATE_ASYNC_WAITIO path: open an h2 connection, send only the\n+# HTTP/2 preface + an empty SETTINGS frame (no request -&gt; emitted_count==0 -&gt;\n+# mod_http2 returns CONN_STATE_ASYNC_WAITIO), pause (server arms WAITIO), send\n+# another empty SETTINGS to wake the socket (forcing the waitio-&gt;processing\n+# re-dispatch), then we kill the client.  $1 = TLS port.\n+#\n+# IMPORTANT: the feeder is piped DIRECTLY into openssl so `$!` is openssl's own\n+# PID -- we kill that exact process. (Wrapping the pipe in an extra subshell\n+# would make `$!` the subshell and leave openssl running, blocked reading from\n+# a connection the server legitimately parks in ASYNC_WAITIO for the whole\n+# Timeout -- which previously hung the suite.) The feeder subshell on the left\n+# self-terminates: once openssl dies its next write gets SIGPIPE, and its last\n+# action is a bounded sleep regardless.\n+trigger_async_waitio() {\n+    local port=\"$1\" pf i\n+    pf='PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\\000\\000\\000\\004\\000\\000\\000\\000\\000'\n+    { printf \"$pf\"; sleep 2; printf '\\000\\000\\000\\004\\000\\000\\000\\000\\000'; sleep 1; } \\\n+        | openssl s_client -connect localhost:\"$port\" -alpn h2 -quiet &gt;/dev/null 2&gt;&amp;1 &amp;\n+    local op=$!   # PID of openssl (right-most command in the pipe)\n+    # Bounded wait for the feed to play out, then kill openssl directly.\n+    for i in $(seq 1 10); do\n+        kill -0 \"$op\" 2&gt;/dev/null || break\n+        sleep 0.5\n+    done\n+    kill \"$op\" 2&gt;/dev/null\n+    wait \"$op\" 2&gt;/dev/null\n+    return 0\n+}\n+\n+# Parse one statistic out of h2load's summary output.\n+#   h2load_stat \"\" \"\"\n+# e.g. h2load_stat \"$out\" 'requests:'  -&gt; \"1000 total, 1000 started, ...\"\n+# Returns the matching line (caller extracts the field).\n+h2load_stat() { printf '%s\\n' \"$1\" | grep -E \"$2\" | head -1; }\n+\n+# Fail the run if the error log shows anything alarming.\n+scan_log_clean() {\n+    local bad\n+    bad=\"$(grep -iE 'segfault|crash|core dump|exit signal|\\[crit\\]|\\[emerg\\]|assert|deadlock' \\\n+           \"$RUNDIR/logs/error_log\" 2&gt;/dev/null | grep -v 'resuming normal' || true)\"\n+    if [ -n \"$bad\" ]; then\n+        fail \"error log contains alarming entries:\"\n+        echo \"$bad\" | sed 's/^/      /'\n+    else\n+        pass \"error log clean (no crash/crit/emerg/assert/deadlock)\"\n+    fi\n+}\n+\n+cleanup() {\n+    stop_httpd\n+    if [ \"${KEEP:-0}\" = \"1\" ]; then\n+        echo \"KEEP=1 set; leaving run dir: $RUNDIR\"\n+    elif [ -n \"$RUNDIR\" ]; then\n+        rm -rf \"$RUNDIR\"\n+    fi\n+}\n+\n+summary() {\n+    echo\n+    echo \"==== $(basename \"$0\"): $PASS passed, $FAIL failed ====\"\n+    [ \"$FAIL\" -eq 0 ]\n+}\ndiff --git a/server/mpm/motorz/test/run-all.sh b/server/mpm/motorz/test/run-all.sh\nnew file mode 100755\nindex 0000000000..c3eb39acb2\n--- /dev/null\n+++ b/server/mpm/motorz/test/run-all.sh\n@@ -0,0 +1,24 @@\n+#!/bin/sh\n+#\n+# Run the full motorz MPM test suite (HTTP/1.1 + HTTP/2). Exits non-zero if\n+# any sub-suite reports a failure. The HTTP/2 suite self-skips (exit 0) when\n+# ssl/http2/openssl/h2-curl are unavailable.\n+#\n+# Usage: server/mpm/motorz/test/run-all.sh\n+\n+here=\"$(dirname \"$0\")\"\n+rc=0\n+\n+sh \"$here/smoke.sh\" || rc=1\n+echo\n+sh \"$here/run-http1.sh\" || rc=1\n+echo\n+sh \"$here/run-http2.sh\" || rc=1\n+\n+echo\n+if [ \"$rc\" -eq 0 ]; then\n+    echo \"######## motorz: ALL SUITES PASSED ########\"\n+else\n+    echo \"######## motorz: FAILURES PRESENT ########\"\n+fi\n+exit $rc\ndiff --git a/server/mpm/motorz/test/run-http1.sh b/server/mpm/motorz/test/run-http1.sh\nnew file mode 100755\nindex 0000000000..eb6dcf96e0\n--- /dev/null\n+++ b/server/mpm/motorz/test/run-http1.sh\n@@ -0,0 +1,134 @@\n+#!/bin/sh\n+#\n+# motorz MPM -- HTTP/1.1 functional + lifecycle regression suite.\n+#\n+# Launches httpd with the motorz MPM (2 pollers) and exercises the connection\n+# state machine that server/mpm/motorz/motorz.c implements: basic requests,\n+# keep-alive reuse, the non-blocking lingering-close path, concurrency, a\n+# slow/partial-request client (read-wait), and the graceful restart / stop /\n+# restart-churn lifecycle that this branch hardened.\n+#\n+# Usage:   server/mpm/motorz/test/run-http1.sh\n+# Env:     PORT=NNNN   KEEP=1 (keep the temp ServerRoot for inspection)\n+#\n+# Requires only: motorz, unixd, authz_core, authz_host, log_config, mime, dir.\n+# Uses `ab` if present for load; falls back to parallel curl otherwise.\n+\n+. \"$(dirname \"$0\")/lib.sh\"\n+\n+require_httpd\n+make_rundir\n+trap cleanup EXIT INT TERM\n+\n+cat &gt; \"$RUNDIR/httpd.conf\" &lt;\n+  Require all granted\n+\n+EOF\n+\n+echo \"==== motorz HTTP/1.1 suite (port $PORT) ====\"\n+\n+echo \"-- config syntax\"\n+\"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -t &gt;/dev/null 2&gt;&amp;1\n+assert_eq 0 $? \"httpd -t (config valid, motorz loads)\"\n+\n+start_httpd\n+\n+base=\"http://127.0.0.1:$PORT\"\n+\n+echo \"-- basic request\"\n+code=$(curl -s -o /dev/null -w '%{http_code}' \"$base/\")\n+assert_eq 200 \"$code\" \"GET / returns 200\"\n+body=$(curl -s \"$base/\")\n+assert_eq \"hello-motorz\" \"$body\" \"GET / body\"\n+assert_eq 404 \"$(curl -s -o /dev/null -w '%{http_code}' \"$base/nope\")\" \"GET /nope returns 404\"\n+\n+echo \"-- keep-alive reuse (5 requests, 1 connection)\"\n+# curl prints %{num_connects} once per URL; with keep-alive only the first\n+# request opens a connection, so the values are 1,0,0,0,0 and the sum is 1.\n+conns=$(curl -s -o /dev/null -w '%{num_connects}\\n' \"$base/\" \"$base/\" \"$base/\" \"$base/\" \"$base/\" \\\n+        | awk '{s+=$1} END{print s}')\n+assert_eq 1 \"$conns\" \"5 keep-alive requests reuse a single connection\"\n+\n+echo \"-- large body (200KB, write-completion cycling)\"\n+sz=$(curl -s -o /dev/null -w '%{size_download}' \"$base/big.txt\")\n+assert_eq 200000 \"$sz\" \"GET /big.txt full body\"\n+\n+echo \"-- concurrency / load\"\n+if command -v ab &gt;/dev/null 2&gt;&amp;1; then\n+    # Keep-alive correctness is verified with curl, not ab: curl parses HTTP\n+    # status reliably, whereas ab miscounts a server-closed keep-alive\n+    # connection's final read as a \"Non-2xx\"/\"Length\" failure (a known ab\n+    # artifact, more frequent with MOTORZ_ENABLE_ASYNC=0 because idle keep-alive\n+    # connections close via the blocking path). 3000 reused requests, all 200.\n+    n_ok=$(seq 1 50 | xargs -P 20 -I{} sh -c \\\n+             'curl -s -o /dev/null -w \"%{http_code}\\n\" $(for j in $(seq 1 60); do echo \"'\"$base\"'/\"; done)' \\\n+           | grep -c '^200$')\n+    assert_eq 3000 \"$n_ok\" \"curl keep-alive: 3000/3000 reused requests returned 200\"\n+\n+    # ab still used for raw completion count + the non-keep-alive linger path\n+    # (no -k =&gt; fresh connection per request =&gt; no keep-alive close artifact).\n+    out=$(ab -n 2000 -c 20 -k -q \"$base/\" 2&gt;&amp;1)\n+    complete=$(printf '%s\\n' \"$out\" | awk '/Complete requests:/{print $3}')\n+    assert_eq 2000 \"$complete\" \"ab: 2000 keep-alive requests completed\"\n+\n+    out=$(ab -n 1000 -c 30 -q \"$base/\" 2&gt;&amp;1)   # no -k: exercises lingering close\n+    assert_eq 0 \"$(printf '%s\\n' \"$out\" | awk '/Failed requests:/{print $3}')\" \\\n+              \"ab: 0 failed (non-keepalive / linger path)\"\n+else\n+    echo \"  (ab not found; using parallel curl)\"\n+    n_ok=$(seq 1 200 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\\n' \"$base/\" \\\n+           | grep -c '^200$')\n+    assert_eq 200 \"$n_ok\" \"parallel curl: 200/200 returned 200\"\n+fi\n+\n+echo \"-- slow client (partial request line, then complete: read-wait path)\"\n+resp=$( ( printf 'GET / HTTP/1.1\\r\\nHost: x\\r\\n'; sleep 2; printf '\\r\\n'; sleep 1 ) \\\n+        | nc 127.0.0.1 \"$PORT\" 2&gt;/dev/null | head -1 | tr -d '\\r' )\n+assert_eq \"HTTP/1.1 200 OK\" \"$resp\" \"partial-then-complete request served\"\n+\n+echo \"-- graceful restart under load\"\n+( command -v ab &gt;/dev/null 2&gt;&amp;1 &amp;&amp; ab -n 3000 -c 20 -k -q \"$base/\" &gt;/dev/null 2&gt;&amp;1 ) &amp;\n+lpid=$!\n+graceful\n+wait $lpid 2&gt;/dev/null\n+assert_eq 200 \"$(curl -s -o /dev/null -w '%{http_code}' \"$base/\")\" \"serving after graceful restart\"\n+\n+echo \"-- restart churn (5 gracefuls under continuous load)\"\n+( for _ in 1 2 3 4 5 6; do\n+    command -v ab &gt;/dev/null 2&gt;&amp;1 &amp;&amp; ab -n 1000 -c 20 -k -q \"$base/\" &gt;/dev/null 2&gt;&amp;1 \\\n+        || curl -s -o /dev/null \"$base/\"\n+  done ) &amp;\n+lpid=$!\n+for _ in 1 2 3 4 5; do graceful; done\n+wait $lpid 2&gt;/dev/null\n+assert_eq 200 \"$(curl -s -o /dev/null -w '%{http_code}' \"$base/\")\" \"serving after 5x restart churn\"\n+\n+scan_log_clean\n+\n+summary\ndiff --git a/server/mpm/motorz/test/run-http2.sh b/server/mpm/motorz/test/run-http2.sh\nnew file mode 100755\nindex 0000000000..fcd4b0db34\n--- /dev/null\n+++ b/server/mpm/motorz/test/run-http2.sh\n@@ -0,0 +1,259 @@\n+#!/bin/sh\n+#\n+# motorz MPM -- HTTP/2-over-TLS suite (mod_http2 + mod_ssl on motorz).\n+#\n+# Confirms h2 ALPN negotiation, request multiplexing over a single connection,\n+# a large multi-frame body, and h2load load (incl. high-churn n=.. c=50 m=1)\n+# with zero dropped requests. Also asserts that the async HTTP/2 handoff is\n+# ENABLED (MOTORZ_ENABLE_ASYNC 1): CONN_STATE_ASYNC_WAITIO arms and c1 is\n+# returned to MPM monitoring, while churn stays lossless thanks to the mod_http2\n+# c1/c2 close-ordering fix -- see MOTORZ.README \"HTTP/2 async handoff\". With\n+# global trace8 it inspects the motorz connection state-machine traces.\n+#\n+# Usage:   server/mpm/motorz/test/run-http2.sh\n+# Env:     TLS_PORT=NNNN   KEEP=1\n+#\n+# Requires: motorz + unixd + authz_core/host + log_config + mime + dir\n+#           + socache_shmcb + ssl + http2, an `openssl` CLI, and a curl built\n+#           with HTTP/2 (`curl -V | grep -i http2`). The suite self-skips with\n+#           a clear message if any prerequisite is missing.\n+\n+. \"$(dirname \"$0\")/lib.sh\"\n+\n+PORT=\"$TLS_PORT\"\n+\n+require_httpd\n+\n+# -- prerequisite checks (skip, don't fail, if the build lacks h2/ssl) -------\n+need_skip=\"\"\n+command -v openssl &gt;/dev/null 2&gt;&amp;1 || need_skip=\"openssl CLI not found\"\n+if ! curl -V 2&gt;/dev/null | grep -qi 'http2'; then\n+    need_skip=\"${need_skip:+$need_skip; }curl lacks HTTP/2 support\"\n+fi\n+for n in mod_ssl.so mod_http2.so mod_socache_shmcb.so; do\n+    find \"$TOP/modules\" -name \"$n\" -path '*/.libs/*' 2&gt;/dev/null | grep -q . \\\n+        || need_skip=\"${need_skip:+$need_skip; }$n not built\"\n+done\n+if [ -n \"$need_skip\" ]; then\n+    echo \"==== motorz HTTP/2 suite: SKIPPED ($need_skip) ====\"\n+    exit 0\n+fi\n+\n+make_rundir\n+trap cleanup EXIT INT TERM\n+\n+# self-signed cert\n+openssl req -x509 -newkey rsa:2048 -nodes \\\n+    -keyout \"$RUNDIR/key.pem\" -out \"$RUNDIR/cert.pem\" \\\n+    -days 2 -subj \"/CN=localhost\" &gt;/dev/null 2&gt;&amp;1 \\\n+    || { echo \"ERROR: openssl cert generation failed\" &gt;&amp;2; exit 3; }\n+\n+cat &gt; \"$RUNDIR/httpd.conf\" &lt;\n+  Require all granted\n+\n+\n+\n+  ServerName localhost\n+  SSLEngine on\n+  SSLCertificateFile \"$RUNDIR/cert.pem\"\n+  SSLCertificateKeyFile \"$RUNDIR/key.pem\"\n+  Protocols h2 http/1.1\n+\n+EOF\n+\n+echo \"==== motorz HTTP/2-over-TLS suite (port $PORT) ====\"\n+\n+echo \"-- config syntax\"\n+\"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -t &gt;/dev/null 2&gt;&amp;1\n+assert_eq 0 $? \"httpd -t (motorz + ssl + http2)\"\n+\n+start_httpd\n+\n+base=\"https://localhost:$PORT\"\n+C=\"curl -sk --http2\"\n+\n+echo \"-- h2 negotiation\"\n+ver=$($C -o /dev/null -w '%{http_version}' \"$base/\")\n+assert_eq 2 \"$ver\" \"ALPN negotiates HTTP/2\"\n+assert_eq 200 \"$($C -o /dev/null -w '%{http_code}' \"$base/\")\" \"h2 GET / returns 200\"\n+assert_eq \"hello-motorz\" \"$($C \"$base/\")\" \"h2 GET / body\"\n+\n+echo \"-- large body over h2 (200KB)\"\n+assert_eq 200000 \"$($C -o /dev/null -w '%{size_download}' \"$base/big.txt\")\" \"h2 GET /big.txt full body\"\n+\n+echo \"-- multiplexing: 10 streams, 1 connection\"\n+urls=\"\"; i=1; while [ $i -le 10 ]; do urls=\"$urls $base/?q=$i\"; i=$((i+1)); done\n+conns=$($C -o /dev/null -w '%{num_connects}\\n' $urls | awk '{s+=$1} END{print s}')\n+assert_eq 1 \"$conns\" \"10 h2 requests over a single connection\"\n+\n+# --- h2load: real HTTP/2 load with concurrent clients &amp; multiplexed streams.\n+# This is the robust smoke test -- it pounds motorz with genuine multiplexed\n+# h2 traffic (not curl's one-request-per-fetch) and verifies zero failures,\n+# which exercises the WRITE_COMPLETION / keep-alive / stream-handling paths\n+# under real concurrency. Skipped (not failed) if h2load is unavailable.\n+if command -v h2load &gt;/dev/null 2&gt;&amp;1; then\n+    echo \"-- h2load: 5000 requests / 20 clients / 25 streams (small body)\"\n+    out=$(h2load -n 5000 -c 20 -m 25 \"$base/\" 2&gt;&amp;1)\n+    req_line=$(h2load_stat \"$out\" '^requests:')\n+    sc_line=$(h2load_stat \"$out\" '^status codes:')\n+    echo \"      $req_line\"\n+    echo \"      $sc_line\"\n+    # \"requests: N total, N started, N done, N succeeded, N failed, ...\"\n+    succeeded=$(printf '%s' \"$req_line\" | sed -nE 's/.* ([0-9]+) succeeded.*/\\1/p')\n+    failed=$(printf '%s'    \"$req_line\" | sed -nE 's/.* ([0-9]+) failed.*/\\1/p')\n+    errored=$(printf '%s'   \"$req_line\" | sed -nE 's/.* ([0-9]+) errored.*/\\1/p')\n+    twoxx=$(printf '%s'     \"$sc_line\"  | sed -nE 's/.* ([0-9]+) 2xx.*/\\1/p')\n+    assert_eq 5000 \"${succeeded:-0}\" \"h2load: all 5000 requests succeeded\"\n+    assert_eq 0    \"${failed:-x}\"    \"h2load: 0 failed\"\n+    assert_eq 0    \"${errored:-x}\"   \"h2load: 0 errored\"\n+    assert_eq 5000 \"${twoxx:-0}\"     \"h2load: all 5000 responses were 2xx\"\n+\n+    echo \"-- h2load: 1000 requests / 10 clients / 50 streams (200KB body, flow control)\"\n+    out=$(h2load -n 1000 -c 10 -m 50 \"$base/big.txt\" 2&gt;&amp;1)\n+    req_line=$(h2load_stat \"$out\" '^requests:')\n+    echo \"      $req_line\"\n+    succeeded=$(printf '%s' \"$req_line\" | sed -nE 's/.* ([0-9]+) succeeded.*/\\1/p')\n+    failed=$(printf '%s'    \"$req_line\" | sed -nE 's/.* ([0-9]+) failed.*/\\1/p')\n+    assert_eq 1000 \"${succeeded:-0}\" \"h2load: 1000 large-body requests succeeded\"\n+    assert_eq 0    \"${failed:-x}\"    \"h2load: 0 failed (large body / flow control)\"\n+\n+    echo \"-- h2load: rate-limited connections (idle gaps between streams)\"\n+    # -r2 opens 2 new connections/sec with brief inactivity, nudging sessions\n+    # toward the idle/keepalive paths between bursts.\n+    out=$(h2load -n 600 -c 12 -m 5 -r 2 \"$base/\" 2&gt;&amp;1)\n+    req_line=$(h2load_stat \"$out\" '^requests:')\n+    echo \"      $req_line\"\n+    failed=$(printf '%s' \"$req_line\" | sed -nE 's/.* ([0-9]+) failed.*/\\1/p')\n+    assert_eq 0 \"${failed:-x}\" \"h2load: 0 failed (rate-limited / idle connections)\"\n+\n+    # --- churn regression: NO RESPONSE LOSS under max connection churn -------\n+    # This is the workload the mod_http2 c1/c2 close-ordering fix targets\n+    # (MOTORZ.README \"HTTP/2 async handoff\"): many short connections, one stream\n+    # each (-m 1), so each client sends a graceful GOAWAY right after its single\n+    # request -- the exact path that used to abort a just-finished c2 and drop\n+    # its response.\n+    #\n+    # We assert on RESPONSE LOSS (started - succeeded), NOT on h2load's \"failed\"\n+    # total. Those are different: failed = (total - started) + (started -\n+    # succeeded). The first term is connection-ESTABLISHMENT error (ephemeral\n+    # port / accept-queue pressure on a busy loopback) -- environmental, seen\n+    # with AND without the fix, and not what this fix is about. The original bug\n+    # is response loss on connections that DID start: started &gt; succeeded. That\n+    # is what must be zero, and it is the precise, non-flaky signal for this fix.\n+    #\n+    # NB: run at the current \"info\" level, NOT the trace8 phase below. The bug\n+    # is a Heisenbug; trace8 slows the hot path enough to hide it, which would\n+    # make this assertion pass vacuously (verified: a deliberately broken fix\n+    # still showed 0 response loss under trace8). info keeps the path hot.\n+    echo \"-- [churn regression] h2 connection churn must not drop responses\"\n+    resp_lost_total=0; started_total=0\n+    for _crun in 1 2 3; do\n+        out=$(h2load -n 10000 -c 50 -m 1 \"$base/\" 2&gt;&amp;1)\n+        rl=$(h2load_stat \"$out\" '^requests:')\n+        started=$(printf '%s' \"$rl\"   | sed -nE 's/.* total, ([0-9]+) started.*/\\1/p')\n+        succeeded=$(printf '%s' \"$rl\" | sed -nE 's/.* ([0-9]+) succeeded.*/\\1/p')\n+        lost=$(( ${started:-0} - ${succeeded:-0} ))\n+        [ \"$lost\" -lt 0 ] &amp;&amp; lost=0\n+        echo \"      run $_crun: started=${started:-?} succeeded=${succeeded:-?} response-loss=$lost\"\n+        resp_lost_total=$(( resp_lost_total + lost ))\n+        started_total=$(( started_total + ${started:-0} ))\n+    done\n+    echo \"      total response-loss=$resp_lost_total over started=$started_total (expected 0)\"\n+    assert_eq 0 \"$resp_lost_total\" \"h2 churn: 0 dropped responses on started connections (close-ordering fix)\"\n+else\n+    echo \"-- h2load: SKIPPED (h2load not on PATH; install nghttp2 for full load coverage)\"\n+fi\n+\n+# ---- trace phase: switch to trace8 for light, instrumented traffic only ----\n+# (Heavy load above ran at \"info\" so the log stayed small.) From here on the\n+# error_log only grows by a handful of trace8 lines per request, so the greps\n+# below stay fast. We mark the switch point and scan only past it.\n+echo \"-- switching to LogLevel trace8 for state-machine inspection\"\n+phase_marker=\"##TRACE-PHASE-$$##\"\n+echo \"$phase_marker\" &gt;&gt; \"$RUNDIR/logs/error_log\"\n+set_loglevel trace8\n+\n+echo \"-- motorz state-machine traces while serving h2\"\n+# A couple of completed h2 requests (curl drives keepalive=1) exercise these.\n+$C -o /dev/null \"$base/\" \"$base/big.txt\" \"$base/\" &gt;/dev/null 2&gt;&amp;1\n+sleep 0.3\n+states=$(awk -v m=\"$phase_marker\" '$0~m{f=1} f' \"$RUNDIR/logs/error_log\" \\\n+         | grep -oE \"motorz_io_process\\(\\): [a-z][a-z -&gt;]*[a-z]\" | sort -u)\n+echo \"$states\" | sed 's/^/      seen: /'\n+echo \"$states\" | grep -q \"processing\"        &amp;&amp; pass \"saw CONN_STATE_PROCESSING\"        || fail \"no PROCESSING trace\"\n+# NOTE: with async enabled (MOTORZ_ENABLE_ASYNC 1) and CAN_WAITIO, mod_http2\n+# hands an idle c1 back as CONN_STATE_ASYNC_WAITIO rather than via\n+# WRITE_COMPLETION, so motorz's \"write completion\" state is still not exercised\n+# by h2. (It is covered for HTTP/1.1 in run-http1.sh.) Only PROCESSING is\n+# asserted here.\n+\n+echo \"-- async HTTP/2 handoff is ENABLED (MOTORZ_ENABLE_ASYNC 1)\"\n+# motorz reports AP_MPMQ_IS_ASYNC=1 / AP_MPMQ_CAN_WAITIO=1, so mod_http2 takes\n+# the async c1 hand-back path. This is only safe because of the mod_http2\n+# c1/c2 close-ordering fix (h2_session_ev_remote_goaway / ST_IDLE draining):\n+# the c1 connection is closed only after every secondary connection (c2) has\n+# finished and flushed, so connection churn stays lossless (asserted above and\n+# in smoke.sh). See MOTORZ.README \"HTTP/2 async handoff\". Consequences asserted\n+# here, inverted from the async-off era:\n+#   - the CONN_STATE_ASYNC_WAITIO arm trace (AH10557) DOES appear when a client\n+#     opens an h2 connection and then idles: mod_http2 requests WAITIO of an\n+#     async MPM and motorz arms it.\n+#   - mod_http2 DOES return the c1 connection to the MPM (\"returning to mpm c1\n+#     monitoring\") between requests.\n+marker=\"##WAITIO-$$##\"\n+echo \"$marker\" &gt;&gt; \"$RUNDIR/logs/error_log\"\n+trigger_async_waitio \"$PORT\"\n+waitio_arm=$(awk -v m=\"$marker\" '$0~m{f=1} f' \"$RUNDIR/logs/error_log\" | grep -c 'AH10557')\n+mon=$(grep -c 'returning to mpm c1 monitoring' \"$RUNDIR/logs/error_log\")\n+echo \"      waitio-arm(AH10557)=$waitio_arm   mpm-c1-monitoring=$mon  (both expected &gt; 0)\"\n+assert_gt \"$waitio_arm\" 0 \"CONN_STATE_ASYNC_WAITIO armed (async enabled)\"\n+assert_gt \"$mon\" 0        \"h2 returns to MPM c1 monitoring (IS_ASYNC=1)\"\n+\n+echo \"-- h2 graceful restart\"\n+graceful\n+assert_eq 2 \"$($C -o /dev/null -w '%{http_version}' \"$base/\")\" \"h2 still negotiated after graceful restart\"\n+\n+scan_log_clean\n+\n+summary\ndiff --git a/server/mpm/motorz/test/setup.sh b/server/mpm/motorz/test/setup.sh\nnew file mode 100755\nindex 0000000000..9cb68b2ed8\n--- /dev/null\n+++ b/server/mpm/motorz/test/setup.sh\n@@ -0,0 +1,157 @@\n+#!/bin/sh\n+#\n+# setup.sh -- configure and build httpd so the motorz MPM test suite can run.\n+#\n+# The motorz tests (smoke.sh / run-http1.sh / run-http2.sh / run-all.sh, and the\n+# bench.sh comparison) drive a real httpd built from THIS tree against a\n+# throwaway config. They need:\n+#   - the motorz MPM, built as a shared module (mod_mpm_motorz.so)\n+#   - the event MPM too (bench.sh compares against it)\n+#   - these shared modules: unixd, authz_core, authz_host, log_config, mime,\n+#     dir, and -- for the HTTP/2 suite -- socache_shmcb, ssl, http2\n+#   - bundled APR/APR-Util (--with-included-apr), so no system APR is required\n+#\n+# This script runs buildconf (only if ./configure is missing), ./configure with\n+# the right flags, and make. It is idempotent: re-running reconfigures + rebuilds.\n+#\n+# Usage (from anywhere):\n+#   server/mpm/motorz/test/setup.sh                 # configure (if needed) + build\n+#   server/mpm/motorz/test/setup.sh --reconfigure   # force re-run ./configure\n+#   server/mpm/motorz/test/setup.sh --jobs N         # parallel make (default: CPUs)\n+#\n+# After it succeeds:\n+#   server/mpm/motorz/test/run-all.sh                # run the test suite\n+#\n+# It does NOT install httpd; the tests run the freshly built ./httpd in place.\n+\n+set -u\n+\n+# --- locate the build tree (this script lives in /server/mpm/motorz/test) -\n+SELF_DIR=\"$(cd \"$(dirname \"$0\")\" &amp;&amp; pwd)\"\n+TOP=\"$(cd \"$SELF_DIR/../../../..\" &amp;&amp; pwd)\"\n+cd \"$TOP\" || { echo \"ERROR: cannot cd to build top $TOP\" &gt;&amp;2; exit 1; }\n+\n+RECONFIGURE=0\n+JOBS=\"\"\n+while [ $# -gt 0 ]; do\n+    case \"$1\" in\n+        --reconfigure) RECONFIGURE=1 ;;\n+        --jobs) shift; JOBS=\"$1\" ;;\n+        --jobs=*) JOBS=\"${1#--jobs=}\" ;;\n+        -h|--help) sed -n '2,30p' \"$0\" | sed 's/^#//;s/^ //'; exit 0 ;;\n+        *) echo \"unknown option: $1 (try --help)\" &gt;&amp;2; exit 2 ;;\n+    esac\n+    shift\n+done\n+\n+if [ -z \"$JOBS\" ]; then\n+    JOBS=\"$( (command -v nproc &gt;/dev/null &amp;&amp; nproc) \\\n+             || sysctl -n hw.ncpu 2&gt;/dev/null || echo 2 )\"\n+fi\n+\n+say()  { printf '\\n==== %s ====\\n' \"$*\"; }\n+die()  { echo \"ERROR: $*\" &gt;&amp;2; exit 1; }\n+have() { command -v \"$1\" &gt;/dev/null 2&gt;&amp;1; }\n+\n+# --- 1. prerequisites -------------------------------------------------------\n+say \"checking prerequisites\"\n+have make || die \"make not found\"\n+have cc || have gcc || have clang || die \"no C compiler (cc/gcc/clang) found\"\n+echo \"  compiler: $(command -v cc gcc clang 2&gt;/dev/null | head -1)\"\n+echo \"  make:     $(command -v make)\"\n+\n+# buildconf (regenerates ./configure) is only needed for a fresh git checkout.\n+if [ ! -x \"$TOP/configure\" ]; then\n+    say \"no ./configure -- running buildconf (fresh checkout)\"\n+    have autoconf   || die \"autoconf required to run buildconf (install autoconf)\"\n+    have python3    || die \"python3 required by buildconf\"\n+    # buildconf wants libtoolize OR glibtoolize (macOS names it glibtoolize)\n+    if ! have libtoolize &amp;&amp; ! have glibtoolize; then\n+        die \"libtoolize/glibtoolize required by buildconf (install libtool)\"\n+    fi\n+    [ -d \"$TOP/srclib/apr\" ] || die \"srclib/apr missing: fetch bundled APR \\\n+(svn co/ git submodule) or use a release tarball that includes it\"\n+    \"$TOP/buildconf\" || die \"buildconf failed\"\n+fi\n+\n+# --- 2. configure -----------------------------------------------------------\n+# Enable motorz AND event as shared MPMs, and explicitly enable the modules the\n+# tests load (rather than relying on the 'most' default), plus bundled APR.\n+CONFIGURE_ARGS='--with-included-apr\n+--enable-mpms-shared=event motorz\n+--enable-so\n+--enable-unixd\n+--enable-authz_core\n+--enable-authz_host\n+--enable-log_config\n+--enable-mime\n+--enable-dir\n+--enable-socache_shmcb\n+--enable-ssl\n+--enable-http2'\n+\n+if [ \"$RECONFIGURE\" -eq 1 ] || [ ! -f \"$TOP/config.status\" ]; then\n+    say \"configuring\"\n+    # shellcheck disable=SC2086  # intentional word-splitting of the arg list\n+    set -f; IFS='\n+'; set -- $CONFIGURE_ARGS; unset IFS; set +f\n+    echo \"  ./configure $*\"\n+    \"$TOP/configure\" \"$@\" || die \"./configure failed (see config.log). If mod_ssl \\\n+or mod_http2 failed, install their dev libs: OpenSSL headers and libnghttp2.\"\n+else\n+    echo \"  config.status present; skipping ./configure (use --reconfigure to force)\"\n+fi\n+\n+# --- 3. build ---------------------------------------------------------------\n+say \"building (make -j$JOBS)\"\n+make \"-j$JOBS\" || die \"make failed\"\n+\n+# --- 4. verify the bits the tests need --------------------------------------\n+say \"verifying build outputs\"\n+rc=0\n+[ -x \"$TOP/httpd\" ] &amp;&amp; echo \"  httpd binary: ok\" || { echo \"  httpd binary: MISSING\"; rc=1; }\n+\n+check_mod() {\n+    if find \"$TOP/modules\" \"$TOP/server/mpm\" -name \"$1\" -path '*/.libs/*' 2&gt;/dev/null \\\n+         | grep -q .; then\n+        echo \"  $1: ok\"\n+    else\n+        echo \"  $1: MISSING${2:+  ($2)}\"\n+        [ -n \"${3:-}\" ] &amp;&amp; rc=1   # required modules fail the verify; optional just warn\n+    fi\n+}\n+# required for the HTTP/1.1 suite + smoke\n+check_mod mod_mpm_motorz.so \"\" required\n+check_mod mod_unixd.so \"\" required\n+check_mod mod_authz_core.so \"\" required\n+check_mod mod_authz_host.so \"\" required\n+check_mod mod_log_config.so \"\" required\n+check_mod mod_mime.so \"\" required\n+check_mod mod_dir.so \"\" required\n+# needed by bench.sh (event comparison)\n+check_mod mod_mpm_event.so \"bench.sh needs this\"\n+# needed by the HTTP/2 suite (otherwise it self-skips)\n+check_mod mod_socache_shmcb.so \"HTTP/2 suite will skip without it\"\n+check_mod mod_ssl.so \"HTTP/2 suite will skip without it\"\n+check_mod mod_http2.so \"HTTP/2 suite will skip without it\"\n+\n+# --- 5. external tools used by the tests ------------------------------------\n+say \"external test tools (not built here)\"\n+for t in openssl curl ab h2load nghttp; do\n+    if have \"$t\"; then echo \"  $t: $(command -v $t)\"; else echo \"  $t: not found\"; fi\n+done\n+have openssl || echo \"  NOTE: openssl CLI absent -&gt; HTTP/2 suite self-skips\"\n+if have curl &amp;&amp; ! curl -V 2&gt;/dev/null | grep -qi http2; then\n+    echo \"  NOTE: curl lacks HTTP/2 -&gt; HTTP/2 suite self-skips\"\n+fi\n+have h2load || echo \"  NOTE: h2load (nghttp2) absent -&gt; h2load load tests skip\"\n+have ab     || echo \"  NOTE: ab (apache2-utils) absent -&gt; HTTP/1.1 uses curl fallback\"\n+\n+# --- done -------------------------------------------------------------------\n+echo\n+if [ \"$rc\" -eq 0 ]; then\n+    echo \"######## setup OK -- now run: server/mpm/motorz/test/run-all.sh ########\"\n+else\n+    echo \"######## setup INCOMPLETE -- a REQUIRED module is missing (see above) ########\"\n+fi\n+exit $rc\ndiff --git a/server/mpm/motorz/test/smoke.sh b/server/mpm/motorz/test/smoke.sh\nnew file mode 100755\nindex 0000000000..9e6ef69cc1\n--- /dev/null\n+++ b/server/mpm/motorz/test/smoke.sh\n@@ -0,0 +1,191 @@\n+#!/bin/sh\n+#\n+# motorz MPM -- robust smoke test mapped to the changes made on this branch.\n+#\n+# Each check targets one concrete change in server/mpm/motorz/motorz.c so a\n+# regression points straight at what broke:\n+#\n+#   1. forward-decl of motorz_update_listeners()  -&gt; the binary built &amp; runs\n+#      (the bug was a C89 implicit-declaration / link error; if motorz loads\n+#       and serves, the declaration is correct).\n+#   2. clogging-input-filters branch honors hook state (not force-LINGER)\n+#      -&gt; HTTP/2 over TLS keep-alives instead of collapsing to one-shot, since\n+#         h2 c2 connections set clogging_input_filters unconditionally.\n+#   3. async HTTP/2 handoff is ENABLED (MOTORZ_ENABLE_ASYNC 1): motorz reports\n+#      AP_MPMQ_IS_ASYNC=1, so mod_http2 hands the c1 connection back to the MPM\n+#      between requests. The mod_http2 c1/c2 close-ordering fix\n+#      (h2_session_ev_remote_goaway / ST_IDLE draining) keeps this lossless: c1\n+#      is closed only after every stream's c2 has finished and flushed. The\n+#      check is a churn regression test: many short h2 connections, asserting 0\n+#      dropped requests. (See MOTORZ.README \"HTTP/2 async handoff\" for the full\n+#      analysis and the fix.)\n+#\n+# It is fast (a few seconds), TLS+h2 if available else HTTP/1.1-only, and runs\n+# under global trace8 so the state-machine assertions can read the log.\n+#\n+# Usage: server/mpm/motorz/test/smoke.sh   [PORT=.. KEEP=1]\n+\n+. \"$(dirname \"$0\")/lib.sh\"\n+\n+require_httpd\n+\n+# Decide whether we can do the full h2 smoke or only HTTP/1.1.\n+H2=1\n+command -v openssl &gt;/dev/null 2&gt;&amp;1 || H2=0\n+curl -V 2&gt;/dev/null | grep -qi 'http2' || H2=0\n+for n in mod_ssl.so mod_http2.so mod_socache_shmcb.so; do\n+    find \"$TOP/modules\" -name \"$n\" -path '*/.libs/*' 2&gt;/dev/null | grep -q . || H2=0\n+done\n+\n+PORT=\"${PORT:-$TLS_PORT}\"\n+make_rundir\n+trap cleanup EXIT INT TERM\n+\n+echo \"==== motorz smoke test (h2=$([ $H2 -eq 1 ] &amp;&amp; echo yes || echo no), port $PORT) ====\"\n+\n+if [ \"$H2\" -eq 1 ]; then\n+    openssl req -x509 -newkey rsa:2048 -nodes \\\n+        -keyout \"$RUNDIR/key.pem\" -out \"$RUNDIR/cert.pem\" \\\n+        -days 2 -subj \"/CN=localhost\" &gt;/dev/null 2&gt;&amp;1 \\\n+        || { echo \"ERROR: cert gen failed\" &gt;&amp;2; exit 3; }\n+    cat &gt; \"$RUNDIR/httpd.conf\" &lt;\n+  Require all granted\n+\n+\n+  ServerName localhost\n+  SSLEngine on\n+  SSLCertificateFile \"$RUNDIR/cert.pem\"\n+  SSLCertificateKeyFile \"$RUNDIR/key.pem\"\n+  Protocols h2 http/1.1\n+\n+EOF\n+    scheme=https\n+else\n+    cat &gt; \"$RUNDIR/httpd.conf\" &lt;\n+  Require all granted\n+\n+EOF\n+    scheme=http\n+fi\n+\n+base=\"$scheme://localhost:$PORT\"\n+[ \"$scheme\" = http ] &amp;&amp; base=\"http://127.0.0.1:$PORT\"\n+CURL=\"curl -sk\"\n+[ \"$H2\" -eq 1 ] &amp;&amp; CURL=\"curl -sk --http2\"\n+\n+# ---- change #1: motorz loads, parses config, serves -----------------------\n+echo \"-- [#1 forward-decl] motorz binary loads &amp; serves\"\n+\"$HTTPD\" -f \"$RUNDIR/httpd.conf\" -t &gt;/dev/null 2&gt;&amp;1\n+assert_eq 0 $? \"config valid (motorz module loads -- forward-decl/link OK)\"\n+start_httpd\n+assert_eq 200 \"$($CURL -o /dev/null -w '%{http_code}' \"$base/\")\" \"serves GET / (200)\"\n+assert_eq \"hello-motorz\" \"$($CURL \"$base/\")\" \"correct body\"\n+\n+if [ \"$H2\" -eq 1 ]; then\n+    echo \"-- [h2] negotiation\"\n+    assert_eq 2 \"$($CURL -o /dev/null -w '%{http_version}' \"$base/\")\" \"ALPN -&gt; HTTP/2\"\n+\n+    # ---- change #2: clogging branch keeps h2 connections alive ------------\n+    echo \"-- [#2 clogging-state] h2 keep-alive (clogging_input_filters honored)\"\n+    conns=$($CURL -o /dev/null -w '%{num_connects}\\n' \"$base/\" \"$base/\" \"$base/\" \\\n+            | awk '{s+=$1} END{print s}')\n+    assert_eq 1 \"$conns\" \"3 h2 requests reuse one connection (not force-LINGERed)\"\n+\n+    # ---- change #3: async enabled -&gt; still no dropped requests under churn -\n+    # With MOTORZ_ENABLE_ASYNC=1, motorz advertises itself async and mod_http2\n+    # takes the c1 hand-back path (\"returning to mpm c1 monitoring\"). The\n+    # mod_http2 c1/c2 close-ordering fix (h2_session_ev_remote_goaway /\n+    # ST_IDLE draining) must keep this lossless under connection churn -- the\n+    # workload that used to drop ~0.2-3% of requests. See MOTORZ.README.\n+    echo \"-- [#3 async-churn] motorz advertises async; h2 churn stays lossless\"\n+    mon=$(grep -c 'returning to mpm c1 monitoring' \"$RUNDIR/logs/error_log\")\n+    assert_gt \"$mon\" 0 \"h2 returns to MPM c1 monitoring (IS_ASYNC=1)\"\n+    if command -v h2load &gt;/dev/null 2&gt;&amp;1; then\n+        # m=1 / high concurrency = max connection churn = the failing workload.\n+        # Assert on RESPONSE LOSS (started - succeeded), not on h2load's \"failed\"\n+        # total: \"failed\" also counts connection-establishment errors (ephemeral\n+        # port / accept-queue pressure on a busy loopback) which are\n+        # environmental and unrelated to this fix. The bug is responses dropped\n+        # on connections that DID start, i.e. started &gt; succeeded.\n+        #\n+        # NB: smoke.sh runs at trace8 (for the state-machine assertions above),\n+        # which slows the hot path enough to MASK this Heisenbug -- so treat this\n+        # as a gross-sanity check only. The real, non-vacuous churn regression\n+        # runs at \"info\" in run-http2.sh ([churn regression]).\n+        out=$(h2load -n 10000 -c 50 -m 1 \"$base/\" 2&gt;&amp;1)\n+        started=$(printf '%s\\n'   \"$out\" | sed -nE 's/.* total, ([0-9]+) started.*/\\1/p')\n+        succeeded=$(printf '%s\\n' \"$out\" | sed -nE 's/.* ([0-9]+) succeeded.*/\\1/p')\n+        lost=$(( ${started:-0} - ${succeeded:-0} )); [ \"$lost\" -lt 0 ] &amp;&amp; lost=0\n+        echo \"      h2load n=10000 c=50 m=1: started=$started succeeded=$succeeded response-loss=$lost\"\n+        assert_eq 0 \"$lost\" \"h2 churn: 0 dropped responses on started connections (sanity; see run-http2.sh)\"\n+    else\n+        echo \"      (h2load absent; churn assertion skipped -- install nghttp2)\"\n+    fi\n+else\n+    echo \"-- [#2/#3] h2 checks SKIPPED (ssl/http2/openssl/h2-curl unavailable)\"\n+    echo \"-- [HTTP/1.1] keep-alive reuse instead\"\n+    conns=$($CURL -o /dev/null -w '%{num_connects}\\n' \"$base/\" \"$base/\" \"$base/\" \\\n+            | awk '{s+=$1} END{print s}')\n+    assert_eq 1 \"$conns\" \"3 HTTP/1.1 requests reuse one connection\"\n+fi\n+\n+# ---- lifecycle: graceful restart still serves -----------------------------\n+echo \"-- [lifecycle] graceful restart\"\n+graceful\n+assert_eq 200 \"$($CURL -o /dev/null -w '%{http_code}' \"$base/\")\" \"serving after graceful restart\"\n+\n+scan_log_clean\n+summary\ndiff --git a/test/modules/http2/test_106_shutdown.py b/test/modules/http2/test_106_shutdown.py\nindex 33fe5adfda..2162a6f61d 100644\n--- a/test/modules/http2/test_106_shutdown.py\n+++ b/test/modules/http2/test_106_shutdown.py\n@@ -52,6 +52,17 @@ class TestShutdown:\n         # PR65731: invalid GOAWAY frame at session start when\n         # MaxRequestsPerChild is reached\n         # Create a low limit and only 2 children, so we'll encounter this easily\n+        #\n+        # This config uses ServerLimit, which only the dynamically-scaled\n+        # process MPMs (prefork/worker/event) register: it caps a daemon count\n+        # that floats below it. mpm_motorz uses a static fixed-size process pool\n+        # (StartServers IS the hard daemon limit, so ServerLimit is meaningless)\n+        # and does not register it, making the config a syntax error there.\n+        # (MaxConnectionsPerChild/MaxRequestsPerChild IS supported by motorz --\n+        # it is a core directive honored by the supervisor -- so only ServerLimit\n+        # forces the skip.)\n+        if env.mpm_module not in ['mpm_prefork', 'mpm_worker', 'mpm_event']:\n+            pytest.skip(f\"{env.mpm_module} does not support ServerLimit\")\n         conf = H2Conf(env, extras={\n             'base': [\n                 \"ServerLimit 2\",\n", "creation_timestamp": "2026-06-02T15:08:50.000000Z"}, {"uuid": "e76fa174-b3aa-4990-8cea-16d16f74136a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://t.me/true_secator/8179", "content": "\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0432\u0430\u0436\u043d\u044b\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 \u043d\u0438\u043c\u0438 \u0443\u0433\u0440\u043e\u0437\u044b, \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f:\n\n1. \u0412 Android \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f RCE-\u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c, CVE-2026-0073, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u041e\u0421 \u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 \u043e\u0442 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 \u0431\u0435\u0437 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0439 \u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0431\u0435\u0437 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c.\u00a0\n\n\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u0435\u0442 adbd (Android Debug Bridge daemon), \u0444\u043e\u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 Android \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 \u0441\u0432\u044f\u0437\u044c\u044e \u043c\u0435\u0436\u0434\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c \u0438 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u043e\u043c, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0443 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0435.\n\n\u041f\u043e\u043a\u0430 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u043f\u0440\u0438\u0437\u043d\u0430\u043a\u043e\u0432 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c CVE-2026-0073 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0430\u0441\u044c \u0432 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0445 \u0430\u0442\u0430\u043a\u0430\u0445 \u043d\u0435\u0442, \u043d\u043e \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043a\u0430.\n\n2. Apache \u0432\u044b\u043f\u0443\u0441\u0442\u0438\u043b\u0430 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u0434\u0435\u0441\u044f\u0442\u043a\u0430 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0432 HTTP Server \u0438 MINA, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0438 \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u043b\u0438 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b \u0434\u043b\u044f RCE.\n\n\u0412 Apache HTTP Server 2.4.67 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u0434\u043b\u044f 11 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439, 10 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u044e\u0442 \u0432\u0441\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0438.\n\n\u041f\u0435\u0440\u0432\u0430\u044f \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c - CVE-2026-23918, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0443 \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u043c\u044f\u0442\u0438 \u0438 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0432 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0435 HTTP/2. \u0418\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u044f \u043f\u0440\u0435\u0436\u0434\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0441\u0431\u0440\u043e\u0441, \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c DoS \u0438 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434.\n\n\u0414\u0440\u0443\u0433\u0430\u044f, CVE-2026-28780, \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0431\u0443\u0444\u0435\u0440\u0430 \u0432 \u043a\u0443\u0447\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0430\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 AJP-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0432\u044b\u0437\u044b\u0432\u0430\u044f DoS \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044f \u043a\u043e\u0434.\n\n\u0422\u0440\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438, CVE-2026-29168, CVE-2026-29169 \u0438 CVE-2026-33007, \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a DoS, \u0430 \u0435\u0449\u0435 \u0447\u0435\u0442\u044b\u0440\u0435 (CVE-2026-24072, CVE-2026-33857, CVE-2026-34032 \u0438 CVE-2026-34059) - \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u0438\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.\n\n\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u0430\u043a\u0436\u0435 \u0443\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u043d\u0435\u0439\u0442\u0440\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0435\u0439 CRLF (CVE-2026-33523), \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c HTTP-\u043e\u0442\u0432\u0435\u0442\u0430\u043c\u0438, \u0438 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0445 \u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 (CVE-2026-33006), \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043e\u0431\u0445\u043e\u0434\u0443 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 Digest.\n\n\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, Apache\u00a0\u043e\u0431\u044a\u044f\u0432\u0438\u043b\u0430\u00a0\u043e \u0432\u044b\u043f\u0443\u0441\u043a\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 MINA 2.2.7 \u0438 MINA 2.1.12, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0445 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0434\u0432\u0443\u0445 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439 - CVE-2026-42778 \u0438 CVE-2026-42779. \n\n\u041f\u0435\u0440\u0432\u0430\u044f - \u044d\u0442\u043e \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f CVE-2026-41409, \u043a\u043e\u0442\u043e\u0440\u0430\u044f, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u043b\u043d\u044b\u043c \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0434\u043b\u044f CVE-2024-52046, \u043d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0439 \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f RCE.\n\n\u0412\u0442\u043e\u0440\u0430\u044f - \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f CVE-2026-41635, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0435\u0439 \u0441\u043e\u0431\u043e\u0439 \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0449\u0443\u044e \u043a \u043e\u0431\u0445\u043e\u0434\u0443 \u0441\u043f\u0438\u0441\u043a\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043d\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044e \u043a\u043e\u0434\u0430.\n\n3. VulnCheck \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442, \u0447\u0442\u043e \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e CVE-2026-29014\u00a0(CVSS: 9,8) \u0432 CMS \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c MetInfo.\n\n\u041e\u043d\u0430 \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u0435\u0442 MetInfo CMS 7.9, 8.0 \u0438 8.1 \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0430\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u043e \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u043c PHP-\u043a\u043e\u0434\u043e\u043c.\n\n\u0417\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u0443\u044e \u043d\u0435\u0439\u0442\u0440\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 \u0434\u043b\u044f RCE \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u043d\u0430\u0434 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c.", "creation_timestamp": "2026-05-06T18:50:06.000000Z"}, {"uuid": "ae434ea7-ba11-47ec-beb0-1925f11a73c2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-34059", "type": "seen", "source": "https://bsky.app/profile/keiwork35.bsky.social/post/3mlxp2nhjfm2j", "content": "\u3010\u8106\u5f31\u6027\u60c5\u5831\u3011 CVE-2026-34059 Apache HTTP\u00a0Server\u306e\u8106\u5f31\u6027\u306b\u3064\u3044\u3066\n\nApache HTTP Server\u306b\u304a\u3051\u308b\u30d0\u30c3\u30d5\u30a1\u30aa\u30fc\u30d0\u30fc\u30ea\u30fc\u30c9\u306e\u8106\u5f31\u6027\u3067\u3059\u3002\u3053\u306e\u554f\u984c\u306f\u3001\u30d0\u30fc\u30b8\u30e7\u30f32.4.66\u307e\u3067\u306eApache HTTP Server\u306b\u5f71\u97ff\u3092\u53ca\u307c\u3057\u307e\u3059\u3002", "creation_timestamp": "2026-05-16T11:02:06.582736Z"}]}