1mod table;
29
30use core::hash::Hash;
31#[cfg(loom)]
32use loom::sync::{Arc, RwLock};
33#[cfg(not(loom))]
34use parking_lot::RwLock;
35#[cfg(not(loom))]
36use std::sync::Arc;
37use std::{fmt, hash::Hasher, num::NonZeroU64};
38
39use table::OpenTable;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct AddressInUse<A>(pub A);
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum ClaimError<A> {
48 AddressInUse(A),
50 RegistrationIdsExhausted(A),
54}
55
56struct RegistrationScopeMarker {
57 _private: u8,
58}
59
60#[derive(Clone)]
67pub struct RegistrationScopeId(Arc<RegistrationScopeMarker>);
68
69impl PartialEq for RegistrationScopeId {
70 fn eq(&self, other: &Self) -> bool {
71 Arc::ptr_eq(&self.0, &other.0)
72 }
73}
74
75impl Eq for RegistrationScopeId {}
76
77impl core::hash::Hash for RegistrationScopeId {
78 fn hash<H: Hasher>(&self, state: &mut H) {
79 Arc::as_ptr(&self.0).hash(state);
80 }
81}
82
83impl fmt::Debug for RegistrationScopeId {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter
86 .debug_tuple("RegistrationScopeId")
87 .field(&Arc::as_ptr(&self.0))
88 .finish()
89 }
90}
91
92#[derive(Clone, PartialEq, Eq, Hash)]
98pub struct RegistrationId {
99 scope: RegistrationScopeId,
100 generation: NonZeroU64,
101}
102
103impl RegistrationId {
104 #[must_use]
106 pub fn scope_id(&self) -> &RegistrationScopeId {
107 &self.scope
108 }
109}
110
111impl fmt::Debug for RegistrationId {
112 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113 formatter
114 .debug_struct("RegistrationId")
115 .field("scope", &self.scope)
116 .field("generation", &self.generation)
117 .finish()
118 }
119}
120
121struct Inner<A, E> {
122 scope: RegistrationScopeId,
123 entries: RwLock<OpenTable<A, E>>,
124}
125
126impl<A, E> Inner<A, E> {
127 #[cfg(loom)]
128 fn read_entries(&self) -> loom::sync::RwLockReadGuard<'_, OpenTable<A, E>> {
129 self.entries.read().unwrap()
132 }
133
134 #[cfg(loom)]
135 fn write_entries(&self) -> loom::sync::RwLockWriteGuard<'_, OpenTable<A, E>> {
136 self.entries.write().unwrap()
137 }
138
139 #[cfg(not(loom))]
140 fn read_entries(&self) -> parking_lot::RwLockReadGuard<'_, OpenTable<A, E>> {
141 self.entries.read()
142 }
143
144 #[cfg(not(loom))]
145 fn write_entries(&self) -> parking_lot::RwLockWriteGuard<'_, OpenTable<A, E>> {
146 self.entries.write()
147 }
148}
149
150pub struct AddressSpace<A, E> {
152 inner: Arc<Inner<A, E>>,
153}
154
155impl<A, E> Clone for AddressSpace<A, E> {
156 fn clone(&self) -> Self {
157 Self {
158 inner: self.inner.clone(),
159 }
160 }
161}
162
163impl<A, E> Default for AddressSpace<A, E> {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl<A, E> AddressSpace<A, E> {
170 #[must_use]
172 pub fn new() -> Self {
173 Self {
174 inner: Arc::new(Inner {
175 scope: RegistrationScopeId(Arc::new(RegistrationScopeMarker { _private: 0 })),
176 entries: RwLock::new(OpenTable::new()),
177 }),
178 }
179 }
180
181 #[must_use]
183 pub fn registration_scope_id(&self) -> RegistrationScopeId {
184 self.inner.scope.clone()
185 }
186
187 #[must_use]
189 pub fn len(&self) -> usize {
190 self.inner.read_entries().len()
191 }
192
193 #[must_use]
195 pub fn is_empty(&self) -> bool {
196 self.len() == 0
197 }
198}
199
200impl<A: Eq + Hash, E> AddressSpace<A, E> {
201 pub fn shrink_to_fit(&self) {
210 self.inner.write_entries().shrink_to_fit();
211 }
212}
213
214impl<A, E> AddressSpace<A, E>
215where
216 A: Eq + Hash + Clone,
217{
218 pub fn claim(&self, address: A, endpoint: E) -> Result<Lease<A, E>, AddressInUse<A>> {
236 match self.try_claim(address, endpoint) {
237 Ok(lease) => Ok(lease),
238 Err(ClaimError::AddressInUse(address)) => Err(AddressInUse(address)),
239 Err(ClaimError::RegistrationIdsExhausted(_)) => {
240 panic!("registration identities exhausted")
241 }
242 }
243 }
244
245 pub fn try_claim(&self, address: A, endpoint: E) -> Result<Lease<A, E>, ClaimError<A>> {
258 let key = address.clone();
261 let mut entries = self.inner.write_entries();
262 if entries.get(&address).is_some() {
263 return Err(ClaimError::AddressInUse(address));
264 }
265 let Some(generation) = entries.take_generation() else {
266 return Err(ClaimError::RegistrationIdsExhausted(address));
267 };
268 entries.insert(key, generation.get(), endpoint);
269 Ok(Lease {
270 inner: self.inner.clone(),
271 address,
272 generation,
273 released: false,
274 })
275 }
276}
277
278impl<A, E> AddressSpace<A, E>
279where
280 A: Eq + Hash,
281 E: Clone,
282{
283 #[must_use]
290 pub fn resolve(&self, address: &A) -> Option<E> {
291 let endpoint = {
292 let guard = self.inner.read_entries();
293 guard.get(address).map(|entry| Arc::clone(&entry.endpoint))
294 };
295 endpoint.as_deref().cloned()
296 }
297}
298
299pub struct Lease<A, E>
301where
302 A: Eq + Hash,
303{
304 inner: Arc<Inner<A, E>>,
305 address: A,
306 generation: NonZeroU64,
307 released: bool,
308}
309
310impl<A, E> Lease<A, E>
311where
312 A: Eq + Hash,
313{
314 #[must_use]
316 pub fn address(&self) -> &A {
317 &self.address
318 }
319
320 #[must_use]
325 pub fn registration_id(&self) -> RegistrationId {
326 RegistrationId {
327 scope: self.inner.scope.clone(),
328 generation: self.generation,
329 }
330 }
331
332 pub fn release(mut self) {
342 self.release_inner();
343 }
344
345 fn release_inner(&mut self) {
346 if self.released {
347 return;
348 }
349 self.released = true;
350 let removed = {
354 let mut entries = self.inner.write_entries();
355 entries.remove_if(&self.address, self.generation.get())
356 };
357 drop(removed);
358 }
359}
360
361impl<A, E> Drop for Lease<A, E>
362where
363 A: Eq + Hash,
364{
365 fn drop(&mut self) {
366 self.release_inner();
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::{AddressSpace, ClaimError};
373 use std::collections::HashSet;
374
375 #[test]
376 fn lease_owns_and_releases_one_registration() {
377 let space = AddressSpace::new();
378 let lease = space.claim(7, "first").unwrap();
379 assert_eq!(space.resolve(&7), Some("first"));
380 assert!(matches!(
381 space.try_claim(7, "second"),
382 Err(ClaimError::AddressInUse(7))
383 ));
384 drop(lease);
385 assert_eq!(space.resolve(&7), None);
386 let replacement = space.claim(7, "second").unwrap();
387 assert_eq!(space.resolve(&7), Some("second"));
388 drop(replacement);
389 }
390
391 #[test]
392 fn registration_identity_distinguishes_replacements() {
393 let space = AddressSpace::new();
394 let first = space.claim("worker", 1).unwrap();
395 let first_id = first.registration_id();
396 first.release();
397
398 let second = space.claim("worker", 2).unwrap();
399 let second_id = second.registration_id();
400 assert_ne!(first_id, second_id);
401 assert_eq!(first_id.scope_id(), second_id.scope_id());
402
403 let ids = HashSet::from([first_id, second_id]);
404 assert_eq!(ids.len(), 2);
405 }
406
407 #[test]
408 fn registration_identity_is_scoped_to_the_address_space() {
409 let first = AddressSpace::new();
410 let first_peer = first.clone();
411 let second = AddressSpace::new();
412
413 assert_eq!(
414 first.registration_scope_id(),
415 first_peer.registration_scope_id()
416 );
417 assert_ne!(
418 first.registration_scope_id(),
419 second.registration_scope_id()
420 );
421
422 let first_id = first.claim((1_u8, 2_u8), ()).unwrap().registration_id();
423 let second_id = second.claim((1_u8, 2_u8), ()).unwrap().registration_id();
424 assert_ne!(first_id, second_id);
425 }
426
427 #[test]
428 fn retaining_an_identity_grants_no_registration_lifetime() {
429 let space = AddressSpace::new();
430 let lease = space
431 .claim(String::from("worker"), String::from("mailbox"))
432 .unwrap();
433 let identity = lease.registration_id();
434 drop(lease);
435
436 assert_eq!(space.resolve(&String::from("worker")), None);
437 let replacement = space
438 .claim(String::from("worker"), String::from("replacement"))
439 .unwrap();
440 assert_ne!(identity, replacement.registration_id());
441 }
442
443 #[test]
444 fn exhausted_space_rejects_claims_without_reusing_an_identity() {
445 let space = AddressSpace::new();
446 space.inner.write_entries().set_next_generation(u64::MAX);
447
448 let last = space.claim(1_u64, "last").unwrap();
449 let last_id = last.registration_id();
450 last.release();
451
452 assert!(matches!(
453 space.try_claim(2, "never inserted"),
454 Err(ClaimError::RegistrationIdsExhausted(2))
455 ));
456 assert!(space.is_empty());
457 assert_eq!(last_id.scope_id(), &space.registration_scope_id());
458 }
459}