1use crate::result::{ResultStore, TaskResultValue};
70use crate::state::TaskState;
71use crate::{BrokerMessage, CelersError, Result, SerializedTask, TaskId};
72use std::collections::HashMap;
73use std::sync::atomic::{AtomicU64, Ordering};
74use tokio::sync::{Mutex, Notify};
75use uuid::Uuid;
76
77#[derive(Debug)]
83struct QueueEntry {
84 task: SerializedTask,
86 seq: u64,
88}
89
90#[derive(Debug, Default)]
92struct BrokerState {
93 ready: Vec<QueueEntry>,
95 in_flight: HashMap<String, SerializedTask>,
98 cancelled: HashMap<TaskId, ()>,
102}
103
104impl BrokerState {
105 fn push_ready(&mut self, task: SerializedTask, seq: u64) {
108 let entry = QueueEntry { task, seq };
109 let idx = self.ready.partition_point(|existing| {
113 existing.task.metadata.priority > entry.task.metadata.priority
114 || (existing.task.metadata.priority == entry.task.metadata.priority
115 && existing.seq < entry.seq)
116 });
117 self.ready.insert(idx, entry);
118 }
119}
120
121#[derive(Debug, Default)]
135pub struct InMemoryBroker {
136 state: Mutex<BrokerState>,
138 notify: Notify,
140 seq: AtomicU64,
142}
143
144impl InMemoryBroker {
145 #[must_use]
147 pub fn new() -> Self {
148 Self {
149 state: Mutex::new(BrokerState::default()),
150 notify: Notify::new(),
151 seq: AtomicU64::new(0),
152 }
153 }
154
155 pub async fn in_flight_len(&self) -> usize {
159 self.state.lock().await.in_flight.len()
160 }
161
162 pub async fn is_empty(&self) -> bool {
164 let guard = self.state.lock().await;
165 guard.ready.is_empty() && guard.in_flight.is_empty()
166 }
167
168 pub async fn clear(&self) {
171 let mut guard = self.state.lock().await;
172 guard.ready.clear();
173 guard.in_flight.clear();
174 guard.cancelled.clear();
175 }
176
177 fn pop_deliverable(state: &mut BrokerState) -> Option<SerializedTask> {
181 while !state.ready.is_empty() {
182 let entry = state.ready.remove(0);
183 if state.cancelled.remove(&entry.task.metadata.id).is_some() {
184 continue;
186 }
187 return Some(entry.task);
188 }
189 None
190 }
191
192 fn try_dequeue_locked(state: &mut BrokerState) -> Option<BrokerMessage> {
197 let task = Self::pop_deliverable(state)?;
198 let receipt = Uuid::new_v4().to_string();
199 state.in_flight.insert(receipt.clone(), task.clone());
200 Some(BrokerMessage::with_receipt_handle(task, receipt))
201 }
202}
203
204#[async_trait::async_trait]
205impl crate::Broker for InMemoryBroker {
206 async fn enqueue(&self, task: SerializedTask) -> Result<TaskId> {
207 let task_id = task.metadata.id;
208 let seq = self.seq.fetch_add(1, Ordering::Relaxed);
209 {
210 let mut guard = self.state.lock().await;
211 guard.push_ready(task, seq);
212 }
213 self.notify.notify_one();
215 Ok(task_id)
216 }
217
218 async fn dequeue(&self) -> Result<Option<BrokerMessage>> {
219 loop {
220 let notified = self.notify.notified();
223 {
224 let mut guard = self.state.lock().await;
225 if let Some(msg) = Self::try_dequeue_locked(&mut guard) {
226 return Ok(Some(msg));
227 }
228 }
229 notified.await;
231 }
232 }
233
234 async fn ack(&self, _task_id: &TaskId, receipt_handle: Option<&str>) -> Result<()> {
235 let Some(handle) = receipt_handle else {
236 return Err(CelersError::Broker(
237 "in-memory broker ack requires a receipt handle".to_string(),
238 ));
239 };
240 let mut guard = self.state.lock().await;
241 if guard.in_flight.remove(handle).is_none() {
242 return Err(CelersError::Broker(format!(
243 "unknown receipt handle on ack: {handle}"
244 )));
245 }
246 Ok(())
247 }
248
249 async fn reject(
250 &self,
251 _task_id: &TaskId,
252 receipt_handle: Option<&str>,
253 requeue: bool,
254 ) -> Result<()> {
255 let Some(handle) = receipt_handle else {
256 return Err(CelersError::Broker(
257 "in-memory broker reject requires a receipt handle".to_string(),
258 ));
259 };
260 let task = {
261 let mut guard = self.state.lock().await;
262 match guard.in_flight.remove(handle) {
263 Some(task) => task,
264 None => {
265 return Err(CelersError::Broker(format!(
266 "unknown receipt handle on reject: {handle}"
267 )));
268 }
269 }
270 };
271
272 if !requeue {
273 return Ok(());
274 }
275
276 let task_id = task.metadata.id;
277 let seq = self.seq.fetch_add(1, Ordering::Relaxed);
278 {
279 let mut guard = self.state.lock().await;
280 if guard.cancelled.remove(&task_id).is_some() {
282 return Ok(());
283 }
284 guard.push_ready(task, seq);
285 }
286 self.notify.notify_one();
287 Ok(())
288 }
289
290 async fn queue_size(&self) -> Result<usize> {
291 Ok(self.state.lock().await.ready.len())
292 }
293
294 async fn cancel(&self, task_id: &TaskId) -> Result<bool> {
295 let mut guard = self.state.lock().await;
296 if let Some(pos) = guard
298 .ready
299 .iter()
300 .position(|entry| entry.task.metadata.id == *task_id)
301 {
302 guard.ready.remove(pos);
303 guard.cancelled.remove(task_id);
305 return Ok(true);
306 }
307
308 let in_flight = guard
312 .in_flight
313 .values()
314 .any(|task| task.metadata.id == *task_id);
315 if in_flight {
316 guard.cancelled.insert(*task_id, ());
317 return Ok(true);
318 }
319
320 Ok(false)
321 }
322
323 async fn enqueue_batch(&self, tasks: Vec<SerializedTask>) -> Result<Vec<TaskId>> {
324 if tasks.is_empty() {
325 return Ok(Vec::new());
326 }
327 let mut ids = Vec::with_capacity(tasks.len());
328 {
329 let mut guard = self.state.lock().await;
330 for task in tasks {
331 ids.push(task.metadata.id);
332 let seq = self.seq.fetch_add(1, Ordering::Relaxed);
333 guard.push_ready(task, seq);
334 }
335 }
336 self.notify.notify_waiters();
338 Ok(ids)
339 }
340
341 async fn dequeue_batch(&self, count: usize) -> Result<Vec<BrokerMessage>> {
342 let mut messages = Vec::with_capacity(count.min(64));
347 let mut guard = self.state.lock().await;
348 for _ in 0..count {
349 match Self::try_dequeue_locked(&mut guard) {
350 Some(msg) => messages.push(msg),
351 None => break,
352 }
353 }
354 Ok(messages)
355 }
356}
357
358#[derive(Debug, Clone)]
360struct StoredResult {
361 value: TaskResultValue,
363}
364
365#[derive(Debug, Default)]
367struct BackendState {
368 results: HashMap<TaskId, StoredResult>,
370 tombstones: HashMap<TaskId, crate::ResultTombstone>,
372}
373
374#[derive(Debug, Default)]
385pub struct InMemoryResultBackend {
386 state: Mutex<BackendState>,
388}
389
390impl InMemoryResultBackend {
391 #[must_use]
393 pub fn new() -> Self {
394 Self {
395 state: Mutex::new(BackendState::default()),
396 }
397 }
398
399 pub async fn len(&self) -> usize {
401 self.state.lock().await.results.len()
402 }
403
404 pub async fn is_empty(&self) -> bool {
406 self.state.lock().await.results.is_empty()
407 }
408
409 pub async fn clear(&self) {
411 let mut guard = self.state.lock().await;
412 guard.results.clear();
413 guard.tombstones.clear();
414 }
415
416 fn state_for(value: &TaskResultValue) -> TaskState {
418 match value {
419 TaskResultValue::Pending => TaskState::Pending,
420 TaskResultValue::Received => TaskState::Received,
421 TaskResultValue::Started => TaskState::Running,
422 TaskResultValue::Success(v) => {
423 let bytes = serde_json::to_vec(v).unwrap_or_default();
427 TaskState::Succeeded(bytes)
428 }
429 TaskResultValue::Failure { error, .. } => TaskState::Failed(error.clone()),
430 TaskResultValue::Revoked => TaskState::Revoked,
431 TaskResultValue::Retry { attempt, .. } => TaskState::Retrying(*attempt),
432 TaskResultValue::Rejected { .. } => TaskState::Rejected,
433 }
434 }
435}
436
437#[async_trait::async_trait]
438impl ResultStore for InMemoryResultBackend {
439 async fn store_result(&self, task_id: TaskId, result: TaskResultValue) -> Result<()> {
440 let mut guard = self.state.lock().await;
441 guard.tombstones.remove(&task_id);
443 guard
444 .results
445 .insert(task_id, StoredResult { value: result });
446 Ok(())
447 }
448
449 async fn get_result(&self, task_id: TaskId) -> Result<Option<TaskResultValue>> {
450 Ok(self
451 .state
452 .lock()
453 .await
454 .results
455 .get(&task_id)
456 .map(|stored| stored.value.clone()))
457 }
458
459 async fn get_state(&self, task_id: TaskId) -> Result<TaskState> {
460 let guard = self.state.lock().await;
461 match guard.results.get(&task_id) {
462 Some(stored) => Ok(Self::state_for(&stored.value)),
463 None => Ok(TaskState::Pending),
464 }
465 }
466
467 async fn forget(&self, task_id: TaskId) -> Result<()> {
468 self.state.lock().await.results.remove(&task_id);
469 Ok(())
470 }
471
472 async fn has_result(&self, task_id: TaskId) -> Result<bool> {
473 Ok(self.state.lock().await.results.contains_key(&task_id))
474 }
475
476 async fn store_tombstone(&self, tombstone: crate::ResultTombstone) -> Result<()> {
477 self.state
478 .lock()
479 .await
480 .tombstones
481 .insert(tombstone.task_id, tombstone);
482 Ok(())
483 }
484
485 async fn get_tombstone(&self, task_id: TaskId) -> Result<Option<crate::ResultTombstone>> {
486 Ok(self.state.lock().await.tombstones.get(&task_id).cloned())
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use crate::Broker;
494 use serde_json::json;
495
496 fn task(name: &str) -> SerializedTask {
497 SerializedTask::new(name.to_string(), vec![1, 2, 3])
498 }
499
500 #[tokio::test]
501 async fn enqueue_dequeue_ack_round_trip() {
502 let broker = InMemoryBroker::new();
503 let t = task("a");
504 let id = broker.enqueue(t).await.unwrap();
505 assert_eq!(broker.queue_size().await.unwrap(), 1);
506
507 let msg = broker.dequeue().await.unwrap().expect("message");
508 assert_eq!(msg.task_id(), id);
509 assert!(msg.has_receipt_handle());
510 assert_eq!(broker.queue_size().await.unwrap(), 0);
512 assert_eq!(broker.in_flight_len().await, 1);
513
514 broker
515 .ack(&id, msg.receipt_handle.as_deref())
516 .await
517 .unwrap();
518 assert_eq!(broker.in_flight_len().await, 0);
519 assert!(broker.is_empty().await);
520 }
521
522 #[tokio::test]
523 async fn ack_without_handle_errors() {
524 let broker = InMemoryBroker::new();
525 let id = broker.enqueue(task("a")).await.unwrap();
526 let _ = broker.dequeue().await.unwrap().unwrap();
527 let err = broker.ack(&id, None).await.unwrap_err();
528 assert!(err.is_broker());
529 }
530
531 #[tokio::test]
532 async fn ack_unknown_handle_errors() {
533 let broker = InMemoryBroker::new();
534 let id = broker.enqueue(task("a")).await.unwrap();
535 let err = broker.ack(&id, Some("does-not-exist")).await.unwrap_err();
536 assert!(err.is_broker());
537 }
538
539 #[tokio::test]
540 async fn fifo_order_within_same_priority() {
541 let broker = InMemoryBroker::new();
542 let id1 = broker.enqueue(task("first")).await.unwrap();
543 let id2 = broker.enqueue(task("second")).await.unwrap();
544 let id3 = broker.enqueue(task("third")).await.unwrap();
545
546 let m1 = broker.dequeue().await.unwrap().unwrap();
547 let m2 = broker.dequeue().await.unwrap().unwrap();
548 let m3 = broker.dequeue().await.unwrap().unwrap();
549 assert_eq!(m1.task_id(), id1);
550 assert_eq!(m2.task_id(), id2);
551 assert_eq!(m3.task_id(), id3);
552 }
553
554 #[tokio::test]
555 async fn higher_priority_delivered_first() {
556 let broker = InMemoryBroker::new();
557 let low = broker.enqueue(task("low").with_priority(1)).await.unwrap();
558 let high = broker
559 .enqueue(task("high").with_priority(10))
560 .await
561 .unwrap();
562 let mid = broker.enqueue(task("mid").with_priority(5)).await.unwrap();
563
564 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), high);
565 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), mid);
566 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), low);
567 }
568
569 #[tokio::test]
570 async fn priority_then_fifo_combined() {
571 let broker = InMemoryBroker::new();
572 let a = broker.enqueue(task("a").with_priority(5)).await.unwrap();
573 let b = broker.enqueue(task("b").with_priority(5)).await.unwrap();
574 let c = broker.enqueue(task("c").with_priority(9)).await.unwrap();
575
576 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), c);
578 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), a);
579 assert_eq!(broker.dequeue().await.unwrap().unwrap().task_id(), b);
580 }
581
582 #[tokio::test]
583 async fn reject_with_requeue_returns_task() {
584 let broker = InMemoryBroker::new();
585 let id = broker.enqueue(task("a")).await.unwrap();
586 let msg = broker.dequeue().await.unwrap().unwrap();
587
588 broker
589 .reject(&id, msg.receipt_handle.as_deref(), true)
590 .await
591 .unwrap();
592 assert_eq!(broker.queue_size().await.unwrap(), 1);
594 assert_eq!(broker.in_flight_len().await, 0);
595
596 let msg2 = broker.dequeue().await.unwrap().unwrap();
597 assert_eq!(msg2.task_id(), id);
598 }
599
600 #[tokio::test]
601 async fn reject_without_requeue_drops_task() {
602 let broker = InMemoryBroker::new();
603 let id = broker.enqueue(task("a")).await.unwrap();
604 let msg = broker.dequeue().await.unwrap().unwrap();
605
606 broker
607 .reject(&id, msg.receipt_handle.as_deref(), false)
608 .await
609 .unwrap();
610 assert!(broker.is_empty().await);
611 }
612
613 #[tokio::test]
614 async fn cancel_ready_task() {
615 let broker = InMemoryBroker::new();
616 let id = broker.enqueue(task("a")).await.unwrap();
617 assert!(broker.cancel(&id).await.unwrap());
618 assert_eq!(broker.queue_size().await.unwrap(), 0);
619 assert!(!broker.cancel(&id).await.unwrap());
621 }
622
623 #[tokio::test]
624 async fn cancel_in_flight_suppresses_requeue() {
625 let broker = InMemoryBroker::new();
626 let id = broker.enqueue(task("a")).await.unwrap();
627 let msg = broker.dequeue().await.unwrap().unwrap();
628
629 assert!(broker.cancel(&id).await.unwrap());
631 broker
633 .reject(&id, msg.receipt_handle.as_deref(), true)
634 .await
635 .unwrap();
636 assert!(broker.is_empty().await);
637 }
638
639 #[tokio::test]
640 async fn dequeue_empty_returns_when_enqueued() {
641 let broker = std::sync::Arc::new(InMemoryBroker::new());
642 let b2 = broker.clone();
643 let handle = tokio::spawn(async move { b2.dequeue().await });
644
645 tokio::task::yield_now().await;
647 let id = broker.enqueue(task("late")).await.unwrap();
648
649 let msg = handle.await.unwrap().unwrap().unwrap();
650 assert_eq!(msg.task_id(), id);
651 }
652
653 #[tokio::test]
654 async fn enqueue_batch_and_dequeue_batch() {
655 let broker = InMemoryBroker::new();
656 let tasks = vec![task("a"), task("b"), task("c")];
657 let ids = broker.enqueue_batch(tasks).await.unwrap();
658 assert_eq!(ids.len(), 3);
659 assert_eq!(broker.queue_size().await.unwrap(), 3);
660
661 let msgs = broker.dequeue_batch(10).await.unwrap();
662 assert_eq!(msgs.len(), 3);
663 assert_eq!(broker.queue_size().await.unwrap(), 0);
664 }
665
666 #[tokio::test]
667 async fn clear_empties_broker() {
668 let broker = InMemoryBroker::new();
669 broker.enqueue(task("a")).await.unwrap();
670 let _ = broker.dequeue().await.unwrap();
671 broker.enqueue(task("b")).await.unwrap();
672 broker.clear().await;
673 assert!(broker.is_empty().await);
674 }
675
676 #[tokio::test]
679 async fn backend_store_get_forget() {
680 let backend = InMemoryResultBackend::new();
681 let id = Uuid::new_v4();
682 assert!(!backend.has_result(id).await.unwrap());
683 assert!(backend.get_result(id).await.unwrap().is_none());
684
685 backend
686 .store_result(id, TaskResultValue::Success(json!({"x": 1})))
687 .await
688 .unwrap();
689 assert!(backend.has_result(id).await.unwrap());
690 assert_eq!(backend.len().await, 1);
691
692 let got = backend.get_result(id).await.unwrap().unwrap();
693 assert!(got.is_successful());
694
695 backend.forget(id).await.unwrap();
696 assert!(!backend.has_result(id).await.unwrap());
697 assert!(backend.is_empty().await);
698 }
699
700 #[tokio::test]
701 async fn backend_get_state_maps_values() {
702 let backend = InMemoryResultBackend::new();
703 let id = Uuid::new_v4();
704 assert_eq!(backend.get_state(id).await.unwrap(), TaskState::Pending);
706
707 backend
708 .store_result(
709 id,
710 TaskResultValue::Failure {
711 error: "boom".to_string(),
712 traceback: None,
713 },
714 )
715 .await
716 .unwrap();
717 assert_eq!(
718 backend.get_state(id).await.unwrap(),
719 TaskState::Failed("boom".to_string())
720 );
721
722 backend
723 .store_result(
724 id,
725 TaskResultValue::Retry {
726 attempt: 2,
727 max_retries: 5,
728 },
729 )
730 .await
731 .unwrap();
732 assert_eq!(backend.get_state(id).await.unwrap(), TaskState::Retrying(2));
733
734 backend
735 .store_result(id, TaskResultValue::Success(json!(7)))
736 .await
737 .unwrap();
738 match backend.get_state(id).await.unwrap() {
739 TaskState::Succeeded(bytes) => {
740 assert_eq!(
741 serde_json::from_slice::<serde_json::Value>(&bytes).unwrap(),
742 json!(7)
743 );
744 }
745 other => panic!("expected Succeeded, got {other:?}"),
746 }
747 }
748
749 #[tokio::test]
750 async fn backend_overwrite_clears_tombstone() {
751 let backend = InMemoryResultBackend::new();
752 let id = Uuid::new_v4();
753 backend
754 .store_result(id, TaskResultValue::Success(json!(1)))
755 .await
756 .unwrap();
757 backend
759 .forget_with_tombstone(crate::ResultTombstone::new(id))
760 .await
761 .unwrap();
762 assert!(backend.has_tombstone(id).await.unwrap());
763 assert!(!backend.has_result(id).await.unwrap());
764
765 backend
767 .store_result(id, TaskResultValue::Success(json!(2)))
768 .await
769 .unwrap();
770 assert!(!backend.has_tombstone(id).await.unwrap());
771 assert!(backend.has_result(id).await.unwrap());
772 }
773
774 #[tokio::test]
775 async fn backend_result_existence_tri_state() {
776 use crate::result_tombstone::ResultExistence;
777 let backend = InMemoryResultBackend::new();
778 let id = Uuid::new_v4();
779 assert!(matches!(
781 backend.result_existence(id).await.unwrap(),
782 ResultExistence::Absent
783 ));
784 backend
786 .store_result(id, TaskResultValue::Success(json!(1)))
787 .await
788 .unwrap();
789 assert!(matches!(
790 backend.result_existence(id).await.unwrap(),
791 ResultExistence::Present
792 ));
793 backend
795 .forget_with_tombstone(crate::ResultTombstone::new(id))
796 .await
797 .unwrap();
798 assert!(matches!(
799 backend.result_existence(id).await.unwrap(),
800 ResultExistence::Tombstoned(_)
801 ));
802 }
803}