use downcast_rs::{Downcast, impl_downcast};
use dyn_clone::{DynClone, clone_trait_object};
use std::sync::atomic::{AtomicU64, Ordering};
pub trait Isolation:
seal::Sealed + Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static
{
fn compatible(&self, other: &dyn Isolation) -> bool;
fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
fn enables_long_lived_circuits(&self) -> bool {
false
}
}
mod seal {
pub trait Sealed {}
impl<T: super::IsolationHelper> Sealed for T {}
}
impl_downcast!(Isolation);
clone_trait_object!(Isolation);
impl<T: Isolation> From<T> for Box<dyn Isolation> {
fn from(isolation: T) -> Self {
Box::new(isolation)
}
}
impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
fn compatible(&self, other: &dyn Isolation) -> bool {
if let Some(other) = other.as_any().downcast_ref() {
self.compatible_same_type(other)
} else {
false
}
}
fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
if let Some(other) = other.as_any().downcast_ref() {
self.join_same_type(other)
.map(|res| Box::new(res) as Box<dyn Isolation>)
} else {
None
}
}
fn enables_long_lived_circuits(&self) -> bool {
IsolationHelper::enables_long_lived_circuits(self)
}
}
pub trait IsolationHelper: Sized {
fn compatible_same_type(&self, other: &Self) -> bool;
fn join_same_type(&self, other: &Self) -> Option<Self>;
fn enables_long_lived_circuits(&self) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct IsolationToken(u64);
#[allow(clippy::new_without_default)]
impl IsolationToken {
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
let token = COUNTER.fetch_add(1, Ordering::Relaxed);
assert!(token < u64::MAX);
IsolationToken(token)
}
pub fn no_isolation() -> Self {
IsolationToken(0)
}
}
impl IsolationHelper for IsolationToken {
fn compatible_same_type(&self, other: &Self) -> bool {
self == other
}
fn join_same_type(&self, other: &Self) -> Option<Self> {
if self.compatible_same_type(other) {
Some(*self)
} else {
None
}
}
fn enables_long_lived_circuits(&self) -> bool {
false
}
}
macro_rules! tuple_impls {
($(
$Tuple:ident {
$(($idx:tt) -> $T:ident)+
}
)+) => {
$(
impl<$($T:IsolationHelper),+> IsolationHelper for ($($T,)+) {
fn compatible_same_type(&self, other: &Self) -> bool {
$(self.$idx.compatible_same_type(&other.$idx))&&+
}
fn join_same_type(&self, other: &Self) -> Option<Self> {
Some((
$(self.$idx.join_same_type(&other.$idx)?,)+
))
}
fn enables_long_lived_circuits(&self) -> bool {
$(self.$idx.enables_long_lived_circuits() || )+ false
}
}
)+
}
}
tuple_impls! {
Tuple1 {
(0) -> A
}
Tuple2 {
(0) -> A
(1) -> B
}
Tuple3 {
(0) -> A
(1) -> B
(2) -> C
}
Tuple4 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
}
Tuple5 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
}
Tuple6 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
}
Tuple7 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
}
Tuple8 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
}
Tuple9 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
}
Tuple10 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
}
Tuple11 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
}
Tuple12 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
(11) -> L
}
}
#[derive(Clone, Debug, derive_builder::Builder)]
pub struct StreamIsolation {
#[builder(default = "Box::new(IsolationToken::no_isolation())")]
stream_isolation: Box<dyn Isolation>,
#[builder(default = "IsolationToken::no_isolation()")]
owner_token: IsolationToken,
}
impl StreamIsolation {
pub fn no_isolation() -> Self {
StreamIsolationBuilder::new()
.build()
.expect("Bug constructing StreamIsolation")
}
pub fn builder() -> StreamIsolationBuilder {
StreamIsolationBuilder::new()
}
}
impl IsolationHelper for StreamIsolation {
fn compatible_same_type(&self, other: &StreamIsolation) -> bool {
self.owner_token == other.owner_token
&& self
.stream_isolation
.compatible(other.stream_isolation.as_ref())
}
fn join_same_type(&self, other: &StreamIsolation) -> Option<StreamIsolation> {
if self.owner_token != other.owner_token {
return None;
}
self.stream_isolation
.join(other.stream_isolation.as_ref())
.map(|stream_isolation| StreamIsolation {
stream_isolation,
owner_token: self.owner_token,
})
}
fn enables_long_lived_circuits(&self) -> bool {
self.stream_isolation.enables_long_lived_circuits()
}
}
impl StreamIsolationBuilder {
pub fn new() -> Self {
StreamIsolationBuilder::default()
}
}
#[cfg(test)]
pub(crate) mod test {
#![allow(clippy::unwrap_used)]
use super::*;
pub(crate) trait IsolationTokenEq {
fn isol_eq(&self, other: &Self) -> bool;
}
macro_rules! assert_isoleq {
{ $arg1:expr, $arg2:expr } => {
assert!($arg1.isol_eq(&$arg2))
}
}
pub(crate) use assert_isoleq;
impl IsolationTokenEq for IsolationToken {
fn isol_eq(&self, other: &Self) -> bool {
self == other
}
}
impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
fn isol_eq(&self, other: &Self) -> bool {
match (self, other) {
(Some(this), Some(other)) => this.isol_eq(other),
(None, None) => true,
_ => false,
}
}
}
impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
fn isol_eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
self.iter()
.zip(other.iter())
.all(|(this, other)| this.isol_eq(other))
}
}
impl IsolationTokenEq for dyn Isolation {
fn isol_eq(&self, other: &Self) -> bool {
let this = self.as_any().downcast_ref::<IsolationToken>();
let other = other.as_any().downcast_ref::<IsolationToken>();
match (this, other) {
(Some(this), Some(other)) => this == other,
_ => false,
}
}
}
impl IsolationTokenEq for StreamIsolation {
fn isol_eq(&self, other: &Self) -> bool {
self.stream_isolation
.isol_eq(other.stream_isolation.as_ref())
&& self.owner_token == other.owner_token
}
}
#[derive(PartialEq, Clone, Copy, Debug, Eq)]
struct OtherIsolation(usize);
impl IsolationHelper for OtherIsolation {
fn compatible_same_type(&self, other: &Self) -> bool {
self == other
}
fn join_same_type(&self, other: &Self) -> Option<Self> {
if self.compatible_same_type(other) {
Some(*self)
} else {
None
}
}
}
#[test]
fn isolation_token() {
let token_1 = IsolationToken::new();
let token_2 = IsolationToken::new();
assert!(token_1.compatible_same_type(&token_1));
assert!(token_2.compatible_same_type(&token_2));
assert!(!token_1.compatible_same_type(&token_2));
assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
assert_eq!(token_1.join_same_type(&token_2), None);
}
#[test]
fn isolation_trait() {
let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
assert!(token_1.compatible(token_1.as_ref()));
assert!(token_2.compatible(token_2.as_ref()));
assert!(!token_1.compatible(token_2.as_ref()));
assert!(other_1.compatible(other_1.as_ref()));
assert!(other_2.compatible(other_2.as_ref()));
assert!(!other_1.compatible(other_2.as_ref()));
assert!(!token_1.compatible(other_1.as_ref()));
assert!(!other_1.compatible(token_1.as_ref()));
assert!(token_1.join(token_1.as_ref()).is_some());
assert!(token_1.join(token_2.as_ref()).is_none());
assert!(other_1.join(other_1.as_ref()).is_some());
assert!(other_1.join(other_2.as_ref()).is_none());
assert!(token_1.join(other_1.as_ref()).is_none());
assert!(other_1.join(token_1.as_ref()).is_none());
}
#[test]
fn isolation_tuple() {
let token_1 = IsolationToken::new();
let token_2 = IsolationToken::new();
let other_1 = OtherIsolation(0);
let other_2 = OtherIsolation(1);
let token_12: Box<dyn Isolation> = Box::new((token_1, token_2));
let token_21: Box<dyn Isolation> = Box::new((token_2, token_1));
let mix_11: Box<dyn Isolation> = Box::new((token_1, other_1));
let mix_12: Box<dyn Isolation> = Box::new((token_1, other_2));
let revmix_11: Box<dyn Isolation> = Box::new((other_1, token_1));
let join_token = token_12.join(token_12.as_ref()).unwrap();
assert!(join_token.compatible(token_12.as_ref()));
let join_mix = mix_12.join(mix_12.as_ref()).unwrap();
assert!(join_mix.compatible(mix_12.as_ref()));
let isol_list = [token_12, token_21, mix_11, mix_12, revmix_11];
for (i, isol1) in isol_list.iter().enumerate() {
for (j, isol2) in isol_list.iter().enumerate() {
assert_eq!(isol1.compatible(isol2.as_ref()), i == j);
}
}
}
#[test]
fn build_isolation() {
let no_isolation = StreamIsolation::no_isolation();
let no_isolation2 = StreamIsolation::builder()
.owner_token(IsolationToken::no_isolation())
.stream_isolation(Box::new(IsolationToken::no_isolation()))
.build()
.unwrap();
assert_eq!(no_isolation.owner_token, no_isolation2.owner_token);
assert_eq!(
no_isolation
.stream_isolation
.as_ref()
.as_any()
.downcast_ref::<IsolationToken>(),
no_isolation2
.stream_isolation
.as_ref()
.as_any()
.downcast_ref::<IsolationToken>()
);
assert!(no_isolation.compatible(&no_isolation2));
let tok = IsolationToken::new();
let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap();
let some_isolation2 = StreamIsolation::builder()
.stream_isolation(Box::new(tok))
.build()
.unwrap();
assert!(!no_isolation.compatible(&some_isolation));
assert!(!no_isolation.compatible(&some_isolation2));
assert!(!some_isolation.compatible(&some_isolation2));
assert!(some_isolation.compatible(&some_isolation));
}
}