use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
pub struct EntityResource<T> {
value: Arc<RwLock<Option<T>>>,
active: Arc<AtomicBool>,
close_token: CancellationToken,
}
impl<T> EntityResource<T> {
pub fn new(value: T) -> Self {
Self {
value: Arc::new(RwLock::new(Some(value))),
active: Arc::new(AtomicBool::new(true)),
close_token: CancellationToken::new(),
}
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Acquire)
}
pub async fn read(&self) -> Option<tokio::sync::RwLockReadGuard<'_, Option<T>>> {
let guard = self.value.read().await;
if guard.is_some() {
Some(guard)
} else {
None
}
}
pub async fn write(&self) -> Option<tokio::sync::RwLockWriteGuard<'_, Option<T>>> {
let guard = self.value.write().await;
if guard.is_some() {
Some(guard)
} else {
None
}
}
pub async fn close(&self) {
let mut guard = self.value.write().await;
*guard = None;
self.active.store(false, Ordering::Release);
self.close_token.cancel();
}
pub fn close_token(&self) -> CancellationToken {
self.close_token.clone()
}
}
impl<T> Clone for EntityResource<T> {
fn clone(&self) -> Self {
Self {
value: Arc::clone(&self.value),
active: Arc::clone(&self.active),
close_token: self.close_token.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn new_resource_is_active() {
let resource = EntityResource::new(42i32);
assert!(resource.is_active());
}
#[tokio::test]
async fn read_returns_value() {
let resource = EntityResource::new(42i32);
let guard = resource.read().await.unwrap();
assert_eq!(*guard, Some(42));
}
#[tokio::test]
async fn write_allows_mutation() {
let resource = EntityResource::new(42i32);
{
let mut guard = resource.write().await.unwrap();
*guard = Some(99);
}
let guard = resource.read().await.unwrap();
assert_eq!(*guard, Some(99));
}
#[tokio::test]
async fn close_marks_inactive() {
let resource = EntityResource::new(42i32);
resource.close().await;
assert!(!resource.is_active());
}
#[tokio::test]
async fn read_after_close_returns_none() {
let resource = EntityResource::new(42i32);
resource.close().await;
assert!(resource.read().await.is_none());
}
#[tokio::test]
async fn write_after_close_returns_none() {
let resource = EntityResource::new(42i32);
resource.close().await;
assert!(resource.write().await.is_none());
}
#[tokio::test]
async fn close_token_is_cancelled_on_close() {
let resource = EntityResource::new(42i32);
let token = resource.close_token();
assert!(!token.is_cancelled());
resource.close().await;
assert!(token.is_cancelled());
}
#[tokio::test]
async fn clone_shares_state() {
let resource = EntityResource::new(42i32);
let cloned = resource.clone();
assert!(cloned.is_active());
resource.close().await;
assert!(!cloned.is_active());
assert!(cloned.read().await.is_none());
}
#[tokio::test]
async fn close_is_idempotent() {
let resource = EntityResource::new(42i32);
resource.close().await;
resource.close().await;
assert!(!resource.is_active());
}
}