diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs index bff47b1..ce83155 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs @@ -237,15 +237,22 @@ public sealed class WorkerClient : IWorkerClient // CalculateSize. WriteLoopAsync stamps envelope.Sequence immediately before the write // (GWC-28), and a non-zero varint field grows the encoding — so the number computed here // is a lower bound on the frame the writer actually emits, never the frame length. Passing - // it as a knownSize would under-length the prefix and desync the worker's framing. The - // pre-check stays a pre-check: it is conservative in the right direction. + // it as a knownSize would under-length the prefix and desync the worker's framing. + // + // IPC-39: being a lower bound is conservative for the knownSize question but the wrong + // direction for this pre-check, which must not admit an envelope the writer will then + // reject with a session-fatal MessageTooLarge. Compare against the frame maximum less the + // MaxSequenceEncodingBytes the stamp can add, so every envelope this check accepts still + // fits once the sequence is written. int envelopeSize = commandEnvelope.CalculateSize(); - if (envelopeSize > _connection.FrameOptions.MaxMessageBytes) + int preCheckLimit = _connection.FrameOptions.MaxMessageBytes - MaxSequenceEncodingBytes; + if (envelopeSize > preCheckLimit) { throw new WorkerClientException( WorkerClientErrorCode.CommandTooLarge, $"Worker command {method} serializes to {envelopeSize} bytes, exceeding the negotiated " - + $"worker-frame maximum of {_connection.FrameOptions.MaxMessageBytes} bytes."); + + $"worker-frame maximum of {_connection.FrameOptions.MaxMessageBytes} bytes less the " + + $"{MaxSequenceEncodingBytes}-byte sequence-stamp allowance."); } await EnqueueAsync(commandEnvelope, cancellationToken).ConfigureAwait(false); @@ -455,6 +462,18 @@ public sealed class WorkerClient : IWorkerClient _stopCts.Dispose(); } + /// + /// Worst-case number of bytes 's sequence stamp adds to an + /// envelope that was sized before it: one tag byte plus a ten-byte varint, the maximum + /// encoding of a 64-bit protobuf field. The CommandTooLarge pre-check subtracts this + /// from the negotiated frame maximum (IPC-39) because it measures the envelope while + /// Sequence is still 0 — an unset field costs nothing to encode, so the measured size + /// is a lower bound on the frame the writer emits, and without the allowance an envelope in + /// the last eleven bytes of the range would pass the pre-check and then fault the session on + /// the write. + /// + private const int MaxSequenceEncodingBytes = 11; + /// Manages writing envelopes to the worker pipe. private async Task WriteLoopAsync() {