A race condition occurs in CronScheduler when a job is stopped with stopJob(...) and its cron expression is very frequent (e.g., every second: */1 * * * * *). If the job is configured with runImmediately = true, a second execution may start immediately after the first one is interrupted, overwriting the TIMEOUT state with RUNNING.
- Schedule a job with:
cron = "*/1 * * * * *"(every second)runImmediately = true- long-running body (e.g.,
Thread.sleep(5000))
- Call
stopJob(jobId, Duration.ofMillis(50))after the first execution starts. - Wait ~1s for the next cron tick.
- Query the job state.
JobState actual = scheduler.query(jobId).get().state;
// expected: TIMEOUT
// actual: RUNNINGThe second cron tick re-schedules the task and resets state to RUNNING.
Once a job enters a terminal state like TIMEOUT, no future execution should override it without explicit user intervention (e.g., removal and re-addition of the job).
- The job is stopped by
stopJob(), which interrupts the thread and sets state =TIMEOUT. - Before the thread fully terminates and the test checks the state, the next cron tick arrives.
- The scheduler submits a new run (because the job still exists and is eligible), which updates the state to
RUNNING.
This causes test assertions like the following to intermittently fail:
assertEquals(JobState.TIMEOUT, scheduler.query(id).get().state);In JobRecord, introduce a flag like terminalEventSent:
AtomicBoolean terminalEventSent = new AtomicBoolean(false);Gate any new executions:
if (record.terminalEventSent.get()) return;Automatically deregister jobs on terminal states (TIMEOUT, CANCELLED, FAILED, COMPLETED), or prevent re-submission.
Make tests resilient by:
- Using very rare cron (e.g.,
"0 0 0 1 1 0") to prevent accidental re-execution. - Waiting for
JobState != RUNNINGbefore asserting the final state.
timeoutDoesNotFlipToCancelled_andNoDuplicateEventsinCronSchedulerTest
🟡 Medium — does not affect correctness in production, but introduces test instability and confusing state transitions.
race-conditioncron-schedulerstate-managementtest-instability