Interface IJobStorage
The Layer 0 job storage contract (ARCHITECTURE.md §4). Providers implement a small surface
with strict atomicity guarantees (§4.2) — everything clever (state machines, retry math,
cron) lives in the engine. The conformance kit in Millrace.Storage.Verification enforces
every normative statement in these docs; a provider that passes is a supported provider.
public interface IJobStorage
Remarks
Time. Providers take a TimeProvider at construction and use it for
every now comparison (lease expiry, due checks, claims). They never read database
server time. Multi-node deployments therefore require clock synchronization; configuration
must satisfy LeaseDuration > HeartbeatInterval + clock skew + renewal margin
(defaults tolerate ~4 minutes of skew). Excess skew degrades to duplicate execution or early
recurring fires — never state corruption; the transition fence holds at any skew.
Properties
- Capabilities
Optional powers this provider offers, which the engine adapts to.
Methods
- ActivateDueJobsAsync(DateTimeOffset, int, CancellationToken)
Moves due Scheduled/Failed jobs (
DueAt <= now) to Enqueued, clearing DueAt, inDueAt ASCorder (oldest first; ties by enqueue order), up tobatchSize; returns the number moved. Safe to run concurrently on every node — each job activates exactly once.
- ApplyAsync(JobTransition, CancellationToken)
Applies an engine-computed transition atomically behind the fence (§4.2.3); false = fence rejected, nothing changed.
- ClaimAsync(ClaimRequest, CancellationToken)
Exclusively claims up to MaxCount jobs (§4.2.1–2).
- EnqueueAsync(IReadOnlyList<JobRecord>, CancellationToken)
Inserts jobs all-or-nothing and returns their effective ids positionally.
- GetDueRecurringAsync(DateTimeOffset, int, CancellationToken)
Plain read of records with
NextFireTime <= now, orderedNextFireTime ASC(most overdue first, so a backlog cannot starve old definitions), up tobatchSize.
- GetJobAsync(JobId, CancellationToken)
Reads one job, or null if no such job exists.
- GetRecurringAsync(string, CancellationToken)
Reads one recurring definition, or null if none is registered under that id.
- RemoveRecurringAsync(string, CancellationToken)
Removes a recurring definition.
- RenewLeasesAsync(string, IReadOnlyList<JobId>, TimeSpan, CancellationToken)
Extends leases for in-flight jobs; returns the ids actually renewed.
- TryCancelAsync(JobId, CancellationToken)
Cancels a job. Atomic: Scheduled/Enqueued/Failed/Awaiting ⇒ Cancelled (FinishedAt set, key released, transitive Awaiting-descendant cascade) and returns true; Processing ⇒ sets CancelRequested only (state and fence untouched) and returns true; terminal or unknown ⇒ false, no mutation. CancelRequested never blocks a fenced ApplyAsync(JobTransition, CancellationToken) — a completing worker may still win with Succeeded.
- TryFireRecurringAsync(string, DateTimeOffset, DateTimeOffset, JobRecord, CancellationToken)
Fenced fire (§4.2.5, strengthened): compare-and-set on (
id,expectedFireTime) advancing NextFireTime tonextFireTimeand setting LastFireTime = expected, insertingjobin the same atomic operation iff the CAS wins. Returns whether this caller won — exactly one node enqueues each occurrence, with no crash window between fence and enqueue.
- TryRunNowAsync(JobId, CancellationToken)
Makes a job that is waiting out its retry backoff claimable immediately (§11.32). Atomic: Failed ⇒ Enqueued with DueAt cleared, returning true; any other state, or unknown, ⇒ false with no mutation.
- UpsertRecurringAsync(RecurringJobRecord, CancellationToken)
Single atomic upsert. Insert stores the record as given. Update overwrites Cron/Queue/Invocation/Retry/Priority/TenantId/UpdatedAt; takes NextFireTime from the record iff the stored Cron differs from the record's (else preserves the stored value — the engine always passes a freshly computed NextFireTime, conditionally unused); always preserves LastFireTime/CreatedAt.