use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Arc;
use cordis::{Context, Service};
use parking_lot::RwLock;
pub struct TenantRealms {
realms: RwLock<HashMap<String, Arc<Context>>>,
tools: TypeId,
execute: TypeId,
}
impl TenantRealms {
pub fn new(tools: TypeId, execute: TypeId) -> Self {
Self {
realms: RwLock::new(HashMap::new()),
tools,
execute,
}
}
pub fn open(&self, root: &Arc<Context>, tenant_id: &str) -> Arc<Context> {
if let Some(existing) = self.realms.read().get(tenant_id) {
return existing.clone();
}
let mut map = self.realms.write();
if let Some(existing) = map.get(tenant_id) {
return existing.clone();
}
let _ = self.execute;
let child = root.extend().isolate_type(self.tools, tenant_id);
map.insert(tenant_id.to_string(), child.clone());
child
}
pub async fn dispose(&self, tenant_id: &str) {
let child = self.realms.write().remove(tenant_id);
if let Some(child) = child {
if let Err(error) = child.fiber().dispose().await {
tracing::error!(tenant_id, %error, "TenantRealms: fiber stuck in transition during dispose");
}
}
}
}
impl Service for TenantRealms {
fn name(&self) -> &'static str {
"TenantRealms"
}
}
#[cfg(test)]
mod tests {
use super::*;
use cordis::Context;
struct DummyTools;
impl Service for DummyTools {}
struct DummyExecute;
impl Service for DummyExecute {}
struct ChildOnly;
impl Service for ChildOnly {}
#[tokio::test]
async fn tenant_realm_dispose_drops_isolated_tools() {
let root = Context::new_root();
root.provide(DummyTools);
let realms = TenantRealms::new(TypeId::of::<DummyTools>(), TypeId::of::<DummyExecute>());
let child = realms.open(&root, "acme");
child.provide(ChildOnly);
assert!(child.get::<ChildOnly>().is_some());
assert!(root.get::<DummyTools>().is_some());
realms.dispose("acme").await;
assert!(
child.get::<ChildOnly>().is_none(),
"child-only provide must undo on fiber dispose"
);
assert!(
root.get::<DummyTools>().is_some(),
"root DummyTools must survive realm dispose"
);
}
}