fix(IPC-33): bound the DrainEvents byte budget against the gRPC send cap, not just the pipe frame max
The worker budgeted a DrainEvents reply against the negotiated pipe frame maximum less one 64 KiB reserve, but the gateway returns that same MxCommandReply verbatim through Invoke, where MaxSendMessageSize applies. The two limits were coupled only from below (pipe >= gRPC + reserve), so any configuration the validator permitted with a wider gap let the worker pack a reply the pipe carried and the gRPC layer then refused with ResourceExhausted -- after Drain had already removed the events from the worker queue, losing them to both the caller and the event stream. Option (a) from the finding: close the coupling at both ends. - GatewayOptionsValidator adds the upper half of the headroom rule (Worker:MaxMessageBytes <= Protocol:MaxGrpcMessageBytes + 2 x reserve), with a message naming the DrainEvents budget as the reason and the two knobs to move. - WorkerPipeSession.ResolveDrainReplyByteBudget subtracts the reserve twice -- once for the pipe frame, once so the MxCommandReply wrapper clears the gRPC send cap -- keeping the MaxMessageBytes/2 floor that makes small frame maxima usable. The two changes are load-bearing for each other: the validator band is what makes "frame max - 2 x reserve" provably at or below the gRPC cap, and the doubled subtraction is what makes the band sufficient. Docs updated in the same change (GatewayConfiguration.md both option rows plus the headroom prose, WorkerFrameProtocol.md drain budget, gateway.md DrainEvents byte axis). Tests: new GatewayOptionsValidatorTests cases for the upper bound (reject) and both edges of the band (accept); GatewayOptionsTests' override case now moves both message-size knobs together, since the band makes moving one alone illegal. The worker half cannot be built on macOS; verified on windev separately.
This commit is contained in:
@@ -43,9 +43,23 @@ guarantees hellos, heartbeats, acks, and faults fit.
|
||||
Every worker-to-gateway frame must serialize within this limit, control replies
|
||||
included, so reply builders truncate to fit rather than emit a frame the writer
|
||||
will reject. `WorkerPipeSession` pre-sizes a `DrainEvents` reply below the
|
||||
negotiated maximum (less a fixed envelope/reply-wrapper reserve) and reports the
|
||||
truncation in the reply's `DiagnosticMessage`; the caller contract is to repeat
|
||||
`DrainEvents` until it returns an empty reply. Should a reply still overshoot,
|
||||
negotiated maximum and reports the truncation in the reply's
|
||||
`DiagnosticMessage`; the caller contract is to repeat `DrainEvents` until it
|
||||
returns an empty reply. The event budget is `max(MaxMessageBytes - 2 x 64 KiB,
|
||||
MaxMessageBytes / 2)`: the reserve is subtracted **twice** because the reply has
|
||||
two boundaries to clear, not one. The first reserve covers the
|
||||
`WorkerEnvelope`/`WorkerCommandReply`/`MxCommandReply` wrapper on the pipe; the
|
||||
second exists because the gateway returns that same `MxCommandReply` verbatim
|
||||
through `Invoke`, where the public gRPC send cap applies. `DrainEvents` removes
|
||||
the events it packs from the worker queue, so a reply the pipe carries and the
|
||||
gRPC layer then refuses would lose them for good — the gateway's validator
|
||||
therefore also caps `MxGateway:Worker:MaxMessageBytes` at
|
||||
`MxGateway:Protocol:MaxGrpcMessageBytes` plus **two** reserves, which is what
|
||||
makes the doubled subtraction sufficient rather than merely generous. The
|
||||
`MaxMessageBytes / 2` floor keeps the budget usable at frame maxima small enough
|
||||
that two reserves would consume them (below 256 KiB), where a collapsed budget
|
||||
would truncate every drain and never let the drain-until-empty caller finish.
|
||||
Should a reply still overshoot,
|
||||
`MessageTooLarge` at the reply-write seam is answered with a small
|
||||
`InvalidRequest` reply for that correlation, not with session teardown — no
|
||||
diagnostics command may kill a session.
|
||||
|
||||
Reference in New Issue
Block a user