fix: Publish the data source status before releasing ready waiters - #431
Draft
jsonbailey wants to merge 2 commits into
Draft
fix: Publish the data source status before releasing ready waiters#431jsonbailey wants to merge 2 commits into
jsonbailey wants to merge 2 commits into
Conversation
jsonbailey
marked this pull request as ready for review
September 2, 2026 21:50
kinyoklion
reviewed
Sep 2, 2026
|
|
||
| expect(listener.statuses.count).to eq(1) | ||
| expect(listener.statuses[0].state).to eq(Interfaces::DataSource::Status::VALID) | ||
| # The poll thread sets the ready event before it publishes the VALID |
Member
There was a problem hiding this comment.
Is there a reason it is in this order? Go was also doing this and I just swapped them because you would expect the status to be set before the ready event.
FDv1 polling and the FDv2 synchronizer loop both set the ready event before publishing the VALID data source status. A caller that returned from start could therefore read a status that did not yet reflect the successful poll. Both now publish the status first and set the ready event afterwards, matching the OFF path fixed in #429. The polling spec no longer needs to wait for the VALID status, because the broadcaster notifies listeners inline on the poll thread and the ready event is now set after that. The recoverable-error spec keeps its wait: that path publishes INTERRUPTED and never sets the ready event, so there is nothing to synchronize against.
jsonbailey
force-pushed
the
jb/polling-spec-flake
branch
from
September 9, 2026 21:58
ea48542 to
b3fcc65
Compare
jsonbailey
marked this pull request as draft
September 9, 2026 22:00
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.
Requirements
Related issues
Follows #429, which fixed the same ordering problem on the unrecoverable-error path. Reworked from a spec-only change after @kinyoklion pointed out that the ordering itself was the bug — matching the change he has already merged in the Go SDK.
Describe the solution you've provided
1. Publish the status before releasing ready waiters
PollingProcessor#pollset the ready event and only then publishedVALID. A caller that returned fromstartcould read adata_source_status_provider.statusthat still saidINITIALIZINGeven though the poll had succeeded and the flags were already in the store. The FDv2 synchronizer loop had the same ordering.Both now publish the status first and set the ready event afterwards. This is the same argument #429 made for the
OFFpath, applied to the success path.2. The success spec no longer needs to wait
This PR started as a spec-only fix that waited for the listener to be notified. With the ordering corrected that wait is unnecessary, so
status is set to valid when data is receivedis back to its original form, unchanged frommain.Worth correcting one claim from the original description: delivery here is not asynchronous. The polling spec uses
SynchronousExecutor, whosepostjust yields, soBroadcaster#broadcastcallslistener.updateinline. The distinction is which thread — inline on the poll thread, not the spec thread. That is why the old ordering was racy and why the new ordering makes the assertion deterministic: by the time@ready.setruns, the listener has already returned on that same thread.3. The recoverable-error spec still needs its wait
verify_recoverable_http_error(408, 429, 503) keepswait_for_count(2). That path publishesINTERRUPTEDand never sets the ready event, so there is no event to reorder and the pre-existingready.wait(1)was only passing time.ListenerSpy's mutex and condition variable stay for the same reason — without an event there is no happens-before for the cross-thread read, and a bare array appended from the poll thread and read from the spec thread is a real data race on JRuby.wait_for_statusis removed;wait_for_countis the only helper still used.Describe alternatives you've considered
Keeping the spec-only fix. It would have made CI green while leaving an observable ordering problem in the SDK, which is the same trade #429 declined.
Fixing the FDv2 initializer path as well. On success it sets the ready event and publishes no status at all — the status stays
INITIALIZINGuntil the synchronizer's first update lands. That looks like a larger bug than the ordering one and is worth its own discussion, so it is deliberately not touched here.Additional context
The broader problem is that listeners can only be registered after the constructor returns, so the initial status broadcast is unobservable no matter which order these two lines run in — and
UpdateSink#update_statusdrops a repeat of the same state, so it is never replayed. The reliable pattern is subscribe, then readdata_source_status_provider.status. Separatingstartfrom construction would remove the question entirely; that is being discussed separately.The new polling spec is a genuine regression guard — it fails deterministically without the production change, because it asserts that the listener does not observe an already-set ready event. The FDv2 spec is a contract assertion rather than a guard: it passes either way on CRuby, since the GIL hides the window there, and the FDv2 status broadcaster uses a real thread pool so a listener-ordering assertion would itself be racy.
The FDv2
sleepflake called out in the original description is now fixed onmainby #432.Validation
polling_spec.rb+fdv2_datasystem_spec.rbin a loop, 15 runs: 15 of 15 passed.