use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::errors::Result;
pub type CancelFn = Arc<dyn Fn() + Send + Sync>;
pub type SendCancelFn =
Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("appkit: query already in progress for app key")]
pub struct ErrQueryBusy;
struct GateState {
cancels: HashMap<String, CancelFn>,
send_cancels: HashMap<String, SendCancelFn>,
}
pub struct QueryGate {
inner: Mutex<GateState>,
}
impl QueryGate {
pub fn new() -> Self {
Self {
inner: Mutex::new(GateState {
cancels: HashMap::new(),
send_cancels: HashMap::new(),
}),
}
}
pub fn acquire(
&self,
app_key: &str,
cancel: CancelFn,
send_cancel: Option<SendCancelFn>,
) -> std::result::Result<(), ErrQueryBusy> {
let mut state = self.inner.lock().expect("query gate mutex poisoned");
if state.cancels.contains_key(app_key) {
return Err(ErrQueryBusy);
}
state.cancels.insert(app_key.to_string(), cancel);
if let Some(send) = send_cancel {
state.send_cancels.insert(app_key.to_string(), send);
}
Ok(())
}
pub async fn cancel(&self, app_key: &str) -> Result<()> {
let (cancel, send_cancel) = {
let mut state = self.inner.lock().expect("query gate mutex poisoned");
let cancel = state.cancels.remove(app_key);
let send_cancel = state.send_cancels.remove(app_key);
(cancel, send_cancel)
};
if cancel.is_none() && send_cancel.is_none() {
return Ok(());
}
if let Some(send_cancel) = send_cancel {
let fut = send_cancel();
let _ = tokio::time::timeout(Duration::from_secs(10), fut).await;
}
if let Some(cancel) = cancel {
cancel();
}
Ok(())
}
pub fn release(&self, app_key: &str) {
let mut state = self.inner.lock().expect("query gate mutex poisoned");
state.cancels.remove(app_key);
state.send_cancels.remove(app_key);
}
pub fn set_send_cancel(&self, app_key: &str, send_cancel: SendCancelFn) {
let mut state = self.inner.lock().expect("query gate mutex poisoned");
if !state.cancels.contains_key(app_key) {
return;
}
state.send_cancels.insert(app_key.to_string(), send_cancel);
}
pub fn replace_cancel(&self, app_key: &str, cancel: CancelFn) {
let mut state = self.inner.lock().expect("query gate mutex poisoned");
if !state.cancels.contains_key(app_key) {
return;
}
state.cancels.insert(app_key.to_string(), cancel);
}
pub fn is_active(&self, app_key: &str) -> bool {
self.inner
.lock()
.expect("query gate mutex poisoned")
.cancels
.contains_key(app_key)
}
}
impl Default for QueryGate {
fn default() -> Self {
Self::new()
}
}