Skip to main content

didbot_serve/
subscribe.rs

1//! `com.atproto.sync.subscribeRepos`: this server's repositories, as atproto
2//! wants to hear about them.
3//!
4//! A WebSocket of framed DAG-CBOR carrying signed commits and the blocks
5//! that prove them, which is what a relay crawls.
6//!
7//! # The frame
8//!
9//! One WebSocket binary message is two concatenated DAG-CBOR values: a header
10//! and a body. The header is `{"op": 1, "t": "#commit"}` for a message, and
11//! `{"op": -1}` for an error, whose body is `{"error": .., "message": ..}`.
12//! Nothing separates the two values; a reader decodes the header and continues
13//! from where that decode stopped.
14//!
15//! # Sequence numbers and cursors
16//!
17//! Every message frame carries a sequence number, and a consumer hands back
18//! the last one it applied as `?cursor=`. The cursor is a bare integer with
19//! nowhere to name the run of the server that minted it, so the number
20//! itself survives a restart — see
21//! [`didbot_pds::sequence`] for how, and for why a restart skips numbers
22//! rather than repeating them.
23//!
24//! The replay buffer holds at most [`DEFAULT_REPLAY_FRAMES`] frames and
25//! [`DEFAULT_REPLAY_BYTES`] of them, evicting the oldest first, unless a
26//! deployment sets its own [`ReplayBounds`]. It lives in memory, so a restart
27//! empties it even though the numbering survives. A cursor the buffer cannot
28//! reach is answered with `#info OutdatedCursor` and
29//! then everything still held: the consumer has a hole it must fill by reading the
30//! repositories, and being told so is the point. A cursor naming a number
31//! this server has not assigned yet is a different thing — it cannot be
32//! filled in from anywhere — so it is the `FutureCursor` error the lexicon
33//! declares, and the connection closes.
34//!
35//! A boot numbers frames before anything can connect: the positions that
36//! changed while the process was down, and a restore's resets. A producer
37//! built with [`Repos::booting`] keeps every one of them until
38//! [`Repos::listening`], and its bounds' worth more on top, so a relay that
39//! connects after the boot is handed all of them.
40//!
41//! # One copy of each frame
42//!
43//! The replay buffer is the only place a frame is held. A consumer keeps its
44//! position in the buffer and reads each frame from there when it is ready
45//! to send it, the replay and the live stream alike.
46//!
47//! A frame an open stream has not sent yet is kept past the bounds, up to
48//! twice them, so a consumer resuming from the oldest frame held has as much
49//! room to fall behind as one following live. Past twice the bounds the
50//! oldest frames go anyway, and a consumer that had not sent one is ended
51//! with `ConsumerTooSlow`: its next frame is gone. So the firehose's memory
52//! is at most twice the bounds, plus the one frame each connection is
53//! writing to its socket.
54//!
55//! # Only what is durable
56//!
57//! A record write is in the kernel when it returns and on the medium within
58//! [`DEFAULT_SYNC_INTERVAL`]. A `#commit` published in between announces a
59//! commit a power cut can take back, and a relay that applied it holds a
60//! head this server no longer has. So a producer over a write-ahead log holds
61//! each commit, beside the position the log had reached when it was handed
62//! over, and publishes it once the log has synced through that position. A
63//! commit's frame is therefore up to one sync interval later than its write.
64//!
65//! Every other frame syncs the log and publishes the commits held before it
66//! first, so the stream keeps the order the writes made: an erasure's
67//! `#account` after the repository's last `#commit`, and a restore's `#sync`
68//! after the commit it carries is on the medium.
69
70use std::collections::{HashMap, VecDeque};
71use std::sync::{Arc, Condvar, Mutex, Weak};
72use std::time::Duration;
73
74use bytes::Bytes;
75use didbot_data::{dag_cbor, Cid, Value};
76use didbot_pds::journal::Mark;
77use didbot_pds::wal::DEFAULT_SYNC_INTERVAL;
78use didbot_pds::{RepoAccount, RepoCommit, RepoEventSink, RepoIdentity, RepoSync, Sequence, Wal};
79use tokio::sync::watch;
80use tracing::{debug, info, warn};
81
82/// How many frames the replay buffer holds, if a deployment does not choose.
83///
84/// Most frames are a few kilobytes — the commit block, the tree nodes on one
85/// path and one record — so at this many the frame bound is the one reached
86/// first, and it is a consumer's reconnect window. Each frame held costs a
87/// few dozen bytes beyond its encoding, which is what this bound limits when
88/// frames are small. See `[capacity] firehose_replay_frames`.
89pub const DEFAULT_REPLAY_FRAMES: usize = 4096;
90
91/// How many encoded bytes the replay buffer holds, if a deployment does not
92/// choose.
93///
94/// A commit may carry up to 2,000,000 bytes of blocks, so a frame count
95/// alone does not bound memory: 4096 such frames are 8 GB. The buffer holds
96/// up to twice this for streams that have not sent their frames, and 128 MiB
97/// is a sixteenth of the 2 GiB the default instance has. 64 MiB still holds
98/// [`DEFAULT_REPLAY_FRAMES`] frames of up to 16 KiB, or 32 of the largest.
99/// See `[capacity] firehose_replay_bytes`.
100pub const DEFAULT_REPLAY_BYTES: usize = 64 * 1024 * 1024;
101
102/// How much the replay buffer holds. It evicts its oldest frame when a new
103/// one would take it past either bound, unless an open stream has not sent
104/// that frame yet. Those it keeps up to twice the bounds. See the module docs.
105///
106/// The newest frame is always held, even when it alone is larger than
107/// `bytes`: consumers read live frames from the buffer, so a frame it did
108/// not hold would reach no one.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct ReplayBounds {
111    /// The most frames. At least 1.
112    pub frames: usize,
113    /// The most encoded bytes, counting each frame's header and body.
114    pub bytes: usize,
115}
116
117impl Default for ReplayBounds {
118    fn default() -> Self {
119        Self {
120            frames: DEFAULT_REPLAY_FRAMES,
121            bytes: DEFAULT_REPLAY_BYTES,
122        }
123    }
124}
125
126impl ReplayBounds {
127    /// `frames` frames, and the default byte bound.
128    #[must_use]
129    pub fn frames(frames: usize) -> Self {
130        Self {
131            frames,
132            ..Self::default()
133        }
134    }
135}
136
137/// The `op` a message frame's header carries.
138const OP_MESSAGE: i64 = 1;
139
140/// The `op` an error frame's header carries.
141const OP_ERROR: i64 = -1;
142
143/// Which of the lexicon's message types a frame is.
144///
145/// Kept beside the encoded bytes so a per-connection filter can drop a frame
146/// without decoding it again — see `excludeCommit` and its siblings.
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum FrameKind {
149    /// `#commit`.
150    Commit,
151    /// `#identity`.
152    Identity,
153    /// `#account`.
154    Account,
155    /// `#sync`.
156    Sync,
157}
158
159impl FrameKind {
160    /// The `t` this kind's header carries.
161    #[must_use]
162    pub fn tag(&self) -> &'static str {
163        match self {
164            Self::Commit => "#commit",
165            Self::Identity => "#identity",
166            Self::Account => "#account",
167            Self::Sync => "#sync",
168        }
169    }
170}
171
172/// One numbered frame, already encoded.
173///
174/// The bytes are built once, when the frame is numbered, and the buffer keeps
175/// them: a replay is neither a re-encode nor a copy, and two consumers reading
176/// the same frame are guaranteed the same bytes.
177///
178/// They are [`Bytes`] rather than a `Vec<u8>` because every consumer sending
179/// a frame holds it while the socket write is under way. As a `Vec<u8>` each
180/// of those is a copy. Cloning `Bytes` bumps a refcount instead, and `axum`'s
181/// `Message::Binary` takes one, so the frame reaches the socket without ever
182/// being copied.
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct Frame {
185    /// Where this frame sits in the stream.
186    pub seq: u64,
187    /// Which message type it carries.
188    pub kind: FrameKind,
189    /// Header and body, concatenated, as they go on the wire.
190    pub bytes: Bytes,
191}
192
193/// Encodes one message frame: the header, then the body.
194///
195/// Public because the tests that matter here decode what this produced, and a
196/// consumer written against this server is entitled to the same encoder.
197#[must_use]
198pub fn encode_message(tag: &str, body: &Value) -> Vec<u8> {
199    let mut bytes = dag_cbor::encode(&map([
200        ("op", Value::Integer(OP_MESSAGE)),
201        ("t", Value::String(tag.to_owned())),
202    ]));
203    bytes.extend(dag_cbor::encode(body));
204    bytes
205}
206
207/// Encodes one error frame, which closes the connection after it.
208#[must_use]
209pub fn encode_error(error: &str, message: &str) -> Vec<u8> {
210    let mut bytes = dag_cbor::encode(&map([("op", Value::Integer(OP_ERROR))]));
211    bytes.extend(dag_cbor::encode(&map([
212        ("error", Value::String(error.to_owned())),
213        ("message", Value::String(message.to_owned())),
214    ])));
215    bytes
216}
217
218/// The lexicon's `seq`, which is a signed integer where this counter is not.
219///
220/// A deployment that assigned more than nine quintillion frames has other
221/// problems; saturating is still the right answer, because the alternative is
222/// a frame that wraps to a number below the one before it and a consumer that
223/// concludes the stream has gone backwards.
224fn clamp(seq: u64) -> i64 {
225    seq.min(i64::MAX as u64) as i64
226}
227
228/// A DAG-CBOR map from pairs, so the bodies below read as their lexicons.
229fn map<const N: usize>(pairs: [(&str, Value); N]) -> Value {
230    Value::Map(
231        pairs
232            .into_iter()
233            .map(|(key, value)| (key.to_owned(), value))
234            .collect(),
235    )
236}
237
238/// A CID-link, or null for the fields the lexicon marks nullable.
239///
240/// A CID that will not parse is written as null rather than dropped or
241/// guessed at. It cannot happen — every one of these came from a repository
242/// this process just signed — and if it did, a null in a nullable field is a
243/// frame a consumer can still read.
244fn link(cid: Option<&str>) -> Value {
245    match cid.map(Cid::parse) {
246        Some(Ok(cid)) => Value::Link(cid),
247        Some(Err(error)) => {
248            warn!(%error, "a commit named a CID that will not parse; sending null");
249            Value::Null
250        }
251        None => Value::Null,
252    }
253}
254
255/// The `#commit` body, as the lexicon defines it.
256///
257/// `rebase`, `tooBig` and `blobs` are the lexicon's three deprecated fields.
258/// They are still required, so they are written at their fixed empty values —
259/// false, false and the empty list — rather than omitted.
260fn commit_body(seq: u64, event: &RepoCommit, time: &str) -> Value {
261    let ops = event
262        .ops
263        .iter()
264        .map(|op| {
265            let mut fields = map([
266                ("action", Value::String(op.action().to_owned())),
267                ("cid", link(op.cid.as_deref())),
268                ("path", Value::String(op.path.clone())),
269            ]);
270            // Optional rather than nullable, and the lexicon says a creation
271            // "should not define" it — so it is absent for a create and
272            // carries the replaced or removed record's CID otherwise. A
273            // consumer inverting a commit reads the old value here and
274            // nowhere else.
275            if let (Some(prev), Value::Map(fields)) = (&op.prev, &mut fields) {
276                fields.insert("prev".to_owned(), link(Some(prev)));
277            }
278            fields
279        })
280        .collect();
281    let mut body = map([
282        ("blobs", Value::List(Vec::new())),
283        ("blocks", Value::Bytes(event.blocks.clone())),
284        ("commit", link(Some(&event.commit))),
285        ("ops", Value::List(ops)),
286        ("rebase", Value::Bool(false)),
287        ("repo", Value::String(event.did.clone())),
288        ("rev", Value::String(event.rev.clone())),
289        ("seq", Value::Integer(clamp(seq))),
290        (
291            "since",
292            match &event.since {
293                Some(rev) => Value::String(rev.clone()),
294                None => Value::Null,
295            },
296        ),
297        ("time", Value::String(time.to_owned())),
298        ("tooBig", Value::Bool(false)),
299    ]);
300    // Optional rather than nullable, so it is absent rather than null when
301    // there is no previous commit to name a tree for.
302    if let Some(prev) = &event.prev_data {
303        if let Value::Map(fields) = &mut body {
304            fields.insert("prevData".to_owned(), link(Some(prev)));
305        }
306    }
307    body
308}
309
310/// The `#identity` body.
311fn identity_body(seq: u64, event: &RepoIdentity, time: &str) -> Value {
312    let mut body = map([
313        ("did", Value::String(event.did.clone())),
314        ("seq", Value::Integer(clamp(seq))),
315        ("time", Value::String(time.to_owned())),
316    ]);
317    // Optional, so absent rather than null for a deployment that issues no
318    // handles.
319    if let Some(handle) = &event.handle {
320        if let Value::Map(fields) = &mut body {
321            fields.insert("handle".to_owned(), Value::String(handle.clone()));
322        }
323    }
324    body
325}
326
327/// The `#account` body.
328fn account_body(seq: u64, event: &RepoAccount, time: &str) -> Value {
329    let mut body = map([
330        ("active", Value::Bool(event.active)),
331        ("did", Value::String(event.did.clone())),
332        ("seq", Value::Integer(clamp(seq))),
333        ("time", Value::String(time.to_owned())),
334    ]);
335    // "If active=false, this optional field indicates a reason" — so an
336    // active account carries no `status` at all rather than one saying it is
337    // fine.
338    if let Some(status) = event.status {
339        if let Value::Map(fields) = &mut body {
340            fields.insert(
341                "status".to_owned(),
342                Value::String(status.as_str().to_owned()),
343            );
344        }
345    }
346    body
347}
348
349/// The `#sync` body.
350fn sync_body(seq: u64, event: &RepoSync, time: &str) -> Value {
351    map([
352        ("blocks", Value::Bytes(event.blocks.clone())),
353        ("did", Value::String(event.did.clone())),
354        ("rev", Value::String(event.rev.clone())),
355        ("seq", Value::Integer(clamp(seq))),
356        ("time", Value::String(time.to_owned())),
357    ])
358}
359
360/// The `#info` body.
361fn info_body(name: &str, message: &str) -> Value {
362    map([
363        ("message", Value::String(message.to_owned())),
364        ("name", Value::String(name.to_owned())),
365    ])
366}
367
368/// The one `name` this server ever sends on an `#info` frame.
369pub const OUTDATED_CURSOR: &str = "OutdatedCursor";
370
371/// The lexicon's error for a cursor ahead of the stream.
372pub const FUTURE_CURSOR: &str = "FutureCursor";
373
374/// Which message types one connection wants.
375///
376/// The lexicon's `excludeCommit`, `excludeIdentity` and `excludeAccount`, read
377/// once at connect time. They are a *delivery* filter and nothing more:
378/// sequence numbers are not renumbered per consumer, and an excluded frame
379/// still spends its number. That is what lets a consumer that excluded
380/// something reconnect with a cursor which skips past the frames it never
381/// saw — the numbers it did see are the same numbers everyone else saw.
382#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
383pub struct Exclusions {
384    /// Drop `#commit` frames.
385    pub commit: bool,
386    /// Drop `#identity` frames.
387    pub identity: bool,
388    /// Drop `#account` frames.
389    pub account: bool,
390}
391
392impl Exclusions {
393    /// Whether a frame of this kind should reach the consumer.
394    ///
395    /// No parameter excludes `#sync`: it says a repository was reset, which
396    /// a consumer holding that repository cannot do without.
397    #[must_use]
398    pub fn wants(&self, kind: FrameKind) -> bool {
399        match kind {
400            FrameKind::Commit => !self.commit,
401            FrameKind::Identity => !self.identity,
402            FrameKind::Account => !self.account,
403            FrameKind::Sync => true,
404        }
405    }
406
407    /// Whether anything at all is excluded.
408    #[must_use]
409    pub fn any(&self) -> bool {
410        self.commit || self.identity || self.account
411    }
412}
413
414/// What a connection asked for.
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416enum Resume {
417    /// From now. No cursor was given.
418    Live,
419    /// After this sequence number.
420    After(u64),
421    /// Ahead of anything this server has assigned.
422    Future(u64),
423}
424
425/// Where one consumer starts reading the replay buffer, decided in
426/// [`Repos::start_for`].
427struct Start {
428    /// The oldest sequence number the buffer held, or 0 when it was empty.
429    oldest: u64,
430    /// Whether the cursor named a frame the buffer no longer held, which is
431    /// what puts an `#info OutdatedCursor` in front of the stream.
432    outdated: bool,
433    /// The stream sends the frames after this sequence number.
434    after: u64,
435    /// The stream's key in [`Ring::streams`].
436    stream: u64,
437}
438
439/// The producer: the sequence and the replay buffer consumers read from.
440///
441/// One per server. Hand a clone to
442/// [`Provisioner::with_repo_sink`](didbot_pds::Provisioner::with_repo_sink)
443/// and the same one to [`app_with_repos`](crate::app_with_repos); every
444/// clone shares `Inner`.
445#[derive(Debug, Clone)]
446pub struct Repos {
447    inner: Arc<Inner>,
448}
449
450#[derive(Debug)]
451struct Inner {
452    sequence: Arc<Sequence>,
453    bounds: ReplayBounds,
454    /// The replay buffer, and the lock that makes the stream ordered.
455    ///
456    /// The sequence number is assigned and the frame buffered while this is
457    /// held, so two concurrent writes cannot reach the buffer in the
458    /// opposite order to their numbers. Out of order, a consumer would be
459    /// handed a frame it had already applied and skip one it had not.
460    ring: Mutex<Ring>,
461    /// The newest sequence number buffered, which every stream waits on for
462    /// its next frame. One receiver per stream.
463    published: watch::Sender<u64>,
464    /// Commits waiting for the log to sync past them, for a producer over a
465    /// log. See the module docs.
466    held: Option<Arc<Held>>,
467    /// Set once, when every stream ends after what it holds. See
468    /// [`Repos::close`].
469    closing: watch::Sender<bool>,
470}
471
472/// Commits handed over before the log they are written to has synced past
473/// them. See the module docs.
474#[derive(Debug)]
475struct Held {
476    log: Arc<Wal>,
477    /// Oldest first, each beside the position the log had reached when it
478    /// arrived. The position is read under this lock, so the positions never
479    /// go down.
480    commits: Mutex<VecDeque<(Mark, RepoCommit)>>,
481    /// Signalled when a commit arrives.
482    arrived: Condvar,
483}
484
485impl Held {
486    fn over(log: Arc<Wal>) -> Self {
487        Self {
488            log,
489            commits: Mutex::new(VecDeque::new()),
490            arrived: Condvar::new(),
491        }
492    }
493
494    fn lock(&self) -> std::sync::MutexGuard<'_, VecDeque<(Mark, RepoCommit)>> {
495        self.commits
496            .lock()
497            .unwrap_or_else(|poisoned| poisoned.into_inner())
498    }
499
500    /// Holds `event` until the log has synced past where it is now, which is
501    /// past every entry the commit wrote.
502    fn hold(&self, event: &RepoCommit) {
503        let mut commits = self.lock();
504        commits.push_back((self.log.mark(), event.clone()));
505        self.arrived.notify_one();
506    }
507
508    /// The position the oldest held commit waits for, waiting up to
509    /// `timeout` for a commit when none is held.
510    fn oldest(&self, timeout: Duration) -> Option<Mark> {
511        let commits = self.lock();
512        let (commits, _) = self
513            .arrived
514            .wait_timeout_while(commits, timeout, |commits| commits.is_empty())
515            .unwrap_or_else(|poisoned| poisoned.into_inner());
516        commits.front().map(|(mark, _)| *mark)
517    }
518
519    /// The oldest held commit, if the log has synced through `synced` past
520    /// it.
521    fn take_through(&self, synced: Mark) -> Option<RepoCommit> {
522        let mut commits = self.lock();
523        if commits.front().is_some_and(|(mark, _)| *mark <= synced) {
524            return commits.pop_front().map(|(_, event)| event);
525        }
526        None
527    }
528}
529
530/// Publishes held commits as the log syncs past them, on a thread of its
531/// own.
532///
533/// Numbering a frame can reserve sequence numbers, which is an append that
534/// syncs the log, so publishing cannot happen inside a sync. The thread holds
535/// the producer weakly and ends at the first wake after it is dropped.
536fn publish_as_synced(inner: &Arc<Inner>, held: Arc<Held>) -> std::io::Result<()> {
537    let producer = Arc::downgrade(inner);
538    std::thread::Builder::new()
539        .name("didbot-firehose-held".to_owned())
540        .spawn(move || loop {
541            let oldest = held.oldest(DEFAULT_SYNC_INTERVAL);
542            if producer.strong_count() == 0 {
543                return;
544            }
545            let Some(oldest) = oldest else {
546                continue;
547            };
548            let synced = held.log.wait_synced(oldest, DEFAULT_SYNC_INTERVAL);
549            let Some(inner) = producer.upgrade() else {
550                return;
551            };
552            if synced {
553                Repos { inner }.release_synced();
554            }
555        })
556        .map(drop)
557}
558
559/// The replay buffer's frames, and how much it holds.
560#[derive(Debug)]
561struct Ring {
562    frames: VecDeque<Frame>,
563    /// What `frames` come to, in encoded bytes.
564    bytes: usize,
565    /// The most it holds, or `None` while a boot is numbering, when it holds
566    /// every frame. See [`Repos::listening`].
567    bound: Option<ReplayBounds>,
568    /// How far past `bound` it holds frames an open stream has not sent.
569    slack: ReplayBounds,
570    /// The newest sequence number evicted, or 0 before the first eviction.
571    /// A stream whose position is below it has lost a frame.
572    evicted: u64,
573    /// Each open stream's position, by stream: the frames after it are the
574    /// ones that stream has yet to send.
575    streams: HashMap<u64, u64>,
576    /// The key the next stream is given in `streams`.
577    next_stream: u64,
578}
579
580impl Ring {
581    /// Buffers `frame`, first evicting the oldest frames until it fits.
582    ///
583    /// A frame an open stream has yet to send is kept while the buffer is
584    /// within its bound plus its slack.
585    fn push(&mut self, frame: Frame) {
586        if let Some(bound) = self.bound {
587            let incoming = frame.bytes.len();
588            let within = |ring: &Self, frames: usize, bytes: usize| {
589                ring.frames.len() < frames && ring.bytes.saturating_add(incoming) <= bytes
590            };
591            let hard_frames = bound.frames.saturating_add(self.slack.frames);
592            let hard_bytes = bound.bytes.saturating_add(self.slack.bytes);
593            while let Some(oldest) = self.frames.front() {
594                if within(self, bound.frames, bound.bytes) {
595                    break;
596                }
597                let unsent = self.pinned().is_some_and(|after| oldest.seq > after);
598                if unsent && within(self, hard_frames, hard_bytes) {
599                    break;
600                }
601                self.bytes -= oldest.bytes.len();
602                self.evicted = oldest.seq;
603                self.frames.pop_front();
604            }
605        }
606        self.bytes += frame.bytes.len();
607        self.frames.push_back(frame);
608    }
609
610    /// The lowest position of any open stream that has not already lost a
611    /// frame. A stream that has is ended when it next reads, so keeping
612    /// frames for it would only hold memory.
613    fn pinned(&self) -> Option<u64> {
614        self.streams
615            .values()
616            .copied()
617            .filter(|after| *after >= self.evicted)
618            .min()
619    }
620}
621
622impl Inner {
623    /// Takes the ring lock, recovering from poisoning.
624    ///
625    /// Every operation under it is
626    /// a push and a pop on a deque, so a panic elsewhere cannot have left it
627    /// half-updated, and refusing to serve the stream over an unrelated panic
628    /// would be a worse outcome than serving it.
629    fn ring(&self) -> std::sync::MutexGuard<'_, Ring> {
630        self.ring
631            .lock()
632            .unwrap_or_else(|poisoned| poisoned.into_inner())
633    }
634}
635
636impl Repos {
637    /// Builds a producer numbering frames from `sequence` and holding up to
638    /// `bounds` of them for replay.
639    #[must_use]
640    pub fn new(sequence: Arc<Sequence>, bounds: ReplayBounds) -> Self {
641        Self::build(sequence, bounds, false, None)
642    }
643
644    /// Builds a producer for a boot: it holds every frame numbered until
645    /// [`Self::listening`], and `bounds` more after it.
646    ///
647    /// With `log`, the write-ahead log commits are written to, a commit is
648    /// published only once `log` has synced past it. See the module docs.
649    ///
650    /// # Errors
651    ///
652    /// The thread that publishes held commits would not start.
653    pub fn booting(
654        sequence: Arc<Sequence>,
655        bounds: ReplayBounds,
656        log: Option<Arc<Wal>>,
657    ) -> std::io::Result<Self> {
658        let held = log.map(|log| Arc::new(Held::over(log)));
659        let repos = Self::build(sequence, bounds, true, held.clone());
660        if let Some(held) = held {
661            publish_as_synced(&repos.inner, held)?;
662        }
663        Ok(repos)
664    }
665
666    fn build(
667        sequence: Arc<Sequence>,
668        bounds: ReplayBounds,
669        booting: bool,
670        held: Option<Arc<Held>>,
671    ) -> Self {
672        let bounds = ReplayBounds {
673            frames: bounds.frames.max(1),
674            ..bounds
675        };
676        Self {
677            inner: Arc::new(Inner {
678                sequence,
679                bounds,
680                ring: Mutex::new(Ring {
681                    frames: VecDeque::new(),
682                    bytes: 0,
683                    bound: (!booting).then_some(bounds),
684                    slack: bounds,
685                    evicted: 0,
686                    streams: HashMap::new(),
687                    next_stream: 0,
688                }),
689                published: watch::Sender::new(0),
690                held,
691                closing: watch::Sender::new(false),
692            }),
693        }
694    }
695
696    /// Ends the boot [`Self::booting`] began: the buffer keeps every frame
697    /// the boot numbered, and holds [`Self::bounds`] more on top of them,
698    /// with the same slack for frames a stream has not sent.
699    ///
700    /// Called once the listener is bound. Nothing before that could have
701    /// read a frame, and a relay that reconnects after a boot is owed every
702    /// one the boot numbered. A producer that is not booting is left as it
703    /// is.
704    pub fn listening(&self) {
705        let mut ring = self.ring();
706        if ring.bound.is_none() {
707            let held = ring.frames.len();
708            let held_bytes = ring.bytes;
709            let bounds = self.inner.bounds;
710            ring.bound = Some(ReplayBounds {
711                frames: held.saturating_add(bounds.frames),
712                bytes: held_bytes.saturating_add(bounds.bytes),
713            });
714            info!(
715                held,
716                held_bytes,
717                frames = bounds.frames,
718                bytes = bounds.bytes,
719                "the replay buffer holds every frame the boot numbered"
720            );
721        }
722    }
723
724    /// The newest sequence number assigned, or the restart floor if none has
725    /// been.
726    #[must_use]
727    pub fn newest(&self) -> u64 {
728        self.inner.sequence.latest()
729    }
730
731    /// How many streams are open right now.
732    ///
733    /// The per-connection state this producer holds is one receiver of
734    /// `published` and one of `closing`, so this figure is the whole of it: a consumer that hung up
735    /// and left its receiver behind would show up here and nowhere else.
736    /// Observability rather than a bound — nothing refuses a subscriber for
737    /// being the n-th — and the number a test watches to see a departed
738    /// consumer actually freed.
739    #[must_use]
740    pub fn subscribers(&self) -> usize {
741        self.inner.published.receiver_count()
742    }
743
744    /// How much the replay buffer holds at most, once any boot is over.
745    #[must_use]
746    pub fn bounds(&self) -> ReplayBounds {
747        self.inner.bounds
748    }
749
750    fn ring(&self) -> std::sync::MutexGuard<'_, Ring> {
751        self.inner.ring()
752    }
753
754    /// The oldest sequence number the buffer can still replay, or 0 when it
755    /// is empty. As [`Self::since`], for tests: the replay path reads the
756    /// same figure under the lock it does everything else under, in
757    /// [`Self::start_for`].
758    #[cfg(test)]
759    fn oldest(&self) -> u64 {
760        self.ring()
761            .frames
762            .front()
763            .map(|frame| frame.seq)
764            .unwrap_or(0)
765    }
766
767    /// Everything held after `seq`, oldest first.
768    ///
769    /// A stream reads the buffer a frame at a time, in [`ReposStream::next`];
770    /// this is how a test looks at the buffer.
771    #[cfg(test)]
772    fn since(&self, seq: u64) -> Vec<Frame> {
773        self.ring()
774            .frames
775            .iter()
776            .filter(|frame| frame.seq > seq)
777            .cloned()
778            .collect()
779    }
780
781    /// Everything held, oldest first. As [`Self::since`], for tests.
782    #[cfg(test)]
783    fn all(&self) -> Vec<Frame> {
784        self.ring().frames.iter().cloned().collect()
785    }
786
787    /// Where a consumer resuming from `resume` starts, decided under one
788    /// acquisition of the ring lock, which also registers its stream so the
789    /// frames after its start are kept for it.
790    ///
791    /// One acquisition for the whole decision, which is what makes the
792    /// `outdated` answer true of the position beside it. Measuring the
793    /// buffer and then reading it takes two acquisitions, and a publish
794    /// landing between them evicts the very frame the cursor was just tested
795    /// against: the consumer is then handed a later start with no `#info
796    /// OutdatedCursor` in front of it, and believes it applied everything.
797    /// A frame evicted after the decision is the stream's to notice, in
798    /// [`ReposStream::next`].
799    fn start_for(&self, resume: Resume, newest: u64) -> Start {
800        let mut ring = self.ring();
801        let oldest = ring.frames.front().map(|frame| frame.seq).unwrap_or(0);
802        let (outdated, after) = match resume {
803            Resume::Live => (false, ring.frames.back().map_or(0, |frame| frame.seq)),
804            // The buffer is empty exactly when this run has published nothing,
805            // so there is nothing to be behind. A cursor from before a restart
806            // is still out of reach, so it is outdated even though nothing
807            // this run sent is missing from it.
808            Resume::After(seq) if oldest == 0 => (seq < newest, seq),
809            // `seq + 1` is the frame the consumer wants next. It has to still
810            // be in the buffer, or something between the two was dropped.
811            Resume::After(seq) if seq + 1 >= oldest => (false, seq),
812            // Everything held.
813            Resume::After(_) => (true, oldest - 1),
814            Resume::Future(_) => unreachable!("refused before the buffer is read"),
815        };
816        let stream = ring.next_stream;
817        ring.next_stream += 1;
818        ring.streams.insert(stream, after);
819        Start {
820            oldest,
821            outdated,
822            after,
823            stream,
824        }
825    }
826
827    /// Numbers, encodes, buffers and publishes one commit.
828    ///
829    /// Returns the frame so a caller — a test, mostly — can see what went
830    /// out, and `None` when the stream could not number it: a frame numbered
831    /// over a reservation that did not reach the log wears a number the next
832    /// run will hand out again. See [`didbot_pds::Sequence::next`].
833    pub fn publish_commit(&self, event: &RepoCommit) -> Option<Frame> {
834        let time = now_rfc3339();
835        self.publish(FrameKind::Commit, |seq| commit_body(seq, event, &time))
836    }
837
838    /// Numbers, encodes, buffers and publishes one identity event.
839    pub fn publish_identity(&self, event: &RepoIdentity) -> Option<Frame> {
840        let time = now_rfc3339();
841        self.publish(FrameKind::Identity, |seq| identity_body(seq, event, &time))
842    }
843
844    /// Numbers, encodes, buffers and publishes one account event.
845    pub fn publish_account(&self, event: &RepoAccount) -> Option<Frame> {
846        let time = now_rfc3339();
847        self.publish(FrameKind::Account, |seq| account_body(seq, event, &time))
848    }
849
850    /// Numbers, encodes, buffers and publishes one repository reset.
851    pub fn publish_sync(&self, event: &RepoSync) -> Option<Frame> {
852        let time = now_rfc3339();
853        self.publish(FrameKind::Sync, |seq| sync_body(seq, event, &time))
854    }
855
856    /// Numbers a frame, puts it in the buffer and hands it to the live
857    /// channel, under one lock.
858    ///
859    /// `None` when the number could not be reserved durably. The event is not
860    /// buffered and not sent: a frame numbered over a reservation that did
861    /// not land wears a number the next run will hand out again, and the
862    /// consumer holding it as a cursor is told nothing. See
863    /// [`didbot_pds::Sequence::next`].
864    fn publish(&self, kind: FrameKind, body: impl FnOnce(u64) -> Value) -> Option<Frame> {
865        let mut ring = self.ring();
866        self.publish_locked(&mut ring, kind, body)
867    }
868
869    /// [`Self::publish`], under a ring lock the caller holds.
870    fn publish_locked(
871        &self,
872        ring: &mut Ring,
873        kind: FrameKind,
874        body: impl FnOnce(u64) -> Value,
875    ) -> Option<Frame> {
876        // Inside the lock, so the buffer's order and the numbering agree.
877        let Some(seq) = self.inner.sequence.next() else {
878            warn!(
879                ?kind,
880                "an event was not put on subscribeRepos: it could not be numbered"
881            );
882            return None;
883        };
884        let frame = Frame {
885            seq,
886            kind,
887            bytes: Bytes::from(encode_message(kind.tag(), &body(seq))),
888        };
889        ring.push(frame.clone());
890
891        // Waking the streams never waits on one, so a consumer that cannot
892        // keep up costs a writer nothing: the buffer evicts past it, and it
893        // is told it fell behind when it next reads.
894        self.inner.published.send_replace(seq);
895        debug!(
896            seq,
897            ?kind,
898            bytes = frame.bytes.len(),
899            held = ring.frames.len(),
900            held_bytes = ring.bytes,
901            subscribers = self.inner.published.receiver_count(),
902            "firehose frame published"
903        );
904        Some(frame)
905    }
906
907    /// Syncs the log and publishes every commit held back for it, oldest
908    /// first.
909    ///
910    /// A clean shutdown calls this once serving has stopped, so the commits
911    /// held back go out before [`Self::close`] ends the streams.
912    pub fn release(&self) {
913        self.sync_held_log();
914        self.release_synced();
915    }
916
917    /// Publishes every held commit the log has synced past, oldest first.
918    fn release_synced(&self) {
919        let mut ring = self.ring();
920        self.release_locked(&mut ring);
921    }
922
923    fn release_locked(&self, ring: &mut Ring) {
924        let Some(held) = &self.inner.held else {
925            return;
926        };
927        let synced = held.log.synced();
928        while let Some(event) = held.take_through(synced) {
929            let time = now_rfc3339();
930            self.publish_locked(ring, FrameKind::Commit, |seq| {
931                commit_body(seq, &event, &time)
932            });
933        }
934    }
935
936    /// Syncs the log the held commits are in, when there is one.
937    fn sync_held_log(&self) {
938        if let Some(held) = &self.inner.held {
939            if let Err(error) = held.log.sync() {
940                warn!(%error, "the log did not sync; the commits held for it stay held");
941            }
942        }
943    }
944
945    /// Publishes a frame that is not a commit, after every commit handed over
946    /// before it. See the module docs.
947    fn publish_after_held(
948        &self,
949        kind: FrameKind,
950        body: impl FnOnce(u64) -> Value,
951    ) -> Option<Frame> {
952        self.sync_held_log();
953        let mut ring = self.ring();
954        self.release_locked(&mut ring);
955        self.publish_locked(&mut ring, kind, body)
956    }
957
958    /// Ends every consumer's stream once it has sent what is already
959    /// published.
960    ///
961    /// A `subscribeRepos` connection outlives the server that upgraded it
962    /// until this process exits. A clean shutdown calls this after
963    /// [`Self::release`], so each connection sends what it holds, closes and
964    /// gives its slot back. See [`Self::detached`].
965    pub fn close(&self) {
966        self.inner.closing.send_replace(true);
967    }
968
969    /// Waits, for at most `timeout`, until no consumer is attached. Answers
970    /// how many still are.
971    pub async fn detached(&self, timeout: Duration) -> usize {
972        let deadline = tokio::time::Instant::now() + timeout;
973        while self.subscribers() > 0 && tokio::time::Instant::now() < deadline {
974            tokio::time::sleep(Duration::from_millis(10)).await;
975        }
976        self.subscribers()
977    }
978
979    /// Opens one connection's stream, resuming from `cursor` if it can.
980    ///
981    /// `Err` is the frame to send before closing, which is how the lexicon's
982    /// declared errors go out: an error is a frame on the socket, not an HTTP
983    /// status, because by the time a cursor has been read the upgrade has
984    /// already happened.
985    pub fn subscribe(
986        &self,
987        cursor: Option<i64>,
988        exclude: Exclusions,
989    ) -> Result<ReposStream, Vec<u8>> {
990        let newest = self.newest();
991        let resume = match cursor {
992            None => Resume::Live,
993            // A negative cursor names nothing. Read as "from the beginning",
994            // which is the only reading that is not an error, and the buffer
995            // then says how far back the beginning actually goes.
996            Some(seq) if seq < 0 => Resume::After(0),
997            Some(seq) if (seq as u64) > newest => Resume::Future(seq as u64),
998            Some(seq) => Resume::After(seq as u64),
999        };
1000
1001        if let Resume::Future(seq) = resume {
1002            warn!(
1003                cursor = seq,
1004                newest,
1005                "a consumer asked to resume from a frame this server has not sent; if this data \
1006                 directory was restored from a snapshot, the restore skipped creating {}",
1007                didbot_pds::RESTORED_FILE
1008            );
1009            return Err(encode_error(
1010                FUTURE_CURSOR,
1011                &format!("cursor {seq} is ahead of this stream; the newest frame sent is {newest}"),
1012            ));
1013        }
1014
1015        let Start {
1016            oldest,
1017            outdated,
1018            after,
1019            stream,
1020        } = self.start_for(resume, newest);
1021
1022        if outdated {
1023            warn!(
1024                ?cursor,
1025                oldest, newest, "a consumer asked for a cursor this server no longer holds"
1026            );
1027        }
1028        let info = outdated.then(|| {
1029            Bytes::from(encode_message(
1030                "#info",
1031                &info_body(
1032                    OUTDATED_CURSOR,
1033                    &format!(
1034                        "this cursor is outside the replay buffer, which holds {oldest} to \
1035                         {newest}. Everything before it must be read from the repositories"
1036                    ),
1037                ),
1038            ))
1039        });
1040        info!(
1041            ?cursor,
1042            outdated,
1043            excluding = exclude.any(),
1044            after,
1045            "subscribeRepos consumer connected"
1046        );
1047
1048        Ok(ReposStream {
1049            info,
1050            producer: Arc::downgrade(&self.inner),
1051            stream,
1052            after,
1053            published: self.inner.published.subscribe(),
1054            exclude,
1055            lagged: 0,
1056            closing: self.inner.closing.subscribe(),
1057        })
1058    }
1059}
1060
1061/// A commit is held when the producer is over a log; every other frame
1062/// publishes after the commits held before it. See the module docs.
1063impl RepoEventSink for Repos {
1064    fn commit(&self, event: &RepoCommit) {
1065        match &self.inner.held {
1066            Some(held) => held.hold(event),
1067            None => {
1068                self.publish_commit(event);
1069            }
1070        }
1071    }
1072
1073    fn identity(&self, event: &RepoIdentity) {
1074        let time = now_rfc3339();
1075        self.publish_after_held(FrameKind::Identity, |seq| identity_body(seq, event, &time));
1076    }
1077
1078    fn account(&self, event: &RepoAccount) -> bool {
1079        let time = now_rfc3339();
1080        self.publish_after_held(FrameKind::Account, |seq| account_body(seq, event, &time))
1081            .is_some()
1082    }
1083
1084    fn sync(&self, event: &RepoSync) -> bool {
1085        let time = now_rfc3339();
1086        self.publish_after_held(FrameKind::Sync, |seq| sync_body(seq, event, &time))
1087            .is_some()
1088    }
1089}
1090
1091impl Default for Repos {
1092    fn default() -> Self {
1093        Self::new(Arc::new(Sequence::in_memory()), ReplayBounds::default())
1094    }
1095}
1096
1097/// One consumer's stream: the replay, then whatever arrives live, both read
1098/// from the replay buffer.
1099///
1100/// A plain reader rather than a `Stream`, because a WebSocket is written to
1101/// rather than polled from — see `routes::subscribe_repos`.
1102#[derive(Debug)]
1103pub struct ReposStream {
1104    /// The `#info OutdatedCursor` frame to send before anything else.
1105    info: Option<Bytes>,
1106    /// Held weakly, so a stream does not keep a dropped producer alive.
1107    producer: Weak<Inner>,
1108    /// This stream's key in [`Ring::streams`].
1109    stream: u64,
1110    /// The highest sequence number this stream has sent or passed over. It
1111    /// sends the buffer's frames after this one. Copied to [`Ring::streams`]
1112    /// whenever it moves.
1113    after: u64,
1114    /// Changes whenever a frame is buffered.
1115    published: watch::Receiver<u64>,
1116    /// Which message types this consumer asked not to be sent.
1117    exclude: Exclusions,
1118    /// How many sequence numbers the buffer evicted before this consumer
1119    /// was sent them.
1120    lagged: u64,
1121    /// Whether the producer is closing. See [`Repos::close`].
1122    closing: watch::Receiver<bool>,
1123}
1124
1125/// What one look at the buffer found for a stream.
1126enum Read {
1127    /// The next frame to send.
1128    Frame(Bytes),
1129    /// The buffer evicted frames after the stream's position; this many
1130    /// sequence numbers' worth.
1131    Behind(u64),
1132    /// Nothing new.
1133    Nothing,
1134}
1135
1136impl ReposStream {
1137    /// How many frames this consumer has been too slow to receive.
1138    #[must_use]
1139    pub fn lagged(&self) -> u64 {
1140        self.lagged
1141    }
1142
1143    /// The next frame's bytes, or `None` when the producer is gone, or is
1144    /// closing and this stream has sent what it was published.
1145    ///
1146    /// A consumer the buffer evicted a frame from before it was sent is
1147    /// dropped rather than silently skipped: the lexicon has
1148    /// `ConsumerTooSlow` for exactly this, and a stream that resumed after a
1149    /// gap would leave a relay believing it had seen every commit.
1150    pub async fn next(&mut self) -> Option<Bytes> {
1151        if let Some(info) = self.info.take() {
1152            return Some(info);
1153        }
1154        loop {
1155            // Marked seen before the buffer is read, so a frame buffered
1156            // after the read wakes the wait below.
1157            self.published.borrow_and_update();
1158            let producer = self.producer.upgrade()?;
1159            match self.read(&producer) {
1160                Read::Frame(bytes) => return Some(bytes),
1161                Read::Behind(skipped) => {
1162                    self.lagged = self.lagged.saturating_add(skipped);
1163                    warn!(
1164                        skipped,
1165                        after = self.after,
1166                        "a subscribeRepos consumer fell behind"
1167                    );
1168                    return None;
1169                }
1170                Read::Nothing => {}
1171            }
1172            drop(producer);
1173            // Everything buffered has gone out, so a close ends the stream.
1174            if *self.closing.borrow() {
1175                return None;
1176            }
1177            tokio::select! {
1178                changed = self.published.changed() => {
1179                    if changed.is_err() {
1180                        return None;
1181                    }
1182                }
1183                _ = self.closing.wait_for(|closing| *closing) => {}
1184            }
1185        }
1186    }
1187
1188    /// The next frame after this stream's position that it wants, moving the
1189    /// position past it and past any frame the exclusions drop.
1190    fn read(&mut self, producer: &Inner) -> Read {
1191        let mut ring = producer.ring();
1192        if ring.evicted > self.after {
1193            return Read::Behind(ring.evicted - self.after);
1194        }
1195        let start = ring.frames.partition_point(|frame| frame.seq <= self.after);
1196        let mut found = Read::Nothing;
1197        for frame in ring.frames.range(start..) {
1198            self.after = frame.seq;
1199            // A dropped frame's sequence number is still spent, so the next
1200            // frame this consumer sees has a gap in front of it that it
1201            // asked for.
1202            if self.exclude.wants(frame.kind) {
1203                found = Read::Frame(frame.bytes.clone());
1204                break;
1205            }
1206        }
1207        ring.streams.insert(self.stream, self.after);
1208        found
1209    }
1210}
1211
1212/// Gives back the frames this stream was keeping in the buffer.
1213impl Drop for ReposStream {
1214    fn drop(&mut self) {
1215        if let Some(producer) = self.producer.upgrade() {
1216            producer.ring().streams.remove(&self.stream);
1217        }
1218    }
1219}
1220
1221/// The current time, RFC 3339, seconds precision.
1222fn now_rfc3339() -> String {
1223    time::OffsetDateTime::now_utc()
1224        .replace_nanosecond(0)
1225        .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
1226        .format(&time::format_description::well_known::Rfc3339)
1227        .unwrap_or_default()
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232    use super::*;
1233    use didbot_pds::{AccountStatus, RepoAction, RepoOp, RepoSync};
1234
1235    fn commit(rev: &str, since: Option<&str>) -> RepoCommit {
1236        RepoCommit {
1237            did: "did:web:a.agents.localhost%3A3000".to_owned(),
1238            commit: "bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm".to_owned(),
1239            rev: rev.to_owned(),
1240            since: since.map(ToOwned::to_owned),
1241            prev_data: None,
1242            blocks: vec![0x0a, 0x0b],
1243            ops: vec![RepoOp {
1244                action: RepoAction::Create,
1245                path: "com.example.thing/3lq".to_owned(),
1246                cid: Some("bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm".to_owned()),
1247                prev: None,
1248            }],
1249        }
1250    }
1251
1252    /// Decodes a frame back into its header and body, the way a consumer does:
1253    /// one value, then another starting where the first stopped.
1254    fn decode(bytes: &[u8]) -> (Value, Value) {
1255        // The encoder is the only thing that knows where the split is, so the
1256        // test finds it by trying every offset a header could end at. Cheap,
1257        // and it proves the two values really are concatenated rather than
1258        // wrapped in something.
1259        for split in 1..bytes.len() {
1260            let (Ok(header), Ok(body)) = (
1261                dag_cbor::decode(&bytes[..split]),
1262                dag_cbor::decode(&bytes[split..]),
1263            ) else {
1264                continue;
1265            };
1266            return (header, body);
1267        }
1268        panic!("a frame did not decode as two concatenated DAG-CBOR values");
1269    }
1270
1271    /// The `seq` a frame's body carries.
1272    fn seq_of(bytes: &[u8]) -> u64 {
1273        let body = decode(bytes).1;
1274        let Value::Integer(seq) = field(&body, "seq") else {
1275            panic!("seq is not an integer");
1276        };
1277        *seq as u64
1278    }
1279
1280    fn field<'a>(value: &'a Value, key: &str) -> &'a Value {
1281        let Value::Map(fields) = value else {
1282            panic!("not a map");
1283        };
1284        fields.get(key).unwrap_or_else(|| panic!("no {key}"))
1285    }
1286
1287    #[test]
1288    fn a_commit_frame_is_a_header_then_the_body_the_lexicon_names() {
1289        let repos = Repos::default();
1290        let frame = repos
1291            .publish_commit(&commit("3lqaaa", None))
1292            .expect("the frame was numbered");
1293        let (header, body) = decode(&frame.bytes);
1294        assert_eq!(field(&header, "op"), &Value::Integer(1));
1295        assert_eq!(field(&header, "t"), &Value::String("#commit".to_owned()));
1296        assert_eq!(field(&body, "seq"), &Value::Integer(1));
1297        assert_eq!(
1298            field(&body, "repo"),
1299            &Value::String(commit("3lqaaa", None).did)
1300        );
1301        assert_eq!(field(&body, "rev"), &Value::String("3lqaaa".to_owned()));
1302        assert_eq!(field(&body, "blocks"), &Value::Bytes(vec![0x0a, 0x0b]));
1303        assert_eq!(field(&body, "rebase"), &Value::Bool(false));
1304        assert_eq!(field(&body, "tooBig"), &Value::Bool(false));
1305        assert_eq!(field(&body, "blobs"), &Value::List(Vec::new()));
1306    }
1307
1308    /// `since` is required and nullable, so a repository's first commit sends
1309    /// it as null rather than leaving it out.
1310    #[test]
1311    fn a_first_commit_sends_a_null_since_rather_than_no_since() {
1312        let repos = Repos::default();
1313        let first = repos
1314            .publish_commit(&commit("3lqaaa", None))
1315            .expect("the frame was numbered");
1316        assert_eq!(field(&decode(&first.bytes).1, "since"), &Value::Null);
1317        let second = repos
1318            .publish_commit(&commit("3lqbbb", Some("3lqaaa")))
1319            .expect("the frame was numbered");
1320        assert_eq!(
1321            field(&decode(&second.bytes).1, "since"),
1322            &Value::String("3lqaaa".to_owned())
1323        );
1324    }
1325
1326    #[test]
1327    fn an_op_carries_its_action_path_and_cid_link() {
1328        let repos = Repos::default();
1329        let frame = repos
1330            .publish_commit(&commit("3lqaaa", None))
1331            .expect("the frame was numbered");
1332        let body = decode(&frame.bytes).1;
1333        let Value::List(ops) = field(&body, "ops") else {
1334            panic!("ops is not a list");
1335        };
1336        assert_eq!(ops.len(), 1);
1337        assert_eq!(
1338            field(&ops[0], "action"),
1339            &Value::String("create".to_owned())
1340        );
1341        assert_eq!(
1342            field(&ops[0], "path"),
1343            &Value::String("com.example.thing/3lq".to_owned())
1344        );
1345        assert!(matches!(field(&ops[0], "cid"), Value::Link(_)));
1346    }
1347
1348    #[test]
1349    fn a_deletion_sends_a_null_cid_and_names_what_it_removed() {
1350        let repos = Repos::default();
1351        let mut event = commit("3lqaaa", None);
1352        event.ops[0] = RepoOp {
1353            action: RepoAction::Delete,
1354            path: "com.example.thing/3lq".to_owned(),
1355            cid: None,
1356            prev: Some("bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm".to_owned()),
1357        };
1358        let frame = repos
1359            .publish_commit(&event)
1360            .expect("the frame was numbered");
1361        let body = decode(&frame.bytes).1;
1362        let Value::List(ops) = field(&body, "ops") else {
1363            panic!("ops is not a list");
1364        };
1365        assert_eq!(
1366            field(&ops[0], "action"),
1367            &Value::String("delete".to_owned())
1368        );
1369        assert_eq!(field(&ops[0], "cid"), &Value::Null);
1370        assert!(
1371            matches!(field(&ops[0], "prev"), Value::Link(_)),
1372            "a delete op must name the record it removed, or nothing can invert it"
1373        );
1374    }
1375
1376    /// A create carries no `prev` at all — not `prev: null`. The lexicon
1377    /// marks the field optional and says a creation "should not define" it,
1378    /// and a null would claim the key held something nameless before.
1379    #[test]
1380    fn a_create_leaves_prev_off_the_wire_entirely() {
1381        let repos = Repos::default();
1382        let frame = repos
1383            .publish_commit(&commit("3lqaaa", None))
1384            .expect("the frame was numbered");
1385        let body = decode(&frame.bytes).1;
1386        let Value::List(ops) = field(&body, "ops") else {
1387            panic!("ops is not a list");
1388        };
1389        let Value::Map(fields) = &ops[0] else {
1390            panic!("an op is not a map");
1391        };
1392        assert!(!fields.contains_key("prev"), "a create defined `prev`");
1393    }
1394
1395    #[test]
1396    fn sequence_numbers_start_at_one_and_do_not_skip() {
1397        let repos = Repos::default();
1398        let seqs: Vec<u64> = (0..5)
1399            .map(|_| {
1400                repos
1401                    .publish_commit(&commit("3lqaaa", None))
1402                    .expect("the frame was numbered")
1403                    .seq
1404            })
1405            .collect();
1406        assert_eq!(seqs, vec![1, 2, 3, 4, 5]);
1407        assert_eq!(repos.newest(), 5);
1408    }
1409
1410    /// The replay is a share of the buffer's frames, not a copy of them.
1411    ///
1412    /// Pointer identity rather than a memory measurement: what makes a
1413    /// `?cursor=0` connection affordable is that its queue and the ring point
1414    /// at the same allocation, and that is a structural fact a test can hold
1415    /// even though "how many bytes did that cost" is not.
1416    #[tokio::test]
1417    async fn a_replayed_frame_points_at_the_buffer_rather_than_a_copy() {
1418        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(8));
1419        for _ in 0..3 {
1420            repos
1421                .publish_commit(&commit("3lqaaa", None))
1422                .expect("the frame was numbered");
1423        }
1424        let held: Vec<*const u8> = repos
1425            .ring()
1426            .frames
1427            .iter()
1428            .map(|frame| frame.bytes.as_ptr())
1429            .collect();
1430
1431        let mut stream = repos
1432            .subscribe(Some(0), Exclusions::default())
1433            .expect("a cursor of 0 replays everything held");
1434        let mut replayed = Vec::new();
1435        for _ in 0..held.len() {
1436            replayed.push(stream.next().await.expect("a replayed frame").as_ptr());
1437        }
1438        assert_eq!(
1439            replayed, held,
1440            "every replayed frame is the buffer's own allocation"
1441        );
1442    }
1443
1444    /// The same for the live half, which is the other place a frame is
1445    /// handed to every stream.
1446    #[tokio::test]
1447    async fn a_live_frame_reaches_every_subscriber_as_one_allocation() {
1448        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(8));
1449        let mut streams: Vec<ReposStream> = (0..3)
1450            .map(|_| {
1451                repos
1452                    .subscribe(None, Exclusions::default())
1453                    .expect("a live subscription refuses nothing")
1454            })
1455            .collect();
1456
1457        let published = repos
1458            .publish_commit(&commit("3lqaaa", None))
1459            .expect("the frame was numbered")
1460            .bytes
1461            .as_ptr();
1462        for stream in &mut streams {
1463            let got = stream.next().await.expect("the live frame");
1464            assert_eq!(
1465                got.as_ptr(),
1466                published,
1467                "a live frame is shared with every receiver, not copied per receiver"
1468            );
1469        }
1470    }
1471
1472    /// The live channel hands frames out in sequence order, however many
1473    /// writers race to publish: a relay reads `seq` as monotonic within a
1474    /// connection, so a frame that arrives after a higher one is a gap and
1475    /// then a stale frame, not a reordering it can undo.
1476    ///
1477    /// Arrival order, not merely the set: the replay buffer is ordered by
1478    /// the same lock, so a sorted comparison would pass with the send outside
1479    /// it. See [`crate::race`] for what makes the race one this test loses
1480    /// every time. The channel is deep enough that the reader never lags.
1481    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1482    async fn live_frames_arrive_in_sequence_order_under_concurrent_writes() {
1483        const WRITERS: usize = 16;
1484        const EACH: usize = 200;
1485        let total = WRITERS * EACH;
1486        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(total));
1487        let mut stream = repos
1488            .subscribe(None, Exclusions::default())
1489            .expect("a live subscription refuses nothing");
1490
1491        let writer = repos.clone();
1492        crate::race::under_pressure(WRITERS, EACH, move || {
1493            writer
1494                .publish_commit(&commit("3lqaaa", None))
1495                .expect("the frame was numbered");
1496        });
1497
1498        let mut arrived = Vec::with_capacity(total);
1499        for _ in 0..total {
1500            let bytes = stream.next().await.expect("every published frame arrives");
1501            arrived.push(seq_of(&bytes));
1502        }
1503        let expected: Vec<u64> = (1..=total as u64).collect();
1504        assert_eq!(
1505            arrived, expected,
1506            "a live subscriber received a frame out of sequence order"
1507        );
1508        assert_eq!(stream.lagged(), 0);
1509    }
1510
1511    fn sync(did: &str) -> RepoSync {
1512        RepoSync {
1513            did: did.to_owned(),
1514            rev: "3lqaaa".to_owned(),
1515            blocks: vec![0x0a, 0x0b],
1516        }
1517    }
1518
1519    /// `#sync` carries the five fields the lexicon requires, and no others.
1520    #[test]
1521    fn a_sync_frame_carries_the_did_the_rev_and_the_commit_block() {
1522        let repos = Repos::default();
1523        let frame = repos
1524            .publish_sync(&sync("did:web:a"))
1525            .expect("the frame was numbered");
1526        let (header, body) = decode(&frame.bytes);
1527        assert_eq!(field(&header, "t"), &Value::String("#sync".to_owned()));
1528        let Value::Map(fields) = &body else {
1529            panic!("not a map");
1530        };
1531        assert_eq!(
1532            fields.keys().map(String::as_str).collect::<Vec<_>>(),
1533            ["blocks", "did", "rev", "seq", "time"]
1534        );
1535        assert_eq!(field(&body, "blocks"), &Value::Bytes(vec![0x0a, 0x0b]));
1536        assert_eq!(field(&body, "did"), &Value::String("did:web:a".to_owned()));
1537        assert_eq!(field(&body, "rev"), &Value::String("3lqaaa".to_owned()));
1538        assert_eq!(field(&body, "seq"), &Value::Integer(1));
1539    }
1540
1541    /// A consumer excluding every kind a parameter names still hears that a
1542    /// repository was reset.
1543    #[tokio::test]
1544    async fn no_exclusion_drops_a_sync() {
1545        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(8));
1546        repos
1547            .publish_commit(&commit("3lqaaa", None))
1548            .expect("the frame was numbered");
1549        repos
1550            .publish_sync(&sync("did:web:a"))
1551            .expect("the frame was numbered");
1552        let mut stream = repos
1553            .subscribe(
1554                Some(0),
1555                Exclusions {
1556                    commit: true,
1557                    identity: true,
1558                    account: true,
1559                },
1560            )
1561            .expect("a resumable cursor");
1562        let (header, _) = decode(&stream.next().await.expect("the #sync"));
1563        assert_eq!(field(&header, "t"), &Value::String("#sync".to_owned()));
1564    }
1565
1566    /// Every frame a boot numbers is held for the first consumer, however
1567    /// many there are, and once the listener binds the buffer holds its
1568    /// capacity on top of them.
1569    #[tokio::test]
1570    async fn a_boot_keeps_every_frame_it_numbered_and_its_capacity_on_top() {
1571        let repos = Repos::booting(
1572            Arc::new(Sequence::in_memory()),
1573            ReplayBounds::frames(3),
1574            None,
1575        )
1576        .expect("a producer");
1577        for _ in 0..10 {
1578            repos
1579                .publish_account(&account("did:web:a", true))
1580                .expect("the frame was numbered");
1581        }
1582        assert_eq!(repos.all().len(), 10, "a boot evicts nothing");
1583
1584        repos.listening();
1585        for _ in 0..3 {
1586            repos
1587                .publish_commit(&commit("3lqaaa", None))
1588                .expect("the frame was numbered");
1589        }
1590        assert_eq!(repos.all().len(), 13, "the capacity is on top of the boot");
1591        repos
1592            .publish_commit(&commit("3lqbbb", None))
1593            .expect("the frame was numbered");
1594        assert_eq!(repos.all().len(), 13, "and bounded after it");
1595        assert_eq!(repos.oldest(), 2);
1596
1597        let mut stream = repos
1598            .subscribe(Some(1), Exclusions::default())
1599            .expect("a resumable cursor");
1600        let mut seqs = Vec::new();
1601        for _ in 0..13 {
1602            seqs.push(seq_of(&stream.next().await.expect("a replayed frame")));
1603        }
1604        assert_eq!(seqs, (2..=14).collect::<Vec<u64>>());
1605    }
1606
1607    #[test]
1608    fn the_buffer_drops_its_oldest_rather_than_growing() {
1609        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(3));
1610        for _ in 0..6 {
1611            repos
1612                .publish_commit(&commit("3lqaaa", None))
1613                .expect("the frame was numbered");
1614        }
1615        assert_eq!(repos.oldest(), 4);
1616        assert_eq!(repos.all().len(), 3);
1617    }
1618
1619    #[test]
1620    fn resuming_replays_exactly_what_came_after_the_cursor() {
1621        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(16));
1622        for _ in 0..5 {
1623            repos
1624                .publish_commit(&commit("3lqaaa", None))
1625                .expect("the frame was numbered");
1626        }
1627        let seqs: Vec<u64> = repos.since(2).iter().map(|frame| frame.seq).collect();
1628        assert_eq!(seqs, vec![3, 4, 5]);
1629    }
1630
1631    /// A cursor naming a frame this server has not sent cannot be filled in
1632    /// from anywhere, so it is the lexicon's error rather than an `#info`.
1633    #[test]
1634    fn a_future_cursor_is_the_error_the_lexicon_declares() {
1635        let repos = Repos::default();
1636        repos
1637            .publish_commit(&commit("3lqaaa", None))
1638            .expect("the frame was numbered");
1639        let refused = repos
1640            .subscribe(Some(9), Exclusions::default())
1641            .expect_err("a future cursor errors");
1642        let (header, body) = decode(&refused);
1643        assert_eq!(field(&header, "op"), &Value::Integer(-1));
1644        assert_eq!(
1645            field(&body, "error"),
1646            &Value::String(FUTURE_CURSOR.to_owned())
1647        );
1648        assert!(
1649            repos.subscribe(Some(1), Exclusions::default()).is_ok(),
1650            "the newest frame is not future"
1651        );
1652    }
1653
1654    fn identity(did: &str) -> RepoIdentity {
1655        RepoIdentity {
1656            did: did.to_owned(),
1657            handle: Some("kestrel.agents.localhost".to_owned()),
1658        }
1659    }
1660
1661    fn account(did: &str, active: bool) -> RepoAccount {
1662        RepoAccount {
1663            did: did.to_owned(),
1664            active,
1665            status: (!active).then_some(AccountStatus::Deleted),
1666        }
1667    }
1668
1669    #[test]
1670    fn an_identity_frame_names_the_did_and_the_handle_it_answers_to() {
1671        let repos = Repos::default();
1672        let frame = repos
1673            .publish_identity(&identity("did:web:a"))
1674            .expect("the frame was numbered");
1675        let (header, body) = decode(&frame.bytes);
1676        assert_eq!(field(&header, "t"), &Value::String("#identity".to_owned()));
1677        assert_eq!(field(&body, "did"), &Value::String("did:web:a".to_owned()));
1678        assert_eq!(
1679            field(&body, "handle"),
1680            &Value::String("kestrel.agents.localhost".to_owned())
1681        );
1682        assert_eq!(field(&body, "seq"), &Value::Integer(1));
1683    }
1684
1685    /// The lexicon says `status` is a reason an account is *not* active, so an
1686    /// active one carries no status at all rather than one saying it is fine.
1687    #[test]
1688    fn an_active_account_carries_no_status_and_a_deleted_one_says_deleted() {
1689        let repos = Repos::default();
1690        let live = decode(
1691            &repos
1692                .publish_account(&account("did:web:a", true))
1693                .expect("the frame was numbered")
1694                .bytes,
1695        )
1696        .1;
1697        assert_eq!(field(&live, "active"), &Value::Bool(true));
1698        let Value::Map(fields) = &live else {
1699            panic!("not a map");
1700        };
1701        assert!(!fields.contains_key("status"), "an active account has none");
1702
1703        let gone = decode(
1704            &repos
1705                .publish_account(&account("did:web:a", false))
1706                .expect("the frame was numbered")
1707                .bytes,
1708        )
1709        .1;
1710        assert_eq!(field(&gone, "active"), &Value::Bool(false));
1711        assert_eq!(field(&gone, "status"), &Value::String("deleted".to_owned()));
1712    }
1713
1714    /// The three kinds share one sequence space, so a consumer resuming from
1715    /// a cursor sees all of them, interleaved, in order, once each.
1716    #[tokio::test]
1717    async fn all_three_kinds_share_one_sequence_and_replay_together() {
1718        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(64));
1719        repos
1720            .publish_identity(&identity("did:web:a"))
1721            .expect("the frame was numbered");
1722        repos
1723            .publish_account(&account("did:web:a", true))
1724            .expect("the frame was numbered");
1725        repos
1726            .publish_commit(&commit("3lqaaa", None))
1727            .expect("the frame was numbered");
1728        repos
1729            .publish_commit(&commit("3lqbbb", Some("3lqaaa")))
1730            .expect("the frame was numbered");
1731        repos
1732            .publish_account(&account("did:web:a", false))
1733            .expect("the frame was numbered");
1734
1735        let mut stream = repos
1736            .subscribe(Some(1), Exclusions::default())
1737            .expect("a resumable cursor");
1738        let mut seen = Vec::new();
1739        for _ in 0..4 {
1740            let bytes = stream.next().await.expect("a replayed frame");
1741            let (header, _) = decode(&bytes);
1742            let Value::String(tag) = field(&header, "t") else {
1743                panic!("no tag");
1744            };
1745            seen.push((tag.clone(), seq_of(&bytes)));
1746        }
1747        assert_eq!(
1748            seen,
1749            vec![
1750                ("#account".to_owned(), 2),
1751                ("#commit".to_owned(), 3),
1752                ("#commit".to_owned(), 4),
1753                ("#account".to_owned(), 5),
1754            ],
1755            "the three kinds must share one gapless, ordered sequence"
1756        );
1757    }
1758
1759    /// The load-bearing one for the exclusions: a consumer that excludes a
1760    /// kind never sees it, still sees the rest, and can reconnect with a
1761    /// cursor that skipped past frames it never saw.
1762    #[tokio::test]
1763    async fn an_exclusion_drops_frames_without_disturbing_the_sequence() {
1764        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(64));
1765        repos
1766            .publish_identity(&identity("did:web:a"))
1767            .expect("the frame was numbered");
1768        repos
1769            .publish_commit(&commit("3lqaaa", None))
1770            .expect("the frame was numbered");
1771        repos
1772            .publish_identity(&identity("did:web:b"))
1773            .expect("the frame was numbered");
1774        repos
1775            .publish_account(&account("did:web:b", true))
1776            .expect("the frame was numbered");
1777
1778        let exclude = Exclusions {
1779            identity: true,
1780            ..Exclusions::default()
1781        };
1782        let mut stream = repos.subscribe(Some(0), exclude).expect("from the start");
1783        let first = stream.next().await.expect("a frame");
1784        assert_eq!(seq_of(&first), 2, "the identity at 1 was not delivered");
1785        let second = stream.next().await.expect("a frame");
1786        assert_eq!(seq_of(&second), 4, "the identity at 3 was not delivered");
1787
1788        // Its cursor is now 4, which is past a frame it never saw. Resuming
1789        // from it is neither an error nor a replay of the excluded frames.
1790        repos
1791            .publish_commit(&commit("3lqbbb", Some("3lqaaa")))
1792            .expect("the frame was numbered");
1793        let mut again = repos.subscribe(Some(4), exclude).expect("a live cursor");
1794        assert_eq!(seq_of(&again.next().await.expect("a frame")), 5);
1795    }
1796
1797    /// Excluding everything is legal and produces a connection that stays
1798    /// open and says nothing, rather than one that errors.
1799    #[tokio::test]
1800    async fn excluding_every_kind_leaves_a_stream_that_is_merely_quiet() {
1801        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(64));
1802        repos
1803            .publish_commit(&commit("3lqaaa", None))
1804            .expect("the frame was numbered");
1805        let mut stream = repos
1806            .subscribe(
1807                Some(0),
1808                Exclusions {
1809                    commit: true,
1810                    identity: true,
1811                    account: true,
1812                },
1813            )
1814            .expect("excluding everything is not an error");
1815        assert!(
1816            tokio::time::timeout(std::time::Duration::from_millis(50), stream.next())
1817                .await
1818                .is_err(),
1819            "a frame arrived on a stream that excluded every kind"
1820        );
1821    }
1822
1823    /// A cursor at the eviction boundary, resumed while writes are landing.
1824    ///
1825    /// Whether the cursor is still in the buffer and what the buffer holds
1826    /// are one answer, taken from one snapshot. Read apart, a publish between
1827    /// them evicts the frame the cursor was just tested against, and the
1828    /// consumer is handed a later start with no `#info OutdatedCursor` in
1829    /// front of it — believing it applied everything, which is the one thing
1830    /// that frame exists to prevent.
1831    ///
1832    /// The assertion is one-sided: a replay that neither begins at
1833    /// `cursor + 1` nor is announced as outdated is only reachable through
1834    /// that gap, so this fails when the gap is back and at no other time.
1835    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1836    async fn a_cursor_evicted_while_the_buffer_is_read_is_told_it_is_outdated() {
1837        use std::sync::atomic::{AtomicBool, Ordering};
1838
1839        const CAPACITY: usize = 4;
1840        const ROUNDS: usize = 4096;
1841        let repos = Repos::new(
1842            Arc::new(Sequence::in_memory()),
1843            ReplayBounds::frames(CAPACITY),
1844        );
1845        // One frame up front, so the first round resumes against a buffer
1846        // that already holds something.
1847        repos
1848            .publish_commit(&commit("3lqaaa", None))
1849            .expect("the frame was numbered");
1850
1851        let writing = Arc::new(AtomicBool::new(true));
1852        let writer = repos.clone();
1853        let publishing = writing.clone();
1854        let publisher = std::thread::spawn(move || {
1855            while publishing.load(Ordering::Relaxed) {
1856                writer
1857                    .publish_commit(&commit("3lqaaa", None))
1858                    .expect("the frame was numbered");
1859            }
1860        });
1861
1862        for _ in 0..ROUNDS {
1863            // One eviction from being out of reach, which is the only cursor
1864            // the gap can be observed through.
1865            let cursor = repos.oldest().saturating_sub(1);
1866            let mut stream = repos
1867                .subscribe(Some(cursor as i64), Exclusions::default())
1868                .expect("a cursor inside the buffer is not ahead of the stream");
1869            // `None` is a consumer that fell behind the live channel, which
1870            // is its own answer and not this one.
1871            let Some(frame) = stream.next().await else {
1872                continue;
1873            };
1874            let (header, body) = decode(&frame);
1875            if field(&header, "t") == &Value::String("#info".to_owned()) {
1876                assert_eq!(
1877                    field(&body, "name"),
1878                    &Value::String(OUTDATED_CURSOR.to_owned()),
1879                    "the only `#info` this server sends is an outdated cursor"
1880                );
1881                continue;
1882            }
1883            assert_eq!(
1884                seq_of(&frame),
1885                cursor + 1,
1886                "a consumer whose cursor was evicted while the buffer was read \
1887                 was served a later frame with nothing in front of it saying so"
1888            );
1889        }
1890
1891        writing.store(false, Ordering::Relaxed);
1892        publisher.join().expect("the publisher does not panic");
1893    }
1894
1895    #[tokio::test]
1896    async fn a_cursor_outside_the_buffer_gets_an_info_frame_and_then_what_is_left() {
1897        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(3));
1898        for _ in 0..6 {
1899            repos
1900                .publish_commit(&commit("3lqaaa", None))
1901                .expect("the frame was numbered");
1902        }
1903        let mut stream = repos
1904            .subscribe(Some(1), Exclusions::default())
1905            .expect("1 is not a future cursor");
1906        let (header, body) = decode(&stream.next().await.expect("an info frame"));
1907        assert_eq!(field(&header, "t"), &Value::String("#info".to_owned()));
1908        assert_eq!(
1909            field(&body, "name"),
1910            &Value::String(OUTDATED_CURSOR.to_owned())
1911        );
1912        // Then everything the buffer still holds, in order and once each.
1913        let mut seqs = Vec::new();
1914        for _ in 0..3 {
1915            seqs.push(seq_of(&stream.next().await.expect("a replayed frame")));
1916        }
1917        assert_eq!(seqs, vec![4u64, 5, 6]);
1918    }
1919
1920    /// What a restart does to a cursor.
1921    ///
1922    /// The numbering survives a restart and the replay buffer does not, so
1923    /// every cursor minted before one is out of reach. It must be answered
1924    /// with `#info OutdatedCursor` — a consumer that is silently treated as
1925    /// caught up would resume against a repository it has an unfillable hole
1926    /// in and never learn of it. And it must *not* be refused as a
1927    /// `FutureCursor`: it names a number below the reservation floor this run
1928    /// resumed at, so it is behind, not ahead.
1929    #[tokio::test]
1930    async fn a_cursor_from_before_a_restart_is_told_it_is_outdated() {
1931        // The previous run handed out seven numbers and reserved a whole
1932        // block, so this one resumes at the top of that block.
1933        let floor = didbot_pds::RESERVATION;
1934        let sequence = Sequence::new(Arc::new(didbot_pds::MemorySequenceLog), floor);
1935        let repos = Repos::new(Arc::new(sequence), ReplayBounds::frames(64));
1936        assert_eq!(repos.newest(), floor, "the restart resumed at the floor");
1937        assert_eq!(repos.all().len(), 0, "and with an empty replay buffer");
1938
1939        let mut stream = repos
1940            .subscribe(Some(7), Exclusions::default())
1941            .expect("a cursor from before a restart is behind, not ahead");
1942        // Timed out rather than awaited: the failure this guards against is
1943        // a stream that says nothing at all, and an unbounded await on one
1944        // would hang instead of failing.
1945        let frame = tokio::time::timeout(std::time::Duration::from_secs(10), stream.next())
1946            .await
1947            .expect("a consumer from before the restart was quietly treated as caught up")
1948            .expect("the stream is open");
1949        let (header, body) = decode(&frame);
1950        assert_eq!(field(&header, "t"), &Value::String("#info".to_owned()));
1951        assert_eq!(
1952            field(&body, "name"),
1953            &Value::String(OUTDATED_CURSOR.to_owned())
1954        );
1955
1956        // And nothing this run sends can wear a number the last one did.
1957        assert!(
1958            repos
1959                .publish_commit(&commit("3lqaaa", None))
1960                .expect("the frame was numbered")
1961                .seq
1962                > floor,
1963            "a frame repeated a number a previous run could have handed out"
1964        );
1965    }
1966
1967    /// The load-bearing one for a consumer that stops reading: it is *ended*,
1968    /// not silently fast-forwarded. A stream that resumed after a gap would
1969    /// leave a relay believing it had applied every commit when it had not,
1970    /// and the gap would never be visible again.
1971    #[tokio::test]
1972    async fn a_stream_that_fell_behind_ends_rather_than_skipping_to_the_present() {
1973        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(4));
1974        let mut stream = repos
1975            .subscribe(None, Exclusions::default())
1976            .expect("a live consumer");
1977        // Nothing is read while these are published, so the live channel
1978        // overruns and the frames at the front of it are gone.
1979        for _ in 0..12 {
1980            repos
1981                .publish_commit(&commit("3lqaaa", None))
1982                .expect("the frame was numbered");
1983        }
1984        assert_eq!(
1985            stream.next().await,
1986            None,
1987            "a consumer that fell behind was handed a frame with a hole in front of it"
1988        );
1989        assert!(
1990            stream.lagged() > 0,
1991            "the stream ended without recording that anything was dropped"
1992        );
1993    }
1994
1995    /// The writer's side of the same fact: publishing does not wait on a
1996    /// subscriber that is not reading. `Repos` is driven from inside a write,
1997    /// so a `publish` that blocked here would be a way for any consumer to
1998    /// halt every write on the server.
1999    #[tokio::test]
2000    async fn publishing_does_not_wait_on_a_subscriber_that_never_reads() {
2001        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(4));
2002        let _idle = repos
2003            .subscribe(None, Exclusions::default())
2004            .expect("a live consumer");
2005        let published = tokio::task::spawn_blocking({
2006            let repos = repos.clone();
2007            move || {
2008                // Far past the channel's depth, from a thread with no
2009                // reactor under it: if `publish` waited on the reader, this
2010                // never returns.
2011                (0..64)
2012                    .map(|_| {
2013                        repos
2014                            .publish_commit(&commit("3lqaaa", None))
2015                            .expect("the frame was numbered")
2016                            .seq
2017                    })
2018                    .collect::<Vec<u64>>()
2019            }
2020        });
2021        let published = tokio::time::timeout(std::time::Duration::from_secs(10), published)
2022            .await
2023            .expect("publishing blocked on a subscriber that was not reading")
2024            .expect("the publishing thread did not panic");
2025        assert_eq!(
2026            published,
2027            (1..=64).collect::<Vec<u64>>(),
2028            "every write was numbered, in order, with the reader stalled"
2029        );
2030        assert_eq!(repos.all().len(), 4, "and the buffer stayed at its bound");
2031    }
2032
2033    /// The exact edge of the replay window. A cursor naming the frame just
2034    /// before the oldest one held is still reachable — the next frame the
2035    /// consumer wants is the oldest one — and one number below that is not.
2036    #[tokio::test]
2037    async fn the_oldest_frame_held_is_reachable_and_the_one_before_it_is_not() {
2038        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(3));
2039        for _ in 0..6 {
2040            repos
2041                .publish_commit(&commit("3lqaaa", None))
2042                .expect("the frame was numbered");
2043        }
2044        let oldest = repos.oldest();
2045        assert_eq!(oldest, 4, "the buffer holds 4, 5 and 6");
2046
2047        let mut reachable = repos
2048            .subscribe(Some(oldest as i64 - 1), Exclusions::default())
2049            .expect("not a future cursor");
2050        assert_eq!(
2051            seq_of(&reachable.next().await.expect("a frame")),
2052            oldest,
2053            "a cursor one below the oldest frame held must replay it, not admit a gap"
2054        );
2055
2056        let mut lost = repos
2057            .subscribe(Some(oldest as i64 - 2), Exclusions::default())
2058            .expect("not a future cursor");
2059        let (header, body) = decode(&lost.next().await.expect("a frame"));
2060        assert_eq!(field(&header, "t"), &Value::String("#info".to_owned()));
2061        assert_eq!(
2062            field(&body, "name"),
2063            &Value::String(OUTDATED_CURSOR.to_owned()),
2064            "a cursor two below the oldest frame held has a hole and must be told so"
2065        );
2066    }
2067
2068    /// The load-bearing one: a consumer that resumes from its last cursor sees
2069    /// every frame after it exactly once, with nothing repeated and nothing
2070    /// skipped, including frames published while it was reconnecting.
2071    #[tokio::test]
2072    async fn a_resumed_consumer_sees_every_frame_after_its_cursor_exactly_once() {
2073        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(64));
2074        for _ in 0..3 {
2075            repos
2076                .publish_commit(&commit("3lqaaa", None))
2077                .expect("the frame was numbered");
2078        }
2079        let mut first = repos
2080            .subscribe(None, Exclusions::default())
2081            .expect("a live consumer");
2082        for _ in 0..2 {
2083            repos
2084                .publish_commit(&commit("3lqbbb", None))
2085                .expect("the frame was numbered");
2086        }
2087        let mut seen = Vec::new();
2088        for _ in 0..2 {
2089            seen.push(seq_of(&first.next().await.expect("a live frame")));
2090        }
2091        assert_eq!(seen, vec![4, 5]);
2092
2093        // It reconnects from the last number it applied, and two more frames
2094        // landed while it was away.
2095        drop(first);
2096        for _ in 0..2 {
2097            repos
2098                .publish_commit(&commit("3lqccc", None))
2099                .expect("the frame was numbered");
2100        }
2101        let mut again = repos
2102            .subscribe(Some(5), Exclusions::default())
2103            .expect("a resumable cursor");
2104        for _ in 0..2 {
2105            seen.push(seq_of(&again.next().await.expect("a replayed frame")));
2106        }
2107        assert_eq!(seen, vec![4, 5, 6, 7], "no gaps and no repeats");
2108    }
2109
2110    /// A commit at the size the lexicon allows its blocks.
2111    fn largest_commit() -> RepoCommit {
2112        RepoCommit {
2113            blocks: vec![0; 2_000_000],
2114            ..commit("3lqaaa", None)
2115        }
2116    }
2117
2118    /// The byte bound is what holds when records are large: a frame count
2119    /// alone lets a burst of the largest commits take gigabytes.
2120    #[test]
2121    fn a_burst_of_large_frames_stays_under_the_byte_bound_and_keeps_the_newest() {
2122        const BYTES: usize = 16 * 1024 * 1024;
2123        let repos = Repos::new(
2124            Arc::new(Sequence::in_memory()),
2125            ReplayBounds {
2126                frames: 4096,
2127                bytes: BYTES,
2128            },
2129        );
2130        for _ in 0..64 {
2131            repos
2132                .publish_commit(&largest_commit())
2133                .expect("the frame was numbered");
2134        }
2135        let held = repos.all();
2136        let total: usize = held.iter().map(|frame| frame.bytes.len()).sum();
2137        assert!(
2138            total <= BYTES,
2139            "the buffer holds {total} bytes, past {BYTES}"
2140        );
2141        assert_eq!(
2142            repos.ring().bytes,
2143            total,
2144            "the running total is the real one"
2145        );
2146        assert_eq!(
2147            held.len(),
2148            BYTES / held[0].bytes.len(),
2149            "as many frames as fit are kept"
2150        );
2151        let seqs: Vec<u64> = held.iter().map(|frame| frame.seq).collect();
2152        let first = 64 - held.len() as u64 + 1;
2153        assert_eq!(
2154            seqs,
2155            (first..=64).collect::<Vec<u64>>(),
2156            "the oldest went first"
2157        );
2158    }
2159
2160    /// Live consumers read from the buffer, so it keeps the newest frame
2161    /// even when that frame alone is past the byte bound.
2162    #[test]
2163    fn a_frame_larger_than_the_byte_bound_is_still_held_alone() {
2164        let repos = Repos::new(
2165            Arc::new(Sequence::in_memory()),
2166            ReplayBounds {
2167                frames: 64,
2168                bytes: 1024,
2169            },
2170        );
2171        repos
2172            .publish_commit(&commit("3lqaaa", None))
2173            .expect("the frame was numbered");
2174        repos
2175            .publish_commit(&largest_commit())
2176            .expect("the frame was numbered");
2177        let seqs: Vec<u64> = repos.all().iter().map(|frame| frame.seq).collect();
2178        assert_eq!(seqs, vec![2]);
2179    }
2180
2181    #[tokio::test]
2182    async fn a_cursor_the_byte_bound_evicted_is_told_it_is_outdated() {
2183        let size = Repos::default()
2184            .publish_commit(&commit("3lqaaa", None))
2185            .expect("the frame was numbered")
2186            .bytes
2187            .len();
2188        let repos = Repos::new(
2189            Arc::new(Sequence::in_memory()),
2190            ReplayBounds {
2191                frames: 64,
2192                bytes: 3 * size,
2193            },
2194        );
2195        for _ in 0..6 {
2196            repos
2197                .publish_commit(&commit("3lqaaa", None))
2198                .expect("the frame was numbered");
2199        }
2200        assert_eq!(repos.oldest(), 4, "three frames fit in the byte bound");
2201
2202        let mut stream = repos
2203            .subscribe(Some(1), Exclusions::default())
2204            .expect("1 is not a future cursor");
2205        let (header, body) = decode(&stream.next().await.expect("an info frame"));
2206        assert_eq!(field(&header, "t"), &Value::String("#info".to_owned()));
2207        assert_eq!(
2208            field(&body, "name"),
2209            &Value::String(OUTDATED_CURSOR.to_owned())
2210        );
2211        let mut seqs = Vec::new();
2212        for _ in 0..3 {
2213            seqs.push(seq_of(&stream.next().await.expect("a replayed frame")));
2214        }
2215        assert_eq!(seqs, vec![4u64, 5, 6]);
2216    }
2217
2218    /// A consumer that stops reading holds nothing the buffer has let go
2219    /// of. The buffer evicts past it, and it is ended rather than handed a
2220    /// gap.
2221    #[tokio::test]
2222    async fn a_stalled_consumer_is_ended_once_the_byte_bound_evicts_past_it() {
2223        const BYTES: usize = 8 * 1024 * 1024;
2224        let repos = Repos::new(
2225            Arc::new(Sequence::in_memory()),
2226            ReplayBounds {
2227                frames: 4096,
2228                bytes: BYTES,
2229            },
2230        );
2231        let mut stalled = repos
2232            .subscribe(None, Exclusions::default())
2233            .expect("a live consumer");
2234        for _ in 0..16 {
2235            repos
2236                .publish_commit(&largest_commit())
2237                .expect("the frame was numbered");
2238            assert!(
2239                repos.ring().bytes <= 2 * BYTES,
2240                "a stream's unsent frames held the buffer past twice its bound"
2241            );
2242        }
2243        assert!(
2244            repos.ring().bytes <= BYTES,
2245            "a stream that had already lost a frame kept the buffer past its bound"
2246        );
2247        assert_eq!(
2248            stalled.next().await,
2249            None,
2250            "a consumer the buffer evicted past was handed a frame with a hole in front of it"
2251        );
2252        assert!(stalled.lagged() > 0);
2253    }
2254
2255    /// The review probe for a resume at the eviction edge. The buffer is
2256    /// full, the cursor is inside it, and one more frame lands before the
2257    /// stream reads. The stream must still send every frame after its
2258    /// cursor: its unsent frames are kept for it.
2259    #[tokio::test]
2260    async fn a_cursor_at_the_eviction_edge_replays_everything_after_it() {
2261        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(4));
2262        for _ in 0..4 {
2263            repos
2264                .publish_commit(&commit("3lqaaa", None))
2265                .expect("the frame was numbered");
2266        }
2267        let mut stream = repos
2268            .subscribe(Some(0), Exclusions::default())
2269            .expect("a cursor inside the buffer");
2270        repos
2271            .publish_commit(&commit("3lqbbb", None))
2272            .expect("the frame was numbered");
2273
2274        let mut seqs = Vec::new();
2275        for _ in 0..5 {
2276            let frame = stream
2277                .next()
2278                .await
2279                .expect("a stream with a valid cursor was ended");
2280            seqs.push(seq_of(&frame));
2281        }
2282        assert_eq!(seqs, vec![1, 2, 3, 4, 5]);
2283        assert_eq!(stream.lagged(), 0);
2284
2285        // Once sent, the extra frame is let go at the next publish.
2286        repos
2287            .publish_commit(&commit("3lqccc", None))
2288            .expect("the frame was numbered");
2289        assert_eq!(repos.all().len(), 4, "the bound applies again");
2290    }
2291
2292    /// An outdated cursor starts at the oldest frame held, which is the
2293    /// eviction edge. It must get room to catch up, not be cut off by the
2294    /// next write and loop between `OutdatedCursor` and `ConsumerTooSlow`.
2295    #[tokio::test]
2296    async fn an_outdated_stream_starting_at_the_oldest_frame_is_not_cut_off() {
2297        let repos = Repos::new(Arc::new(Sequence::in_memory()), ReplayBounds::frames(4));
2298        for _ in 0..6 {
2299            repos
2300                .publish_commit(&commit("3lqaaa", None))
2301                .expect("the frame was numbered");
2302        }
2303        let mut stream = repos
2304            .subscribe(Some(0), Exclusions::default())
2305            .expect("not a future cursor");
2306        for _ in 0..3 {
2307            repos
2308                .publish_commit(&commit("3lqbbb", None))
2309                .expect("the frame was numbered");
2310        }
2311        let (header, _) = decode(&stream.next().await.expect("an info frame"));
2312        assert_eq!(field(&header, "t"), &Value::String("#info".to_owned()));
2313        let mut seqs = Vec::new();
2314        for _ in 0..7 {
2315            seqs.push(seq_of(&stream.next().await.expect("a frame")));
2316        }
2317        assert_eq!(seqs, (3..=9).collect::<Vec<u64>>());
2318    }
2319
2320    /// The edge of falling behind: the buffer evicting the very frame a
2321    /// stream last sent costs that stream nothing.
2322    #[tokio::test]
2323    async fn a_stream_survives_the_eviction_of_a_frame_it_already_sent() {
2324        let size = Repos::default()
2325            .publish_commit(&commit("3lqaaa", None))
2326            .expect("the frame was numbered")
2327            .bytes
2328            .len();
2329        let repos = Repos::new(
2330            Arc::new(Sequence::in_memory()),
2331            ReplayBounds {
2332                frames: 4096,
2333                bytes: 2 * size,
2334            },
2335        );
2336        let mut stream = repos
2337            .subscribe(None, Exclusions::default())
2338            .expect("a live consumer");
2339        repos
2340            .publish_commit(&commit("3lqaaa", None))
2341            .expect("the frame was numbered");
2342        assert_eq!(seq_of(&stream.next().await.expect("frame 1")), 1);
2343
2344        for _ in 0..2 {
2345            repos
2346                .publish_commit(&commit("3lqaaa", None))
2347                .expect("the frame was numbered");
2348        }
2349        assert_eq!(repos.oldest(), 2, "the byte bound evicted frame 1");
2350        assert_eq!(repos.ring().evicted, 1);
2351
2352        assert_eq!(seq_of(&stream.next().await.expect("frame 2")), 2);
2353        assert_eq!(seq_of(&stream.next().await.expect("frame 3")), 3);
2354        assert_eq!(stream.lagged(), 0);
2355    }
2356}