Fix flaky TestDataSampler/GetSamplesForPCollectionsTooManySamples#38736
Conversation
Since GetSamples is a destructive operation that clears stored samples, polling it in a loop is flaky. If it is called before all asynchronous samples have finished processing, it consumes the early samples and clears them, causing verifySampledElements to fail. In this PR, we wait for the sampler to finish before reading to ensure all elements are successfully captured.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a race condition in the DataSampler test suite where the destructive nature of the GetSamples operation caused tests to fail when polled prematurely. By removing the loop and waiting for the sampler to complete its work, the test stability is significantly improved. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request simplifies the test logic in TestDataSampler by replacing a polling loop with a hardcoded sleep of 1 second. The reviewer correctly notes that a hardcoded sleep is a test anti-pattern that can lead to flakiness in CI environments and unnecessary delays. They recommend implementing a polling and accumulation loop with a much smaller sleep interval (10ms) to ensure both speed and reliability.
| time.Sleep(1 * time.Second) | ||
| samples := dataSampler.GetSamples(test.pids) | ||
| samplesCount := len(samples) |
There was a problem hiding this comment.
Using a hardcoded time.Sleep(1 * time.Second) in unit tests is an anti-pattern. It introduces unnecessary delays when tests run quickly, and can still cause flakiness under heavy CI load if the asynchronous operations take longer than 1 second.
Since GetSamples is a destructive operation that clears the internal buffer, we can resolve this by polling and accumulating the samples in a loop with a small sleep interval (e.g., 10ms) until all expected samples are received. This makes the test both faster and highly resilient.
samples := make(map[string][]*DataSample)
for i := 0; i < 100; i++ {
for pid, s := range dataSampler.GetSamples(test.pids) {
samples[pid] = append(samples[pid], s...)
}
match := true
for pid, wantSamples := range test.want {
if len(samples[pid]) < len(wantSamples) {
match = false
break
}
}
if match {
break
}
time.Sleep(10 * time.Millisecond)
}
samplesCount := len(samples)There was a problem hiding this comment.
Is it really anti-pattern?
There was a problem hiding this comment.
It is. And 1 second is especially long.
These days one might try and use the synctest package which subverts time, but the nature of beam kinda prevents the necessary goroutine shenanigans.
I need to look at this on a real screen before i stamp it. Synctest might work if this isn't in a pipeline execution context...
There was a problem hiding this comment.
It is not in a pipeline execution context. Only a unit test for data sampler.
There was a problem hiding this comment.
OK, we could probably use synctest, but outside of the academic value of doing so, I don't think there's a good point at this stage.
This is still better than it was before (one sleep instead of several lossy samples. The main issue with the previous test is the lossiness since I guess it assumed that it was all or nothing and didn't maintain the intermediate samples during it's five checks. That said this feels like it might be just as flaky as before...
There was a problem hiding this comment.
Ack.
The 1-second sleep acts as a hard timeout to ensure we don't miss anything. Gemini's proposal is only a more efficient implementation of that same timeout, where it allows to finish faster if all samples arrive early.
The core issue is that we don't have a deterministic signal for when all asynchronous samples have finished processing. And it remains unsolved here.
There was a problem hiding this comment.
Let's see if this will reduce the flakiness. If not, then we will revisit the test and try synctest maybe.
|
Assigning reviewers: R: @lostluck for label go. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
| time.Sleep(1 * time.Second) | ||
| samples := dataSampler.GetSamples(test.pids) | ||
| samplesCount := len(samples) |
There was a problem hiding this comment.
OK, we could probably use synctest, but outside of the academic value of doing so, I don't think there's a good point at this stage.
This is still better than it was before (one sleep instead of several lossy samples. The main issue with the previous test is the lossiness since I guess it assumed that it was all or nothing and didn't maintain the intermediate samples during it's five checks. That said this feels like it might be just as flaky as before...
Since GetSamples is a destructive operation that clears stored samples, polling it in a loop is flaky. If it is called before all asynchronous samples have finished processing, it consumes the early samples and clears them, causing verifySampledElements to fail.
In this PR, we wait for the sampler to finish before reading to ensure all elements are successfully captured.
Failed test:
https://github.com/apache/beam/actions/runs/26611540188/job/78418312179?pr=38713