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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! The limiter seam every algorithm and composite shares.
use Duration;
use crateDecision;
use crateDecision as D;
/// The contract a limiter satisfies: take a cost and report the [`Decision`].
///
/// This is the Tier-3 extension point. The single token bucket
/// ([`Throttle`](crate::Throttle)) implements it, and the composite limiters
/// that arrive with the rest of v0.2 (hybrid, layered, per-key) both implement
/// it and consume it — a hybrid limiter, for instance, holds several `Limiter`s
/// and a request must clear all of them.
///
/// [`acquire_cost`](Self::acquire_cost) is the **synchronous, consuming** core:
/// it deducts the tokens on success and never blocks. The waiting
/// [`acquire`](crate::Throttle::acquire) surface is a thin layer on top that
/// sleeps for the returned [`Decision::Retry`] interval and tries again. Keeping
/// the core synchronous is what lets composites combine outcomes (take the
/// longest wait, fail fast on [`Decision::Impossible`]) without spawning tasks.
///
/// Implementors are `Send + Sync` so a limiter can be shared across tasks behind
/// an [`Arc`](std::sync::Arc).
///
/// # Composition
///
/// [`peek`](Self::peek) is what makes "a request must pass *all* of these"
/// correct. A composite cannot simply call [`acquire_cost`](Self::acquire_cost)
/// on each constituent: if an early one grants (and deducts) while a later one
/// refuses, the early tokens are spent for a request that never happened. So a
/// composite first `peek`s every constituent — a non-consuming check — and only
/// commits (via `acquire_cost`) once all of them would grant.
///
/// # Examples
///
/// ```
/// use throttle_net::{Decision, Limiter, Throttle};
///
/// fn drain(limiter: &dyn Limiter) -> u32 {
/// let mut granted = 0;
/// while limiter.acquire_cost(1) == Decision::Acquired {
/// granted += 1;
/// }
/// granted
/// }
///
/// // A bucket that starts full grants exactly its capacity before refusing.
/// let throttle = Throttle::per_second(8);
/// assert_eq!(drain(&throttle), 8);
/// ```
/// A limiter addressed by a key, with its clock type erased so composites can
/// store it without carrying a clock type parameter.
///
/// Implemented by [`PerKey`](crate::PerKey) for every clock, this lets
/// [`Layered`](crate::Layered) hold per-key and per-endpoint scopes behind a
/// trait object and still accept stores built on any clock (a `ManualClock` in
/// tests, the `SystemClock` in production).
pub
/// Aggregates a non-consuming peek across constituents that must *all* grant.
///
/// Returns [`Decision::Impossible`] if any constituent could never grant,
/// [`Decision::Retry`] with the longest constituent wait if any is short of
/// tokens, or [`Decision::Acquired`] only when every one would grant now.
/// Consumes nothing.
pub
/// Peeks every constituent and, only if all would grant, commits each.
///
/// This is the correctness core of "must pass all": [`peek_all`] confirms
/// availability before any tokens are taken. If a concurrent acquisition makes a
/// commit fail after the peek succeeded, the already-committed constituents keep
/// their deduction and the call reports the resulting [`Decision::Retry`] — the
/// composite is then momentarily *more* conservative (tokens spent without
/// admitting), never less, so it can never over-admit.
///
/// `items` is iterated twice (peek pass, then commit pass), hence the `Clone`
/// bound; for slice-backed iterators this is a cheap pointer copy.
pub