use super::finalization::FinalizationGuard;
use monoloop_contracts::{ChannelId, SessionKey, TransactionId};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
#[derive(Debug)]
pub enum ControlMessage {
Cancel,
ForceTerminate,
}
pub struct ActiveTransaction {
pub transaction_id: TransactionId,
pub session_key: Option<SessionKey>,
#[allow(dead_code)]
pub channel_id: ChannelId,
pub guard: Arc<FinalizationGuard>,
pub control_tx: mpsc::Sender<ControlMessage>,
pub actor_join: tokio::task::JoinHandle<()>,
pub release_capacity: Arc<dyn Fn() + Send + Sync>,
}
#[derive(Default)]
pub struct ActiveTransactionRegistry {
by_tx: HashMap<TransactionId, ActiveTransaction>,
by_session: HashMap<SessionKey, TransactionId>,
}
impl ActiveTransactionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.by_tx.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.by_tx.is_empty()
}
pub fn session_active(&self, key: &SessionKey) -> bool {
self.by_session.contains_key(key)
}
pub fn distinct_sessions_on_channel(&self, channel: &ChannelId) -> usize {
self.by_session
.keys()
.filter(|k| k.channel_id == *channel)
.count()
}
pub fn insert(
&mut self,
entry: ActiveTransaction,
max_distinct_sessions: Option<usize>,
) -> Result<
(),
(
monoloop_contracts::AdmissionErrorKind,
Box<ActiveTransaction>,
),
> {
if let Some(ref sk) = entry.session_key {
if self.by_session.contains_key(sk) {
return Err((
monoloop_contracts::AdmissionErrorKind::SessionAlreadyActive,
Box::new(entry),
));
}
if let Some(max) = max_distinct_sessions {
if self.distinct_sessions_on_channel(&sk.channel_id) >= max {
return Err((
monoloop_contracts::AdmissionErrorKind::CapacityExceeded,
Box::new(entry),
));
}
}
}
if self.by_tx.contains_key(&entry.transaction_id) {
return Err((
monoloop_contracts::AdmissionErrorKind::SpawnFailed,
Box::new(entry),
));
}
if let Some(ref sk) = entry.session_key {
self.by_session.insert(sk.clone(), entry.transaction_id);
}
self.by_tx.insert(entry.transaction_id, entry);
Ok(())
}
pub fn claim_session(
&mut self,
transaction_id: TransactionId,
key: SessionKey,
max_distinct_sessions: Option<usize>,
) -> Result<(), ClaimSessionError> {
if self.by_session.contains_key(&key) {
return Err(ClaimSessionError::Collision);
}
if let Some(max) = max_distinct_sessions {
if self.distinct_sessions_on_channel(&key.channel_id) >= max {
return Err(ClaimSessionError::CapacityExceeded);
}
}
let entry = self
.by_tx
.get_mut(&transaction_id)
.ok_or(ClaimSessionError::UnknownTransaction)?;
if entry.session_key.is_some() {
return Err(ClaimSessionError::AlreadyClaimed);
}
entry.session_key = Some(key.clone());
entry.guard.set_session_id(key.session_id.clone());
self.by_session.insert(key, transaction_id);
Ok(())
}
pub fn control_tx(&self, id: &TransactionId) -> Option<mpsc::Sender<ControlMessage>> {
self.by_tx.get(id).map(|e| e.control_tx.clone())
}
pub fn control_tx_by_session(&self, key: &SessionKey) -> Option<mpsc::Sender<ControlMessage>> {
let id = self.by_session.get(key)?;
self.control_tx(id)
}
pub fn remove(&mut self, id: &TransactionId) -> Option<ActiveTransaction> {
let entry = self.by_tx.remove(id)?;
if let Some(ref sk) = entry.session_key {
self.by_session.remove(sk);
}
Some(entry)
}
#[allow(dead_code)]
pub fn transaction_ids(&self) -> Vec<TransactionId> {
self.by_tx.keys().copied().collect()
}
pub fn drain_all(&mut self) -> Vec<ActiveTransaction> {
self.by_session.clear();
self.by_tx.drain().map(|(_, v)| v).collect()
}
#[allow(dead_code)]
pub fn guard(&self, id: &TransactionId) -> Option<Arc<FinalizationGuard>> {
self.by_tx.get(id).map(|e| Arc::clone(&e.guard))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClaimSessionError {
Collision,
UnknownTransaction,
AlreadyClaimed,
CapacityExceeded,
}