Skip to content

Feat/lease deadlines cancellation - #1

Merged
achraf059 merged 4 commits into
mainfrom
feat/lease-deadlines-cancellation
Sep 5, 2026
Merged

Feat/lease deadlines cancellation#1
achraf059 merged 4 commits into
mainfrom
feat/lease-deadlines-cancellation

Conversation

@achraf059

Copy link
Copy Markdown
Owner

Summary

Adds time-bounded attempt leases and cooperative cancellation to close the
liveness gap where a task can remain stuck on a healthy worker indefinitely.

Changes

  • add terminal CANCELLED job state
  • add coordinator-controlled per-attempt execution deadlines
  • detect expired attempts and route them through the existing retry policy
  • add TASK_CANCEL protocol message with jobId + attemptId matching
  • cooperatively interrupt cancelled worker tasks
  • add client cancel <job-id> support
  • persist execution timeout/deadline fields in SQLite
  • migrate older database schemas in place
  • preserve restart semantics by reissuing fresh deadlines after recovery
  • update README and distributed-systems documentation

Verification

  • 50 unit tests passing
  • 26 integration tests passing
  • 76 automated tests total
  • worker timeout/reassignment tested
  • stale timed-out result rejection tested
  • queued/running job cancellation tested
  • coordinator restart/deadline interaction tested
  • ./mvnw clean verify passes
  • git diff --check passes

Semantics

Cancellation is cooperative, not hard preemption.
Execution remains at-least-once.
Late results from revoked attempts are rejected using the existing attempt-lease mechanism.

- CANCELLED terminal state, reachable from QUEUED, RETRY_WAIT, and
  RUNNING; enforced by the state machine like every other transition
- Attempt leases are now time-bounded: assignTo stamps a deadline
  (coordinator clock + configurable execution timeout, default 10 min,
  --task-timeout-millis); the deadline is cleared with the lease
- Job.cancel revokes any active lease; Job.isDeadlineExpired documents
  that expiry is suspicion of a stuck task, not proof of worker death
- SQLite schema gains execution_timeout and deadline columns, with an
  in-place migration so pre-deadline databases remain loadable
- Unit tests: CANCELLED transitions, deadline stamping/clearing,
  cancellation from each state, legacy-database migration
Closes the liveness gap where a task wedges on a worker that stays alive
and keeps heartbeating: worker liveness no longer implies task progress.

Coordinator (single-writer core, unchanged model):
- the sweep gains detectExpiredDeadlines(): a RUNNING job past its
  coordinator-clock deadline has its lease revoked and is retried or
  failed via the existing retry policy, exactly like a lost worker
- revokeAttemptOnWorker() sends a best-effort TASK_CANCEL and frees the
  worker's capacity slot; a send failure is harmless because the lease is
  already revoked, so any late result is stale-rejected as before
- cancelJob() supports client cancellation: QUEUED/RETRY_WAIT/RUNNING ->
  CANCELLED (revoking any lease), terminal jobs reported unchanged

Protocol (additive, unknown-type-tolerant):
- TASK_CANCEL (coordinator->worker: jobId + attemptId)
- CANCEL_JOB / JOB_CANCEL (client request/reply)

Worker:
- cooperative cancellation via thread interruption; InFlightTasks tracks
  each future by (jobId, attemptId) so a cancel for a superseded attempt
  can never interrupt a newer one. No hard preemption; a task that
  ignores interruption still has its lease revoked coordinator-side.

Client CLI: new 'cancel <job-id>' command.
…sults

New DeadlineAndCancellationIT (7 tests):
- task exceeds its deadline on a healthy, still-heartbeating worker ->
  TASK_CANCEL sent, job reassigned, completes on attempt 2
- late success from a timed-out attempt is stale-rejected
- deadline expiry on the final attempt -> FAILED with a timeout error
- cancel a QUEUED job -> CANCELLED, never scheduled even once a worker joins
- cancel a RUNNING SLEEP -> real worker interrupted (finishes in <5s not
  30s), capacity freed, state CANCELLED
- a worker that ignores the cancel and reports success cannot resurrect a
  CANCELLED job
- cancelling terminal/unknown jobs is reported, not misapplied

CoordinatorRestartIT: a restart discards the old wall-clock deadline and
requeues the in-flight job (rather than letting a stale deadline expire
it immediately) — the fresh assignment gets a fresh deadline.

ScriptedWorker records received TASK_CANCEL messages (awaitCancel);
MessageSerializationTest round-trips the three new message types.
- README: CANCELLED in the state diagram, time-bounded lease explanation,
  execution-deadline run instructions, cancel command, updated
  semantics and limitations
- PROTOCOL.md: TASK_CANCEL, CANCEL_JOB/JOB_CANCEL, CANCELLED in snapshots
- FAILURE_MODEL.md: stuck-task-on-healthy-worker scenario, client
  cancellation, stale-deadline recovery rule, tuning entry
- DESIGN_DECISIONS.md: ADR 13 on time-bounded leases — coordinator-clock
  expiry, reuse over new machinery, cooperative (not preemptive) cancel,
  and the explicit non-claims (still at-least-once, not real-time)
- ARCHITECTURE.md: deadline enforcer in the sweep and core components
@achraf059
achraf059 merged commit 83da12d into main Sep 5, 2026
1 check passed
@achraf059
achraf059 deleted the feat/lease-deadlines-cancellation branch September 5, 2026 04:55
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.

1 participant