use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use tokio_util::sync::CancellationToken;
static NEXT_SESSION_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Debug)]
pub(crate) struct ConnectionSession {
pub(crate) session_id: u64,
pub(crate) cancel_token: CancellationToken,
closed: Arc<AtomicBool>,
}
impl ConnectionSession {
pub(crate) fn new() -> Self {
Self {
session_id: NEXT_SESSION_ID.fetch_add(1, Ordering::Relaxed),
cancel_token: CancellationToken::new(),
closed: Arc::new(AtomicBool::new(false)),
}
}
pub(crate) fn try_close(&self) -> bool {
self.closed
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
}
#[allow(dead_code)]
pub(crate) fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
pub(crate) fn cancel(&self) {
self.cancel_token.cancel();
}
pub(crate) fn is_cancelled(&self) -> bool {
self.cancel_token.is_cancelled()
}
}
impl Default for ConnectionSession {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_id_monotonic() {
let s1 = ConnectionSession::new();
let s2 = ConnectionSession::new();
assert!(s2.session_id > s1.session_id);
}
#[test]
fn test_try_close_idempotent() {
let session = ConnectionSession::new();
assert!(session.try_close());
assert!(!session.try_close());
assert!(session.is_closed());
}
#[test]
fn test_cancel_token() {
let session = ConnectionSession::new();
assert!(!session.is_cancelled());
session.cancel();
assert!(session.is_cancelled());
}
#[test]
fn test_clone_shares_state() {
let s1 = ConnectionSession::new();
let s2 = s1.clone();
s1.cancel();
assert!(s2.is_cancelled());
assert!(s1.try_close());
assert!(!s2.try_close());
}
}