Skip to content

Honor QUIESCE/SHUTDOWN in the send-stall retry loops - #206

Merged
mgrossmann merged 1 commit into
mainfrom
issue-205-stall-honors-quiesce
Aug 18, 2026
Merged

Honor QUIESCE/SHUTDOWN in the send-stall retry loops#206
mgrossmann merged 1 commit into
mainfrom
issue-205-stall-honors-quiesce

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Fixes #205

The paced no-progress retry from #200 (send_all() in httpprtv.c) and #202 (send_raw_all() in httpsend.c) waited out up to 10 s of closed receive window without looking at httpd->flag — a worker sitting in that loop when the operator enters P HTTPD delayed shutdown by up to the full stall budget, since shutdown waits for the workers (#122 family).

Both loops now check HTTPD_FLAG_QUIESCE | HTTPD_FLAG_SHUTDOWN before each pause and fail the send instead of waiting, so a shutdown interrupts an in-progress stall within one SEND_STALL_PAUSE (100 ms).

Deliberately narrow: only the stall wait is cut short. A send making progress (rc > 0) continues during quiesce, so in-flight responses to healthy clients still drain gracefully. A cut-off in the chunked path goes through the torn-chunk handling from #202 (framing dropped, keep-alive cleared, CSTATE_DONE set).

Verification

  • make modules clean (cc370, -Wall -Werror), make test-host 98 assertions pass.
  • Live check on MVS: start a throttled fetch of a large chunked response (curl --limit-rate 1k on /.dm?l=4096), issue P HTTPD mid-transfer; the server must come down promptly (within ~100 ms of the workers' next stall iteration) instead of waiting out the stall budget, and the curl gets a truncated-but-cleanly-closed connection.

The paced no-progress retry in send_all() and send_raw_all() waits out
up to 10 seconds of closed receive window without looking at
httpd->flag. A worker sitting in that loop when the operator enters
P HTTPD keeps sleeping and retrying against a client that has stopped
reading, and shutdown waits for the workers -- so a stalled client
delayed the stop by up to the full stall budget. Same family as #122:
every wait a worker performs has to honor quiesce.

Both loops now check HTTPD_FLAG_QUIESCE/SHUTDOWN before each pause and
fail the send instead of waiting, so a shutdown interrupts an
in-progress stall within one SEND_STALL_PAUSE. Deliberately narrow:
only the stall wait is cut short. A send that is making progress
continues during quiesce, so in-flight responses to healthy clients
still drain gracefully; only a client that has stopped reading while
the server is stopping is cut off, and in the chunked path the torn-
chunk handling (drop framing, clear keep-alive) applies as usual.

Fixes #205
@mgrossmann

Copy link
Copy Markdown
Contributor Author

Live-Test auf MVS: die Stall-Strecke selbst ist erstmals bestätigt, die Quiesce-Prüfung dieses PRs aber noch nicht.

Warum ein Stall von außen schwer zu erzeugen ist. Die MVS-Sockets laufen bei dieser Konfiguration über Host-Sockets (Linux-Listener auf 0.0.0.0:8080), und der Linux-Sendepuffer tunt bis tcp_wmem max = 4 MB hoch. Ein Client, der nichts liest, blockiert HTTPD also erst, wenn mehr als ~4 MB ungelesen anstehen. Alle kleineren Versuche (eine 94-KB-Antwort, 20 Requests = 1,9 MB, 60 Requests = 5,7 MB) wurden vollständig ausgeliefert — der Worker war nie im Stall. Auch --limit-rate hilft nicht: ein Client, der langsam aber stetig liest, macht Fortschritt, und der Stall-Zähler wird bei jedem rc > 0 zurückgesetzt.

Bestätigt (#200/#202, Stall-Budget): 150 pipelined Requests (~13,8 MB), Client liest 40 s lang gar nichts, SO_RCVBUF=2048 von einem Linux-Client:

drained 2.26 MB in 3.1s, server_closed=True

Nur 2,26 von 13,8 MB kamen an, danach hat der Server die Verbindung geschlossen. Genau das erwartete Verhalten: Puffer voll → send() liefert EWOULDBLOCK → Stall-Schleife → nach dem 10-Sekunden-Budget CSTATE_DONE und Abbruch. Kein Endlos-Spin (#199), kein Hänger, sauberer Close.

Nicht bestätigt (dieser PR): Dass ein P HTTPD einen laufenden Stall innerhalb von ~100 ms abbricht statt das Budget auszusitzen. Dafür müsste das P in das ~10-Sekunden-Fenster fallen, in dem der Worker pausiert. Ein früherer Versuch mit einem lesenden Client (--limit-rate 1k) zeigte die andere Hälfte des Designs korrekt: Listener nach 1,22 s zu, die laufende Antwort wurde über 58 s vollständig ausgeliefert — ein gesunder Client wird während der Quiesce also nicht abgewürgt.

Warnung für Nachahmer: Der 150-Request-Test hat Hercules zum Absturz gebracht (SIGSEGV im Watchdog-Diagnosepfad, HHC00822S PROCESSOR CPnn APPEARS TO BE HUNG!threads_cmd → Segfault). Das ist ein Hercules-Bug, kein HTTPD-Problem, aber die Last provoziert ihn zuverlässig.

@mgrossmann
mgrossmann merged commit f292e23 into main Aug 18, 2026
1 check passed
@mgrossmann
mgrossmann deleted the issue-205-stall-honors-quiesce branch August 18, 2026 07:56
@mgrossmann

Copy link
Copy Markdown
Contributor Author

Live-Nachweis erbracht — GRÜN. Er war erst möglich, nachdem der darunterliegende Plattformfehler behoben war: Hercules' X'75'-SEND (tcpip.c) rief ein blockierendes Host-send() direkt auf dem CPU-Thread, wodurch ein nicht lesender Client einen CP einfror und der Watchdog den Emulator absichtlich crashte (impl.c:731 CRASH()). Auf dieser Plattform kam bei einem vollen Sendepuffer also nie ein EWOULDBLOCK beim Gast an — die Stall-Strecke aus #200/#202/#206 war schlicht unerreichbar. Mit gepatchtem SEND (MSG_DONTWAIT, Rückgabevertrag wie RECV/ACCEPT) liefert die Plattform jetzt echte EWOULDBLOCKs.

Testaufbau: durchgehende Stall-Last statt Einzelschuss — 4 gestaffelte Verbindungen, je 150 pipelined Requests (~14 MB), die nie lesen und sich alle 15 s erneuern. Dass die Worker wirklich in Stall-Pausen sitzen, ist an der Antwortzeit messbar: 0,95 s statt der üblichen 0,07 s.

Ergebnis:

12:50:23  P HTTPD
12:50:23  HTTPD098I HTTPD SHUTTING DOWN        <-- dieselbe Sekunde
12:50:26  IEF404I HTTPD - ENDED

Port 8080 nach 1,12 s zu, Adressraum nach 3 s beendet — auf dem Niveau eines Stops ganz ohne Last (Baseline 1,22 s). Kein C HTTPD nötig, kein wedged Worker, Emulator unbeeindruckt. Ohne die Quiesce-Prüfung hätte jeder blockierte Worker sein Restbudget ausgesessen (bis 10 s pro Worker, bei sich erneuernden Stalls entsprechend länger); das dokumentierte Verhalten vorher war „Adressraum bleibt stehen, nur C HTTPD hilft".

Nebenbefund, ebenfalls erstmals live bestätigt: Das Stall-Budget aus #200/#202 feuert — 150 pipelined Requests mit 40 s Funkstille enden mit serverseitigem Close statt Endlos-Spin, während httpd parallel in ~90 ms weiter ausliefert.

Repro, Patch und beide Messreihen sind in docs/hercules-x75-send-stall-repro.md festgehalten.

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.

Send-stall retry loops do not honor QUIESCE/SHUTDOWN: a stalled client delays P HTTPD by up to 10 s

1 participant