1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Shared `PackMissing` retry policy for read- and fetch-side races
//! against a concurrent `manage gc sweep`.
//!
//! The packchain engine has two read-shaped surfaces that load
//! `chain.json` once, then issue follow-up GETs against the packs that
//! chain points at:
//!
//! - [`crate::packchain::read::read_blob`] (issue #136 wired the retry
//! here first).
//! - [`crate::packchain::fetch::fetch_batch`] (issue #148 extended the
//! same policy here).
//!
//! Both can race a `manage gc sweep` that compacts the chain and
//! deletes packs the original snapshot named. A naive caller surfaces
//! [`PackchainError::PackMissing`] for a key that was perfectly
//! reachable at the moment of the first load. The right shape is:
//! reload `chain.json`, observe that the failing key is no longer
//! referenced, and retry against the fresh chain.
//!
//! This module centralises the retry constants ([`PACK_MISSING_MAX_RETRIES`],
//! [`PACK_MISSING_RETRY_BACKOFFS`]) and the
//! [`chain_references_pack_key`] discriminator so both call sites stay
//! in sync. Genuine bucket inconsistency (the reloaded chain still
//! names the missing key) and non-`PackMissing` errors fail fast —
//! waiting through the backoff schedule when the operation has no
//! chance of succeeding wastes wall-clock for no recovery upside.
use Duration;
use PackchainError;
use ;
use ChainManifest;
/// Maximum number of times a read- or fetch-side caller reloads
/// `chain.json` and retries the failing operation after observing a
/// [`PackchainError::PackMissing`] that the reloaded chain shows is no
/// longer referenced — i.e. a concurrent `manage gc sweep` deleted
/// packs the original chain snapshot pointed at (issue #136 for read,
/// issue #148 for fetch). After this many retries the caller surfaces
/// [`PackchainError::ConcurrentGcRetriesExhausted`].
pub const PACK_MISSING_MAX_RETRIES: u32 = 3;
/// Backoff schedule (per retry attempt) for the chain reload loop.
/// The schedule must have exactly [`PACK_MISSING_MAX_RETRIES`]
/// entries: index `i` is the sleep before the `i`-th retry. The
/// growing pattern (100 ms → 500 ms → 2 s) gives a vigorous compact
/// cycle time to settle without unnecessarily blocking a quick caller
/// when the race was a one-shot.
pub const PACK_MISSING_RETRY_BACKOFFS: = ;
/// Compile-time pin: the backoff schedule must have exactly one entry
/// per retry attempt. If the cap and the schedule ever drift apart
/// (e.g. someone bumps the cap without extending the array, or trims
/// the array without lowering the cap), the resulting index into
/// `PACK_MISSING_RETRY_BACKOFFS` at the loop's last attempt would
/// panic at runtime. Catch the desync at build time instead.
const _: = assert!;
/// Whether any segment of `chain` would produce a pack or idx key
/// equal to `missing_key`. Used by the retry loops to distinguish a
/// concurrent `manage gc sweep` (the key is *not* in the reloaded
/// chain → retry is safe) from a genuine bucket inconsistency (the
/// key *is* still referenced → data loss, fail fast). Returns the
/// parse error from [`segment_pack_sha`] if a segment's `pack` field
/// is malformed.
pub