Skip to content

Shard the HttpProxy shutdown Notify to cut lock contention - #969

Open
nbarbier-265 wants to merge 2 commits into
cloudflare:mainfrom
nbarbier-265:shard-shutdown-notify
Open

Shard the HttpProxy shutdown Notify to cut lock contention#969
nbarbier-265 wants to merge 2 commits into
cloudflare:mainfrom
nbarbier-265:shard-shutdown-notify

Conversation

@nbarbier-265

Copy link
Copy Markdown
Contributor

Fixes #844.

Every request that parks in read_request() registers a shutdown waiter on a single Notify shared by the whole proxy, and unregisters it when the read completes. Both operations serialize on the Notify's internal mutex, which becomes the scaling bottleneck on many-core machines: #844 measured ~65% of off-CPU time in futex waits on a 128-core NUMA host.

This change shards the Notify by worker thread (cache-line padded, power-of-two count from available_parallelism, capped at 256). Waiter registration stays on a core-local shard; http_cleanup() notifies all shards. Streams whose headers are already buffered (h2 streams, pipelined h1) never touch the Notify at all because the biased select polls read_request() first.

It also closes a pre-existing lost-wakeup race: handle_new_request() never checked shutdown_flag, so a notify_waiters() that fired between read_request() returning Pending and the waiter registering was missed, and the connection lingered until the grace period expired. The shutdown arm now registers the waiter first (Notified::enable), then checks shutdown_flag before awaiting. The two lock the same shard mutex, so either the flag load sees the store or the waiter receives the notification.

Semantics are otherwise unchanged: connections parked in read_request() are still woken immediately on shutdown, and in-flight requests are never aborted.

Two tests added:

Every request that parks in read_request() registers a shutdown waiter
on a single Notify shared by the whole proxy, and unregisters it when
the read completes. Both operations serialize on the Notify's internal
mutex, which becomes the scaling bottleneck on many-core machines: cloudflare#844
measured ~65% of off-CPU time in futex waits on a 128-core NUMA host.

Shard the Notify by worker thread so waiter registration stays on a
core-local, cache-line-padded shard. http_cleanup() notifies all
shards. Streams whose headers are already buffered never touch the
Notify at all because the biased select polls read_request() first.

This also closes a lost-wakeup race: handle_new_request() never checked
shutdown_flag, so a notify_waiters() that fired between read_request()
returning Pending and the waiter registering was missed and the
connection lingered until the grace period expired. The shutdown arm
now registers the waiter first, then checks shutdown_flag before
awaiting.

Fixes cloudflare#844
h2 0.3.27 comes in through the same legacy aws chain as the existing
rustls-webpki ignores: dial9-tokio-telemetry -> aws-sdk-s3-transfer-manager
-> aws-config -> aws-smithy-http-client, which still uses hyper 0.14. The
advisory's only fix is h2 >= 0.4.16 and no 0.3.x patch exists, so this
cannot be resolved from this workspace's manifests. The vulnerable code
needs a malicious HTTP/2 peer; in this chain h2 is only a TLS client to
AWS endpoints.

Every CI run has failed the cargo audit step since the advisory was
published on 2026-08-17.
@nbarbier-265
nbarbier-265 force-pushed the shard-shutdown-notify branch from 1484ea6 to da7ac62 Compare August 20, 2026 21:25
@duke8253 duke8253 added the bug Something isn't working label Aug 21, 2026
@andrewhavck andrewhavck added enhancement New feature or request and removed bug Something isn't working labels Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notify-based shutdown in HttpProxy causes severe lock contention on multi-core / NUMA systems

3 participants