soothe_client/appkit/
query_gate.rs1use std::collections::HashMap;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::{Arc, Mutex};
13use std::time::Duration;
14
15use crate::errors::Result;
16
17pub type CancelFn = Arc<dyn Fn() + Send + Sync>;
19
20pub type SendCancelFn =
22 Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
23
24#[derive(Debug, Clone, Copy, thiserror::Error)]
26#[error("appkit: query already in progress for app key")]
27pub struct ErrQueryBusy;
28
29struct GateState {
30 cancels: HashMap<String, CancelFn>,
31 send_cancels: HashMap<String, SendCancelFn>,
32}
33
34pub struct QueryGate {
36 inner: Mutex<GateState>,
37}
38
39impl QueryGate {
40 pub fn new() -> Self {
42 Self {
43 inner: Mutex::new(GateState {
44 cancels: HashMap::new(),
45 send_cancels: HashMap::new(),
46 }),
47 }
48 }
49
50 pub fn acquire(
57 &self,
58 app_key: &str,
59 cancel: CancelFn,
60 send_cancel: Option<SendCancelFn>,
61 ) -> std::result::Result<(), ErrQueryBusy> {
62 let mut state = self.inner.lock().expect("query gate mutex poisoned");
63 if state.cancels.contains_key(app_key) {
64 return Err(ErrQueryBusy);
65 }
66 state.cancels.insert(app_key.to_string(), cancel);
67 if let Some(send) = send_cancel {
68 state.send_cancels.insert(app_key.to_string(), send);
69 }
70 Ok(())
71 }
72
73 pub async fn cancel(&self, app_key: &str) -> Result<()> {
79 let (cancel, send_cancel) = {
80 let mut state = self.inner.lock().expect("query gate mutex poisoned");
81 let cancel = state.cancels.remove(app_key);
82 let send_cancel = state.send_cancels.remove(app_key);
83 (cancel, send_cancel)
84 };
85
86 if cancel.is_none() && send_cancel.is_none() {
87 return Ok(());
88 }
89
90 if let Some(send_cancel) = send_cancel {
91 let fut = send_cancel();
92 let _ = tokio::time::timeout(Duration::from_secs(10), fut).await;
93 }
94
95 if let Some(cancel) = cancel {
96 cancel();
97 }
98 Ok(())
99 }
100
101 pub fn release(&self, app_key: &str) {
106 let mut state = self.inner.lock().expect("query gate mutex poisoned");
107 state.cancels.remove(app_key);
108 state.send_cancels.remove(app_key);
109 }
110
111 pub fn set_send_cancel(&self, app_key: &str, send_cancel: SendCancelFn) {
113 let mut state = self.inner.lock().expect("query gate mutex poisoned");
114 if !state.cancels.contains_key(app_key) {
115 return;
116 }
117 state.send_cancels.insert(app_key.to_string(), send_cancel);
118 }
119
120 pub fn replace_cancel(&self, app_key: &str, cancel: CancelFn) {
122 let mut state = self.inner.lock().expect("query gate mutex poisoned");
123 if !state.cancels.contains_key(app_key) {
124 return;
125 }
126 state.cancels.insert(app_key.to_string(), cancel);
127 }
128
129 pub fn is_active(&self, app_key: &str) -> bool {
131 self.inner
132 .lock()
133 .expect("query gate mutex poisoned")
134 .cancels
135 .contains_key(app_key)
136 }
137}
138
139impl Default for QueryGate {
140 fn default() -> Self {
141 Self::new()
142 }
143}