use std::sync::{Mutex, MutexGuard, PoisonError};
use tokio::runtime::Handle;
static REGISTRY: Mutex<Registry> = Mutex::new(Registry {
slots: Vec::new(),
free: Vec::new(),
});
struct Registry {
slots: Vec<Slot>,
free: Vec<u32>,
}
struct Slot {
generation: u32,
runtime: Option<Handle>,
}
fn registry() -> MutexGuard<'static, Registry> {
REGISTRY.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RuntimeSlot {
index: u32,
generation: u32,
}
impl RuntimeSlot {
pub(crate) fn runtime(self) -> Option<Handle> {
registry()
.slots
.get(self.index as usize)
.filter(|slot| slot.generation == self.generation)
.and_then(|slot| slot.runtime.clone())
}
}
#[derive(Debug)]
pub(crate) struct RuntimeRegistration {
slot: RuntimeSlot,
}
impl RuntimeRegistration {
pub(crate) fn new(runtime: Handle) -> Self {
let mut registry = registry();
let slot = if let Some(index) = registry.free.pop() {
let slot = &mut registry.slots[index as usize];
slot.runtime = Some(runtime);
RuntimeSlot {
index,
generation: slot.generation,
}
} else {
let index = u32::try_from(registry.slots.len()).expect("fewer than 2^32 connections");
registry.slots.push(Slot {
generation: 0,
runtime: Some(runtime),
});
RuntimeSlot {
index,
generation: 0,
}
};
drop(registry);
Self { slot }
}
pub(crate) const fn slot(&self) -> RuntimeSlot {
self.slot
}
}
impl Drop for RuntimeRegistration {
fn drop(&mut self) {
let mut registry = registry();
if let Some(slot) = registry.slots.get_mut(self.slot.index as usize) {
slot.runtime = None;
slot.generation = slot.generation.wrapping_add(1);
registry.free.push(self.slot.index);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_slot_finds_its_runtime_until_the_connection_goes() {
let registration = RuntimeRegistration::new(Handle::current());
let slot = registration.slot();
assert!(
slot.runtime().is_some(),
"a live connection's slot resolves"
);
drop(registration);
assert!(slot.runtime().is_none(), "a freed slot resolves to nothing");
let reused = RuntimeRegistration::new(Handle::current());
assert!(
slot.runtime().is_none(),
"a reused slot must not answer a delivery of the connection that left it"
);
assert!(reused.slot().runtime().is_some());
}
}