aion_store/timer.rs
1//! `TimerEntry` and timer-facing types.
2
3use aion_core::{TimerId, WorkflowId};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// Durable timer record returned by [`crate::ReadableEventStore::expired_timers`].
8#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
9pub struct TimerEntry {
10 /// Workflow that owns the timer.
11 pub workflow_id: WorkflowId,
12 /// Timer identifier within the owning workflow.
13 pub timer_id: TimerId,
14 /// Instant at which the timer is due to fire.
15 pub fire_at: DateTime<Utc>,
16 /// The workflow-history sequence of the `TimerStarted` event that armed
17 /// this row, or `0` for an arming that records no `TimerStarted` (the
18 /// schedule coordinator's trigger timers — sequence numbers start at 1,
19 /// so `0` can never collide with a recorded arming).
20 ///
21 /// This is the row-identity component that makes value-conditional
22 /// retirement discriminate a RE-ARM TO THE IDENTICAL INSTANT: without it,
23 /// a named timer re-armed to the same `fire_at` encodes byte-identically
24 /// to the consumed arming, so a stale retirement deletes the live
25 /// replacement's row — and a past-due replacement is never re-armed by
26 /// the boot sweep (which only restores future armings), a permanently
27 /// lost wake. Every re-arm records a new `TimerStarted` with a strictly
28 /// higher sequence, so `(fire_at, armed_seq)` is unique per arming. The
29 /// store treats the value as an opaque identity component: it never
30 /// orders or interprets it.
31 pub armed_seq: u64,
32}
33
34/// The outcome of a value-conditional
35/// [`retire_timer`](crate::ReadableEventStore::retire_timer) call.
36///
37/// Both variants are success — the distinction exists so callers (the boot
38/// sweep's counters in particular) can report what actually happened to the
39/// row instead of counting attempts as retirements.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum TimerRetirement {
42 /// The key no longer holds the retired arming's row: it was deleted by
43 /// this call, or was already gone (never scheduled, or retired earlier —
44 /// the idempotent shape). A distributed backend reports an absent key as
45 /// `Retired` too: its tombstone lands unconditionally on an empty key.
46 Retired,
47 /// A DIFFERENT arming now owns the key (same timer name, new
48 /// `(fire_at, armed_seq)` identity — possibly the SAME instant re-armed
49 /// under a newer `TimerStarted`). The replacement's row was left
50 /// untouched — it is the re-armed timer's only durable claim to a
51 /// recovery fire.
52 Superseded,
53}