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