Skip to content

Fix absoluteTimeoutFiresEvenIfCallsKeepArriving flake: stop debouncing before the key is freed - #597

Merged
devhawk merged 2 commits into
mainfrom
fix-debouncer-timeout-flake
Oct 1, 2026
Merged

devhawk merged 2 commits into
mainfrom
fix-debouncer-timeout-flake

Conversation

@devhawk

@devhawk devhawk commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Closes #596.

DebouncerTest.absoluteTimeoutFiresEvenIfCallsKeepArriving flaked with expected: <1> but was: <2> on the callCount assertion. The debouncer behaves as documented; the test raced against it.

Cause

The test kept calling debounce while callCount == 0.

When the debounce deadline passes, the delayed-workflow sweep moves the row to ENQUEUED and clears its deduplication ID, which frees the key. The workflow only runs later, when the queue poller dequeues it, so callCount stays 0 in between. A call in that gap started a second workflow, as the Debouncer Javadoc says it should.

Passing runs create that second workflow too. They pass only because the assertion runs before it does. This run's getResult took about a second to return, and the second workflow ran in that time.

Fix

The test now stops at the first call whose own 800 ms period would end past the deadline:

  • Every call lands while the row is DELAYED. That call starts about 650 ms before the deadline, and the row holds its key until the first sweep after the deadline, so no call can start a second workflow.
  • The deadline decides when it runs. The call's uncapped delay is always past the deadline, so it is always capped. The test asserts the row's stored delay_until_epoch_ms equals debounce_deadline_epoch_ms. This checks the cap directly, with no assertion on timing.
  • Every call bounces the first workflow. Each call asserts it got that workflow's ID back. A call that missed the key now fails there with a clear message, not as callCount == 2.
  • One run with the last argument. The workflow runs once, with the last call's argument ("result:" + lastArg). The old test checked only startsWith("result:").

The test is the same on release/v1.2, so this should be cherry-picked there.

Testing

  • DebouncerTest (52 tests) passes on Postgres.
  • absoluteTimeoutFiresEvenIfCallsKeepArriving passed 5 times in a row with --rerun.

🤖 Generated with Claude Code

…sKeepArriving

The test kept calling debounce while callCount was 0. Once the debounce
deadline passes, the delayed-workflow sweep moves the row to ENQUEUED and
frees the key, but the workflow only runs when the queue poller dequeues it.
A call in that gap started a second workflow. When getResult was slow to
return, that second workflow ran before the assertion and callCount was 2.

Stop at the first call whose own period would end past the deadline, which
is still well before the deadline, so every call lands while the row holds
the key. Assert every call bounced the first workflow, the workflow ran once
with the last call's argument, and its delay ended at the deadline rather
than the last call's period.

Fixes #596
@devhawk
devhawk requested review from kraftp, maxdml and qianl15 and a balanced review from Copilot October 1, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The loop can still call debounce after the deadline because its cutoff is checked only afterward.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Updates the debouncer timeout test to avoid creating a second workflow after key release.

Changes:

  • Bounds debounce calls by the stored deadline.
  • Verifies workflow identity, final arguments, and deadline clamping.
File Description
DebouncerTest.java Strengthens absolute-timeout behavior assertions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…the key window

The loop decided to stop only after each call, so a pause long enough to
outlast the window in which the row holds its key (a GC pause or a starved
runner overrunning the sleep) let the next call start a fresh workflow and
fail the test. Before each call, check that it is still at least 200ms
inside that window: the earlier of the previous call's period and the
deadline. If it is not, abort the run, since it cannot test the scenario.
@devhawk
devhawk merged commit 2f1fd0f into main Oct 1, 2026
12 checks passed
@devhawk
devhawk deleted the fix-debouncer-timeout-flake branch October 1, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DebouncerTest.absoluteTimeoutFiresEvenIfCallsKeepArriving keeps calling after the key is freed and starts a second workflow

3 participants