Skip to content

Add jetstream consumer priority groups (ADR-42) - #1015

Draft
caspervonb wants to merge 2 commits into
mainfrom
js-priority-groups
Draft

Add jetstream consumer priority groups (ADR-42)#1015
caspervonb wants to merge 2 commits into
mainfrom
js-priority-groups

Conversation

@caspervonb

Copy link
Copy Markdown
Collaborator

Supersedes #786, which had drifted behind main. Carries @oliverlambson's original commit as-is, plus a follow-up commit addressing the review findings on that PR and a few more found while rebasing:

  • PriorityPolicy.NONE sent ""; the server only accepts "none".
  • priority=0 (the highest priority) was dropped from pull requests by a truthiness check.
  • min_pending/min_ack_pending/priority were only validated on the single-message fetch path.
  • ConsumerInfo raised on any consumer with an unpinned priority group, since pinned_client_id was required but the server omits it until a client is pinned. pinned_ts is now parsed as well.
  • Overflow/pinned/unpin tests were gated on 2.12 instead of 2.11.

Closes #784

oliverlambson and others added 2 commits August 29, 2026 18:21
- Use "none" as the wire value for PriorityPolicy.NONE; the server
  rejects an empty string.
- Send priority=0 in pull requests; it is the highest priority, and
  a truthiness check dropped it.
- Validate min_pending/min_ack_pending/priority in fetch() so the
  batch path is covered too, not only _fetch_one.
- Make PriorityGroupState.pinned_client_id optional and parse
  pinned_ts; the server omits both until a client is pinned, which
  made consumer_info raise on any unpinned group.
- Convert priority_groups in ConsumerInfo.from_response.
- Only send priority_timeout when set.
- Gate overflow/pinned/unpin tests on 2.11, not 2.12.
@caspervonb caspervonb added the nats-legacy Legacy asyncio NATS client label Aug 29, 2026
@synadia-claude-reviewer

synadia-claude-reviewer Bot commented Aug 29, 2026

Copy link
Copy Markdown

Claude finished @caspervonb's task in 5m 21s —— View job


PR Review: Add JetStream Consumer Priority Groups (ADR-42)

  • Gather context and read PR diff
  • Review nats/src/nats/js/api.py changes
  • Review nats/src/nats/js/client.py changes
  • Review nats/src/nats/js/errors.py changes
  • Review nats/src/nats/js/manager.py changes
  • Review nats/tests/test_js.py changes
  • Post inline comments for specific issues
  • Post overall summary

Summary

The PR correctly addresses all the bugs called out in the description (the empty PriorityPolicy.NONE, the priority=0 truthiness drop, the ConsumerInfo crash on unpinned groups, and the wrong version gate). The overall structure — new enum, new config fields, PriorityGroupState, unpin_consumer, and three-parameter fetch() — is clean and follows existing patterns. A few things worth discussing:

Correctness

  • min_pending / min_ack_pending truthiness (inline comment, line 1175): priority=0 was correctly fixed with an is not None guard, but min_pending and min_ack_pending still use if min_pending: / if min_ack_pending:. Today the > 0 validation makes this safe, but it's an inconsistency that could silently break if constraints change. Low risk, easy fix.

  • PIN_ID_MISMATCH behaviour differs between fetch(1) and fetch(n) (inline comment, line 1311): _fetch_one converts a 423 to TimeoutError (via _is_temporary_error). In _fetch_n's no-wait phase the 423 clears the pin and falls through without raising; the caller may get an empty list instead of an error. Whether this is intentional ("best-effort batch") or an oversight is worth a comment in the code if intentional.

Style

  • "Nats-Pin-Id" literal repeated 5× (inline comment, line 1212): Other NATS header names live in the Header enum in api.py; PIN_ID = "Nats-Pin-Id" belongs there.

No issues found in api.py (serialisation round-trip, nanosecond conversion, optional pinned_client_id/pinned_ts), errors.py (new 423 class wired into from_error), manager.py (unpin_consumer), or the tests (version gating, overflow/pinned/unpin/prioritized scenarios all look correct).

pin_id = self.pin_id
if pin_id:
next_req["id"] = pin_id
if min_pending:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The priority fix correctly uses if priority is not None: to handle priority=0, but min_pending and min_ack_pending still use truthiness checks here (and in two more spots in _fetch_n). Currently safe because validation rejects 0, but the inconsistency could become a silent bug if those constraints ever relax. Suggest if min_pending is not None: / if min_ack_pending is not None: throughout for consistency.

Fix this →

# Any other type of status message is an error.
raise nats.js.errors.APIError.from_msg(msg)
else:
pin_id = msg.headers.get("Nats-Pin-Id") if msg.headers else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Nats-Pin-Id" is duplicated across 5 lines in _fetch_one and _fetch_n. The existing Header enum in api.py is the canonical home for NATS header names — adding PIN_ID = "Nats-Pin-Id" there would eliminate the repeated literals.

Fix this →

# a possible i/o timeout error or due to a disconnection.
got_any_response = True
pass
elif JetStreamContext._is_pin_id_mismatch_error(status):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

_fetch_one raises nats.errors.TimeoutError on PIN_ID_MISMATCH (via _is_temporary_error), whereas here in _fetch_n a PIN_ID_MISMATCH in the no-wait phase just clears the pin and falls through silently, eventually returning an empty or partial result. The behaviour is observable: fetch(1) raises on mismatch, fetch(2) does not. Raising (or at minimum documenting) the difference would avoid surprise for callers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

nats-legacy Legacy asyncio NATS client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing support for jetstream consumer groups

2 participants