willow_data_model/
store.rs

1use std::{
2    cell::RefCell,
3    collections::VecDeque,
4    error::Error,
5    fmt::{Debug, Display},
6    future::Future,
7    rc::Rc,
8};
9
10use either::Either::{self, Left, Right};
11use slab::Slab;
12use ufotofu::{BulkProducer, Producer};
13use wb_async_utils::TakeCell;
14
15use crate::{entry::AuthorisedEntry, grouping::Area, Entry, LengthyAuthorisedEntry, Path};
16
17#[cfg(feature = "dev")]
18use arbitrary::Arbitrary;
19
20/// Returned when an entry could be ingested into a [`Store`].
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum EntryIngestionSuccess<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
23    /// The entry was successfully ingested.
24    Success,
25    /// The entry was not ingested because a newer entry with same
26    Obsolete {
27        /// The obsolete entry which was not ingested.
28        obsolete: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
29        /// The newer entry which was not overwritten.
30        newer: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
31    },
32}
33
34/// Returned when an entry cannot be ingested into a [`Store`].
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub enum EntryIngestionError<OE> {
37    /// The ingestion would have triggered prefix pruning when that was not desired.
38    PruningPrevented,
39    /// Something specific to this store implementation went wrong.
40    OperationsError(OE),
41}
42
43impl<OE: Display + Error> Display for EntryIngestionError<OE> {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            EntryIngestionError::PruningPrevented => {
47                write!(f, "Entry ingestion would have triggered undesired pruning.")
48            }
49            EntryIngestionError::OperationsError(err) => Display::fmt(err, f),
50        }
51    }
52}
53
54impl<OE: Display + Error> Error for EntryIngestionError<OE> {}
55
56/// Returned when a payload is successfully appended to the [`Store`].
57#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
58pub enum PayloadAppendSuccess {
59    /// The payload was appended to but not completed.
60    Appended,
61    /// The payload was completed by the appendment.
62    Completed,
63}
64
65/// Returned when a payload fails to be appended into the [`Store`].
66#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
67pub enum PayloadAppendError<PayloadSourceError, OE> {
68    /// No entry for the given subspace and path exists in this store.
69    NoSuchEntry,
70    /// The operation supplied an expected payload_digest, but it did not match the digest of the entry.
71    WrongEntry,
72    /// The payload source produced more bytes than were expected for this payload.
73    TooManyBytes,
74    /// The completed payload's digest is not what was expected.
75    DigestMismatch,
76    /// The source that provided the payload bytes emitted an error.
77    SourceError {
78        source_error: PayloadSourceError,
79        /// Returns how many bytes of payload the store now stores for this entry.
80        total_length_now_available: u64,
81    },
82    /// Something specific to this store implementation went wrong.
83    OperationError(OE),
84}
85
86impl<PayloadSourceError: Display + Error, OE: Display + Error> Display
87    for PayloadAppendError<PayloadSourceError, OE>
88{
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            PayloadAppendError::NoSuchEntry => {
92                write!(
93                    f,
94                    "No entry for the given subspace and path exists in this store"
95                )
96            }
97            PayloadAppendError::WrongEntry => {
98                write!(
99                    f,
100                    "The entry to whose payload to append to had an unexpected payload_digest."
101                )
102            }
103            PayloadAppendError::TooManyBytes => write!(
104                f,
105                "The payload source produced more bytes than were expected for this payload."
106            ),
107            PayloadAppendError::DigestMismatch => {
108                write!(f, "The complete payload's digest is not what was expected.")
109            }
110            PayloadAppendError::SourceError { source_error, .. } => {
111                write!(f, "The payload source emitted an error: {}", source_error)
112            }
113            PayloadAppendError::OperationError(err) => std::fmt::Display::fmt(err, f),
114        }
115    }
116}
117
118impl<PayloadSourceError: Display + Error, OE: Display + Error> Error
119    for PayloadAppendError<PayloadSourceError, OE>
120{
121}
122
123/// Returned when forgetting an entry fails.
124#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
125pub enum ForgetEntryError<OE> {
126    /// The operation supplied an expected payload_digest, but it did not match the digest of the entry.
127    WrongEntry,
128    /// Something specific to this store implementation went wrong.
129    OperationError(OE),
130}
131
132impl<OE: Display + Error> Display for ForgetEntryError<OE> {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            ForgetEntryError::WrongEntry => {
136                write!(
137                    f,
138                    "The entry to whose payload to append to had an unexpected payload_digest."
139                )
140            }
141            ForgetEntryError::OperationError(err) => std::fmt::Display::fmt(err, f),
142        }
143    }
144}
145
146impl<OE: Display + Error> Error for ForgetEntryError<OE> {}
147
148/// Returned when forgetting a payload fails.
149#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
150pub enum ForgetPayloadError<OE> {
151    /// No entry for the given subspace and path exists in this store.
152    NoSuchEntry,
153    /// The operation supplied an expected payload_digest, but it did not match the digest of the entry.
154    WrongEntry,
155    /// Something specific to this store implementation went wrong.
156    OperationError(OE),
157}
158
159impl<OE: Display + Error> Display for ForgetPayloadError<OE> {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            ForgetPayloadError::NoSuchEntry => {
163                write!(
164                    f,
165                    "No entry for the given subspace and path exists in this store"
166                )
167            }
168            ForgetPayloadError::WrongEntry => {
169                write!(
170                    f,
171                    "The entry to whose payload to append to had an unexpected payload_digest."
172                )
173            }
174            ForgetPayloadError::OperationError(err) => std::fmt::Display::fmt(err, f),
175        }
176    }
177}
178
179impl<OE: Display + Error> Error for ForgetPayloadError<OE> {}
180
181/// Returned when retrieving a payload fails.
182#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
183pub enum PayloadError<OE> {
184    /// The operation supplied an expected payload_digest, but it did not match the digest of the entry.
185    WrongEntry,
186    /// Something specific to this store implementation went wrong.
187    OperationError(OE),
188}
189
190impl<OE: Display + Error> Display for PayloadError<OE> {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        match self {
193            PayloadError::WrongEntry => {
194                write!(
195                    f,
196                    "The entry to whose payload to append to had an unexpected payload_digest."
197                )
198            }
199            PayloadError::OperationError(err) => std::fmt::Display::fmt(err, f),
200        }
201    }
202}
203
204impl<OE: Display + Error> Error for PayloadError<OE> {}
205
206/// A notification about changes in a [`Store`]. You can obtain a producer of these via the [`Store::subscribe_area`] method.
207///
208/// An event subscription takes two parameters: the [`Area`] within events should be reported (any store mutations outside that area will not be reported to that subscription), and some optional `QueryIgnoreParams` for optionally filtering events based on whether they correspond to entries whose payload is the empty string and/or whose payload is not fully available in the local store. A more detailed description of how these ignore options impact events is given in the docs for each enum variant, but the general intuition is for the subscription to act as if it was on a store that did not inlcude ignored entries in the first place.
209///
210/// In the description of the enum variants, we write `sub_area` for the area of the subscription, and `ignores` for the subscription `QueryIgnoreParams`.
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212pub enum StoreEvent<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
213    /// Emitted when an entry is inserted in `area`.
214    ///
215    /// - If `ignores.ignore_empty_payloads`, this is not emitted if the payload of the entry is the empty payload.
216    /// - If `ignores.ignore_incomplete_payloads`, this event is not emitted upon entry insertion, but only once its payload has been fully added to the store. In this case, the ingestion event is guaranteed to be emitted *before* the corresponding payload append event.
217    Ingested {
218        /// The entry that was inserted.
219        entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
220        /// A tag that determines whether we ourselves *created* this entry, or whether it arrived from some other data source. In the latter case, the data source is identified by a u64 id. This is not necessarily intented for application-dev-facing APIs, but rather for efficiently implementing replication services (where you want to forward new entries to other peers, but not to those from which you have just received them).
221        origin: EntryOrigin,
222    },
223    /// Emitted whenever an entry is inserted into the store that *might* cause pruning inside `area`. It is possible that no entry was actually pruned form the area, if nothing got overwritten.
224    ///
225    /// When the inserted entry falls into `area`, then the corresponding `PruneAlert` is always delivered *before* the corresponding `Ingested` event.
226    PruneAlert {
227        /// The entry that caused the pruning.
228        cause: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
229    },
230    /// An existing entry inside `area` received a portion of its corresponding payload.
231    ///
232    /// If `ignores.ignore_incomplete_payloads`, this is only emitted when the payload is now fully available. In this case, the corresponding `Ingested` event is guaranteed to be emitted before this `Appended` event.
233    Appended(LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>),
234    /// Emitted whenever a non-ignored entry in `area` is forgotten via `Store::forget_entry`. No corresponding `PayloadForgotten` event is emitted in this case.
235    EntryForgotten {
236        /// The entry that was forgotten.
237        entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
238    },
239    /// Emitted whenever a call to `Store::forget_area` might affect `area`. No corresponding `AreaPayloadForgotten` event is emitted in this case.
240    AreaForgotten {
241        /// The area that was forgotten.
242        area: Area<MCL, MCC, MPL, S>,
243        /// A subarea that was retained (if any).
244        protected: Option<Area<MCL, MCC, MPL, S>>,
245    },
246    /// Emitted whenever the payload of a non-ignored entry in `area` is forgotten via `Store::forget_payload`. Emitted even if no payload bytes had been available to forget in the first place.
247    PayloadForgotten(AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>),
248    /// Emitted whenever a call to `Store::forget_area_payloads` might affect `area`.
249    AreaPayloadsForgotten {
250        /// The area whose payloads were forgotten.
251        area: Area<MCL, MCC, MPL, S>,
252        /// A subarea whose payloads were retained (if any).
253        protected: Option<Area<MCL, MCC, MPL, S>>,
254    },
255}
256
257/// Describes which entries to ignore during a query.
258///
259/// The `Default::default()` ignores nothing.
260#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Default)]
261#[cfg_attr(feature = "dev", derive(Arbitrary))]
262pub struct QueryIgnoreParams {
263    /// Omit entries with locally incomplete corresponding payloads.
264    pub ignore_incomplete_payloads: bool,
265    /// Omit entries whose payload is the empty string.
266    pub ignore_empty_payloads: bool,
267}
268
269impl QueryIgnoreParams {
270    // An entry for which no payload bytes are available.
271    fn ignores_fresh_entry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>(
272        &self,
273        entry: &Entry<MCL, MCC, MPL, N, S, PD>,
274    ) -> bool
275    where
276        S: PartialEq,
277    {
278        // Ignore if necessary if empty payload
279        if self.ignore_empty_payloads && entry.payload_length() == 0 {
280            return true;
281        }
282
283        // Ignore if necessary if the entry has an incomplete payload (always unless the expected payload length is zero).
284        if self.ignore_incomplete_payloads && entry.payload_length() != 0 {
285            return true;
286        }
287
288        false
289    }
290
291    fn ignores_lengthy_authorised_entry<
292        const MCL: usize,
293        const MCC: usize,
294        const MPL: usize,
295        N,
296        S,
297        PD,
298        AT,
299    >(
300        &self,
301        entry: &LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
302    ) -> bool
303    where
304        S: PartialEq,
305    {
306        // Ignore if necessary if empty payload
307        if self.ignore_empty_payloads && entry.entry().entry().payload_length() == 0 {
308            return true;
309        }
310
311        // Ignore if necessary if the entry has an incomplete payload (always unless the expected payload length is zero).
312        if self.ignore_incomplete_payloads
313            && entry.entry().entry().payload_length() != entry.available()
314        {
315            return true;
316        }
317
318        false
319    }
320}
321
322/// The origin of an entry ingestion event.
323#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
324#[cfg_attr(feature = "dev", derive(Arbitrary))]
325pub enum EntryOrigin {
326    /// The entry was probably created on this machine.
327    Local,
328    /// The entry was sourced from another source with an ID assigned by us.
329    /// This is useful if you want to suppress the forwarding of entries to the peers from which the entry was originally sourced.
330    Remote(u64),
331}
332
333/// A [`Store`] is a set of [`AuthorisedEntry`] belonging to a single namespace, and a  (possibly partial) corresponding set of payloads.
334pub trait Store<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
335    type Error: Display + Error + PartialEq;
336
337    /// Returns the [namespace](https://willowprotocol.org/specs/data-model/index.html#namespace) which all of this store's [`AuthorisedEntry`] belong to.
338    fn namespace_id(&self) -> &N;
339
340    /// Attempts to ingest an [`AuthorisedEntry`] into the [`Store`].
341    ///
342    /// Will fail if the entry belonged to a different namespace than the store's, or if the `prevent_pruning` param is `true` and an ingestion would have triggered [prefix pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning).
343    fn ingest_entry(
344        &self,
345        authorised_entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
346        prevent_pruning: bool,
347        origin: EntryOrigin,
348    ) -> impl Future<
349        Output = Result<
350            EntryIngestionSuccess<MCL, MCC, MPL, N, S, PD, AT>,
351            EntryIngestionError<Self::Error>,
352        >,
353    >;
354
355    /// Attempts to append part of a payload for an entry at a given SubspaceId-Path-pair.
356    ///
357    /// Will report an error if:
358    ///
359    /// - There is no entry for the given SubspaceId-Path pair.
360    /// - The payload digest of the entry at the given subspace_id and path is not equal to the supplied `expected_digest` (*if* one was supplied).
361    /// - The payload source produced more bytes than were expected for this payload.
362    /// - The payload source yielded an error.
363    /// - The final payload's digest did not match the expected digest
364    /// - Something else went wrong, e.g. there was no space for the payload on disk.
365    ///
366    /// This method **does not** and **cannot** verify the integrity of partial payloads. This means that arbitrary (and possibly malicious) payloads smaller than the expected size will be stored unless partial verification is implemented upstream (e.g. as part of a sync protocol).
367    fn append_payload<Producer, PayloadSourceError>(
368        &self,
369        subspace_id: &S,
370        path: &Path<MCL, MCC, MPL>,
371        expected_digest: Option<PD>,
372        payload_source: &mut Producer,
373    ) -> impl Future<
374        Output = Result<PayloadAppendSuccess, PayloadAppendError<PayloadSourceError, Self::Error>>,
375    >
376    where
377        Producer: BulkProducer<Item = u8, Error = PayloadSourceError>;
378
379    /// Locally forgets an entry with a given [`Path`] and [subspace](https://willowprotocol.org/specs/data-model/index.html#subspace) id, returning the forgotten entry, or an error if no entry with that path and subspace ID are held by this store. If an `expected_digest` is supplied and the entry turns out to not have that digest, then this method does nothing and reports an `ForgetEntryError::WrongEntry` error.
380    ///
381    /// If the entry in question is the last remaining reference in the store to a particular [`crate::PayloadDigest`], that payload will be forgotten from the store (if present).
382    ///
383    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent joins with other [`Store`]s may bring the forgotten entry back.
384    fn forget_entry(
385        &self,
386        subspace_id: &S,
387        path: &Path<MCL, MCC, MPL>,
388        expected_digest: Option<PD>,
389    ) -> impl Future<Output = Result<(), ForgetEntryError<Self::Error>>>;
390
391    /// Locally forgets all [`AuthorisedEntry`] [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by a given [`crate::grouping::Area`], returning the number of forgotten entries.
392    ///
393    /// If forgetting many entries causes no there to be no remaining references to certain payload digests, those payloads will be removed (if present).
394    ///
395    /// If `protected` is `Some`, then all entries [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by that [`Area`] will be prevented from being forgotten, even though they are included by `area`.
396    ///
397    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent joins with other [`Store`]s may bring the forgotten entries back.
398    fn forget_area(
399        &self,
400        area: &Area<MCL, MCC, MPL, S>,
401        protected: Option<&Area<MCL, MCC, MPL, S>>,
402    ) -> impl Future<Output = Result<usize, Self::Error>>;
403
404    /// Locally forgets the corresponding payload of the entry with a given path and subspace, panics if no entry with that path and subspace ID is held by this store. If an `expected_digest` is supplied and the entry turns out to not have that digest, then this method does nothing and reports a `ForgetPayloadError::WrongEntry` error.
405    ///
406    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent joins with other [`Store`]s may bring the forgotten payload back.
407    fn forget_payload(
408        &self,
409        subspace_id: &S,
410        path: &Path<MCL, MCC, MPL>,
411        expected_digest: Option<PD>,
412    ) -> impl Future<Output = Result<(), ForgetPayloadError<Self::Error>>>;
413
414    /// Locally forgets all payloads with corresponding ['AuthorisedEntry'] [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by a given [`crate::grouping::Area`], returning a count of forgotten payloads. Payloads corresponding to entries *outside* of the given `area` param will be be prevented from being forgotten.
415    ///
416    /// If `protected` is `Some`, then all payloads corresponding to entries [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by that [`Area`] will be prevented from being forgotten, even though they are included by `area`.
417    ///
418    /// Forgetting is not the same as [pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning)! Subsequent joins with other [`Store`]s may bring the forgotten payloads back.
419    fn forget_area_payloads(
420        &self,
421        area: &Area<MCL, MCC, MPL, S>,
422        protected: Option<&Area<MCL, MCC, MPL, S>>,
423    ) -> impl Future<Output = Result<usize, Self::Error>>;
424
425    /// Forces persistence of all previous mutations
426    fn flush(&self) -> impl Future<Output = Result<(), Self::Error>>;
427
428    /// Returns a [`ufotofu::Producer`] of bytes for the payload corresponding to the given subspace id and path. If an `expected_digest` is supplied and the entry turns out to not have that digest, then this method does nothing and reports a `PayloadError::WrongEntry` error.
429    fn payload(
430        &self,
431        subspace_id: &S,
432        path: &Path<MCL, MCC, MPL>,
433        expected_digest: Option<PD>,
434    ) -> impl Future<
435        Output = Result<
436            Option<impl BulkProducer<Item = u8, Final = (), Error = Self::Error>>,
437            PayloadError<Self::Error>,
438        >,
439    >;
440
441    /// Returns a [`LengthyAuthorisedEntry`] with the given [`Path`] and [subspace](https://willowprotocol.org/specs/data-model/index.html#subspace) ID, if present.
442    fn entry(
443        &self,
444        subspace_id: &S,
445        path: &Path<MCL, MCC, MPL>,
446        ignore: QueryIgnoreParams,
447    ) -> impl Future<
448        Output = Result<Option<LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>>, Self::Error>,
449    >;
450
451    /// Queries which entries are [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by an [`Area`], returning a producer of [`LengthyAuthorisedEntry`] **produced in an arbitrary order decided by the store implementation**.
452    fn query_area(
453        &self,
454        area: &Area<MCL, MCC, MPL, S>,
455        ignore: QueryIgnoreParams,
456    ) -> impl Future<
457        Output = Result<
458            impl Producer<Item = LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>, Final = ()>,
459            Self::Error,
460        >,
461    >;
462
463    /// Subscribes to events concerning entries [included](https://willowprotocol.org/specs/grouping-entries/index.html#area_include) by an [`crate::grouping::Area`], returning a producer of `StoreEvent`s which occurred since the moment of calling this function.
464    fn subscribe_area(
465        &self,
466        area: &Area<MCL, MCC, MPL, S>,
467        ignore: QueryIgnoreParams,
468    ) -> impl Future<
469        Output = impl Producer<
470            Item = StoreEvent<MCL, MCC, MPL, N, S, PD, AT>,
471            Final = (),
472            Error = Self::Error,
473        >,
474    >;
475}
476
477//---------------------------//
478// In-Memory Event Queue     //
479//---------------------------//
480
481// What follows is one possible technique for implementing the event subscription service offered by stores. This technique maintains a queue of (relevant) store operations. Subscribers maintain an offset into this queue; producing events works by advancing through the queue, ignoring irrelevant operations, and emitting events whenever appropriate. The queue has a maximum capacity, if it is reached, but some subscriber has not yet processed the oldest operation, then either the queue blocks or the subscriber is removed.
482
483// A more sophisticated implementation could go beyond a mere queue and remove operations that have been obsoleted by later operations. We don't do this here. A later implementation that stores the queue on persistent storage *must* implement such optimisations however, since "deleted" data must also disappear from the operations queue.
484
485/// An operation, as stored in the operations queue. Note that there is no op corresponding to the `PruneAlert` event, since those are generated from insertion ops. Note also that we use LengthyAuthorisedEntries instead of merely AuthorisedEntries for EntryForgotten and PayloadForgotten. This is so that we can respect QueryIgnoreParams.
486#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
487enum QueuedOp<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
488    Insertion {
489        entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
490        origin: EntryOrigin,
491    },
492    Appended {
493        lengthy_entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
494    },
495    EntryForgotten {
496        entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
497    },
498    AreaForgotten {
499        area: Area<MCL, MCC, MPL, S>,
500        protected: Option<Area<MCL, MCC, MPL, S>>,
501    },
502    PayloadForgotten {
503        entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
504    },
505    AreaPayloadsForgotten {
506        area: Area<MCL, MCC, MPL, S>,
507        protected: Option<Area<MCL, MCC, MPL, S>>,
508    },
509}
510
511// The store maintains a queue of `QueuedOp`s. Subscribers must be able to track their offset into the queue. But since items may be popped off, absolute offsets into the queue would be cumbersome. Instead, ops are addressed by a successively incremented counter (i.e., they get numbered sequentially). The store only needs to store the total number of ops that ever got popped off the queue in order to convert these absolute, unique, sequential op ids into local offsets in its queue.
512
513// The interesting part is how we organise our subscribers. Considerations:
514//
515// - we need to cheaply add and remove subscribers
516// - we need to cheaply query for the (a) subscriber which has the (a) lowest op id (so that we know whether we can pop off old events or not)
517// - we need to notify subscribers which reached the end of the queue when another op has been pushed
518//
519// We can elegantly satisfy these requirements by organising subscribers in a doubly-linked list which we keep sorted by the op id up to which the subscribers have processed the operations. But. Doubly-linked lists in rust (and in general, for that matter) are an absolute pain, because ownership and stuff. Read https://rust-unofficial.github.io/too-many-lists/ if you do not know what I am talking about.
520
521// So instead of the theoretically really nice solution, we'll hack something together. We store the subscribers in a [slab](https://docs.rs/slab/latest/slab/). When needing to pop an op, we do not remove any subscribers, their next attempt to produce an event will simply yield the final item of the producer. Dropping the user-facing part of the subscription also removes the internal part. For notifying up-to-date subscribers of a new push, simply iterate through the full slab. For realistic numbers of subscribers, this is probably not only "efficient enough", but actually significantly more performant than a doubly-linked list.
522
523#[derive(Debug)]
524pub struct EventSystem<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT, Err> {
525    op_queue: VecDeque<QueuedOp<MCL, MCC, MPL, N, S, PD, AT>>,
526    // A statically set limit on how many ops to buffer at most at the same time.
527    max_queue_capacity: usize,
528    /// Total number of ops that have been popped of the op_queue so far
529    popped_count: u64,
530    subscribers: Slab<InternalSubscriber<Err>>,
531}
532
533impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT, Err>
534    EventSystem<MCL, MCC, MPL, N, S, PD, AT, Err>
535{
536    /// Creates a new Eventsystem.
537    pub fn new(max_queue_capacity: usize) -> Self {
538        Self {
539            op_queue: VecDeque::new(),
540            max_queue_capacity,
541            popped_count: 0,
542            subscribers: Slab::new(),
543        }
544    }
545
546    /// Create a new subscription: setting up the internals, and returning the external part.
547    pub fn add_subscription(
548        this: Rc<RefCell<Self>>,
549        area: Area<MCL, MCC, MPL, S>,
550        ignore: QueryIgnoreParams,
551    ) -> Subscriber<MCL, MCC, MPL, N, S, PD, AT, Err> {
552        let cell = Rc::new(TakeCell::new());
553
554        let internal = InternalSubscriber {
555            next_op_id: cell.clone(),
556        };
557        let key = this.borrow_mut().subscribers.insert(internal);
558
559        Subscriber {
560            events: this,
561            next_op_id: cell,
562            slab_key: key,
563            area,
564            ignore,
565            buffered_event: None,
566        }
567    }
568
569    /// Call this inside your store impl after it has ingested an entry.
570    pub fn ingested_entry(
571        &mut self,
572        entry: AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
573        origin: EntryOrigin,
574    ) {
575        self.enqueue_op(QueuedOp::Insertion { entry, origin })
576    }
577
578    /// Call this inside your store impl after it has appended to a payload.
579    pub fn appended_payload(
580        &mut self,
581        lengthy_entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>,
582    ) {
583        self.enqueue_op(QueuedOp::Appended { lengthy_entry })
584    }
585
586    /// Call this inside your store impl after it has forgotten an entry.
587    pub fn forgot_entry(&mut self, entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>) {
588        self.enqueue_op(QueuedOp::EntryForgotten { entry })
589    }
590
591    /// Call this inside your store impl after it has forgotten an area.
592    pub fn forgot_area(
593        &mut self,
594        area: Area<MCL, MCC, MPL, S>,
595        protected: Option<Area<MCL, MCC, MPL, S>>,
596    ) {
597        self.enqueue_op(QueuedOp::AreaForgotten { area, protected })
598    }
599
600    /// Call this inside your store impl after it has forgotten a payload.
601    pub fn forgot_payload(&mut self, entry: LengthyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>) {
602        self.enqueue_op(QueuedOp::PayloadForgotten { entry })
603    }
604
605    /// Call this inside your store impl after it has forgotten the payloads of an area.
606    pub fn forgot_area_payloads(
607        &mut self,
608        area: Area<MCL, MCC, MPL, S>,
609        protected: Option<Area<MCL, MCC, MPL, S>>,
610    ) {
611        self.enqueue_op(QueuedOp::AreaPayloadsForgotten { area, protected })
612    }
613
614    // We enqueue an operation. If the max capacity of the queue is reached through that, we pop the oldest op (which might cause straggling subscribers to be cancelled the next time they try to produce an event). If any subscribers have been awaiting a new op, we notify them.
615    fn enqueue_op(&mut self, op: QueuedOp<MCL, MCC, MPL, N, S, PD, AT>) {
616        self.op_queue.push_back(op);
617
618        if self.op_queue.len() > self.max_queue_capacity {
619            self.op_queue.pop_front();
620        }
621
622        for (_, sub) in self.subscribers.iter() {
623            if sub.next_op_id.is_empty() {
624                sub.next_op_id
625                    .set(Ok(self.popped_count + (self.op_queue.len() as u64) - 1));
626            }
627        }
628    }
629
630    /// Given an op id, return the matching stored QueuedOp, or None if the id is too old (the corresponding op has already been popped).
631    fn resolve_op_id(&self, id: u64) -> Option<&QueuedOp<MCL, MCC, MPL, N, S, PD, AT>> {
632        match id.checked_sub(self.popped_count) {
633            None => None,
634            Some(index) => self.op_queue.get(index as usize),
635        }
636    }
637}
638
639/// The internal part of a subscriber.
640#[derive(Debug)]
641pub struct InternalSubscriber<Err> {
642    // This allows the public endpoint to await new entries. Stores Ok(op_id) of the next op_id to retrieve, stores Err(err) to emit an error err, is empty while the subscriber is fully up to date.
643    next_op_id: Rc<TakeCell<Result<u64, Err>>>,
644}
645
646/// The public-facing part of a subscriber (to be returned by Store::subscribe_area).
647#[derive(Debug)]
648pub struct Subscriber<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT, Err> {
649    events: Rc<RefCell<EventSystem<MCL, MCC, MPL, N, S, PD, AT, Err>>>,
650    // Shared with InternalSubscriber.next_op_id.
651    next_op_id: Rc<TakeCell<Result<u64, Err>>>,
652    /// The key by which the internal subscriber part is stored in the EventSystem. Upon dropping, the Subscriber, the corresponding InternalSubscriber is removed from the slab.
653    slab_key: usize,
654    area: Area<MCL, MCC, MPL, S>,
655    ignore: QueryIgnoreParams,
656    /// Some store ops trigger *two* events. In those cases, the second event is stored here. Each call to `produce` checks for a buffered event first before continuing to process the op queue.
657    buffered_event: Option<StoreEvent<MCL, MCC, MPL, N, S, PD, AT>>,
658}
659
660impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT, Err> Drop
661    for Subscriber<MCL, MCC, MPL, N, S, PD, AT, Err>
662{
663    fn drop(&mut self) {
664        self.events.borrow_mut().subscribers.remove(self.slab_key);
665    }
666}
667
668impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT, Err> Producer
669    for Subscriber<MCL, MCC, MPL, N, S, PD, AT, Err>
670where
671    N: Clone,
672    S: PartialEq + Clone,
673    PD: Clone,
674    AT: Clone,
675{
676    type Item = StoreEvent<MCL, MCC, MPL, N, S, PD, AT>;
677
678    type Final = ();
679
680    type Error = Err;
681
682    async fn produce(&mut self) -> Result<Either<Self::Item, Self::Final>, Self::Error> {
683        if let Some(buffered) = self.buffered_event.take() {
684            return Ok(Left(buffered));
685        }
686
687        // We loop and skip over events that we ignore.
688        // We exit the loop when no more events are available or upon hitting an event we do not ignore.
689        loop {
690            match self.next_op_id.take().await {
691                Err(err) => return Err(err),
692                Ok(op_id) => {
693                    match self.events.borrow().resolve_op_id(op_id) {
694                        None => {
695                            // We lag too far behind.
696                            return Ok(Right(()));
697                        }
698                        Some(op) => {
699                            // Advance the op_id.
700                            if op_id + 1
701                                == self.events.borrow().popped_count
702                                    + (self.events.borrow().op_queue.len() as u64)
703                            {
704                                // reached the end of the op queue. Do nothing, the next event insertion will fill self.next_op_id
705                            } else {
706                                self.next_op_id.set(Ok(op_id + 1));
707                            }
708
709                            match op {
710                                QueuedOp::Appended { lengthy_entry } => {
711                                    if !self.area.includes_entry(lengthy_entry.entry().entry())
712                                        || self
713                                            .ignore
714                                            .ignores_lengthy_authorised_entry(lengthy_entry)
715                                    {
716                                        continue;
717                                    }
718
719                                    if self.ignore.ignore_incomplete_payloads {
720                                        // If the entry was ignored due to an incomplete payload, we buffer the append event and emit an insertion event first.
721                                        self.buffered_event =
722                                            Some(StoreEvent::Appended(lengthy_entry.clone()));
723
724                                        return Ok(Left(StoreEvent::Ingested {
725                                            entry: lengthy_entry.entry().clone(),
726                                            origin: EntryOrigin::Local,
727                                        }));
728                                    } else {
729                                        // Otherwise, emit the Appended event directly.
730                                        return Ok(Left(StoreEvent::Appended(
731                                            lengthy_entry.clone(),
732                                        )));
733                                    }
734                                }
735                                QueuedOp::AreaForgotten { area, protected } => {
736                                    if area.intersection(&self.area).is_some() {
737                                        if let Some(prot) = protected {
738                                            if prot.includes_area(&self.area) {
739                                                // continue with area, since the subscribed area is fully protected
740                                                continue;
741                                            }
742                                        }
743
744                                        return Ok(Left(StoreEvent::AreaForgotten {
745                                            area: area.clone(),
746                                            protected: protected.clone(),
747                                        }));
748                                    } else {
749                                        // no-op, continue with next event
750                                    }
751                                }
752                                QueuedOp::AreaPayloadsForgotten { area, protected } => {
753                                    if area.intersection(&self.area).is_some() {
754                                        if let Some(prot) = protected {
755                                            if prot.includes_area(&self.area) {
756                                                // continue with area, since the subscribed area is fully protected
757                                                continue;
758                                            }
759                                        }
760
761                                        return Ok(Left(StoreEvent::AreaPayloadsForgotten {
762                                            area: area.clone(),
763                                            protected: protected.clone(),
764                                        }));
765                                    } else {
766                                        // no-op, continue with next event
767                                    }
768                                }
769                                QueuedOp::EntryForgotten { entry } => {
770                                    if self.area.includes_entry(entry.entry().entry())
771                                        && !self.ignore.ignores_lengthy_authorised_entry(entry)
772                                    {
773                                        return Ok(Left(StoreEvent::EntryForgotten {
774                                            entry: entry.entry().clone(),
775                                        }));
776                                    } else {
777                                        // no-op, continue with next event
778                                    }
779                                }
780
781                                QueuedOp::Insertion { entry, origin } => {
782                                    // Is the entry in the subscribed-to area?
783                                    if self.area.includes_entry(entry.entry()) {
784                                        if self.ignore.ignores_fresh_entry(entry.entry()) {
785                                            return Ok(Left(StoreEvent::PruneAlert {
786                                                cause: entry.clone(),
787                                            }));
788                                        } else {
789                                            // Insertion is not ignored.
790                                            // Buffer the actual insertion event, then emit the prune alert.
791                                            self.buffered_event = Some(StoreEvent::Ingested {
792                                                entry: entry.clone(),
793                                                origin: *origin,
794                                            });
795
796                                            return Ok(Left(StoreEvent::PruneAlert {
797                                                cause: entry.clone(),
798                                            }));
799                                        }
800                                    } else if self.area.could_be_pruned_by(entry.entry()) {
801                                        // Insertion outside area but might still prune something inside the area.
802                                        return Ok(Left(StoreEvent::PruneAlert {
803                                            cause: entry.clone(),
804                                        }));
805                                    } else {
806                                        // no-op, insertion does not affect the area, continue with next event
807                                    }
808                                }
809
810                                QueuedOp::PayloadForgotten { entry } => {
811                                    if self.area.includes_entry(entry.entry().entry())
812                                        && !self.ignore.ignores_lengthy_authorised_entry(entry)
813                                    {
814                                        return Ok(Left(StoreEvent::PayloadForgotten(
815                                            entry.entry().clone(),
816                                        )));
817                                    } else {
818                                        // no-op, continue with next event
819                                    }
820                                }
821                            }
822                        }
823                    }
824                }
825            }
826        }
827    }
828}