Skip to content

Deadlines, cancellation and the reader session, Python 3.11+ (2.5.1) - #54

Merged
majkelx merged 8 commits into
masterfrom
feature/timeouts
Nov 14, 2026
Merged

majkelx merged 8 commits into
masterfrom
feature/timeouts

Conversation

@majkelx

@majkelx majkelx commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

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.
  • 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).

Details and the cases covered by tests: #55.

Tests

New tests/test_timeouts.py and tests/test_cancellation.py; tests/test_policies.py extended.

Closes #41
Closes #32
Closes #47
Closes #19

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

1 open issue
What changed in this PR

This PR prepares serverish 2.5.0 with bounded publishing, reader cancellation hardening, and Python 3.11+ timeout APIs.

Changes:

  • Adds whole-call publish deadlines, per-attempt limits, and shorter journal defaults.
  • Updates reader recovery tracking and cancellation handling, with new regression tests.
  • Introduces a common timeout exception base, deprecates wait_for_psce(), and documents the release.
File Description
tests/​test_timeouts.py Tests deadlines, retries, deduplication, and reader recovery.
tests/​test_policies.py Updates assertions for retry defaults.
tests/​test_cancellation.py Tests reader cancellation and the deprecated shim.
serverish/​messenger/​policies.py Adds per-attempt timeout configuration.
serverish/​messenger/​msg_single_read.py Uses asyncio.timeout() for single reads.
serverish/​messenger/​msg_rpc_req.py Migrates shielded result waits to asyncio.timeout().
serverish/​messenger/​msg_reader.py Adds cancellation checks and recovery-budget tracking.
serverish/​messenger/​msg_publisher.py Implements publish deadlines and bounded attempts.
serverish/​messenger/​msg_journal_pub.py Adds five-second defaults and per-entry timeouts.
serverish/​messenger/​msg_document_read.py Migrates the initial document read timeout.
serverish/​messenger/​msg_core_read.py Migrates queue polling timeouts.
serverish/​base/​task_manager.py Migrates task waits and cancellation cleanup timeouts.
serverish/​base/​exceptions.py Introduces the shared MessengerTimeout base.
serverish/​base/​asyncio_util_functions.py Replaces wait_for_psce() with a deprecated shim.
README.md Adds the 2.5 release notes.
pyproject.toml Bumps to 2.5.0 and requires Python 3.11+.
doc/​messenger.md Documents deadline behavior and retry configuration.

Review effort: Balanced

Comment thread serverish/messenger/msg_reader.py Resolved
@majkelx 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
@majkelx
majkelx requested a balanced review from Copilot November 14, 2026 18:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread serverish/messenger/msg_publisher.py Resolved
Comment thread serverish/messenger/msg_reader.py Resolved

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Explicitly unlimited publish timeouts still inherit nats-py’s finite default, causing unexpected MessengerPublishAckTimeout errors.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Pass unlimited timeout to JetStream publish calls

serverish/​messenger/​msg_publisher.py:250

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.

1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@majkelx
majkelx merged commit 4b8b924 into master Nov 14, 2026
3 checks passed
@majkelx
majkelx deleted the feature/timeouts branch November 14, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants