use super::CleanupTracker;
use crate::Result;
use bon::bon;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use tracing::{debug, error};
pub struct CleanupGuard {
tracker: Arc<CleanupTracker>,
token: CancellationToken,
resource_id: String,
cleanup_on_drop: bool,
}
#[bon]
impl CleanupGuard {
#[builder]
pub fn new(tracker: Arc<CleanupTracker>, resource_id: String) -> Self {
let token = tracker.child_token();
Self {
tracker,
token,
resource_id,
cleanup_on_drop: true,
}
}
pub fn token(&self) -> &CancellationToken {
&self.token
}
pub fn is_cancelled(&self) -> bool {
self.token.is_cancelled()
}
pub fn disarm(mut self) {
self.cleanup_on_drop = false;
debug!("Cleanup guard disarmed for resource: {}", self.resource_id);
}
pub async fn complete(mut self) -> Result<()> {
self.cleanup_on_drop = false;
debug!(
"Operation completed successfully for resource: {}",
self.resource_id
);
Ok(())
}
}
impl Drop for CleanupGuard {
fn drop(&mut self) {
if self.cleanup_on_drop {
debug!(
"CleanupGuard dropped with cleanup enabled for resource: {}",
self.resource_id
);
let tracker = self.tracker.clone();
let resource_id = self.resource_id.clone();
tokio::spawn(async move {
if let Err(e) = tracker.cleanup_all().await {
error!("Failed to cleanup resources for {}: {}", resource_id, e);
}
});
}
}
}
#[async_trait::async_trait]
pub trait WithCleanup {
async fn with_cleanup<F, R>(
&self,
tracker: Arc<CleanupTracker>,
resource_id: String,
f: F,
) -> Result<R>
where
F: std::future::Future<Output = Result<R>>;
}
#[macro_export]
macro_rules! with_cleanup {
($tracker:expr, $resource_id:expr, $body:expr) => {{
let guard = $crate::cleanup::guard::CleanupGuard::builder()
.tracker($tracker)
.resource_id($resource_id)
.build();
let token = guard.token().clone();
tokio::select! {
result = $body => {
match result {
Ok(value) => {
guard.complete().await?;
Ok(value)
}
Err(e) => {
Err(e)
}
}
}
_ = token.cancelled() => {
Err($crate::Error::invalid_state("Operation cancelled", "active", "cancelled"))
}
}
}};
}