Skip to content

Flush the request body guard cross-process-safe - #2

Merged
samwar merged 1 commit into
mainfrom
flush-req-body-guard
Jul 20, 2026
Merged

Flush the request body guard cross-process-safe#2
samwar merged 1 commit into
mainfrom
flush-req-body-guard

Conversation

@samwar

@samwar samwar commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

Merge the flush-req-body-guard branch (already tagged as 1.12.1-fe and consumed downstream) into main so the fix is on the default branch. Upstream tracking PR: webmachine/webmachine#349 (open).

What it does

webmachine_decision_core:finish_response/2's maybe_flush_req_body step (added in 1.12) checks mochiweb_request_recv in the process dictionary to decide whether the body still needs draining. That flag is set by webmachine_request:call({req_body, ...}) inside wrq:req_body/1, but applications that dispatch body reads to a separate worker process end up with the flag on the worker's pdict while the mochiweb loop process's pdict stays empty. The drain then runs and hits the FLUSH_TIMEOUT (5000 ms).

Fix: consult ReqState#wm_reqstate.bodyfetch as a fallback. bodyfetch is set to standard or stream in the same call that would set the pdict flag, and unlike the pdict it travels with the ReqState value across process boundaries. Applications that thread the updated ReqState back to the mochiweb loop process (via the returned Request's wm_state field) hit the fallback and short-circuit the drain.

The existing pdict guard remains as the fast path for the common single-process case. FLUSH_TIMEOUT = 5000 ms stays as the safety net for edge cases where neither signal was set.

Also carried on this branch:

  • Guard the undefined branch on get(mochiweb_request_recv) =:= true — eager reads mark the request as recv'd; there's nothing left on the socket to drain.
  • Thread ?FLUSH_TIMEOUT through a new recv_stream_body/3 variant that the drain fallback uses, so a misbehaving peer cannot wedge the pipeline. Legitimate streaming callers keep ?IDLE_TIMEOUT = infinity via the 2-arg wrapper.

Files

  • src/webmachine_request.erl — 93 insertions, 42 deletions.

Status

webmachine_decision_core:finish_response/2's new maybe_flush_req_body
step (added in 1.12) checks mochiweb_request_recv in the process
dictionary to decide whether the body still needs draining. That flag
is set by webmachine_request:call({req_body, ...}) inside
wrq:req_body/1, but applications that dispatch body reads to a
separate worker process end up with the flag on the worker's pdict
while the mochiweb loop process's pdict stays empty.

Consult ReqState#wm_reqstate.bodyfetch as a fallback. bodyfetch is
set to `standard` or `stream` in the same call that would set the
pdict flag, and unlike the pdict it travels with the ReqState value
across process boundaries. Applications that thread the updated
ReqState back to the mochiweb loop process (via the returned
Request's wm_state field) will hit the fallback and short-circuit
the drain.

Keep the existing pdict guard as the fast path for the common
single-process case, and keep FLUSH_TIMEOUT=5000 as the safety
net for edge cases where neither signal was set.

Also keep the 1.12.1-fe base fix: guard the `undefined` branch on
get(mochiweb_request_recv) =:= true and return `true` immediately
in that case (eager reads mark the request as recv'd; there's
nothing left on the socket to drain), and thread ?FLUSH_TIMEOUT
through a new recv_stream_body/3 variant that the drain fallback
uses so a misbehaving peer cannot wedge the pipeline. Legitimate
streaming callers keep ?IDLE_TIMEOUT = infinity via the 2-arg
wrapper.
@samwar
samwar merged commit 7efc292 into main Jul 20, 2026
6 of 18 checks passed
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.

1 participant