Skip to main content

rivetkit_core/actor/
queue.rs

1use std::collections::BTreeSet;
2use std::fmt;
3use std::future::pending;
4use std::sync::Arc;
5use std::sync::atomic::Ordering;
6use std::time::Duration;
7
8use crate::time::{Instant, SystemTime, UNIX_EPOCH, sleep};
9
10use anyhow::{Context, Result};
11use rivet_error::RivetError;
12use rivetkit_actor_persist::{generated::v4 as persist_v4, versioned as persist_versioned};
13use serde::{Deserialize, Serialize};
14#[cfg(not(target_arch = "wasm32"))]
15use tokio::runtime::{Builder, Handle};
16use tokio::sync::oneshot;
17use tokio_util::sync::CancellationToken;
18
19use crate::actor::config::ActorConfig;
20use crate::actor::context::ActorContext;
21use crate::actor::keys::{
22	QUEUE_MESSAGES_PREFIX, QUEUE_METADATA_KEY, decode_queue_message_key, make_queue_message_key,
23};
24use crate::actor::persist::{
25	decode_latest_with_embedded_version, encode_latest_with_embedded_version,
26};
27use crate::actor::preload::PreloadedKv;
28use crate::actor::task_types::UserTaskKind;
29#[cfg(target_arch = "wasm32")]
30use crate::error::ActorRuntime;
31use crate::types::ListOpts;
32
33#[derive(Clone, Debug, Default)]
34pub struct QueueNextOpts {
35	pub names: Option<Vec<String>>,
36	pub timeout: Option<Duration>,
37	pub signal: Option<CancellationToken>,
38	pub completable: bool,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct QueueWaitOpts {
43	pub timeout: Option<Duration>,
44	pub signal: Option<CancellationToken>,
45	pub completable: bool,
46}
47
48#[derive(Clone, Debug, Default)]
49pub struct EnqueueAndWaitOpts {
50	pub timeout: Option<Duration>,
51	pub signal: Option<CancellationToken>,
52}
53
54#[derive(Clone, Debug)]
55pub struct QueueNextBatchOpts {
56	pub names: Option<Vec<String>>,
57	pub count: u32,
58	pub timeout: Option<Duration>,
59	pub signal: Option<CancellationToken>,
60	pub completable: bool,
61}
62
63impl Default for QueueNextBatchOpts {
64	fn default() -> Self {
65		Self {
66			names: None,
67			count: 1,
68			timeout: None,
69			signal: None,
70			completable: false,
71		}
72	}
73}
74
75#[derive(Clone, Debug, Default)]
76pub struct QueueTryNextOpts {
77	pub names: Option<Vec<String>>,
78	pub completable: bool,
79}
80
81#[derive(Clone, Debug)]
82pub struct QueueTryNextBatchOpts {
83	pub names: Option<Vec<String>>,
84	pub count: u32,
85	pub completable: bool,
86}
87
88impl Default for QueueTryNextBatchOpts {
89	fn default() -> Self {
90		Self {
91			names: None,
92			count: 1,
93			completable: false,
94		}
95	}
96}
97
98pub(super) type QueueWaitActivityCallback = Arc<dyn Fn() + Send + Sync>;
99pub(super) type QueueInspectorUpdateCallback = Arc<dyn Fn(u32) + Send + Sync>;
100
101#[derive(Clone, Debug)]
102pub struct QueueMessage {
103	pub id: u64,
104	pub name: String,
105	pub body: Vec<u8>,
106	pub created_at: i64,
107	completion: Option<CompletionHandle>,
108}
109
110#[derive(Clone, Debug)]
111pub struct CompletableQueueMessage {
112	pub id: u64,
113	pub name: String,
114	pub body: Vec<u8>,
115	pub created_at: i64,
116	completion: CompletionHandle,
117}
118
119#[derive(Clone)]
120struct CompletionHandle(Arc<CompletionHandleInner>);
121
122struct CompletionHandleInner {
123	ctx: ActorContext,
124	message_id: u64,
125	completed: std::sync::atomic::AtomicBool,
126}
127
128pub(super) type QueueMetadata = persist_v4::QueueMetadata;
129type PersistedQueueMessage = persist_v4::QueueMessage;
130
131fn encode_queue_metadata(metadata: &QueueMetadata) -> Result<Vec<u8>> {
132	encode_latest_with_embedded_version::<persist_versioned::QueueMetadata>(
133		metadata.clone(),
134		rivetkit_actor_persist::CURRENT_VERSION,
135		"queue metadata",
136	)
137}
138
139fn decode_queue_metadata(payload: &[u8]) -> Result<QueueMetadata> {
140	let metadata = decode_latest_with_embedded_version::<persist_versioned::QueueMetadata>(
141		payload,
142		"queue metadata",
143	)?;
144	Ok(metadata)
145}
146
147fn encode_queue_message(message: &PersistedQueueMessage) -> Result<Vec<u8>> {
148	encode_latest_with_embedded_version::<persist_versioned::QueueMessage>(
149		message.clone(),
150		rivetkit_actor_persist::CURRENT_VERSION,
151		"queue message",
152	)
153}
154
155fn decode_queue_message(payload: &[u8]) -> Result<PersistedQueueMessage> {
156	let message = decode_latest_with_embedded_version::<persist_versioned::QueueMessage>(
157		payload,
158		"queue message",
159	)?;
160	Ok(message)
161}
162
163#[derive(RivetError, Serialize, Deserialize)]
164#[error(
165	"queue",
166	"full",
167	"Queue is full",
168	"Queue is full. Limit is {limit} messages."
169)]
170struct QueueFull {
171	limit: u32,
172}
173
174#[derive(RivetError, Serialize, Deserialize)]
175#[error(
176	"queue",
177	"message_too_large",
178	"Queue message is too large",
179	"Queue message too large ({size} bytes). Limit is {limit} bytes."
180)]
181struct QueueMessageTooLarge {
182	size: usize,
183	limit: u32,
184}
185
186#[derive(RivetError)]
187#[error("queue", "already_completed", "Queue message was already completed")]
188struct QueueAlreadyCompleted;
189
190#[derive(RivetError, Serialize, Deserialize)]
191#[error(
192	"queue",
193	"complete_not_configured",
194	"Queue message does not support completion",
195	"Queue '{name}' does not support completion responses."
196)]
197struct QueueCompleteNotConfigured {
198	name: String,
199}
200
201#[derive(RivetError)]
202#[error("actor", "aborted", "Actor aborted")]
203struct QueueActorAborted;
204
205#[derive(RivetError, Serialize, Deserialize)]
206#[error(
207	"queue",
208	"timed_out",
209	"Queue wait timed out",
210	"Queue wait timed out after {timeout_ms} ms."
211)]
212struct QueueWaitTimedOut {
213	timeout_ms: u64,
214}
215
216#[derive(RivetError, Serialize, Deserialize)]
217#[error(
218	"queue",
219	"completion_waiter_conflict",
220	"Queue completion waiter conflict",
221	"Queue completion waiter is already registered for message {message_id}."
222)]
223struct QueueCompletionWaiterConflict {
224	message_id: u64,
225}
226
227#[derive(RivetError)]
228#[error(
229	"queue",
230	"completion_waiter_dropped",
231	"Queue completion waiter dropped before response"
232)]
233struct QueueCompletionWaiterDropped;
234
235impl ActorContext {
236	pub async fn send(&self, name: &str, body: &[u8]) -> Result<QueueMessage> {
237		self.enqueue_message(name, body, None).await
238	}
239
240	pub async fn enqueue_and_wait(
241		&self,
242		name: &str,
243		body: &[u8],
244		opts: EnqueueAndWaitOpts,
245	) -> Result<Option<Vec<u8>>> {
246		let (sender, receiver) = oneshot::channel();
247		let message = self.enqueue_message(name, body, Some(sender)).await?;
248		let result = self
249			.wait_for_completion_response(message.id, receiver, opts.timeout, opts.signal.as_ref())
250			.await;
251		self.remove_completion_waiter(message.id).await;
252		result
253	}
254
255	async fn enqueue_message(
256		&self,
257		name: &str,
258		body: &[u8],
259		completion_waiter: Option<oneshot::Sender<Option<Vec<u8>>>>,
260	) -> Result<QueueMessage> {
261		self.ensure_initialized().await?;
262
263		let created_at = current_timestamp_ms()?;
264		let persisted = PersistedQueueMessage {
265			name: name.to_owned(),
266			body: body.to_vec(),
267			created_at,
268			failure_count: None,
269			available_at: None,
270			in_flight: None,
271			in_flight_at: None,
272		};
273		let encoded_message = encode_queue_message(&persisted).context("encode queue message")?;
274		self.clear_preloaded_messages();
275
276		let config = self.config();
277		if encoded_message.len() > config.max_queue_message_size as usize {
278			return Err(QueueMessageTooLarge {
279				size: encoded_message.len(),
280				limit: config.max_queue_message_size,
281			}
282			.build());
283		}
284
285		let mut metadata = self.0.queue_metadata.lock().await;
286		if metadata.size >= config.max_queue_size {
287			return Err(QueueFull {
288				limit: config.max_queue_size,
289			}
290			.build());
291		}
292
293		let id = if metadata.next_id == 0 {
294			1
295		} else {
296			metadata.next_id
297		};
298		metadata.next_id = id.saturating_add(1);
299		metadata.size = metadata.size.saturating_add(1);
300		let encoded_metadata = encode_queue_metadata(&metadata).context("encode queue metadata")?;
301
302		let registered_completion_waiter = if let Some(waiter) = completion_waiter {
303			if self
304				.0
305				.queue_completion_waiters
306				.insert_async(id, waiter)
307				.await
308				.is_err()
309			{
310				metadata.next_id = id;
311				metadata.size = metadata.size.saturating_sub(1);
312				return Err(QueueCompletionWaiterConflict { message_id: id }.build());
313			}
314			true
315		} else {
316			false
317		};
318
319		if let Err(error) = self
320			.0
321			.kv
322			.batch_put(&[
323				(
324					make_queue_message_key(id).as_slice(),
325					encoded_message.as_slice(),
326				),
327				(QUEUE_METADATA_KEY.as_slice(), encoded_metadata.as_slice()),
328			])
329			.await
330		{
331			metadata.next_id = id;
332			metadata.size = metadata.size.saturating_sub(1);
333			if registered_completion_waiter {
334				self.remove_completion_waiter(id).await;
335			}
336			return Err(error).context("persist queue message");
337		}
338
339		let queue_size = metadata.size;
340		drop(metadata);
341		self.0.metrics.add_queue_messages_sent(1);
342		self.0
343			.metrics
344			.set_queue_depth(self.0.queue_metadata.lock().await.size);
345		self.notify_inspector_update(queue_size);
346		self.0.queue_notify.notify_waiters();
347
348		Ok(QueueMessage {
349			id,
350			name: name.to_owned(),
351			body: body.to_vec(),
352			created_at,
353			completion: None,
354		})
355	}
356
357	pub async fn next(&self, opts: QueueNextOpts) -> Result<Option<QueueMessage>> {
358		let mut messages = self
359			.next_batch(QueueNextBatchOpts {
360				names: opts.names,
361				count: 1,
362				timeout: opts.timeout,
363				signal: opts.signal,
364				completable: opts.completable,
365			})
366			.await?;
367		Ok(messages.pop())
368	}
369
370	pub async fn next_batch(&self, opts: QueueNextBatchOpts) -> Result<Vec<QueueMessage>> {
371		self.ensure_initialized().await?;
372
373		let count = opts.count.max(1);
374		let deadline = opts.timeout.map(|timeout| Instant::now() + timeout);
375		let names = normalize_names(opts.names);
376
377		loop {
378			let messages = self
379				.try_receive_batch(names.as_ref(), count, opts.completable)
380				.await?;
381			if !messages.is_empty() {
382				return Ok(messages);
383			}
384
385			let remaining_timeout =
386				deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
387			if matches!(remaining_timeout, Some(timeout) if timeout.is_zero()) {
388				return Ok(Vec::new());
389			}
390
391			let wait_guard = ActiveQueueWaitGuard::new(self);
392			let result = self
393				.wait_for_message(remaining_timeout, opts.signal.as_ref())
394				.await;
395			drop(wait_guard);
396
397			match result {
398				WaitOutcome::Notified => continue,
399				WaitOutcome::TimedOut => return Ok(Vec::new()),
400				WaitOutcome::Aborted => return Err(QueueActorAborted.build()),
401			}
402		}
403	}
404
405	pub async fn wait_for_names(
406		&self,
407		names: Vec<String>,
408		opts: QueueWaitOpts,
409	) -> Result<QueueMessage> {
410		self.ensure_initialized().await?;
411
412		let deadline = opts.timeout.map(|timeout| Instant::now() + timeout);
413		let names = normalize_names(Some(names));
414
415		loop {
416			if let Some(message) = self
417				.try_receive_batch(names.as_ref(), 1, opts.completable)
418				.await?
419				.into_iter()
420				.next()
421			{
422				return Ok(message);
423			}
424
425			let remaining_timeout =
426				deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
427			if let Some(timeout) = remaining_timeout
428				&& timeout.is_zero()
429			{
430				return Err(QueueWaitTimedOut {
431					timeout_ms: opts.timeout.map(duration_ms).unwrap_or(0),
432				}
433				.build());
434			}
435
436			let wait_guard = ActiveQueueWaitGuard::new(self);
437			let result = self
438				.wait_for_message(remaining_timeout, opts.signal.as_ref())
439				.await;
440			drop(wait_guard);
441
442			match result {
443				WaitOutcome::Notified => continue,
444				WaitOutcome::TimedOut => {
445					return Err(QueueWaitTimedOut {
446						timeout_ms: opts.timeout.map(duration_ms).unwrap_or(0),
447					}
448					.build());
449				}
450				WaitOutcome::Aborted => return Err(QueueActorAborted.build()),
451			}
452		}
453	}
454
455	pub async fn wait_for_names_available(
456		&self,
457		names: Vec<String>,
458		opts: QueueWaitOpts,
459	) -> Result<()> {
460		self.ensure_initialized().await?;
461
462		let deadline = opts.timeout.map(|timeout| Instant::now() + timeout);
463		let names = normalize_names(Some(names));
464
465		loop {
466			let messages = self.list_messages().await?;
467			let has_match = if let Some(names) = names.as_ref() {
468				messages
469					.into_iter()
470					.any(|message| names.contains(&message.name))
471			} else {
472				!messages.is_empty()
473			};
474			if has_match {
475				return Ok(());
476			}
477
478			let remaining_timeout =
479				deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
480			if let Some(timeout) = remaining_timeout
481				&& timeout.is_zero()
482			{
483				return Err(QueueWaitTimedOut {
484					timeout_ms: opts.timeout.map(duration_ms).unwrap_or(0),
485				}
486				.build());
487			}
488
489			let wait_guard = ActiveQueueWaitGuard::new(self);
490			let result = self
491				.wait_for_message(remaining_timeout, opts.signal.as_ref())
492				.await;
493			drop(wait_guard);
494
495			match result {
496				WaitOutcome::Notified => continue,
497				WaitOutcome::TimedOut => {
498					return Err(QueueWaitTimedOut {
499						timeout_ms: opts.timeout.map(duration_ms).unwrap_or(0),
500					}
501					.build());
502				}
503				WaitOutcome::Aborted => return Err(QueueActorAborted.build()),
504			}
505		}
506	}
507
508	pub fn try_next(&self, opts: QueueTryNextOpts) -> Result<Option<QueueMessage>> {
509		let mut messages = self.try_next_batch(QueueTryNextBatchOpts {
510			names: opts.names,
511			count: 1,
512			completable: opts.completable,
513		})?;
514		Ok(messages.pop())
515	}
516
517	pub fn try_next_batch(&self, opts: QueueTryNextBatchOpts) -> Result<Vec<QueueMessage>> {
518		self.block_on(async {
519			self.ensure_initialized().await?;
520			self.try_receive_batch(
521				normalize_names(opts.names).as_ref(),
522				opts.count.max(1),
523				opts.completable,
524			)
525			.await
526		})
527	}
528
529	pub async fn inspect_messages(&self) -> Result<Vec<QueueMessage>> {
530		self.ensure_initialized().await?;
531		self.list_messages().await
532	}
533
534	pub fn max_size(&self) -> u32 {
535		self.config().max_queue_size
536	}
537
538	pub(crate) fn configure_queue(&self, config: ActorConfig) {
539		*self.0.queue_config.lock() = config;
540	}
541
542	pub(crate) fn configure_preload(&self, preloaded_kv: Option<PreloadedKv>) {
543		*self.0.queue_preloaded_kv.lock() = preloaded_kv;
544		*self.0.queue_preloaded_message_entries.lock() = None;
545	}
546
547	pub(crate) fn set_wait_activity_callback(&self, callback: Option<Arc<dyn Fn() + Send + Sync>>) {
548		*self.0.queue_wait_activity_callback.lock() = callback;
549	}
550
551	pub(crate) fn set_inspector_update_callback(
552		&self,
553		callback: Option<Arc<dyn Fn(u32) + Send + Sync>>,
554	) {
555		*self.0.queue_inspector_update_callback.lock() = callback;
556	}
557
558	async fn ensure_initialized(&self) -> Result<()> {
559		self.0
560			.queue_initialize
561			.get_or_try_init(|| async {
562				let preload = self.0.queue_preloaded_kv.lock().take();
563				let metadata = if let Some(preloaded) = preload.as_ref() {
564					self.configure_preloaded_messages(preloaded);
565					if let Some(metadata) = self.load_metadata_from_preload(preloaded).await? {
566						metadata
567					} else {
568						self.load_or_create_metadata().await?
569					}
570				} else {
571					self.load_or_create_metadata().await?
572				};
573				let mut state = self.0.queue_metadata.lock().await;
574				*state = metadata;
575				self.0.metrics.set_queue_depth(state.size);
576				Ok(())
577			})
578			.await
579			.map(|_| ())
580	}
581
582	fn configure_preloaded_messages(&self, preloaded: &PreloadedKv) {
583		if let Some(entries) = preloaded.prefix_entries(&QUEUE_MESSAGES_PREFIX) {
584			*self.0.queue_preloaded_message_entries.lock() = Some(entries);
585		}
586	}
587
588	async fn load_metadata_from_preload(
589		&self,
590		preloaded: &PreloadedKv,
591	) -> Result<Option<QueueMetadata>> {
592		match preloaded.key_entry(&QUEUE_METADATA_KEY) {
593			Some(Some(encoded)) => match decode_queue_metadata(&encoded) {
594				Ok(metadata) => Ok(Some(metadata)),
595				Err(error) => {
596					tracing::warn!(
597						?error,
598						"failed to decode preloaded queue metadata, rebuilding"
599					);
600					Ok(self.metadata_from_preloaded_messages())
601				}
602			},
603			Some(None) => Ok(self.metadata_from_preloaded_messages()),
604			None => Ok(None),
605		}
606	}
607
608	fn metadata_from_preloaded_messages(&self) -> Option<QueueMetadata> {
609		let entries = self.0.queue_preloaded_message_entries.lock().clone()?;
610		Some(metadata_from_queue_messages(decode_queue_message_entries(
611			entries,
612		)))
613	}
614
615	async fn load_or_create_metadata(&self) -> Result<QueueMetadata> {
616		let Some(encoded) = self.0.kv.get(&QUEUE_METADATA_KEY).await? else {
617			let metadata = QueueMetadata {
618				next_id: 1,
619				size: 0,
620			};
621			self.0
622				.kv
623				.put(
624					&QUEUE_METADATA_KEY,
625					&encode_queue_metadata(&metadata).context("encode default queue metadata")?,
626				)
627				.await
628				.context("persist default queue metadata")?;
629			return Ok(metadata);
630		};
631
632		match decode_queue_metadata(&encoded) {
633			Ok(metadata) => Ok(metadata),
634			Err(error) => {
635				tracing::warn!(?error, "failed to decode queue metadata, rebuilding");
636				self.rebuild_metadata().await
637			}
638		}
639	}
640
641	async fn rebuild_metadata(&self) -> Result<QueueMetadata> {
642		let messages = self.list_messages().await?;
643		let metadata = metadata_from_queue_messages(messages);
644		self.persist_metadata(&metadata)
645			.await
646			.context("persist rebuilt queue metadata")?;
647		Ok(metadata)
648	}
649
650	async fn persist_metadata(&self, metadata: &QueueMetadata) -> Result<()> {
651		let encoded = encode_queue_metadata(metadata).context("encode queue metadata")?;
652		self.0
653			.kv
654			.put(&QUEUE_METADATA_KEY, &encoded)
655			.await
656			.context("persist queue metadata")?;
657		self.notify_inspector_update(metadata.size);
658		Ok(())
659	}
660
661	async fn try_receive_batch(
662		&self,
663		names: Option<&BTreeSet<String>>,
664		count: u32,
665		completable: bool,
666	) -> Result<Vec<QueueMessage>> {
667		let _receive_guard = self.0.queue_receive_lock.lock().await;
668
669		let messages = self.list_messages().await?;
670		let mut selected = Vec::new();
671		for message in messages {
672			if let Some(names) = names
673				&& !names.contains(&message.name)
674			{
675				continue;
676			}
677
678			selected.push(message);
679			if selected.len() >= count as usize {
680				break;
681			}
682		}
683
684		if selected.is_empty() {
685			return Ok(Vec::new());
686		}
687
688		if completable {
689			let queue_size = self.0.queue_metadata.lock().await.size;
690			self.0
691				.metrics
692				.add_queue_messages_received(selected.len().try_into().unwrap_or(u64::MAX));
693			self.notify_inspector_update(queue_size);
694			return Ok(selected
695				.into_iter()
696				.map(|message| self.attach_completion(message))
697				.collect());
698		}
699
700		self.remove_messages(selected.iter().map(|message| message.id).collect())
701			.await?;
702		self.0
703			.metrics
704			.add_queue_messages_received(selected.len().try_into().unwrap_or(u64::MAX));
705
706		Ok(selected)
707	}
708
709	async fn list_messages(&self) -> Result<Vec<QueueMessage>> {
710		let messages = decode_queue_message_entries(self.list_message_entries().await?);
711
712		let actual_size = messages.len().try_into().unwrap_or(u32::MAX);
713		let mut metadata = self.0.queue_metadata.lock().await;
714		if metadata.size != actual_size {
715			metadata.size = actual_size;
716		}
717		if metadata.next_id == 0 {
718			metadata.next_id = messages
719				.last()
720				.map(|message| message.id.saturating_add(1))
721				.unwrap_or(1);
722		}
723
724		Ok(messages)
725	}
726
727	async fn list_message_entries(&self) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
728		if let Some(entries) = self.0.queue_preloaded_message_entries.lock().take() {
729			return Ok(entries);
730		}
731
732		self.0
733			.kv
734			.list_prefix(
735				&QUEUE_MESSAGES_PREFIX,
736				ListOpts {
737					reverse: false,
738					limit: None,
739				},
740			)
741			.await
742			.context("list queue messages")
743	}
744
745	fn clear_preloaded_messages(&self) {
746		self.0.queue_preloaded_message_entries.lock().take();
747	}
748
749	fn attach_completion(&self, mut message: QueueMessage) -> QueueMessage {
750		message.completion = Some(CompletionHandle::new(self.clone(), message.id));
751		message
752	}
753
754	async fn remove_messages(&self, message_ids: Vec<u64>) -> Result<()> {
755		if message_ids.is_empty() {
756			return Ok(());
757		}
758
759		let keys: Vec<Vec<u8>> = message_ids
760			.into_iter()
761			.map(make_queue_message_key)
762			.collect();
763		let key_refs: Vec<&[u8]> = keys.iter().map(Vec::as_slice).collect();
764
765		self.0
766			.kv
767			.batch_delete(&key_refs)
768			.await
769			.context("delete queue messages")?;
770
771		let encoded_metadata = {
772			let mut metadata = self.0.queue_metadata.lock().await;
773			metadata.size = metadata.size.saturating_sub(key_refs.len() as u32);
774			let queue_size = metadata.size;
775			encode_queue_metadata(&metadata)
776				.context("encode queue metadata after delete")
777				.map(|encoded| (encoded, queue_size))?
778		};
779		let (encoded_metadata, queue_size) = encoded_metadata;
780
781		self.0
782			.kv
783			.put(&QUEUE_METADATA_KEY, &encoded_metadata)
784			.await
785			.context("persist queue metadata after delete")?;
786		self.0
787			.metrics
788			.set_queue_depth(self.0.queue_metadata.lock().await.size);
789		self.notify_inspector_update(queue_size);
790		Ok(())
791	}
792
793	async fn complete_message_by_id(
794		&self,
795		message_id: u64,
796		response: Option<Vec<u8>>,
797	) -> Result<()> {
798		self.remove_messages(vec![message_id]).await?;
799		if let Some(waiter) = self.remove_completion_waiter(message_id).await {
800			let _ = waiter.send(response);
801		}
802		Ok(())
803	}
804
805	async fn remove_completion_waiter(
806		&self,
807		message_id: u64,
808	) -> Option<oneshot::Sender<Option<Vec<u8>>>> {
809		self.0
810			.queue_completion_waiters
811			.remove_async(&message_id)
812			.await
813			.map(|(_, waiter)| waiter)
814	}
815
816	async fn wait_for_message(
817		&self,
818		timeout: Option<Duration>,
819		signal: Option<&CancellationToken>,
820	) -> WaitOutcome {
821		let actor_abort_signal = self.0.queue_abort_signal.lock().clone();
822		if signal.is_some_and(CancellationToken::is_cancelled) {
823			return WaitOutcome::Aborted;
824		}
825		if actor_abort_signal.is_cancelled() {
826			return WaitOutcome::Aborted;
827		}
828
829		let notified = self.0.queue_notify.notified();
830		let actor_aborted = async {
831			actor_abort_signal.cancelled().await;
832		};
833		let external_aborted = async {
834			if let Some(signal) = signal {
835				signal.cancelled().await;
836			} else {
837				pending::<()>().await;
838			}
839		};
840
841		match timeout {
842			Some(timeout) => {
843				tokio::select! {
844					_ = notified => WaitOutcome::Notified,
845					_ = actor_aborted => WaitOutcome::Aborted,
846					_ = external_aborted => WaitOutcome::Aborted,
847					_ = sleep(timeout) => WaitOutcome::TimedOut,
848				}
849			}
850			None => {
851				tokio::select! {
852					_ = notified => WaitOutcome::Notified,
853					_ = actor_aborted => WaitOutcome::Aborted,
854					_ = external_aborted => WaitOutcome::Aborted,
855				}
856			}
857		}
858	}
859
860	/// TS parity: queue-manager.ts keeps `enqueueAndWait` completion waits
861	/// alive across actor aborts; the surrounding tracked user task owns
862	/// shutdown cancellation.
863	async fn wait_for_completion_response(
864		&self,
865		message_id: u64,
866		mut receiver: oneshot::Receiver<Option<Vec<u8>>>,
867		timeout: Option<Duration>,
868		signal: Option<&CancellationToken>,
869	) -> Result<Option<Vec<u8>>> {
870		if signal.is_some_and(CancellationToken::is_cancelled) {
871			return Err(QueueActorAborted.build());
872		}
873
874		let external_aborted = async {
875			if let Some(signal) = signal {
876				signal.cancelled().await;
877			} else {
878				pending::<()>().await;
879			}
880		};
881
882		let wait_result = match timeout {
883			Some(timeout) => {
884				tokio::select! {
885					response = &mut receiver => CompletionWaitOutcome::Response(response),
886					_ = external_aborted => CompletionWaitOutcome::Aborted,
887					_ = sleep(timeout) => CompletionWaitOutcome::TimedOut,
888				}
889			}
890			None => {
891				tokio::select! {
892					response = &mut receiver => CompletionWaitOutcome::Response(response),
893					_ = external_aborted => CompletionWaitOutcome::Aborted,
894				}
895			}
896		};
897
898		match wait_result {
899			CompletionWaitOutcome::Response(Ok(response)) => Ok(response),
900			CompletionWaitOutcome::Response(Err(_)) => Err(QueueCompletionWaiterDropped.build())
901				.context(format!("wait for queue completion on message {message_id}")),
902			CompletionWaitOutcome::TimedOut => Err(QueueWaitTimedOut {
903				timeout_ms: timeout.map(duration_ms).unwrap_or(0),
904			}
905			.build()),
906			CompletionWaitOutcome::Aborted => Err(QueueActorAborted.build()),
907		}
908	}
909
910	fn block_on<T>(&self, future: impl std::future::Future<Output = Result<T>>) -> Result<T> {
911		#[cfg(not(target_arch = "wasm32"))]
912		{
913			if let Ok(handle) = Handle::try_current() {
914				tokio::task::block_in_place(|| handle.block_on(future))
915			} else {
916				Builder::new_current_thread()
917					.enable_all()
918					.build()
919					.context("build temporary runtime for queue operation")?
920					.block_on(future)
921			}
922		}
923
924		#[cfg(target_arch = "wasm32")]
925		{
926			drop(future);
927			Err(ActorRuntime::InvalidOperation {
928				operation: "queue.try_next_batch".to_owned(),
929				reason: "synchronous queue receive requires native runtime support".to_owned(),
930			}
931			.build())
932		}
933	}
934
935	fn config(&self) -> ActorConfig {
936		self.0.queue_config.lock().clone()
937	}
938
939	#[cfg(test)]
940	pub(crate) fn queue_config_for_tests(&self) -> ActorConfig {
941		self.config()
942	}
943
944	fn notify_wait_activity(&self) {
945		if let Some(callback) = self.0.queue_wait_activity_callback.lock().clone() {
946			callback();
947		}
948	}
949
950	fn notify_inspector_update(&self, queue_size: u32) {
951		if let Some(callback) = self.0.queue_inspector_update_callback.lock().clone() {
952			callback(queue_size);
953		}
954	}
955}
956
957impl QueueMessage {
958	pub async fn complete(self, response: Option<Vec<u8>>) -> Result<()> {
959		let completable = self.into_completable()?;
960		completable.complete(response).await
961	}
962
963	pub fn into_completable(self) -> Result<CompletableQueueMessage> {
964		let completion = self.completion.clone().ok_or_else(|| {
965			QueueCompleteNotConfigured {
966				name: self.name.clone(),
967			}
968			.build()
969		})?;
970
971		Ok(CompletableQueueMessage {
972			id: self.id,
973			name: self.name,
974			body: self.body,
975			created_at: self.created_at,
976			completion,
977		})
978	}
979
980	pub fn is_completable(&self) -> bool {
981		self.completion.is_some()
982	}
983}
984
985impl CompletableQueueMessage {
986	pub async fn complete(self, response: Option<Vec<u8>>) -> Result<()> {
987		self.completion.complete(response).await
988	}
989
990	pub fn into_message(self) -> QueueMessage {
991		QueueMessage {
992			id: self.id,
993			name: self.name,
994			body: self.body,
995			created_at: self.created_at,
996			completion: Some(self.completion),
997		}
998	}
999}
1000
1001impl CompletionHandle {
1002	fn new(ctx: ActorContext, message_id: u64) -> Self {
1003		Self(Arc::new(CompletionHandleInner {
1004			ctx,
1005			message_id,
1006			completed: std::sync::atomic::AtomicBool::new(false),
1007		}))
1008	}
1009
1010	async fn complete(&self, response: Option<Vec<u8>>) -> Result<()> {
1011		if self.0.completed.swap(true, Ordering::SeqCst) {
1012			return Err(QueueAlreadyCompleted.build());
1013		}
1014
1015		if let Err(error) = self
1016			.0
1017			.ctx
1018			.complete_message_by_id(self.0.message_id, response)
1019			.await
1020		{
1021			self.0.completed.store(false, Ordering::SeqCst);
1022			return Err(error);
1023		}
1024
1025		Ok(())
1026	}
1027}
1028
1029impl fmt::Debug for CompletionHandle {
1030	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1031		f.debug_struct("CompletionHandle")
1032			.field("message_id", &self.0.message_id)
1033			.field("completed", &self.0.completed.load(Ordering::SeqCst))
1034			.finish()
1035	}
1036}
1037
1038struct ActiveQueueWaitGuard<'a> {
1039	ctx: &'a ActorContext,
1040	started_at: Instant,
1041}
1042
1043impl<'a> ActiveQueueWaitGuard<'a> {
1044	fn new(ctx: &'a ActorContext) -> Self {
1045		ctx.0.active_queue_wait_count.fetch_add(1, Ordering::SeqCst);
1046		ctx.0.metrics.begin_user_task(UserTaskKind::QueueWait);
1047		ctx.notify_wait_activity();
1048		Self {
1049			ctx,
1050			started_at: Instant::now(),
1051		}
1052	}
1053}
1054
1055impl Drop for ActiveQueueWaitGuard<'_> {
1056	fn drop(&mut self) {
1057		self.ctx
1058			.0
1059			.metrics
1060			.end_user_task(UserTaskKind::QueueWait, self.started_at.elapsed());
1061		let previous = self
1062			.ctx
1063			.0
1064			.active_queue_wait_count
1065			.fetch_sub(1, Ordering::SeqCst);
1066		if previous == 0 {
1067			self.ctx
1068				.0
1069				.active_queue_wait_count
1070				.store(0, Ordering::SeqCst);
1071		}
1072		self.ctx.notify_wait_activity();
1073	}
1074}
1075
1076enum WaitOutcome {
1077	Notified,
1078	TimedOut,
1079	Aborted,
1080}
1081
1082enum CompletionWaitOutcome {
1083	Response(Result<Option<Vec<u8>>, oneshot::error::RecvError>),
1084	TimedOut,
1085	Aborted,
1086}
1087
1088fn normalize_names(names: Option<Vec<String>>) -> Option<BTreeSet<String>> {
1089	names.and_then(|names| {
1090		let normalized = names.into_iter().collect::<BTreeSet<_>>();
1091		if normalized.is_empty() {
1092			None
1093		} else {
1094			Some(normalized)
1095		}
1096	})
1097}
1098
1099fn decode_queue_message_entries(entries: Vec<(Vec<u8>, Vec<u8>)>) -> Vec<QueueMessage> {
1100	let mut messages = Vec::with_capacity(entries.len());
1101	for (key, value) in entries {
1102		let id = match decode_queue_message_key(&key) {
1103			Ok(id) => id,
1104			Err(error) => {
1105				tracing::warn!(?error, "failed to decode queue message key");
1106				continue;
1107			}
1108		};
1109
1110		match decode_queue_message(&value) {
1111			Ok(message) => messages.push(QueueMessage {
1112				id,
1113				name: message.name,
1114				body: message.body,
1115				created_at: message.created_at,
1116				completion: None,
1117			}),
1118			Err(error) => {
1119				tracing::warn!(
1120					?error,
1121					queue_message_id = id,
1122					"failed to decode queue message"
1123				);
1124			}
1125		}
1126	}
1127
1128	messages.sort_by_key(|message| message.id);
1129	messages
1130}
1131
1132fn metadata_from_queue_messages(messages: Vec<QueueMessage>) -> QueueMetadata {
1133	let next_id = messages
1134		.last()
1135		.map(|message| message.id.saturating_add(1))
1136		.unwrap_or(1);
1137	QueueMetadata {
1138		next_id,
1139		size: messages.len().try_into().unwrap_or(u32::MAX),
1140	}
1141}
1142
1143fn current_timestamp_ms() -> Result<i64> {
1144	let now = SystemTime::now()
1145		.duration_since(UNIX_EPOCH)
1146		.context("current time is before unix epoch")?;
1147	i64::try_from(now.as_millis()).context("queue timestamp exceeds i64")
1148}
1149
1150fn duration_ms(duration: Duration) -> u64 {
1151	duration.as_millis().try_into().unwrap_or(u64::MAX)
1152}
1153
1154// Test shim keeps moved tests in crate-root tests/ with private-module access.
1155#[cfg(test)]
1156#[path = "../../tests/queue.rs"]
1157mod tests;