Skip to content

http: drain in-flight per-request coroutines on shutdown (#74)#77

Merged
EdmondDantes merged 1 commit into
mainfrom
74-server-shutdown-disposes-server_scope-while-per-request-coroutines-are-still-in-flight
Jun 1, 2026
Merged

http: drain in-flight per-request coroutines on shutdown (#74)#77
EdmondDantes merged 1 commit into
mainfrom
74-server-shutdown-disposes-server_scope-while-per-request-coroutines-are-still-in-flight

Conversation

@EdmondDantes
Copy link
Copy Markdown
Contributor

Fixes #74.

Server shutdown disposed server_scope while per-request handler coroutines were still suspended, aborting in debug builds (scope.c "Scope should be empty before disposal") and otherwise hanging the worker on the parked coroutines.

Fix

start() now drains server_scope on its lifecycle coroutine right after stop() wakes it (never inside stop(), which may be called from a handler living in server_scope), using the Scope's own orderly API:

  1. awaitCompletion(Async\timeout(grace)) — let in-flight handlers finish within the setShutdownTimeout grace window (default 5s; 0 = cancel immediately).
  2. cancel() + awaitAfterCancellation() — hard-terminate whatever is still parked and wait for it to settle.

A guarded fallback runs in http_server_free for the freed-mid-flight path (no-op in teardown/scheduler context).

server_scope is created with DISPOSE_SAFELY cleared so cancel() hard-terminates handlers instead of zombifying them — a dispose-safely scope would zombify them and awaitAfterCancellation would wait forever. Child request scopes inherit the non-safe mode.

Tests

  • New tests/phpt/server/core/045-shutdown-drains-inflight.phpt.
  • Full server phpt suite green (186: 46 core + 140) on debug; core 46/46 on release.
  • Verified end-to-end on release with the issue's request_context() repro: 60s hang → 0.08s clean shutdown.

Note: the request_context()-in-handler variant of the repro also needs php-async 8f0983f (request_context first-accessor null fix); without it the handler fatals and the drain has nothing to wait on.

@EdmondDantes EdmondDantes added this to the Next2 milestone Jun 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Coverage

Total lines: 81.35% → 81.26% (-0.09 pp)

File Baseline Current Δ Touched
src/core/http_connection.c 68.03% 68.03% +0.00 pp
src/core/http_connection.h 81.82% 81.82% +0.00 pp
src/http2/http2_strategy.c 77.10% 77.10% +0.00 pp
src/http3/http3_dispatch.c 79.45% 79.45% +0.00 pp
src/http3/http3_listener.c 75.96% 76.16% +0.20 pp
src/http_server_class.c 68.19% 67.71% -0.48 pp

Server shutdown disposed server_scope while per-request handler
coroutines were still suspended, which aborts in debug builds
(scope.c "Scope should be empty before disposal") and otherwise
leaves the worker hanging on the parked coroutines.

start() now drains server_scope on its lifecycle coroutine right
after stop() wakes it (never inside stop(), which may be called from
a handler living in server_scope), using the Scope's own orderly
API: awaitCompletion() within the shutdown-timeout grace window,
then cancel() + awaitAfterCancellation() for whatever is still
parked. A guarded fallback runs in http_server_free for the
freed-mid-flight path.

server_scope is created with DISPOSE_SAFELY cleared so cancel()
hard-terminates the handlers instead of zombifying them — a safe
scope would zombify them and awaitAfterCancellation would wait
forever. The grace window reuses the existing setShutdownTimeout
knob (default 5s; 0 = cancel immediately).

Adds tests/phpt/server/core/045-shutdown-drains-inflight.phpt.
@EdmondDantes EdmondDantes force-pushed the 74-server-shutdown-disposes-server_scope-while-per-request-coroutines-are-still-in-flight branch from 45e6525 to b9839d5 Compare June 1, 2026 11:24
@EdmondDantes EdmondDantes merged commit 554d8f3 into main Jun 1, 2026
8 checks passed
@EdmondDantes EdmondDantes deleted the 74-server-shutdown-disposes-server_scope-while-per-request-coroutines-are-still-in-flight branch June 1, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server shutdown disposes server_scope while per-request coroutines are still in-flight

1 participant