tako_rs_core/queue/
runtime.rs1use std::collections::VecDeque;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::sync::atomic::AtomicBool;
8use std::sync::atomic::AtomicU64;
9use std::sync::atomic::Ordering;
10use std::time::Duration;
11use std::time::Instant;
12
13use parking_lot::Mutex;
14use scc::HashMap as SccHashMap;
15use tokio::sync::Notify;
16
17use super::DeadJob;
18use super::Job;
19use super::QueueBuilder;
20use super::QueueError;
21use super::RetryPolicy;
22#[cfg(feature = "signals")]
23use super::signal_ids;
24use super::worker::worker_loop;
25#[cfg(feature = "signals")]
26use crate::signals::Signal;
27#[cfg(feature = "signals")]
28use crate::signals::SignalArbiter;
29
30pub(crate) struct PendingJob {
31 pub(crate) id: u64,
32 pub(crate) name: String,
33 pub(crate) payload: Vec<u8>,
34 pub(crate) attempt: u32,
35 pub(crate) run_after: Option<Instant>,
36 pub(crate) dedup_key: Option<String>,
37}
38
39pub(crate) type BoxHandler =
40 Arc<dyn Fn(Job) -> Pin<Box<dyn Future<Output = Result<(), QueueError>> + Send>> + Send + Sync>;
41
42pub(crate) struct QueueInner {
43 pub(crate) pending: Mutex<VecDeque<PendingJob>>,
45 pub(crate) handlers: SccHashMap<String, BoxHandler>,
47 pub(crate) dead_letters: Mutex<Vec<Arc<DeadJob>>>,
55 pub(crate) notify: Notify,
57 pub(crate) next_id: AtomicU64,
59 pub(crate) num_workers: usize,
61 pub(crate) retry_policy: RetryPolicy,
63 pub(crate) shutdown: AtomicBool,
65 pub(crate) inflight: AtomicU64,
67 pub(crate) drain_notify: Notify,
69}
70
71#[derive(Clone)]
80pub struct Queue {
81 pub(crate) inner: Arc<QueueInner>,
82}
83
84impl Queue {
85 pub fn new() -> Self {
87 Self::builder().build()
88 }
89
90 pub fn builder() -> QueueBuilder {
92 QueueBuilder {
93 workers: 4,
94 retry: RetryPolicy::default(),
95 }
96 }
97
98 pub fn register<F, Fut>(&self, name: impl Into<String>, handler: F)
112 where
113 F: Fn(Job) -> Fut + Send + Sync + 'static,
114 Fut: Future<Output = Result<(), QueueError>> + Send + 'static,
115 {
116 let name = name.into();
117 let handler: BoxHandler = Arc::new(move |job| Box::pin(handler(job)));
118 let _ = self.inner.handlers.insert_sync(name, handler);
119 }
120
121 pub async fn push(
125 &self,
126 name: impl Into<String>,
127 payload: &(impl serde::Serialize + ?Sized),
128 ) -> Result<u64, QueueError> {
129 self.push_inner(name.into(), payload, None)
130 }
131
132 pub async fn push_delayed(
136 &self,
137 name: impl Into<String>,
138 payload: &(impl serde::Serialize + ?Sized),
139 delay: Duration,
140 ) -> Result<u64, QueueError> {
141 self.push_inner(name.into(), payload, Some(Instant::now() + delay))
142 }
143
144 pub async fn push_dedup(
151 &self,
152 name: impl Into<String>,
153 payload: &(impl serde::Serialize + ?Sized),
154 dedup_key: impl Into<String>,
155 ) -> Result<u64, QueueError> {
156 if self.inner.shutdown.load(Ordering::SeqCst) {
157 return Err(QueueError::Shutdown);
158 }
159 let key = dedup_key.into();
160 let name = name.into();
161 let bytes =
162 serde_json::to_vec(payload).map_err(|e| QueueError::SerializeError(e.to_string()))?;
163
164 let id = {
170 let mut pending = self.inner.pending.lock();
171 if self.inner.shutdown.load(Ordering::SeqCst) {
172 return Err(QueueError::Shutdown);
173 }
174 for j in pending.iter() {
175 if j.dedup_key.as_deref() == Some(key.as_str()) {
176 return Ok(j.id);
177 }
178 }
179 let id = self.inner.next_id.fetch_add(1, Ordering::SeqCst);
180 pending.push_back(PendingJob {
181 id,
182 name,
183 payload: bytes,
184 attempt: 0,
185 run_after: None,
186 dedup_key: Some(key),
187 });
188 id
189 };
190
191 self.inner.notify.notify_one();
192 Ok(id)
193 }
194
195 fn push_inner(
196 &self,
197 name: String,
198 payload: &(impl serde::Serialize + ?Sized),
199 run_after: Option<Instant>,
200 ) -> Result<u64, QueueError> {
201 if self.inner.shutdown.load(Ordering::SeqCst) {
202 return Err(QueueError::Shutdown);
203 }
204
205 let bytes =
206 serde_json::to_vec(payload).map_err(|e| QueueError::SerializeError(e.to_string()))?;
207
208 let id = self.inner.next_id.fetch_add(1, Ordering::SeqCst);
209
210 #[cfg(feature = "signals")]
211 let job_name = name.clone();
212 {
213 let mut pending = self.inner.pending.lock();
214 if self.inner.shutdown.load(Ordering::SeqCst) {
218 return Err(QueueError::Shutdown);
219 }
220 pending.push_back(PendingJob {
221 id,
222 name,
223 payload: bytes,
224 attempt: 0,
225 run_after,
226 dedup_key: None,
227 });
228 }
229
230 self.inner.notify.notify_one();
231 #[cfg(feature = "signals")]
232 {
233 let arbiter = SignalArbiter::emit_app(
234 Signal::with_capacity(signal_ids::QUEUE_JOB_QUEUED, 2)
235 .meta("name", job_name)
236 .meta("id", id.to_string()),
237 );
238 #[cfg(not(feature = "compio"))]
243 {
244 tokio::spawn(arbiter);
245 }
246 #[cfg(feature = "compio")]
247 {
248 compio::runtime::spawn(arbiter).detach();
249 }
250 }
251 Ok(id)
252 }
253
254 #[cfg(not(feature = "compio"))]
259 pub fn start(&self) {
260 for _ in 0..self.inner.num_workers {
261 let inner = self.inner.clone();
262 tokio::spawn(async move { worker_loop(inner).await });
263 }
264 tracing::debug!("Queue started with {} workers", self.inner.num_workers);
265 }
266
267 #[cfg(feature = "compio")]
269 pub fn start(&self) {
270 for _ in 0..self.inner.num_workers {
271 let inner = self.inner.clone();
272 compio::runtime::spawn(async move { worker_loop(inner).await }).detach();
273 }
274 tracing::debug!("Queue started with {} workers", self.inner.num_workers);
275 }
276
277 pub async fn shutdown(&self, timeout: Duration) {
282 {
288 let _guard = self.inner.pending.lock();
289 self.inner.shutdown.store(true, Ordering::SeqCst);
290 }
291 self.inner.notify.notify_waiters();
301
302 if self.inner.inflight.load(Ordering::SeqCst) > 0 {
303 #[cfg(not(feature = "compio"))]
304 {
305 let _ = tokio::time::timeout(timeout, self.inner.drain_notify.notified()).await;
306 }
307 #[cfg(feature = "compio")]
308 {
309 let drain = std::pin::pin!(self.inner.drain_notify.notified());
310 let sleep = std::pin::pin!(compio::time::sleep(timeout));
311 let _ = futures_util::future::select(drain, sleep).await;
312 }
313 }
314
315 tracing::debug!("Queue shut down");
316 }
317
318 pub fn dead_letters(&self) -> Vec<DeadJob> {
325 self
326 .inner
327 .dead_letters
328 .lock()
329 .iter()
330 .map(|j| (**j).clone())
331 .collect()
332 }
333
334 pub fn dead_letters_arc(&self) -> Vec<Arc<DeadJob>> {
341 self.inner.dead_letters.lock().clone()
342 }
343
344 pub fn dead_letter_count(&self) -> usize {
346 self.inner.dead_letters.lock().len()
347 }
348
349 pub fn clear_dead_letters(&self) {
351 self.inner.dead_letters.lock().clear();
352 }
353
354 pub fn pending_count(&self) -> usize {
356 self.inner.pending.lock().len()
357 }
358
359 pub fn inflight_count(&self) -> u64 {
361 self.inner.inflight.load(Ordering::SeqCst)
362 }
363}
364
365impl Default for Queue {
366 fn default() -> Self {
367 Self::new()
368 }
369}