fix(clients/python): make Consumer thread-safe and correct test xact pattern#129
Merged
fix(clients/python): make Consumer thread-safe and correct test xact pattern#129
Conversation
…pattern Two independent fixes uncovered when running clients/python/tests/ against a live Postgres: 1. Consumer.start() unconditionally registered SIGTERM/SIGINT handlers, which raises ValueError when the consumer runs in a worker thread (the common pattern for tests and embedded use). Detect non-main-thread and skip signal registration; callers stop via Consumer.stop() in that case. 2. The send/tick/receive tests interleaved pgque.send() and pgque.ticker() inside one transaction, then committed. The ticker's snapshot excludes the producing xact's own writes, so the resulting tick references no events and receive() returns []. Commit between send and force_tick so the ticker sees the new events. test_smoke additionally needed the missing create_queue() before subscribe(). 3. test_nack_routes_to_dlq_at_max_retries: nack() reads ev_retry from the canonical batch row, ignoring caller-supplied Message.retry_count. Set queue_max_retries=0 to route the first nack straight to the DLQ instead.
4 tasks
Owner
Author
REV review (Security / Bugs / Tests / Guidelines / Docs) — verdict: PASS (non-blocking nits)1. Security
2. Bugs
3. Test analysis
4. Guidelines
5. Docs
Blockers0 blocking issues. Three non-blocking suggestions:
Verdict: PASS — safe to merge once #84 lands. Generated by Claude Code |
This was referenced Apr 30, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Why
PR #84 turns on real per-client driver tests against live Postgres. The Python suite exposes a real driver bug plus a handful of test patterns that always failed against a live ticker:
Consumer.start()registered SIGTERM/SIGINT signal handlers unconditionally.signal.signal()raisesValueErrorwhen called from a non-main thread, so any caller that runs the consumer from a worker thread (the standard pattern for tests, embedded use, and most multi-consumer apps) crashed before doing any work.pgque.send()andpgque.ticker()inside one transaction, then committed. The ticker's snapshot excludes the producing xact's own writes, so the resulting tick references no events andreceive()returns[]. Worked in isolation only becauseforce_tick()advances the sequence — it does not actually rotate.test_smokecalledpgque.subscribe()without first creating the queue.test_nack_routes_to_dlq_at_max_retriessynthesised a retried message by settingMessage.retry_count = 1in Python. Per Bug: nack() DLQ path trusts caller-supplied pgque.message and allows forged DLQ rows #98,pgque.nack()readsev_retryfrom the canonical batch row and ignores caller-supplied values, so the assertion never fired.What changes
clients/python/pgque/consumer.py: detect non-main-thread invocation and skip signal registration; callers stop viaConsumer.stop()in that case.clients/python/tests/test_send.py,test_receive.py,test_nack.py,test_consumer.py: commit betweenclient.send()andpgque.force_tick()so the ticker sees the new events.clients/python/tests/test_smoke.py: add the missingpgque.create_queue()beforesubscribe().clients/python/tests/test_nack.py: setqueue_max_retries = 0so the first nack routes to DLQ, instead of relying on a client-sideretry_countmutation that the SQL ignores.Coordination
Depends on #84 for live-PG CI to actually exercise these tests. Once merged, the Python job on PR #84 should go green on rebase.
Test plan
PGQUE_TEST_DSN=... pytest -v clients/python/tests/-> 39 passed locally on PG 16Generated by Claude Code