Visitar URL original
[FIXED] MsgsTimeout iterator yields spurious (nil, nil) after a timeout by sueun-dev · Pull Request #2099 · nats-io/nats.go · GitHub
Skip to content

[FIXED] MsgsTimeout iterator yields spurious (nil, nil) after a timeout - #2099

Merged
piotrpio merged 1 commit into
nats-io:mainfrom
sueun-dev:fix-msgstimeout-spurious-yield
Jul 1, 2026
Merged

piotrpio merged 1 commit into
nats-io:mainfrom
sueun-dev:fix-msgstimeout-spurious-yield

Conversation

@sueun-dev

Copy link
Copy Markdown
Contributor

MsgsTimeout returns a range-over-func iterator. When NextMsg times out it
yields (nil, ErrTimeout). ErrTimeout is not fatal, so a consumer is allowed
to keep iterating (e.g. to wait for more messages or do periodic work between
batches). In that case the loop falls through to yield(msg, nil) with
msg == nil and err == nil, so the consumer gets a spurious (nil, nil) pair
after every timeout. Code that treats err == nil as "got a message" then
operates on a nil *Msg.

The Msgs() iterator returns after any error and doesn't have this. The fix is
a continue after the non-fatal timeout branch so it loops back to NextMsg
instead of falling through.

The existing with timeout test didn't catch it because it breaks on the first
timeout, so the iterator returns before reaching the second yield. The added
test keeps iterating across timeouts:

# before the fix
--- FAIL: TestSubscribeIterator/timeout_keeps_yielding_only_ErrTimeout
    nats_iter_test.go:105: iteration 1: got msg=<nil> with nil error, expected ErrTimeout
# after
ok  github.com/nats-io/nats.go/test

TestSubscribeIterator and TestQueueSubscribeIterator pass with -race.

AI assistance disclosure: this fix and test were prepared with the help of an AI
coding assistant; I reviewed and verified the change and tests myself.

Signed-off-by: Sueun Cho sueun.dev@gmail.com

NextMsg timeout returns ErrTimeout, which is not fatal, so a consumer is
allowed to keep ranging over MsgsTimeout. After yielding (nil, ErrTimeout)
the loop fell through to yield(msg, nil) with msg == nil and err == nil,
delivering a spurious (nil, nil) pair after every timeout. A consumer that
treats err == nil as a valid message then operates on a nil *Msg.

Add a continue after the non-fatal timeout branch so the iterator loops back
to NextMsg instead. Matches the Msgs() iterator, which returns after any
error. Adds a regression test that keeps iterating across timeouts; it failed
before the fix (got nil msg with nil error) and passes after.

Signed-off-by: Sueun Cho <sueun.dev@gmail.com>
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 85.495% (-0.04%) from 85.534% — sueun-dev:fix-msgstimeout-spurious-yield into nats-io:main

@piotrpio piotrpio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you for the contribution!

@piotrpio
piotrpio merged commit 77e280d into nats-io:main Jul 1, 2026
6 checks passed
piotrpio added a commit that referenced this pull request Jul 27, 2026
Resolved test conflicts by porting main's new embedded-server tests to the
testservice helpers:
- test/kv_test.go, jetstream/test/kv_test.go: added TestKeyValueUpdateRevisionMismatch
  and TestKeyValueDeleteRevisionMismatch, plus the new ErrKeyRevisionMismatch guard in
  TestKeyValueCreate (#2097/#2098).
- test/nats_iter_test.go: folded main's two phantom-nil MsgsTimeout subtests (#2093/#2099)
  into the migrated TestSubscribeIterator.

Signed-off-by: Piotr Piotrowski <piotr@synadia.com>
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.

3 participants