monoloop_loop/transaction/
callback_service.rs1use super::executor_spawn::try_spawn;
9use monoloop_contracts::{CompletionCallback, TransactionEnd};
10use std::sync::atomic::{AtomicUsize, Ordering};
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::runtime::Handle;
14use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore};
15use tokio::task::{AbortHandle, JoinHandle};
16
17type CallbackJoin = (AbortHandle, JoinHandle<()>);
18type CallbackJoinSet = Arc<Mutex<Vec<CallbackJoin>>>;
19
20#[derive(Clone)]
22pub struct CallbackService {
23 permits: Arc<Semaphore>,
24 reserved: Arc<AtomicUsize>,
26 inflight: Arc<AtomicUsize>,
27 default_deadline: Duration,
28 executor: Handle,
30 joins: CallbackJoinSet,
32}
33
34pub struct CallbackReservation {
36 permits: Arc<Semaphore>,
37 reserved: Arc<AtomicUsize>,
38 permit: Option<OwnedSemaphorePermit>,
39}
40
41impl CallbackReservation {
42 pub fn release(mut self) {
44 let _ = self.permit.take();
45 self.reserved.fetch_sub(1, Ordering::SeqCst);
46 }
47
48 fn into_parts(mut self) -> (Arc<Semaphore>, Arc<AtomicUsize>, OwnedSemaphorePermit) {
49 let permit = self
50 .permit
51 .take()
52 .expect("CallbackReservation permit present");
53 self.reserved.fetch_sub(1, Ordering::SeqCst);
54 (
55 Arc::clone(&self.permits),
56 Arc::clone(&self.reserved),
57 permit,
58 )
59 }
60}
61
62impl Drop for CallbackReservation {
63 fn drop(&mut self) {
64 if self.permit.take().is_some() {
65 self.reserved.fetch_sub(1, Ordering::SeqCst);
66 }
67 }
68}
69
70impl CallbackService {
71 pub fn new(max_concurrent: usize, default_deadline: Duration, executor: Handle) -> Self {
73 Self {
74 permits: Arc::new(Semaphore::new(max_concurrent.max(1))),
75 reserved: Arc::new(AtomicUsize::new(0)),
76 inflight: Arc::new(AtomicUsize::new(0)),
77 default_deadline: if default_deadline.is_zero() {
78 Duration::from_millis(50)
79 } else {
80 default_deadline
81 },
82 executor,
83 joins: Arc::new(Mutex::new(Vec::new())),
84 }
85 }
86
87 pub fn inflight(&self) -> usize {
89 self.inflight.load(Ordering::SeqCst)
90 }
91
92 pub fn reserved(&self) -> usize {
94 self.reserved.load(Ordering::SeqCst)
95 }
96
97 pub fn try_reserve(&self) -> Option<CallbackReservation> {
99 let permit = self.permits.clone().try_acquire_owned().ok()?;
100 self.reserved.fetch_add(1, Ordering::SeqCst);
101 Some(CallbackReservation {
102 permits: Arc::clone(&self.permits),
103 reserved: Arc::clone(&self.reserved),
104 permit: Some(permit),
105 })
106 }
107
108 pub fn schedule_reserved(
113 &self,
114 reservation: CallbackReservation,
115 callback: Box<dyn CompletionCallback>,
116 end: TransactionEnd,
117 deadline: Option<Duration>,
118 ) {
119 let (_permits, _reserved_counter, permit) = reservation.into_parts();
120 let inflight = Arc::clone(&self.inflight);
121 let joins = Arc::clone(&self.joins);
122 let executor = self.executor.clone();
123 let budget = deadline.unwrap_or(self.default_deadline);
124 inflight.fetch_add(1, Ordering::SeqCst);
125 let inflight_child = Arc::clone(&inflight);
126 let handle = match try_spawn(&self.executor, async move {
127 let call =
128 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback.call(end)));
129 if let Ok(fut) = call {
130 let handle = match try_spawn(&executor, fut) {
131 Ok(h) => h,
132 Err(()) => {
133 drop(permit);
134 inflight_child.fetch_sub(1, Ordering::SeqCst);
135 return;
136 }
137 };
138 let abort = handle.abort_handle();
139 match tokio::time::timeout(budget, handle).await {
140 Ok(Ok(_)) | Ok(Err(_)) => {}
141 Err(_) => {
142 abort.abort();
143 }
144 }
145 }
146 drop(permit);
147 inflight_child.fetch_sub(1, Ordering::SeqCst);
148 }) {
149 Ok(h) => h,
150 Err(()) => {
151 inflight.fetch_sub(1, Ordering::SeqCst);
153 return;
154 }
155 };
156 let abort = handle.abort_handle();
157 if let Ok(mut g) = joins.try_lock() {
159 g.push((abort, handle));
160 };
161 }
162
163 pub fn schedule(
168 &self,
169 callback: Box<dyn CompletionCallback>,
170 end: TransactionEnd,
171 deadline: Option<Duration>,
172 ) {
173 let Some(reservation) = self.try_reserve() else {
174 return;
175 };
176 self.schedule_reserved(reservation, callback, end, deadline);
177 }
178
179 pub async fn drain(&self, deadline: Duration) {
182 let start = tokio::time::Instant::now();
183 while self.inflight() > 0 {
184 if start.elapsed() >= deadline {
185 break;
186 }
187 tokio::time::sleep(Duration::from_millis(5)).await;
188 }
189 if self.inflight() == 0 {
190 return;
191 }
192 let remaining = deadline.saturating_sub(start.elapsed());
193 let mut handles = {
194 let mut g = self.joins.lock().await;
195 std::mem::take(&mut *g)
196 };
197 for (abort, join) in handles.drain(..) {
198 if remaining.is_zero() {
199 abort.abort();
200 let _ = join.await;
201 } else {
202 abort.abort();
203 let _ = tokio::time::timeout(remaining, join).await;
204 }
205 }
206 }
207}