Skip to main content

micromegas_object_cache/range_cache/
scheduler.rs

1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
3use std::sync::{Arc, Mutex as StdMutex, Weak};
4
5use anyhow::{Result, anyhow};
6use bytes::Bytes;
7use micromegas_tracing::prelude::*;
8use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore, watch};
9
10use crate::metric_tags;
11
12/// Relative urgency of an origin fetch. Lower is more urgent.
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14#[repr(u8)]
15pub(super) enum Priority {
16    Demand = 0,
17    Prefetch = 1,
18}
19
20impl Priority {
21    fn from_u8(v: u8) -> Self {
22        if v == Priority::Demand as u8 {
23            Priority::Demand
24        } else {
25            Priority::Prefetch
26        }
27    }
28
29    /// The `class` metric-tag value for this priority (see `metric_tags`).
30    pub(super) fn class_label(self) -> &'static str {
31        match self {
32            Priority::Demand => metric_tags::CLASS_DEMAND,
33            Priority::Prefetch => metric_tags::CLASS_PREFETCH,
34        }
35    }
36}
37
38/// The `class` tag for one coalesced run: `Demand` if any of its entries is
39/// currently demand priority, else `Prefetch`. Mirrors `acquire_run_permit`'s
40/// own `all_prefetch` check, evaluated once the permit has been acquired so a
41/// promotion that raced the wait is reflected in the tag.
42pub(super) fn effective_priority(entries: &[Arc<InFlight>]) -> Priority {
43    if entries.iter().any(|e| e.priority() == Priority::Demand) {
44        Priority::Demand
45    } else {
46        Priority::Prefetch
47    }
48}
49
50/// The set of entries submitted in one prefetch call. Used only when
51/// `promote_whole_batch` is enabled: a demand joiner into any sibling entry
52/// promotes every other entry in the batch, not just the one it joined.
53///
54/// Siblings are tracked by `Weak<InFlight>`, captured once each entry is
55/// created or joined, rather than by key string: a key can be removed from
56/// `FetchScheduler::inflight` and later reused by an unrelated fetch (e.g.
57/// after eviction), and promoting by key alone would then spuriously promote
58/// that new, logically unrelated `InFlight`.
59pub(super) struct BatchState {
60    entries: StdMutex<Vec<Weak<InFlight>>>,
61}
62
63impl BatchState {
64    pub(super) fn new(capacity: usize) -> Self {
65        Self {
66            entries: StdMutex::new(Vec::with_capacity(capacity)),
67        }
68    }
69}
70
71type FetchResult = Result<Bytes, Arc<anyhow::Error>>;
72
73/// One outstanding origin fetch (a single block or a `size()` head), shared
74/// across every concurrent caller asking for the same key.
75pub(super) struct InFlight {
76    priority: AtomicU8,
77    promote: Notify,
78    result: watch::Sender<Option<FetchResult>>,
79    batch: Option<Arc<BatchState>>,
80    /// Set by the first `fulfill()` call. Lets a `FulfillGuard`'s panic-path
81    /// fallback tell which entries in a partially-completed run already got
82    /// a real result, so it never clobbers one with a synthesized error.
83    fulfilled: std::sync::atomic::AtomicBool,
84}
85
86impl InFlight {
87    fn new(priority: Priority, batch: Option<Arc<BatchState>>) -> Self {
88        let (result, _rx) = watch::channel(None);
89        Self {
90            priority: AtomicU8::new(priority as u8),
91            promote: Notify::new(),
92            result,
93            batch,
94            fulfilled: std::sync::atomic::AtomicBool::new(false),
95        }
96    }
97
98    fn priority(&self) -> Priority {
99        Priority::from_u8(self.priority.load(Ordering::Acquire))
100    }
101
102    fn promote_to_demand(&self) {
103        self.priority
104            .store(Priority::Demand as u8, Ordering::Release);
105        self.promote.notify_one();
106    }
107
108    /// Deliver `result` to every joiner. Idempotent: only the first call
109    /// actually sends — later calls (notably a `FulfillGuard`'s panic-path
110    /// fallback racing a fetch that in fact completed normally) are no-ops,
111    /// so a real result is never overwritten by a synthesized error.
112    pub(super) fn fulfill(&self, result: FetchResult) {
113        if self.fulfilled.swap(true, Ordering::AcqRel) {
114            return;
115        }
116        // `send_replace`, not `send`: `send` drops the value without storing
117        // it when the channel currently has zero receivers, and joiners
118        // subscribe lazily inside `join()`. A fetch task that completes
119        // before any joiner's `subscribe()` would lose the result and hang
120        // every later joiner forever. `send_replace` stores the value
121        // unconditionally.
122        self.result.send_replace(Some(result));
123    }
124
125    /// Wait for the fetch to complete, returning immediately if it already
126    /// has. Safe against the "subscribe after send" race: a fresh `watch`
127    /// receiver's initial value counts as unseen until `borrow_and_update`
128    /// checks it, so a result sent before this call is still observed here.
129    pub(super) async fn join(&self) -> FetchResult {
130        let mut rx = self.result.subscribe();
131        loop {
132            if let Some(r) = rx.borrow_and_update().clone() {
133                return r;
134            }
135            if rx.changed().await.is_err() {
136                return Err(Arc::new(anyhow!(
137                    "in-flight fetch entry dropped without a result"
138                )));
139            }
140        }
141    }
142}
143
144pub(super) enum Ownership {
145    Owner(Arc<InFlight>),
146    Joiner(Arc<InFlight>),
147}
148
149/// One priority-tiered pool of semaphore permits: `shared` sized `total`,
150/// `prefetch` sized `total - demand_reserved` so prefetch work never starves
151/// demand of its reserved slice. `FetchScheduler` instantiates this twice --
152/// once denominated in runs (`count`), once in bytes (`bytes`) -- so the two
153/// resources (origin-GET parallelism vs. transient fetch memory) are bounded
154/// independently but share the same reservation shape.
155struct PriorityBudget {
156    shared: Arc<Semaphore>,
157    /// Total capacity of `shared`, stored alongside it since
158    /// `tokio::sync::Semaphore` has no capacity accessor. Used by `stats`
159    /// for the saturation sampler.
160    shared_total: usize,
161    prefetch: Arc<Semaphore>,
162    /// Total capacity of `prefetch`, for the same reason as `shared_total`.
163    prefetch_total: usize,
164}
165
166impl PriorityBudget {
167    fn new(total: usize, demand_reserved: usize) -> Self {
168        assert!(total > 0, "fetch budget total must be > 0");
169        // Strictly less: `demand_reserved == total` would leave the prefetch
170        // semaphore with zero permits, hanging every prefetch run forever.
171        assert!(
172            demand_reserved < total,
173            "demand_reserved ({demand_reserved}) must be < total ({total})"
174        );
175        let prefetch_total = total - demand_reserved;
176        Self {
177            shared: Arc::new(Semaphore::new(total)),
178            shared_total: total,
179            prefetch: Arc::new(Semaphore::new(prefetch_total)),
180            prefetch_total,
181        }
182    }
183
184    /// Acquire `n` permits for one coalesced GET covering `entries`, honoring
185    /// promotion: the run's effective priority is the most urgent (minimum)
186    /// of its entries', re-checked every time a promotion wakes this loop so
187    /// a promotion mid-wait drops the prefetch-class requirement.
188    async fn acquire(&self, n: u32, entries: &[Arc<InFlight>]) -> BudgetPermit {
189        loop {
190            let all_prefetch = entries.iter().all(|e| e.priority() == Priority::Prefetch);
191            if !all_prefetch {
192                let shared = self
193                    .shared
194                    .clone()
195                    .acquire_many_owned(n)
196                    .await
197                    .expect("shared semaphore is never closed");
198                return BudgetPermit {
199                    _shared: shared,
200                    _prefetch: None,
201                };
202            }
203
204            tokio::select! {
205                prefetch = self.prefetch.clone().acquire_many_owned(n) => {
206                    let prefetch = prefetch.expect("prefetch semaphore is never closed");
207                    tokio::select! {
208                        shared = self.shared.clone().acquire_many_owned(n) => {
209                            let shared = shared.expect("shared semaphore is never closed");
210                            return BudgetPermit { _shared: shared, _prefetch: Some(prefetch) };
211                        }
212                        _ = any_entry_promoted(entries) => {
213                            drop(prefetch);
214                            continue;
215                        }
216                    }
217                }
218                _ = any_entry_promoted(entries) => {
219                    continue;
220                }
221            }
222        }
223    }
224
225    fn stats(&self) -> BudgetStats {
226        BudgetStats {
227            shared_available: self.shared.available_permits(),
228            shared_total: self.shared_total,
229            prefetch_available: self.prefetch.available_permits(),
230            prefetch_total: self.prefetch_total,
231        }
232    }
233}
234
235/// Held for the duration of one origin GET's share of one `PriorityBudget`;
236/// dropping it frees the slot(s) for the next waiter. Fields are never read,
237/// only held for their `Drop` effect.
238pub(super) struct BudgetPermit {
239    _shared: OwnedSemaphorePermit,
240    _prefetch: Option<OwnedSemaphorePermit>,
241}
242
243/// A fetch-permit budget's current occupancy (one `PriorityBudget`'s worth),
244/// for the saturation sampler (`object-cache-srv/src/saturation_monitor.rs`).
245pub struct BudgetStats {
246    pub shared_available: usize,
247    pub shared_total: usize,
248    pub prefetch_available: usize,
249    pub prefetch_total: usize,
250}
251
252/// Both of `FetchScheduler`'s budgets' occupancy: `count` (one permit per
253/// in-flight run, bounding origin-GET parallelism) and `bytes` (one permit
254/// per in-flight byte, bounding transient fetch memory).
255pub struct FetchBudgetStats {
256    pub count: BudgetStats,
257    pub bytes: BudgetStats,
258}
259
260/// Owns the in-flight single-flight map and the priority-aware origin-fetch
261/// budget.
262pub(super) struct FetchScheduler {
263    inflight: StdMutex<HashMap<String, Arc<InFlight>>>,
264    /// One permit per in-flight coalesced run (blocks + size heads don't
265    /// count against this; only run GETs acquire it). Bounds origin-GET
266    /// parallelism, independent of `bytes`.
267    count: PriorityBudget,
268    /// One permit per byte of an in-flight coalesced run's buffer. Bounds the
269    /// transient origin-fetch memory ceiling, independent of `count`.
270    bytes: PriorityBudget,
271    promote_whole_batch: bool,
272    /// Count of detached fetch tasks (`spawn_run_fetch` runs + `size()` HEADs)
273    /// currently in flight, tracked by `FetchTaskGuard`. Used by graceful
274    /// shutdown to wait for in-flight origin GETs instead of letting the
275    /// tokio runtime drop them.
276    outstanding_tasks: AtomicUsize,
277    /// Notified when `outstanding_tasks` drops to zero. See `wait_drained`.
278    drained: Notify,
279}
280
281impl FetchScheduler {
282    /// `budget_bytes`/`reserved_bytes` are the byte-denominated analog of
283    /// `total`/`demand_reserved`; `max_run_bytes` is the largest byte span one
284    /// coalesced run can reach (`blocks::max_run_bytes`), asserted against
285    /// both so a run can never exceed what either budget can ever grant.
286    pub(super) fn new(
287        total: usize,
288        demand_reserved: usize,
289        budget_bytes: u64,
290        reserved_bytes: u64,
291        max_run_bytes: u64,
292        promote_whole_batch: bool,
293    ) -> Self {
294        // `acquire_many_owned` takes a `u32` permit count, so one run's byte
295        // charge must fit in a `u32` -- checked here once at construction
296        // rather than per-run in `acquire_run_permit`.
297        assert!(
298            max_run_bytes <= u32::MAX as u64,
299            "max_run_bytes ({max_run_bytes}) must fit in u32: acquire_many_owned takes a u32 permit count"
300        );
301        assert!(
302            reserved_bytes < budget_bytes,
303            "fetch_memory_budget reserved_bytes ({reserved_bytes}) must be < budget_bytes ({budget_bytes})"
304        );
305        // Without this, a run larger than the prefetch byte pool would never
306        // complete (and never error) acquiring `bytes` permits: the same hang
307        // the `fetch_memory_budget_mb` floor in `object-cache-srv`'s
308        // `cli.rs::validate` guards against at the CLI boundary.
309        assert!(
310            budget_bytes - reserved_bytes >= max_run_bytes,
311            "fetch memory budget's prefetch pool ({} bytes = budget_bytes {budget_bytes} - \
312             reserved_bytes {reserved_bytes}) must be >= max_run_bytes ({max_run_bytes}), or a \
313             full-size run would hang forever acquiring its byte-budget permits",
314            budget_bytes - reserved_bytes
315        );
316        Self {
317            inflight: StdMutex::new(HashMap::new()),
318            count: PriorityBudget::new(total, demand_reserved),
319            bytes: PriorityBudget::new(
320                usize::try_from(budget_bytes).expect("fetch_memory_budget_bytes fits in usize"),
321                usize::try_from(reserved_bytes).expect("reserved fetch memory bytes fit in usize"),
322            ),
323            promote_whole_batch,
324            outstanding_tasks: AtomicUsize::new(0),
325            drained: Notify::new(),
326        }
327    }
328
329    /// Register one detached fetch task as outstanding. Call *before*
330    /// `tokio::spawn`, synchronously, so there is no window where a
331    /// queued-but-not-yet-polled task is invisible to `wait_drained()`.
332    pub(super) fn track_task(scheduler: &Arc<FetchScheduler>) -> FetchTaskGuard {
333        scheduler.outstanding_tasks.fetch_add(1, Ordering::AcqRel);
334        FetchTaskGuard(scheduler.clone())
335    }
336
337    /// Number of detached fetch tasks currently in flight to origin.
338    pub(super) fn outstanding_tasks(&self) -> usize {
339        self.outstanding_tasks.load(Ordering::Acquire)
340    }
341
342    /// Resolves once no detached fetch task is outstanding. Race-free by
343    /// construction: `Notified::enable`'s docs guarantee that a
344    /// `notify_waiters()` call is observed by a `Notified` future as long as
345    /// that call happens after the `Notified` was created, regardless of
346    /// whether `enable`/`poll` has run yet -- so creating the `Notified`
347    /// *before* re-checking the count (rather than after) means a
348    /// `notify_waiters()` racing the check in between is never missed. This
349    /// is a different guarantee than `any_entry_promoted`'s below, which
350    /// relies on `notify_one`'s stored-permit semantics; `notify_waiters`
351    /// stores no permit, so the ordering above -- not a permit -- is what
352    /// makes this race-free.
353    pub(super) async fn wait_drained(&self) {
354        loop {
355            if self.outstanding_tasks() == 0 {
356                return;
357            }
358            let notified = self.drained.notified();
359            if self.outstanding_tasks() == 0 {
360                return;
361            }
362            notified.await;
363        }
364    }
365
366    /// Both fetch-permit budgets' current occupancy, for the saturation
367    /// sampler (`object-cache-srv/src/saturation_monitor.rs`).
368    pub(super) fn fetch_budget_stats(&self) -> FetchBudgetStats {
369        FetchBudgetStats {
370            count: self.count.stats(),
371            bytes: self.bytes.stats(),
372        }
373    }
374
375    /// Number of keys (blocks or `size()` heads) currently in flight to
376    /// origin.
377    pub(super) fn inflight_len(&self) -> usize {
378        self.inflight.lock().expect("inflight lock").len()
379    }
380
381    /// Look up `key` in the in-flight map: become the owner if absent, or a
382    /// joiner if present. A demand joiner into a prefetch-priority entry
383    /// promotes it (and, if `promote_whole_batch`, its batch siblings) to
384    /// demand so it competes for reserved capacity instead of sitting behind
385    /// other prefetch work.
386    ///
387    /// The entry is registered in `batch` (owner or joiner alike) while the
388    /// inflight lock is still held, i.e. before any concurrent demand joiner
389    /// can find the entry and run `promote_batch_siblings` over a
390    /// partially-populated list.
391    pub(super) fn own_or_join(
392        &self,
393        key: String,
394        prio: Priority,
395        batch: Option<Arc<BatchState>>,
396    ) -> Ownership {
397        let mut promote_batch: Option<(Arc<BatchState>, Arc<InFlight>)> = None;
398        let ownership = {
399            let mut map = self.inflight.lock().expect("inflight lock");
400            if let Some(existing) = map.get(&key) {
401                if prio == Priority::Demand && existing.priority() == Priority::Prefetch {
402                    existing.promote_to_demand();
403                    if self.promote_whole_batch
404                        && let Some(bs) = existing.batch.clone()
405                    {
406                        promote_batch = Some((bs, existing.clone()));
407                    }
408                }
409                if let Some(bs) = &batch {
410                    bs.entries
411                        .lock()
412                        .expect("batch lock")
413                        .push(Arc::downgrade(existing));
414                }
415                Ownership::Joiner(existing.clone())
416            } else {
417                let entry = Arc::new(InFlight::new(prio, batch.clone()));
418                if let Some(bs) = &batch {
419                    bs.entries
420                        .lock()
421                        .expect("batch lock")
422                        .push(Arc::downgrade(&entry));
423                }
424                map.insert(key.clone(), entry.clone());
425                Ownership::Owner(entry)
426            }
427        };
428        if let Some((bs, skip)) = promote_batch {
429            self.promote_batch_siblings(&bs, &skip);
430        }
431        ownership
432    }
433
434    /// Promote every sibling in `batch` other than `skip` to demand priority.
435    /// Siblings are resolved through `Weak<InFlight>` references captured at
436    /// batch-membership time, not by re-looking up their key in `inflight`:
437    /// the key may since have been removed and reused by an unrelated fetch,
438    /// and identity (not the key string) is what must match here.
439    fn promote_batch_siblings(&self, batch: &BatchState, skip: &Arc<InFlight>) {
440        let entries = batch.entries.lock().expect("batch lock");
441        for weak in entries.iter() {
442            if let Some(entry) = weak.upgrade()
443                && !Arc::ptr_eq(&entry, skip)
444            {
445                entry.promote_to_demand();
446            }
447        }
448    }
449
450    pub(super) fn remove_entry(&self, key: &str) {
451        self.inflight.lock().expect("inflight lock").remove(key);
452    }
453}
454
455/// Scope guard held by the task that owns one or more in-flight entries
456/// (either a single `size()` head or the members of one coalesced block
457/// run). If the owning task exits normally, it calls `disarm()` after
458/// fulfilling every entry and removing it from `FetchScheduler::inflight`.
459///
460/// If instead the task panics (e.g. `Bytes::slice` on a short origin read)
461/// tokio catches the unwind at the task boundary and silently drops the
462/// result, but this guard's `Drop` still runs during that unwind: it
463/// fulfills any not-yet-fulfilled entry with an error (the `fulfilled` flag
464/// on `InFlight` makes this a no-op for entries that already got a real
465/// result) and removes every entry from the map. Without this, a panicking
466/// owner would leave `fulfill()` never called, hanging every joiner
467/// (including the owner itself) forever and leaking the entry permanently.
468pub(super) struct FulfillGuard {
469    scheduler: Arc<FetchScheduler>,
470    entries: Vec<(String, Arc<InFlight>)>,
471    armed: bool,
472}
473
474impl FulfillGuard {
475    pub(super) fn new(
476        scheduler: Arc<FetchScheduler>,
477        entries: Vec<(String, Arc<InFlight>)>,
478    ) -> Self {
479        Self {
480            scheduler,
481            entries,
482            armed: true,
483        }
484    }
485
486    /// Call once the normal completion path has fulfilled and removed every
487    /// entry, so the guard's `Drop` becomes a no-op.
488    pub(super) fn disarm(mut self) {
489        self.armed = false;
490    }
491}
492
493impl Drop for FulfillGuard {
494    fn drop(&mut self) {
495        if !self.armed {
496            return;
497        }
498        let n = self.entries.len();
499        // `std::thread::panicking()` is `true` only while the current thread
500        // is unwinding from a real panic; it is `false` when a task future is
501        // simply dropped without being polled to completion (e.g. the tokio
502        // runtime shutting down), so it's exactly the signal needed to tell
503        // the two cases apart.
504        let panicking = std::thread::panicking();
505        if panicking {
506            warn!("fetch task panicked; fulfilling {n} in-flight entries with an error");
507        } else {
508            warn!(
509                "cache service shutting down; abandoning {n} in-flight fetch entries \
510                 (joiners see a synthesized error and must refetch after restart)"
511            );
512        }
513        let msg = if panicking {
514            "fetch task panicked before producing a result"
515        } else {
516            "fetch task was abandoned (cache service shutting down)"
517        };
518        for (key, entry) in &self.entries {
519            entry.fulfill(Err(Arc::new(anyhow!(msg))));
520            self.scheduler.remove_entry(key);
521        }
522    }
523}
524
525/// Scope guard tracking the lifetime of one detached fetch task, held for
526/// its whole body. Distinct from `FulfillGuard`, which tracks *entry
527/// fulfillment*, not task lifetime -- the two guards serve different
528/// purposes and both must survive a panicking OR shutdown-dropped task.
529/// Because this is a plain local variable inside the spawned async block,
530/// its `Drop` runs whether the task finishes normally, panics, or is dropped
531/// without being polled to completion, so `outstanding_tasks` stays accurate
532/// in all three cases without any special-casing.
533pub(super) struct FetchTaskGuard(Arc<FetchScheduler>);
534
535impl Drop for FetchTaskGuard {
536    fn drop(&mut self) {
537        if self.0.outstanding_tasks.fetch_sub(1, Ordering::AcqRel) == 1 {
538            self.0.drained.notify_waiters();
539        }
540    }
541}
542
543/// Held for the duration of one coalesced run: `count` bounds origin-GET
544/// parallelism and is dropped right after the GET returns; `bytes` bounds the
545/// run's transient memory and is held through backend admission. See
546/// `acquire_run_permit` and its caller in `fetch.rs`.
547pub(super) struct RunPermit {
548    pub(super) count: BudgetPermit,
549    pub(super) bytes: BudgetPermit,
550}
551
552/// Resolves when any of `entries`' priority has been promoted since this call
553/// started. Constructing the `Notified` future(s) up front (before the caller
554/// re-checks priority) is what makes this race-free: tokio guarantees a
555/// `notify_one()` that happens after a `Notified` is created is never missed,
556/// even if that `Notified` is later dropped without completing.
557async fn any_entry_promoted(entries: &[Arc<InFlight>]) {
558    match entries {
559        [] => std::future::pending::<()>().await,
560        [only] => only.promote.notified().await,
561        many => {
562            let futs: Vec<_> = many
563                .iter()
564                .map(|e| Box::pin(e.promote.notified()))
565                .collect();
566            futures::future::select_all(futs).await;
567        }
568    }
569}
570
571/// Acquire the permits needed to run one coalesced GET covering `entries`,
572/// spanning `run_bytes` bytes: bytes first, then count. Every run acquires in
573/// this order, so there is no hold-and-wait cycle between the two budgets;
574/// the cost is that a run holding its bytes while queued for a count slot
575/// parks that byte budget idle for the wait, which only bites when the byte
576/// budget is deliberately sized larger than `count * typical run`. Each
577/// underlying `PriorityBudget::acquire` call independently honors promotion
578/// (see its doc).
579pub(super) async fn acquire_run_permit(
580    scheduler: &FetchScheduler,
581    entries: &[Arc<InFlight>],
582    run_bytes: u64,
583) -> RunPermit {
584    let n = u32::try_from(run_bytes)
585        .expect("run size validated <= u32::MAX at construction (FetchScheduler::new asserts max_run_bytes fits)");
586    let bytes = scheduler.bytes.acquire(n, entries).await;
587    let count = scheduler.count.acquire(1, entries).await;
588    RunPermit { count, bytes }
589}
590
591/// Reconstruct an owned error from a shared in-flight failure. `anyhow::Error`
592/// is not `Clone`, so a joiner cannot move it out of the `Arc`; and it must
593/// not be stringified, or a missing key would stop downcasting to
594/// `object_store::Error::NotFound` and would surface as a 500 instead of a
595/// 404 (see `validation::is_not_found`).
596pub(super) fn reconstruct_shared_error(shared: &Arc<anyhow::Error>) -> anyhow::Error {
597    if let Some(object_store::Error::NotFound { path, source }) =
598        shared.downcast_ref::<object_store::Error>()
599    {
600        let rebuilt = object_store::Error::NotFound {
601            path: path.clone(),
602            source: Box::new(std::io::Error::new(
603                std::io::ErrorKind::NotFound,
604                source.to_string(),
605            )),
606        };
607        return anyhow::Error::from(rebuilt);
608    }
609    // `{shared:?}` keeps the full context chain (and backtrace, if captured)
610    // so joiners' 500 log lines are as informative as the owner's.
611    anyhow!("{shared:?}")
612}
613
614pub(super) fn decode_size(data: &Bytes) -> Result<u64> {
615    Ok(u64::from_le_bytes(
616        data[..8].try_into().expect("8-byte size slice"),
617    ))
618}