Skip to main content

sim_kernel/
effect_ledger.rs

1//! The effect ledger: the contract for recording and replaying effects.
2//!
3//! The kernel defines the [`EffectLedger`] record store; libraries decide how
4//! effects are performed and how the ledger is persisted.
5
6use crate::{
7    datum::Datum,
8    datum_store::DatumStore,
9    effect::{Effect, EffectRecord},
10    error::{Error, Result},
11    event::{Event, EventKind, Tick},
12    event_ledger::EventLedger,
13    expr::NumberLiteral,
14    id::Symbol,
15    ref_id::{ContentId, Coordinate, HandleId, Ref},
16};
17
18/// Per-run store of effect records, their events, and replay cassettes.
19#[derive(Clone, Debug)]
20pub struct EffectLedger {
21    run: Ref,
22    events: EventLedger,
23    records: Vec<EffectRecord>,
24    effects_by_ref: Vec<(Ref, Effect)>,
25    cassette_results: Vec<(ContentId, Ref)>,
26}
27
28impl Default for EffectLedger {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl EffectLedger {
35    /// Create a ledger for the default `effect.ledger` run.
36    pub fn new() -> Self {
37        Self::with_run(Ref::Symbol(Symbol::qualified("effect", "ledger")))
38    }
39
40    /// Create a ledger scoped to `run`.
41    pub fn with_run(run: Ref) -> Self {
42        Self {
43            run,
44            events: EventLedger::new(),
45            records: Vec::new(),
46            effects_by_ref: Vec::new(),
47            cassette_results: Vec::new(),
48        }
49    }
50
51    /// All effect records, in request order.
52    pub fn records(&self) -> &[EffectRecord] {
53        &self.records
54    }
55
56    /// The underlying event ledger.
57    pub fn events(&self) -> &EventLedger {
58        &self.events
59    }
60
61    /// Events recorded for this ledger's run, in order.
62    pub fn events_for_run(&self) -> &[Event] {
63        self.events.events_for_run(&self.run)
64    }
65
66    /// Recorded cassette result for replay key `key`, if any.
67    pub fn cassette_result(&self, key: &ContentId) -> Option<&Ref> {
68        self.cassette_results
69            .iter()
70            .rev()
71            .find_map(|(recorded_key, result)| (recorded_key == key).then_some(result))
72    }
73
74    /// Record a cassette result for replay key `key`.
75    pub fn insert_cassette_result(&mut self, key: ContentId, result: Ref) {
76        self.cassette_results.push((key, result));
77    }
78
79    /// The effect recorded under `reference`, if any.
80    pub fn effect(&self, reference: &Ref) -> Option<&Effect> {
81        self.effects_by_ref
82            .iter()
83            .rev()
84            .find_map(|(effect_ref, effect)| (effect_ref == reference).then_some(effect))
85    }
86
87    /// Record an effect request, emitting an [`EventKind::EffectRequested`]
88    /// event and opening a new record.
89    pub fn record_requested(
90        &mut self,
91        datum_store: &mut impl DatumStore,
92        effect: Effect,
93    ) -> Result<EffectRecord> {
94        let effect_ref = effect.id.clone();
95        let event = self.events.push(
96            self.run.clone(),
97            EventKind::EffectRequested {
98                effect: effect_ref.clone(),
99            },
100        )?;
101        let requested_event = event_ref(datum_store, &event)?;
102        let record = EffectRecord {
103            effect: effect_ref.clone(),
104            requested_event,
105            resolved_event: None,
106            result: None,
107            aborted: false,
108        };
109        self.effects_by_ref.push((effect_ref, effect));
110        self.records.push(record.clone());
111        Ok(record)
112    }
113
114    /// Record an effect resolution, emitting an [`EventKind::EffectResolved`]
115    /// event and closing the open record with its result.
116    pub fn record_resolved(
117        &mut self,
118        datum_store: &mut impl DatumStore,
119        effect: Ref,
120        result: Ref,
121    ) -> Result<EffectRecord> {
122        let event = self.events.push(
123            self.run.clone(),
124            EventKind::EffectResolved {
125                effect: effect.clone(),
126                result: result.clone(),
127            },
128        )?;
129        let resolved_event = event_ref(datum_store, &event)?;
130        let record = self.open_record_mut(&effect)?;
131        record.resolved_event = Some(resolved_event);
132        record.result = Some(result.clone());
133        Ok(record.clone())
134    }
135
136    /// Record an effect failure, emitting an [`EventKind::Failed`] event and
137    /// marking the open record aborted.
138    pub fn record_failed(
139        &mut self,
140        datum_store: &mut impl DatumStore,
141        effect: Ref,
142        error: Ref,
143    ) -> Result<EffectRecord> {
144        let event = self
145            .events
146            .push(self.run.clone(), EventKind::Failed(error))?;
147        let failed_event = event_ref(datum_store, &event)?;
148        let record = self.open_record_mut(&effect)?;
149        record.resolved_event = Some(failed_event);
150        record.aborted = true;
151        Ok(record.clone())
152    }
153
154    fn open_record_mut(&mut self, effect: &Ref) -> Result<&mut EffectRecord> {
155        self.records
156            .iter_mut()
157            .rev()
158            .find(|record| &record.effect == effect && record.resolved_event.is_none())
159            .ok_or_else(|| Error::Eval(format!("effect record not found for {effect:?}")))
160    }
161}
162
163fn event_ref(datum_store: &mut impl DatumStore, event: &Event) -> Result<Ref> {
164    let id = datum_store.intern(event_datum(event))?;
165    Ok(Ref::Content(id))
166}
167
168fn event_datum(event: &Event) -> Datum {
169    Datum::Node {
170        tag: core_symbol("Event"),
171        fields: vec![
172            (Symbol::new("run"), ref_datum(event.run.clone())),
173            (Symbol::new("seq"), u64_datum(event.seq)),
174            (
175                Symbol::new("ticks"),
176                Datum::List(event.ticks.iter().map(tick_datum).collect()),
177            ),
178            (Symbol::new("kind"), event_kind_datum(&event.kind)),
179        ],
180    }
181}
182
183fn tick_datum(tick: &Tick) -> Datum {
184    Datum::Node {
185        tag: core_symbol("Tick"),
186        fields: vec![
187            (Symbol::new("clock"), Datum::Symbol(tick.clock.clone())),
188            (Symbol::new("index"), ref_datum(tick.index.clone())),
189        ],
190    }
191}
192
193fn event_kind_datum(kind: &EventKind) -> Datum {
194    match kind {
195        EventKind::Started { request } => tagged_ref("Started", "request", request.clone()),
196        EventKind::Claim { claim } => tagged_ref("Claim", "claim", claim.clone()),
197        EventKind::Diagnostic(diagnostic) => Datum::Node {
198            tag: core_symbol("Diagnostic"),
199            fields: vec![(
200                Symbol::new("debug"),
201                Datum::String(format!("{diagnostic:?}")),
202            )],
203        },
204        EventKind::Trace(trace) => tagged_ref("Trace", "trace", trace.clone()),
205        EventKind::Chunk { payload } => tagged_ref("Chunk", "payload", payload.clone()),
206        EventKind::EffectRequested { effect } => {
207            tagged_ref("EffectRequested", "effect", effect.clone())
208        }
209        EventKind::EffectResolved { effect, result } => Datum::Node {
210            tag: core_symbol("EffectResolved"),
211            fields: vec![
212                (Symbol::new("effect"), ref_datum(effect.clone())),
213                (Symbol::new("result"), ref_datum(result.clone())),
214            ],
215        },
216        EventKind::Capture { effect } => tagged_ref("Capture", "effect", effect.clone()),
217        EventKind::Card { subject, card } => Datum::Node {
218            tag: core_symbol("Card"),
219            fields: vec![
220                (Symbol::new("subject"), ref_datum(subject.clone())),
221                (Symbol::new("card"), ref_datum(card.clone())),
222            ],
223        },
224        EventKind::Final(value) => tagged_ref("Final", "value", value.clone()),
225        EventKind::Failed(error) => tagged_ref("Failed", "error", error.clone()),
226        EventKind::Done => Datum::Node {
227            tag: core_symbol("Done"),
228            fields: Vec::new(),
229        },
230    }
231}
232
233fn tagged_ref(tag: &str, field: &str, reference: Ref) -> Datum {
234    Datum::Node {
235        tag: core_symbol(tag),
236        fields: vec![(Symbol::new(field), ref_datum(reference))],
237    }
238}
239
240fn ref_datum(reference: Ref) -> Datum {
241    match reference {
242        Ref::Symbol(symbol) => Datum::Node {
243            tag: core_symbol("ref"),
244            fields: vec![
245                (Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
246                (Symbol::new("symbol"), Datum::Symbol(symbol)),
247            ],
248        },
249        Ref::Content(content) => Datum::Node {
250            tag: core_symbol("ref"),
251            fields: vec![
252                (Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
253                (Symbol::new("content"), content_id_datum(content)),
254            ],
255        },
256        Ref::Handle(handle) => Datum::Node {
257            tag: core_symbol("ref"),
258            fields: vec![
259                (Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
260                (Symbol::new("id"), handle_id_datum(handle)),
261            ],
262        },
263        Ref::Coord(coordinate) => coordinate_datum(coordinate),
264    }
265}
266
267fn coordinate_datum(coordinate: Coordinate) -> Datum {
268    Datum::Node {
269        tag: core_symbol("ref"),
270        fields: vec![
271            (Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
272            (Symbol::new("space"), Datum::Symbol(coordinate.space)),
273            (Symbol::new("ordinal"), content_id_datum(coordinate.ordinal)),
274        ],
275    }
276}
277
278fn content_id_datum(content: ContentId) -> Datum {
279    Datum::Node {
280        tag: core_symbol("content-id"),
281        fields: vec![
282            (Symbol::new("algorithm"), Datum::Symbol(content.algorithm)),
283            (Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
284        ],
285    }
286}
287
288fn handle_id_datum(handle: HandleId) -> Datum {
289    Datum::Bytes(handle.0.to_be_bytes().to_vec())
290}
291
292fn u64_datum(value: u64) -> Datum {
293    Datum::Number(NumberLiteral {
294        domain: core_symbol("u64"),
295        canonical: value.to_string(),
296    })
297}
298
299fn core_symbol(name: &str) -> Symbol {
300    Symbol::qualified("core", name)
301}