Dependency: READY — start now. No blockers, independent of #172/#173. Pair with the per-match worker watchdog in cs3600_2026#35 — the watchdog stops a match becoming stale, this stops the stale one being cloned.
Stale rescheduler re-publishes in-flight work
GameMatchRepository.findStaleWaitingMatches selects status IN ('waiting','in_progress') AND scheduledAt <= :cutoff, and StaleGameMatchRescheduler (@Scheduled(fixedRate = 60000), threshold 120 min) re-publishes without cancelling the original. A slow or wedged match is therefore re-enqueued every cycle past the threshold, and times_scheduled climbs — the rescheduler multiplies load exactly under the stress it fires in.
Fix
- Exclude
in_progress from the stale sweep, or verify the worker is actually dead before requeuing.
- Cancel/dedupe the prior copy before re-publishing so a match cannot exist in the queue twice.
- Add a bound on
times_scheduled after which a match is failed rather than requeued.
Pairs with the per-match worker watchdog in cs3600_2026#35 (kill a wedged play_game so it never becomes stale in the first place). Part of the wait-time track (#172, #173).
Note on measuring the fix: the rescheduler overwrites scheduled_at, so historical wait-time queries (started_at − scheduled_at) must filter times_scheduled = 1 for clean numbers.
Stale rescheduler re-publishes in-flight work
GameMatchRepository.findStaleWaitingMatchesselectsstatus IN ('waiting','in_progress') AND scheduledAt <= :cutoff, andStaleGameMatchRescheduler(@Scheduled(fixedRate = 60000), threshold 120 min) re-publishes without cancelling the original. A slow or wedged match is therefore re-enqueued every cycle past the threshold, andtimes_scheduledclimbs — the rescheduler multiplies load exactly under the stress it fires in.Fix
in_progressfrom the stale sweep, or verify the worker is actually dead before requeuing.times_scheduledafter which a match is failed rather than requeued.Pairs with the per-match worker watchdog in
cs3600_2026#35(kill a wedgedplay_gameso it never becomes stale in the first place). Part of the wait-time track (#172, #173).Note on measuring the fix: the rescheduler overwrites
scheduled_at, so historical wait-time queries (started_at − scheduled_at) must filtertimes_scheduled = 1for clean numbers.