Skip to content

[13.x] Start a new max wait window after a debounced job runs - #61281

Merged
taylorotwell merged 2 commits into
laravel:13.xfrom
sulimanbenhalim:fix/reset-debounce-max-wait-window
Aug 23, 2026
Merged

[13.x] Start a new max wait window after a debounced job runs#61281
taylorotwell merged 2 commits into
laravel:13.xfrom
sulimanbenhalim:fix/reset-debounce-max-wait-window

Conversation

@sulimanbenhalim

Copy link
Copy Markdown
Contributor

tl;dr a debounced job that already ran can still make the next dispatch skip its debounce completely.

maxWait on #[DebounceFor] keeps a :first_dispatched_at cache key, written on the first dispatch of a burst. it gets cleared in exactly two spots: when maxWait actually trips, and from DebounceLock::release() on a transaction rollback. nothing clears it when the job just runs normally.

so it outlives the burst it belonged to. ttl is max($debounceFor * 10, 300), ie at least 5 minutes, and any dispatch inside that window is measuring elapsed time against a burst thats already finished. if the gap is bigger than maxWait the job gets treated as overdue and queued with delay = 0:

#[DebounceFor(30, maxWait: 60)]
class SyncSearchIndex implements ShouldQueue { /* ... */ }

dispatch(new SyncSearchIndex('post-1')); // delay 30, so it runs at t=30

// queue goes quiet, nothing else dispatched

dispatch(new SyncSearchIndex('post-1')); // t=92, delay 0, runs immediately

so the first event of any burst that starts more than maxWait after the last one doesnt get debounced at all. queued listeners with #[DebounceFor] do the same thing, both paths go through CallQueuedHandler.

fix just clears the max wait timestamp once a job is confirmed not superseded and is about to run. next dispatch then opens a fresh window.

i did think about reusing DebounceLock::release() there instead, but that also forgets the owner token and testTokenPersistsAfterSuccessfulExecution keeps that on purpose. drop it and a superseded job still sitting on the queue would see an empty cache and run via the fail-open branch. hence the narrower releaseMaxWait().

one test change worth flagging: testMaxDebounceWaitForcesImmediateExecution now skips sync. it needs three dispatches to pile up while the jobs stay queued, but on sync every dispatch runs immediately, so by the second one (t=50, debounce 30) the first job has already run. it was only green because of the stale timestamp. five of the other tests in that file already skip sync anyway, and it still runs under database and redis.

@taylorotwell
taylorotwell merged commit 15febe0 into laravel:13.x Aug 23, 2026
54 checks passed
@sulimanbenhalim
sulimanbenhalim deleted the fix/reset-debounce-max-wait-window branch August 23, 2026 19:27
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.

2 participants