Fix relay readiness and drain checks in the resilience harness - #488
Open
jmchilton wants to merge 2 commits into
Open
Fix relay readiness and drain checks in the resilience harness#488jmchilton wants to merge 2 commits into
jmchilton wants to merge 2 commits into
Conversation
The relay registers a poll-waiter only while a long poll is parked in its storage loop, so /messages/poll/stats reports a live consumer intermittently - holding one long poll open and sampling every 50ms showed the waiter in 16-60% of samples, with gaps up to 0.5s. Both checks read that as steady state. wait_until_consuming could therefore time out against a healthy Pulsar: on CI it polled every ~0.198s for 60s and read zero every time while the relay was creating a waiter every 2.004s without a break. A fixed interval can beat against the relay's cycle and miss every window, so jitter it. _wait_relay_setup_waiters_drained took a single zero as proof the old waiter was gone, which a live consumer satisfies on the first sample - the guard has not been doing anything. Require the zeros to persist. Also report the bind marker, sample count and last stats body on timeout, and pin the relay image so an unpinned :latest can't change harness behaviour with no commit here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
This PR description was drafted and posted by Claude (AI assistant) on jmchilton's behalf.
Summary
The resilience harness treats the relay's
/messages/poll/statsas a steady-state "is a consumer attached" signal. It isn't. Both readiness and drain read it wrong, in opposite directions.Evidence
Holding a single long poll open against
ghcr.io/mvdbeek/pulsar-relay:0.2.1and sampling the stats endpoint every 50ms:A second run of the same experiment measured 59.5%, with the longest run of zeros at 0.51s. The relay registers a poll-waiter only while a long poll is parked in its storage loop, so a live consumer reads as absent in most samples.
That breaks two checks:
wait_until_consumingcan time out against a healthy Pulsar. Seen on CI:TimeoutError: Pulsar did not bind relay consumers within 60.0swhile the relay log shows an unbroken chain of 34 poll waiters, one every 2.004s, across the whole 60s. The harness polled every ~0.198s and got HTTP 200 every time, so it was sampling a live consumer ~300 times and reading zero each time. A fixed poll interval can beat against the relay's ~2s cycle and land in the invisible part of every one._wait_relay_setup_waiters_drainedreturns immediately, always. It took a single zero as proof the old waiter was gone. With this duty cycle it reports "drained" on the first sample even when a consumer is fully alive — so the guard it exists to provide (don't let readiness pass against a stale registration) has not been doing anything.Changes
RELAY_DRAIN_CONFIRM_SECONDS(2s, ~4x the largest observed gap) before calling the waiters drained.RELAY_DRAIN_TIMEOUTgoes 7s → 10s to make room.TimeoutError, so the next occurrence diagnoses itself instead of needing this investigation again._relay_setup_waiter_count()into_relay_poll_stats()+_setup_waiter_count(stats)so the raw body is available for that message.0.2.1@sha256:b677658c…), overridable withRELAY_IMAGE. The readiness checks depend on the shape of an undocumented endpoint of that image; leaving it on:latestmeans its behaviour can change with no commit here.The topic-key parsing was not the problem — keys are namespaced
<uuid>/job_setupand the existingendswith("/job_setup")matches correctly. Verified against a live relay.Validation
test/resilience/scenarios/test_pulsar_restart.pyagainst a real stack: 4 passed in relay mode (includingtest_a2_sigkill_during_execution, the CI failure above), then 12 passed across all three messaging modes.Open question
Pinning the relay image means this harness no longer catches relay-side drift. If tracking the relay's tip was the point of
:latest, a scheduled job running withRELAY_IMAGE=ghcr.io/mvdbeek/pulsar-relay:latestwould give that back without letting an upstream change turn a PR red for reasons unrelated to it. Happy to switch to that shape.