1use core::cmp::Ordering;
4use core::num::NonZeroU64;
5use std::sync::Arc;
6
7use super::super::RoleName;
8use super::super::capacity::{PositiveCapacity, ZeroCapacity};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct BindingCapacity {
13 maximum: PositiveCapacity,
14}
15
16impl BindingCapacity {
17 pub fn new(maximum: usize) -> Result<Self, ZeroCapacity> {
23 PositiveCapacity::new(maximum).map(|maximum| Self { maximum })
24 }
25
26 const fn maximum(self) -> usize {
27 self.maximum.get()
28 }
29}
30
31#[derive(Clone)]
39pub struct BindingGeneration {
40 ordinal: NonZeroU64,
41 token: Arc<()>,
42}
43
44impl BindingGeneration {
45 fn issued(ordinal: NonZeroU64, token: Arc<()>) -> Self {
46 Self { ordinal, token }
47 }
48
49 #[must_use]
51 pub const fn get(&self) -> u64 {
52 self.ordinal.get()
53 }
54}
55
56impl core::fmt::Debug for BindingGeneration {
57 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58 formatter
59 .debug_tuple("BindingGeneration")
60 .field(&self.ordinal)
61 .finish()
62 }
63}
64
65impl PartialEq for BindingGeneration {
66 fn eq(&self, other: &Self) -> bool {
67 self.ordinal == other.ordinal && Arc::ptr_eq(&self.token, &other.token)
68 }
69}
70
71impl Eq for BindingGeneration {}
72
73#[derive(Clone, Debug, Eq, PartialEq)]
75pub enum BindingExpectation {
76 Absent,
78 Exact(BindingGeneration),
80}
81
82pub struct BindingEvidence<Role> {
87 generation: BindingGeneration,
88 role: RoleName<Role>,
89}
90
91impl<Role> BindingEvidence<Role> {
92 fn issued(generation: BindingGeneration, role: RoleName<Role>) -> Self {
93 Self { generation, role }
94 }
95
96 #[must_use]
98 pub const fn generation(&self) -> &BindingGeneration {
99 &self.generation
100 }
101
102 #[must_use]
104 pub fn role(&self) -> &Role {
105 self.role.role()
106 }
107}
108
109impl<Role> Clone for BindingEvidence<Role> {
110 fn clone(&self) -> Self {
111 Self {
112 generation: self.generation.clone(),
113 role: self.role.clone(),
114 }
115 }
116}
117
118impl<Role> core::fmt::Debug for BindingEvidence<Role>
119where
120 Role: core::fmt::Debug,
121{
122 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123 formatter
124 .debug_struct("BindingEvidence")
125 .field("generation", &self.generation)
126 .field("role", self.role())
127 .finish()
128 }
129}
130
131#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
133pub struct BindingRequestId(u64);
134
135impl BindingRequestId {
136 #[must_use]
138 pub const fn new(value: u64) -> Self {
139 Self(value)
140 }
141
142 #[must_use]
144 pub const fn get(self) -> u64 {
145 self.0
146 }
147}
148
149struct Binding<Key, Role> {
150 key: Key,
151 evidence: BindingEvidence<Role>,
152}
153
154impl<Key, Role> Binding<Key, Role> {
155 fn into_parts(self) -> (Key, BindingEvidence<Role>) {
156 (self.key, self.evidence)
157 }
158}
159
160struct GenerationSequence {
161 next: Option<NonZeroU64>,
162 token: Arc<()>,
163}
164
165impl GenerationSequence {
166 fn new() -> Self {
167 Self {
168 next: Some(NonZeroU64::MIN),
169 token: Arc::new(()),
170 }
171 }
172
173 fn issue(&mut self) -> Option<BindingGeneration> {
174 let ordinal = self.next?;
175 self.next = ordinal.checked_add(1);
176 Some(BindingGeneration::issued(ordinal, Arc::clone(&self.token)))
177 }
178}
179
180pub(super) enum BindingReservationRejected<Key> {
181 AlreadyBound(Key),
182 CapacityExhausted(Key),
183 GenerationsExhausted(Key),
184}
185
186pub(super) struct BindingTable<Key, Role> {
187 capacity: BindingCapacity,
188 bindings: Vec<Binding<Key, Role>>,
189 generations: GenerationSequence,
190}
191
192impl<Key, Role> BindingTable<Key, Role>
193where
194 Key: Ord,
195{
196 pub(super) fn new(capacity: BindingCapacity) -> Self {
197 Self {
198 capacity,
199 bindings: Vec::new(),
200 generations: GenerationSequence::new(),
201 }
202 }
203
204 pub(super) fn binding(&self, key: &Key) -> Option<&BindingEvidence<Role>> {
205 self.position(key)
206 .ok()
207 .map(|position| &self.bindings[position].evidence)
208 }
209
210 pub(super) fn occupied(&mut self, key: &Key) -> Option<OccupiedBinding<'_, Key, Role>> {
211 let position = self.position(key).ok()?;
212 Some(OccupiedBinding {
213 binding: &mut self.bindings[position],
214 generations: &mut self.generations,
215 })
216 }
217
218 pub(super) fn reserve(
219 &mut self,
220 key: Key,
221 role: RoleName<Role>,
222 ) -> Result<ReservedBinding<'_, Key, Role>, BindingReservationRejected<Key>> {
223 let position = match self.position(&key) {
224 Ok(_) => return Err(BindingReservationRejected::AlreadyBound(key)),
225 Err(position) => position,
226 };
227 match self.bindings.len().cmp(&self.capacity.maximum()) {
228 Ordering::Less => {}
229 Ordering::Equal | Ordering::Greater => {
230 return Err(BindingReservationRejected::CapacityExhausted(key));
231 }
232 }
233 let generation = match self.generations.issue() {
234 Some(generation) => generation,
235 None => return Err(BindingReservationRejected::GenerationsExhausted(key)),
236 };
237 Ok(ReservedBinding {
238 bindings: &mut self.bindings,
239 position,
240 key,
241 generation,
242 role,
243 })
244 }
245
246 pub(super) fn remove(&mut self, key: &Key) -> Option<(Key, BindingEvidence<Role>)> {
247 self.position(key)
248 .ok()
249 .map(|position| self.bindings.remove(position).into_parts())
250 }
251
252 pub(super) fn retire_role(&mut self, role: &Role) -> Vec<(Key, BindingEvidence<Role>)>
253 where
254 Role: Eq,
255 {
256 self.bindings
257 .extract_if(.., |binding| binding.evidence.role() == role)
258 .map(Binding::into_parts)
259 .collect()
260 }
261
262 pub(super) fn drain(&mut self) -> Vec<(Key, BindingEvidence<Role>)> {
263 self.bindings.drain(..).map(Binding::into_parts).collect()
264 }
265
266 fn position(&self, key: &Key) -> Result<usize, usize> {
267 self.bindings
268 .binary_search_by(|binding| binding.key.cmp(key))
269 }
270}
271
272#[must_use = "a reserved binding must commit or return its key"]
273pub(super) struct ReservedBinding<'table, Key, Role> {
274 bindings: &'table mut Vec<Binding<Key, Role>>,
275 position: usize,
276 key: Key,
277 generation: BindingGeneration,
278 role: RoleName<Role>,
279}
280
281pub(super) struct OccupiedBinding<'table, Key, Role> {
282 binding: &'table mut Binding<Key, Role>,
283 generations: &'table mut GenerationSequence,
284}
285
286impl<Key, Role> OccupiedBinding<'_, Key, Role> {
287 pub(super) fn rebind(
288 self,
289 role: RoleName<Role>,
290 ) -> Option<(BindingEvidence<Role>, BindingEvidence<Role>)> {
291 let generation = self.generations.issue()?;
292 let current = BindingEvidence::issued(generation, role);
293 let admitted = current.clone();
294 let prior = core::mem::replace(&mut self.binding.evidence, current);
295 Some((prior, admitted))
296 }
297}
298
299impl<Key, Role> ReservedBinding<'_, Key, Role> {
300 pub(super) fn commit(self) -> BindingEvidence<Role> {
301 let evidence = BindingEvidence::issued(self.generation, self.role);
302 let admitted = evidence.clone();
303 self.bindings.insert(
304 self.position,
305 Binding {
306 key: self.key,
307 evidence,
308 },
309 );
310 admitted
311 }
312
313 pub(super) fn reject(self) -> Key {
314 self.key
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use core::num::NonZeroU64;
321 use std::sync::Arc;
322
323 use super::{
324 BindingCapacity, BindingEvidence, BindingGeneration, BindingReservationRejected,
325 BindingTable, GenerationSequence,
326 };
327 use crate::atomic::RoleName;
328
329 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
330 struct Account(u8);
331
332 #[derive(Debug, Eq, PartialEq)]
333 enum SearchRole {
334 Primary,
335 Replica,
336 }
337
338 fn bindings(maximum: usize) -> BindingTable<Account, SearchRole> {
339 let capacity = BindingCapacity::new(maximum)
340 .unwrap_or_else(|_| panic!("test binding capacity must be positive"));
341 BindingTable::new(capacity)
342 }
343
344 fn commit(
345 table: &mut BindingTable<Account, SearchRole>,
346 account: Account,
347 role: SearchRole,
348 ) -> BindingEvidence<SearchRole> {
349 table
350 .reserve(account, RoleName::new(role))
351 .unwrap_or_else(|_| panic!("test binding reservation must succeed"))
352 .commit()
353 }
354
355 fn first_generation() -> BindingGeneration {
356 BindingGeneration {
357 ordinal: NonZeroU64::MIN,
358 token: Arc::new(()),
359 }
360 }
361
362 #[test]
363 fn independent_pools_cannot_share_a_numeric_generation() {
364 let left = first_generation();
365 let right = first_generation();
366
367 assert_eq!(left.get(), right.get());
368 assert_ne!(left, right);
369 }
370
371 #[test]
372 fn evidence_clones_identity_without_owning_a_key() {
373 let evidence = BindingEvidence {
374 generation: first_generation(),
375 role: RoleName::new(String::from("search")),
376 };
377 let cloned = evidence.clone();
378
379 assert_eq!(evidence.generation(), cloned.generation());
380 assert_eq!(evidence.role(), "search");
381 assert_eq!(cloned.role(), "search");
382 }
383
384 #[test]
385 fn table_preserves_order_and_returns_rejected_keys_without_clone() {
386 let mut table = bindings(2);
387 let second = commit(&mut table, Account(2), SearchRole::Replica);
388 let first = commit(&mut table, Account(1), SearchRole::Primary);
389
390 let found_first = table
391 .binding(&Account(1))
392 .unwrap_or_else(|| panic!("first binding must remain"));
393 let found_second = table
394 .binding(&Account(2))
395 .unwrap_or_else(|| panic!("second binding must remain"));
396 assert_eq!(found_first.generation(), first.generation());
397 assert_eq!(found_first.role(), first.role());
398 assert_eq!(found_second.generation(), second.generation());
399 assert_eq!(found_second.role(), second.role());
400
401 let duplicate = table
402 .reserve(Account(1), RoleName::new(SearchRole::Replica))
403 .err()
404 .unwrap_or_else(|| panic!("duplicate key must be rejected"));
405 assert!(matches!(
406 duplicate,
407 BindingReservationRejected::AlreadyBound(Account(1))
408 ));
409
410 let full = table
411 .reserve(Account(3), RoleName::new(SearchRole::Primary))
412 .err()
413 .unwrap_or_else(|| panic!("full table must reject the owned key"));
414 assert!(matches!(
415 full,
416 BindingReservationRejected::CapacityExhausted(Account(3))
417 ));
418
419 let drained = table.drain();
420 assert_eq!(
421 drained
422 .iter()
423 .map(|(account, _)| account.0)
424 .collect::<Vec<_>>(),
425 vec![1, 2]
426 );
427 }
428
429 #[test]
430 fn rejected_admission_burns_its_generation_without_retaining_the_key() {
431 let mut table = bindings(2);
432 let rejected_key = table
433 .reserve(Account(1), RoleName::new(SearchRole::Primary))
434 .unwrap_or_else(|_| panic!("first reservation must succeed"))
435 .reject();
436 assert_eq!(rejected_key, Account(1));
437 assert!(matches!(table.binding(&Account(1)), None));
438
439 let evidence = commit(&mut table, Account(1), SearchRole::Primary);
440 assert_eq!(evidence.generation().get(), 2);
441 }
442
443 #[test]
444 fn generation_exhaustion_never_wraps_or_loses_the_key() {
445 let maximum = NonZeroU64::new(u64::MAX)
446 .unwrap_or_else(|| panic!("the maximum unsigned value is non-zero"));
447 let mut sequence = GenerationSequence {
448 next: Some(maximum),
449 token: Arc::new(()),
450 };
451 let final_generation = sequence
452 .issue()
453 .unwrap_or_else(|| panic!("the last generation remains issuable"));
454 assert_eq!(final_generation.get(), u64::MAX);
455 let exhausted_generation = sequence.issue();
456 assert!(matches!(exhausted_generation, None));
457
458 let mut table = bindings(1);
459 table.generations.next = None;
460 let rejection = table
461 .reserve(Account(9), RoleName::new(SearchRole::Primary))
462 .err()
463 .unwrap_or_else(|| panic!("exhaustion must reject the reservation"));
464 assert!(matches!(
465 rejection,
466 BindingReservationRejected::GenerationsExhausted(Account(9))
467 ));
468 }
469
470 #[test]
471 fn role_retirement_and_unbind_return_complete_owned_bindings() {
472 let mut table = bindings(3);
473 commit(&mut table, Account(3), SearchRole::Primary);
474 commit(&mut table, Account(1), SearchRole::Replica);
475 commit(&mut table, Account(2), SearchRole::Primary);
476
477 let retired = table.retire_role(&SearchRole::Primary);
478 assert_eq!(
479 retired
480 .iter()
481 .map(|(account, _)| account.0)
482 .collect::<Vec<_>>(),
483 vec![2, 3]
484 );
485 assert!(matches!(table.binding(&Account(2)), None));
486 assert!(matches!(table.binding(&Account(3)), None));
487
488 let (account, evidence) = table
489 .remove(&Account(1))
490 .unwrap_or_else(|| panic!("remaining binding must be removable"));
491 assert_eq!(account, Account(1));
492 assert_eq!(evidence.role(), &SearchRole::Replica);
493 assert!(matches!(table.binding(&Account(1)), None));
494 }
495
496 #[test]
497 fn role_change_commits_once_without_removing_the_binding() {
498 let mut table = bindings(1);
499 let original = commit(&mut table, Account(1), SearchRole::Primary);
500
501 let (prior, current) = table
502 .occupied(&Account(1))
503 .unwrap_or_else(|| panic!("the binding must remain occupied"))
504 .rebind(RoleName::new(SearchRole::Replica))
505 .unwrap_or_else(|| panic!("a fresh generation must remain"));
506
507 assert_eq!(prior.generation(), original.generation());
508 assert_eq!(prior.role(), &SearchRole::Primary);
509 assert_eq!(current.generation().get(), 2);
510 assert_eq!(current.role(), &SearchRole::Replica);
511 let retained = table
512 .binding(&Account(1))
513 .unwrap_or_else(|| panic!("replacement cannot expose absence"));
514 assert_eq!(retained.generation(), current.generation());
515 assert_eq!(retained.role(), current.role());
516 }
517
518 #[test]
519 fn exhausted_role_change_preserves_the_current_binding() {
520 let mut table = bindings(1);
521 let original = commit(&mut table, Account(1), SearchRole::Primary);
522 table.generations.next = None;
523
524 let replacement = table
525 .occupied(&Account(1))
526 .unwrap_or_else(|| panic!("the binding must remain occupied"))
527 .rebind(RoleName::new(SearchRole::Replica));
528 assert!(matches!(replacement, None));
529
530 let retained = table
531 .binding(&Account(1))
532 .unwrap_or_else(|| panic!("exhaustion cannot remove the binding"));
533 assert_eq!(retained.generation(), original.generation());
534 assert_eq!(retained.role(), &SearchRole::Primary);
535 }
536}