You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Deadlines, cancellation and the reader session, Python 3.11+ (2.5.1) - #54
publish(timeout=X) is the deadline of the whole call, retries included. It used to apply to each of up to 3 attempts, so a call could take three times longer than asked.
Finite defaults. Without a timeout a publish gives up after RetryPolicy.total_timeout (15 s); a journal entry after 5 s, logged and never raised. A journal call on an unstable network can no longer hang.
New RetryPolicy.attempt_timeout (5 s): how long one attempt waits for the server's confirmation before asking again. Asking again is safe within the stream's duplicate window (Nats-Msg-Id).
Every timeout is a MessengerTimeout, which is also a builtin TimeoutError.
Readers: a finite total_timeout is a real deadline; its clock starts with a failure and stops when the server answers again.
A cancelled read_next() always ends. A cancellation lost in the client library (a timeout and a cancel in the same loop step) used to leave the reader polling for ever.
Python 3.11+.wait_for_psce() is deprecated, a shim over asyncio.timeout().
Documentation: doc/messenger.md, section "Errors, retries and deadlines"; release note in README.md.
Also in this pull request: the reader session (#55)
Merged into this branch after review; released together as 2.5.1 (2.5.0 was never published).
A reader's consumer and its subscription are one session, replaced as a whole, the new one first. A recovery cut short by the network could leave a reader open and deaf for good (seen in a long-running service after a power outage).
A session is judged by the server's answers to the reader's own pull requests; the periodic consumer_info poll is gone.
Only a missing stream is fatal; a missing consumer and a stream which takes no more consumers are not.
A refused pull request (409) is an error, not "no data".
RetryPolicy.total_timeout of a reader bounds an attempt to recover too (the review finding on this pull request).
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Recovery attempts can exceed the remaining total_timeout, allowing a late successful fetch to clear the error streak instead of propagating the saved error when the budget expires.
majkelx
changed the title
Deadlines and cancellation, Python 3.11+ (2.5.0)
Deadlines, cancellation and the reader session, Python 3.11+ (2.5.1)
Nov 14, 2026
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
A zero-timeout retry loop can block the event loop indefinitely, while swallowed consumer-deletion failures can leave session recovery blocked by the consumer limit.
When both attempt_timeout and total_timeout are UNLIMITED, omitting timeout here restores nats-py's finite JetStream-context default; it does not disable the client's timeout. For example, with attempts=0, a caller explicitly requesting unlimited time still gets MessengerPublishAckTimeout when that default expires. Pass the unlimited budget through to js.publish() as well, so the client does not impose an unrelated attempt limit.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because critical retry-state and buffered-delivery edge cases may stall message consumption or discard already-collected messages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes for users
publish(timeout=X)is the deadline of the whole call, retries included. It used to apply to each of up to 3 attempts, so a call could take three times longer than asked.RetryPolicy.total_timeout(15 s); a journal entry after 5 s, logged and never raised. A journal call on an unstable network can no longer hang.RetryPolicy.attempt_timeout(5 s): how long one attempt waits for the server's confirmation before asking again. Asking again is safe within the stream's duplicate window (Nats-Msg-Id).MessengerTimeout, which is also a builtinTimeoutError.total_timeoutis a real deadline; its clock starts with a failure and stops when the server answers again.read_next()always ends. A cancellation lost in the client library (a timeout and a cancel in the same loop step) used to leave the reader polling for ever.wait_for_psce()is deprecated, a shim overasyncio.timeout().Documentation:
doc/messenger.md, section "Errors, retries and deadlines"; release note inREADME.md.Also in this pull request: the reader session (#55)
Merged into this branch after review; released together as 2.5.1 (2.5.0 was never published).
consumer_infopoll is gone.RetryPolicy.total_timeoutof a reader bounds an attempt to recover too (the review finding on this pull request).Details and the cases covered by tests: #55.
Tests
New
tests/test_timeouts.pyandtests/test_cancellation.py;tests/test_policies.pyextended.Closes #41
Closes #32
Closes #47
Closes #19
🤖 Generated with Claude Code