jetstream: surface ErrMaxBytesExceeded to ConsumeErrHandler#2077
Conversation
When a single message in the stream is larger than the configured PullMaxBytes, the server returns a 409 "Message Size Exceeds MaxBytes" status on every MSG.NEXT and the client keeps retrying until either the offending message is removed or PullMaxBytes is raised. handleStatusMsg already detected the status but returned (nil, nil), so neither the consumer's terminal error path nor the user-facing ErrHandler ever observed it. The retry loop therefore ran silently and burned CPU on both client and server until the user noticed (see nats-io#1718 for the original report). Return ErrMaxBytesExceeded as the non-terminal notification error so that callers using ConsumeErrHandler are informed. The retry behaviour itself is unchanged; whether the loop should stop, back off or auto-raise PullMaxBytes is a separate design discussion (see the issue thread) and is left out of this change. Closes nats-io#1718. Signed-off-by: Ian Chechin <ian00chechin@gmail.com>
|
Claude finished @Deln0r's task in 3m 7s —— View job PR Review: jetstream: surface ErrMaxBytesExceeded to ConsumeErrHandlerTasks:
SummaryThe fix is correct and minimal. The core change in Analysis
The return signature is termErr, notifyErr := sub.handleStatusMsg(msg, msgErr)
if termErr == nil {
sub.checkPending() // ✅ still fires — consumer keeps retrying
...
}
sub.Unlock()
if sub.consumeOpts.ErrHandler != nil && notifyErr != nil {
sub.consumeOpts.ErrHandler(sub, notifyErr) // ✅ now reached
}
ErrHandler call rate Because
At if termErr, _ := s.handleStatusMsg(msg, msgErr); termErr != nil { ... }This is fine in practice because
Test ( Well-structured. Buffered error channel with a No blocking issuesNo correctness bugs, race conditions, API compatibility breaks, or security concerns introduced. The change does what it says. |
Closes #1718.
Problem
When a single message in the stream is larger than the configured
PullMaxBytes, the server responds with409 Message Size Exceeds MaxByteson everyMSG.NEXT.handleStatusMsgalready detects the status but returns(nil, nil), so neither the terminal error path nor the user'sConsumeErrHandlerever sees the condition. The consumer keeps retrying silently and burns CPU on both client and server until either the offending message is removed orPullMaxBytesis raised.Maintainer discussion in the issue thread confirmed this is a client-side concern.
Change
Return
ErrMaxBytesExceededas the non-terminal notification error fromhandleStatusMsg. The error then flows through the existingConsumenotification path and is delivered to the user'sConsumeErrHandler.ErrTimeoutandErrBatchCompletedcontinue to fall through silently — those are internal pull-flow signals, not user-facing errors.The retry behaviour itself is intentionally unchanged. Whether the loop should stop, back off, or auto-raise
PullMaxBytesis a separate design question (see #1718 discussion); this change just makes the situation observable.Test plan
A new subtest
TestPullConsumerConsume/max_bytes_exceeded_surfaces_to_ErrHandlerinjetstream/test/pull_test.gopublishes a single 1 KiB message, startsConsumewithPullMaxBytes(100), and asserts that the registeredConsumeErrHandlerreceives anErrMaxBytesExceededwithin 3 s.