1use std::any::{Any, TypeId};
2use std::io::Cursor;
3use std::marker::PhantomData;
4use std::panic::AssertUnwindSafe;
5use std::sync::Arc;
6use std::time::Duration;
7
8use anyhow::{Context, Result};
9use futures::FutureExt;
10use rivet_error::RivetError;
11use rivetkit_core::actor::ShutdownKind;
12use rivetkit_core::error::{ActorLifecycle, ActorRuntime, action_not_found};
13use rivetkit_core::{ActorEvent, ActorEvents, ActorStart, QueueSendResult, QueueSendStatus, Reply};
14use serde::de::DeserializeOwned;
15use tokio::task::JoinHandle;
16use tokio_util::sync::CancellationToken;
17
18use crate::{
19 action::ActionSet,
20 actor::Actor,
21 context::{ConnCtx, Ctx},
22 event::RuntimeEvent,
23 queue::QueueSet,
24};
25
26#[derive(Debug)]
27pub struct Start<A: Actor> {
28 pub ctx: Ctx<A>,
29 pub input: Input<A>,
30 pub is_new: bool,
31 pub snapshot: Snapshot,
32 pub hibernated: Vec<Hibernated<A>>,
33 pub events: Events<A>,
34 #[doc(hidden)]
35 pub startup_ready: Option<tokio::sync::oneshot::Sender<Result<()>>>,
36}
37
38#[derive(Debug)]
39pub struct Input<A: Actor> {
40 bytes: Option<Vec<u8>>,
41 _p: PhantomData<fn() -> A>,
42}
43
44impl<A: Actor> Input<A> {
45 pub fn is_present(&self) -> bool {
46 self.bytes.is_some()
47 }
48
49 pub fn decode(&self) -> Result<A::Input> {
50 match self.bytes.as_deref() {
51 Some(bytes) => decode_cbor(bytes, "actor input"),
52 None if TypeId::of::<A::Input>() == TypeId::of::<()>() => {
53 let unit: Box<dyn Any> = Box::new(());
54 Ok(*unit
55 .downcast::<A::Input>()
56 .expect("unit input type id should downcast"))
57 }
58 None => Err(ActorRuntime::MissingInput.build()),
59 }
60 }
61
62 pub fn decode_or<F>(&self, f: F) -> Result<A::Input>
63 where
64 F: FnOnce() -> A::Input,
65 {
66 match self.bytes.as_deref() {
67 Some(bytes) => decode_cbor(bytes, "actor input"),
68 None => Ok(f()),
69 }
70 }
71
72 pub fn decode_or_default(&self) -> Result<A::Input>
73 where
74 A::Input: Default,
75 {
76 self.decode_or(A::Input::default)
77 }
78
79 pub fn raw(&self) -> Option<&[u8]> {
80 self.bytes.as_deref()
81 }
82}
83
84#[derive(Debug)]
85pub struct Snapshot {
86 is_new: bool,
87 bytes: Option<Vec<u8>>,
88}
89
90impl Snapshot {
91 pub fn is_new(&self) -> bool {
92 self.is_new
93 }
94
95 pub fn decode<S>(&self) -> Result<Option<S>>
96 where
97 S: DeserializeOwned,
98 {
99 let Some(bytes) = self.bytes.as_deref().filter(|bytes| !bytes.is_empty()) else {
100 return Ok(None);
101 };
102 decode_cbor(bytes, "actor snapshot").map(Some)
103 }
104
105 pub fn decode_or_default<S>(&self) -> Result<S>
106 where
107 S: DeserializeOwned + Default,
108 {
109 Ok(self.decode()?.unwrap_or_default())
110 }
111
112 pub fn raw(&self) -> Option<&[u8]> {
113 self.bytes.as_deref()
114 }
115}
116
117#[derive(Debug)]
118pub struct Hibernated<A: Actor> {
119 pub conn: ConnCtx<A>,
120}
121
122#[derive(Debug)]
123pub struct Events<A: Actor> {
124 ctx: Ctx<A>,
125 rx: ActorEvents,
126 _p: PhantomData<fn() -> A>,
127}
128
129impl<A: Actor> Events<A> {
130 pub(crate) async fn recv_raw(&mut self) -> Option<ActorEvent> {
131 self.rx.recv().await
132 }
133
134 pub async fn recv(&mut self) -> Option<RuntimeEvent<A>> {
135 loop {
136 let event = self.rx.recv().await?;
137 if let Some(event) = self.handle_runtime_event(event).await {
138 return Some(wrap_event(event));
139 }
140 }
141 }
142
143 pub fn try_recv(&mut self) -> Option<RuntimeEvent<A>> {
144 while let Some(event) = self.rx.try_recv() {
145 if let Some(event) = self.handle_runtime_event_sync(event) {
146 return Some(wrap_event(event));
147 }
148 }
149 None
150 }
151
152 async fn handle_runtime_event(&self, event: ActorEvent) -> Option<ActorEvent> {
153 match event {
154 ActorEvent::ConnectionOpen { reply, .. } => {
155 reply.send(Ok(()));
156 None
157 }
158 ActorEvent::DisconnectConn { conn_id, reply } => {
159 reply.send(self.ctx.disconnect_conn(&conn_id).await);
160 None
161 }
162 ActorEvent::RunWake { reply, .. } => {
163 reply.send(Ok(()));
164 None
165 }
166 event => Some(event),
167 }
168 }
169
170 fn handle_runtime_event_sync(&self, event: ActorEvent) -> Option<ActorEvent> {
171 match event {
172 ActorEvent::ConnectionOpen { reply, .. } => {
173 reply.send(Ok(()));
174 None
175 }
176 ActorEvent::DisconnectConn { conn_id, reply } => {
177 let ctx = self.ctx.clone();
178 tokio::spawn(async move {
179 reply.send(ctx.disconnect_conn(&conn_id).await);
180 });
181 None
182 }
183 ActorEvent::RunWake { reply, .. } => {
184 reply.send(Ok(()));
185 None
186 }
187 event => Some(event),
188 }
189 }
190}
191
192pub async fn run_actor<A: Actor>(start: Start<A>) -> Result<()> {
193 let Start {
194 ctx,
195 input,
196 is_new,
197 snapshot,
198 hibernated: _,
199 mut events,
200 startup_ready,
201 } = start;
202
203 let startup = async {
210 let state = match snapshot.decode()? {
211 Some(state) => state,
212 None => A::create_state(&ctx, input.decode_or_default()?).await?,
215 };
216 ctx.set_initial_state(state);
217
218 let actor = Arc::new(A::create(&ctx).await?);
219 if is_new {
220 actor.clone().on_create(ctx.clone()).await?;
221 }
222 actor.clone().on_start(ctx.clone()).await?;
223 Ok::<_, anyhow::Error>(actor)
224 }
225 .await;
226
227 let actor = match startup {
228 Ok(actor) => {
229 if let Some(reply) = startup_ready {
230 let _ = reply.send(Ok(()));
231 }
232 actor
233 }
234 Err(error) => {
235 if let Some(reply) = startup_ready {
236 let _ = reply.send(Err(anyhow::Error::new(RivetError::extract(&error))));
237 }
238 return Err(error);
239 }
240 };
241
242 let run_cancel = CancellationToken::new();
243 let run_task = spawn_run_task(actor.clone(), ctx.clone(), run_cancel.clone());
244
245 while let Some(event) = events.recv_raw().await {
246 let should_stop = handle_actor_event(actor.clone(), ctx.clone(), event).await?;
247 if should_stop {
248 break;
249 }
250 }
251
252 stop_run_task(&ctx, run_cancel, run_task).await
253}
254
255fn spawn_run_task<A: Actor>(
256 actor: Arc<A>,
257 ctx: Ctx<A>,
258 cancel: CancellationToken,
259) -> JoinHandle<Result<()>> {
260 tokio::spawn(async move {
261 let result = tokio::select! {
262 _ = cancel.cancelled() => return Ok(()),
263 result = AssertUnwindSafe(actor.run(ctx.clone())).catch_unwind() => result,
264 };
265 let result = result.unwrap_or_else(|_| Err(anyhow::anyhow!("actor run handler panicked")));
266 if let Err(error) = &result {
267 report_run_error(&ctx, error);
268 }
269 result
270 })
271}
272
273fn report_run_error<A: Actor>(ctx: &Ctx<A>, error: &anyhow::Error) {
279 if ctx.abort_signal().is_cancelled() {
280 tracing::debug!(?error, "actor run task failed during shutdown");
281 return;
282 }
283 if let Err(stop_error) = ctx.stop_with_error(format!("{error:#}")) {
284 if ctx.inner().is_destroy_requested() {
285 tracing::debug!(
286 ?stop_error,
287 run_error = ?error,
288 "actor run failed while a stop was already requested"
289 );
290 } else {
291 tracing::error!(
292 ?stop_error,
293 run_error = ?error,
294 "failed to report actor run error as errored stop"
295 );
296 }
297 }
298}
299
300async fn stop_run_task<A: Actor>(
301 ctx: &Ctx<A>,
302 cancel: CancellationToken,
303 mut task: JoinHandle<Result<()>>,
304) -> Result<()> {
305 ctx.inner().cancel_actor_abort_signal();
306 match tokio::time::timeout(Duration::from_millis(100), &mut task).await {
307 Ok(result) => result.context("join actor run task")?,
308 Err(_) => {
309 cancel.cancel();
310 task.await.context("join actor run task")?
311 }
312 }
313}
314
315async fn handle_actor_event<A: Actor>(
316 actor: Arc<A>,
317 ctx: Ctx<A>,
318 event: ActorEvent,
319) -> Result<bool> {
320 match event {
321 ActorEvent::Action {
322 name,
323 args,
324 conn,
325 reply,
326 ..
327 } => {
328 let handler_ctx = ctx.with_conn(conn.map(ConnCtx::from));
329 match <A::Actions as ActionSet<A>>::dispatch(
330 actor,
331 handler_ctx.clone(),
332 name.as_str(),
333 args.as_slice(),
334 ) {
335 Some(future) => {
336 spawn_action_reply(handler_ctx, reply, future);
337 }
338 None => {
339 reply.send(Err(action_not_found(name)));
340 }
341 }
342 }
343 ActorEvent::HttpRequest { request, reply } => {
344 reply.send(actor.on_fetch(ctx, request).await);
345 }
346 ActorEvent::QueueSend {
347 name,
348 body,
349 conn,
350 reply,
351 ..
352 } => {
353 let handler_ctx = ctx.with_conn(Some(ConnCtx::from(conn)));
354 match <A::Queue as QueueSet<A>>::dispatch(
355 actor,
356 handler_ctx.clone(),
357 name.as_str(),
358 body.as_slice(),
359 ) {
360 Some(future) => {
361 spawn_queue_reply(handler_ctx, reply, future);
362 }
363 None => {
364 reply.send(Err(ActorRuntime::NotFound {
365 resource: "queue handler".to_owned(),
366 id: name,
367 }
368 .build()));
369 }
370 }
371 }
372 ActorEvent::WebSocketOpen {
373 ws, request, reply, ..
374 } => {
375 reply.send(
376 actor
377 .on_websocket(ctx, ws, request.unwrap_or_default())
378 .await,
379 );
380 }
381 ActorEvent::ConnectionPreflight {
382 conn,
383 params,
384 reply,
385 ..
386 } => {
387 let result = async {
388 let params = decode_conn_params::<A>(¶ms)?;
389 actor
390 .clone()
391 .on_before_connect(ctx.clone(), ¶ms)
392 .await?;
393 let conn_state = actor.clone().create_conn_state(ctx.clone(), params).await?;
394 let conn = ConnCtx::from(conn);
395 conn.set_state(&conn_state)?;
396 actor.on_connect(ctx, conn).await
397 }
398 .await;
399 reply.send(result);
400 }
401 ActorEvent::ConnectionOpen { reply, .. } => {
402 reply.send(Ok(()));
403 }
404 ActorEvent::ConnectionClosed { conn } => {
405 actor.on_disconnect(ctx, ConnCtx::from(conn)).await;
406 }
407 ActorEvent::SubscribeRequest {
408 conn,
409 event_name,
410 reply,
411 } => {
412 reply.send(
413 actor
414 .on_subscribe(ctx, ConnCtx::from(conn), event_name)
415 .await,
416 );
417 }
418 ActorEvent::SerializeState { reply, .. } => {
419 let result = async {
420 if ctx.state_dirty() {
421 actor.on_state_change(ctx.clone()).await?;
422 }
423 let delta = ctx.encode_state_delta()?;
424 ctx.clear_state_dirty();
425 Ok(vec![delta])
426 }
427 .await;
428 reply.send(result);
429 }
430 ActorEvent::RunGracefulCleanup { reason, reply } => {
431 let result = match reason {
432 ShutdownKind::Sleep => actor.on_sleep(ctx).await,
433 ShutdownKind::Destroy => actor.on_destroy(ctx).await,
434 };
435 reply.send(result);
436 }
438 ActorEvent::DisconnectConn { conn_id, reply } => {
439 reply.send(ctx.disconnect_conn(&conn_id).await);
440 }
441 ActorEvent::WorkflowHistoryRequested { reply } => {
442 reply.send(Err(not_configured("workflow history")));
443 }
444 ActorEvent::WorkflowReplayRequested { reply, .. } => {
445 reply.send(Err(not_configured("workflow replay")));
446 }
447 ActorEvent::RunWake { reply, .. } => {
448 reply.send(Ok(()));
449 }
450 }
451
452 Ok(false)
453}
454
455fn spawn_action_reply<A: Actor>(
456 ctx: Ctx<A>,
457 reply: Reply<Vec<u8>>,
458 future: crate::action::BoxActionFuture,
459) {
460 tokio::spawn(async move {
461 let abort = ctx.abort_signal();
462 tokio::select! {
463 _ = abort.cancelled() => {
464 reply.send(Err(ActorLifecycle::Stopping.build()));
465 }
466 result = future => {
467 reply.send(result);
468 }
469 }
470 });
471}
472
473fn spawn_queue_reply<A: Actor>(
474 ctx: Ctx<A>,
475 reply: Reply<QueueSendResult>,
476 future: crate::queue::BoxQueueFuture,
477) {
478 tokio::spawn(async move {
479 let abort = ctx.abort_signal();
480 let result = tokio::select! {
481 _ = abort.cancelled() => Err(ActorLifecycle::Stopping.build()),
482 result = future => result.map(|response| QueueSendResult {
483 status: QueueSendStatus::Completed,
484 response,
485 }),
486 };
487 reply.send(result);
488 });
489}
490
491fn not_configured(component: impl Into<String>) -> anyhow::Error {
492 ActorRuntime::NotConfigured {
493 component: component.into(),
494 }
495 .build()
496}
497
498#[doc(hidden)]
499pub fn wrap_start<A: Actor>(core_start: ActorStart) -> Result<Start<A>> {
500 let ActorStart {
501 ctx,
502 input,
503 is_new,
504 snapshot,
505 hibernated,
506 events,
507 startup_ready,
508 } = core_start;
509
510 let hibernated = hibernated
511 .into_iter()
512 .map(|(conn, bytes)| Hibernated {
513 conn: ConnCtx::from({
514 conn.set_state(bytes);
515 conn
516 }),
517 })
518 .collect();
519
520 let ctx = Ctx::new(ctx);
521
522 Ok(Start {
523 ctx: ctx.clone(),
524 input: Input {
525 bytes: input,
526 _p: PhantomData,
527 },
528 is_new,
529 snapshot: Snapshot {
530 is_new,
531 bytes: snapshot,
532 },
533 hibernated,
534 events: Events {
535 ctx,
536 rx: events,
537 _p: PhantomData,
538 },
539 startup_ready,
540 })
541}
542
543fn wrap_event<A: Actor>(event: ActorEvent) -> RuntimeEvent<A> {
544 RuntimeEvent::from_core(event)
545}
546
547fn decode_cbor<T: DeserializeOwned>(bytes: &[u8], label: &str) -> Result<T> {
548 ciborium::from_reader(Cursor::new(bytes)).with_context(|| format!("decode {label} from cbor"))
549}
550
551fn decode_conn_params<A: Actor>(bytes: &[u8]) -> Result<A::ConnParams> {
552 if bytes.is_empty() || bytes == [0xf6] {
553 return Ok(A::ConnParams::default());
554 }
555 decode_cbor(bytes, "connection params")
556}
557
558#[cfg(test)]
559mod tests {
560 use std::future::Future;
561 use std::pin::Pin;
562 use std::sync::OnceLock;
563
564 use async_trait::async_trait;
565 use rivetkit_core::{ConnHandle, QueueNextOpts, StateDelta, WebSocket};
566 use serde::{Deserialize, Serialize};
567 use tokio::sync::mpsc::unbounded_channel;
568 use tokio::sync::{Barrier, oneshot};
569
570 use super::*;
571 use crate::action::{self, Action, Handles, encode_positional};
572 use crate::queue::{HandlesQueue, QueueMessage};
573
574 type BoxTestFuture<T> = Pin<Box<dyn Future<Output = Result<T>> + Send>>;
575 static ACTION_BARRIER: OnceLock<Arc<Barrier>> = OnceLock::new();
576 static QUEUE_PULL_DRAINED: OnceLock<parking_lot::Mutex<Option<oneshot::Sender<Vec<u32>>>>> =
577 OnceLock::new();
578
579 struct EmptyActor;
580
581 impl Actor for EmptyActor {
582 type State = ();
583 type Input = ();
584 type Actions = ();
585 type Events = ();
586 type Queue = ();
587 type ConnParams = ();
588 type ConnState = ();
589 type Action = action::Raw;
590 }
591
592 struct UnitActor;
593
594 impl Actor for UnitActor {
595 type State = ();
596 type Input = UnitInput;
597 type Actions = ();
598 type Events = ();
599 type Queue = ();
600 type ConnParams = ();
601 type ConnState = ();
602 type Action = action::Raw;
603 }
604
605 struct LifecycleActor;
606
607 #[async_trait]
608 impl Actor for LifecycleActor {
609 type State = LifecycleState;
610 type Input = LifecycleInput;
611 type Actions = ();
612 type Events = ();
613 type Queue = ();
614 type ConnParams = ConnParams;
615 type ConnState = ConnState;
616 type Action = action::Raw;
617
618 async fn create_state(_ctx: &Ctx<Self>, input: Self::Input) -> Result<Self::State> {
619 Ok(LifecycleState {
620 count: input.count,
621 log: vec!["create_state".into()],
622 })
623 }
624
625 async fn create(ctx: &Ctx<Self>) -> Result<Self> {
626 ctx.state_mut().log.push("create".into());
627 Ok(Self)
628 }
629
630 async fn run(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
631 ctx.abort_signal().cancelled().await;
632 ctx.state_mut().log.push("run_aborted".into());
633 Ok(())
634 }
635
636 async fn on_create(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
637 ctx.state_mut().log.push("on_create".into());
638 Ok(())
639 }
640
641 async fn on_start(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
642 ctx.state_mut().log.push("on_start".into());
643 Ok(())
644 }
645
646 async fn on_state_change(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
647 ctx.state_mut().log.push("on_state_change".into());
648 Ok(())
649 }
650
651 async fn on_sleep(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
652 ctx.state_mut().log.push("on_sleep".into());
653 Ok(())
654 }
655
656 async fn on_destroy(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
657 ctx.state_mut().log.push("on_destroy".into());
658 Ok(())
659 }
660
661 async fn on_before_connect(
662 self: Arc<Self>,
663 ctx: Ctx<Self>,
664 params: &Self::ConnParams,
665 ) -> Result<()> {
666 if !params.allow {
667 anyhow::bail!("connection rejected");
668 }
669 ctx.state_mut()
670 .log
671 .push(format!("on_before_connect:{}", params.value));
672 Ok(())
673 }
674
675 async fn create_conn_state(
676 self: Arc<Self>,
677 _ctx: Ctx<Self>,
678 params: Self::ConnParams,
679 ) -> Result<Self::ConnState> {
680 Ok(ConnState {
681 value: params.value + 1,
682 })
683 }
684
685 async fn on_connect(self: Arc<Self>, ctx: Ctx<Self>, conn: ConnCtx<Self>) -> Result<()> {
686 let conn_state = conn.state()?;
687 ctx.state_mut()
688 .log
689 .push(format!("on_connect:{}:{}", conn.id(), conn_state.value));
690 Ok(())
691 }
692
693 async fn on_disconnect(self: Arc<Self>, ctx: Ctx<Self>, conn: ConnCtx<Self>) {
694 ctx.state_mut()
695 .log
696 .push(format!("on_disconnect:{}", conn.id()));
697 }
698
699 async fn on_subscribe(
700 self: Arc<Self>,
701 ctx: Ctx<Self>,
702 conn: ConnCtx<Self>,
703 event_name: String,
704 ) -> Result<()> {
705 let conn_state = conn.state()?;
706 ctx.state_mut()
707 .log
708 .push(format!("on_subscribe:{event_name}:{}", conn_state.value));
709 if event_name == "denied" {
710 anyhow::bail!("subscribe denied");
711 }
712 Ok(())
713 }
714 }
715
716 #[derive(Debug, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
717 struct LifecycleInput {
718 count: u32,
719 }
720
721 #[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
722 struct LifecycleState {
723 count: u32,
724 log: Vec<String>,
725 }
726
727 #[derive(Debug, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
728 struct UnitInput;
729
730 #[derive(Debug, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
731 struct ExampleState {
732 count: u32,
733 label: String,
734 }
735
736 #[test]
737 fn input_decode_round_trips_unit() {
738 let bytes = cbor(&());
739 let input = Input::<EmptyActor> {
740 bytes: Some(bytes.clone()),
741 _p: PhantomData,
742 };
743
744 assert!(input.is_present());
745 assert_eq!(input.raw(), Some(bytes.as_slice()));
746 assert_eq!(input.decode().expect("decode unit input"), ());
747 }
748
749 #[test]
750 fn input_decode_round_trips_unit_struct() {
751 let input = Input::<UnitActor> {
752 bytes: Some(cbor(&UnitInput)),
753 _p: PhantomData,
754 };
755
756 assert_eq!(input.decode().expect("decode unit struct input"), UnitInput);
757 }
758
759 #[test]
760 fn input_decode_or_default_uses_default_when_missing() {
761 let input = Input::<DefaultActor> {
762 bytes: None,
763 _p: PhantomData,
764 };
765
766 assert_eq!(
767 input.decode_or_default().expect("default input"),
768 DefaultInput { count: 7 }
769 );
770 }
771
772 #[test]
773 fn input_decode_treats_missing_unit_as_unit() {
774 let input = Input::<EmptyActor> {
775 bytes: None,
776 _p: PhantomData,
777 };
778
779 assert_eq!(input.decode().expect("missing unit input"), ());
780 }
781
782 #[test]
783 fn connection_params_decode_null_as_default() {
784 assert_eq!(
785 decode_conn_params::<LifecycleActor>(&[0xf6]).expect("decode null conn params"),
786 ConnParams::default()
787 );
788 assert_eq!(
789 decode_conn_params::<LifecycleActor>(&[]).expect("decode empty conn params"),
790 ConnParams::default()
791 );
792 }
793
794 #[test]
795 fn snapshot_decode_round_trips_map_struct() {
796 let snapshot = Snapshot {
797 is_new: false,
798 bytes: Some(cbor(&ExampleState {
799 count: 9,
800 label: "hi".into(),
801 })),
802 };
803
804 assert!(!snapshot.is_new());
805 assert_eq!(
806 snapshot.decode::<ExampleState>().expect("decode snapshot"),
807 Some(ExampleState {
808 count: 9,
809 label: "hi".into(),
810 })
811 );
812 }
813
814 #[test]
815 fn snapshot_decode_or_default_uses_default_when_missing() {
816 let snapshot = Snapshot {
817 is_new: true,
818 bytes: None,
819 };
820
821 assert!(snapshot.is_new());
822 assert_eq!(
823 snapshot
824 .decode_or_default::<ExampleState>()
825 .expect("default snapshot"),
826 ExampleState::default()
827 );
828 }
829
830 #[test]
831 fn empty_snapshot_decodes_as_missing_without_changing_newness() {
832 let snapshot = Snapshot {
833 is_new: false,
834 bytes: Some(Vec::new()),
835 };
836
837 assert!(!snapshot.is_new());
838 assert_eq!(
839 snapshot
840 .decode_or_default::<ExampleState>()
841 .expect("default empty snapshot"),
842 ExampleState::default()
843 );
844 }
845
846 #[test]
847 fn wrap_start_rehydrates_hibernated_connection_state() {
848 let (tx, rx) = unbounded_channel();
849 drop(tx);
850 let start = wrap_start::<ConnActor>(ActorStart {
851 ctx: rivetkit_core::testing::actor_context("actor-id", "test", Vec::new(), "local"),
852 input: None,
853 is_new: true,
854 snapshot: None,
855 hibernated: vec![(
856 rivetkit_core::ConnHandle::new(
857 "conn-id",
858 cbor(&()),
859 cbor(&ConnState { value: 1 }),
860 true,
861 ),
862 cbor(&ConnState { value: 5 }),
863 )],
864 events: rx.into(),
865 startup_ready: None,
866 })
867 .expect("wrap start");
868
869 assert_eq!(
870 start.hibernated[0]
871 .conn
872 .state()
873 .expect("decode hibernated conn state"),
874 ConnState { value: 5 }
875 );
876 }
877
878 #[test]
879 fn events_try_recv_wraps_core_events() {
880 let (tx, rx) = unbounded_channel();
881 tx.send(ActorEvent::ConnectionClosed {
882 conn: rivetkit_core::ConnHandle::new("conn-id", cbor(&()), cbor(&()), true),
883 })
884 .expect("queue event");
885
886 let mut events = Events::<EmptyActor> {
887 ctx: Ctx::new(rivetkit_core::testing::actor_context(
888 "actor-id",
889 "test",
890 Vec::new(),
891 "local",
892 )),
893 rx: rx.into(),
894 _p: PhantomData,
895 };
896
897 let Some(RuntimeEvent::ConnClosed(closed)) = events.try_recv() else {
898 panic!("expected typed connection-closed event");
899 };
900
901 assert_eq!(closed.conn.id(), "conn-id");
902 }
903
904 #[test]
905 fn events_try_recv_acknowledges_run_wake_before_next_user_event() {
906 let (tx, rx) = unbounded_channel();
907 let (wake_tx, wake_rx) = oneshot::channel();
908 tx.send(ActorEvent::RunWake {
909 wake_at: 123,
910 wake_revision: 1,
911 reply: wake_tx.into(),
912 })
913 .expect("queue run wake");
914 tx.send(ActorEvent::ConnectionClosed {
915 conn: rivetkit_core::ConnHandle::new("conn-id", cbor(&()), cbor(&()), true),
916 })
917 .expect("queue user event");
918 let mut events = Events::<EmptyActor> {
919 ctx: Ctx::new(rivetkit_core::testing::actor_context(
920 "actor-id",
921 "test",
922 Vec::new(),
923 "local",
924 )),
925 rx: rx.into(),
926 _p: PhantomData,
927 };
928
929 assert!(matches!(
930 events.try_recv(),
931 Some(RuntimeEvent::ConnClosed(_))
932 ));
933 assert!(wake_rx.blocking_recv().expect("run wake reply").is_ok());
934 }
935
936 #[tokio::test]
937 async fn events_recv_acknowledges_run_wake_before_next_user_event() {
938 let (tx, rx) = unbounded_channel();
939 let (wake_tx, wake_rx) = oneshot::channel();
940 tx.send(ActorEvent::RunWake {
941 wake_at: 123,
942 wake_revision: 1,
943 reply: wake_tx.into(),
944 })
945 .expect("queue run wake");
946 tx.send(ActorEvent::ConnectionClosed {
947 conn: rivetkit_core::ConnHandle::new("conn-id", cbor(&()), cbor(&()), true),
948 })
949 .expect("queue user event");
950 let mut events = Events::<EmptyActor> {
951 ctx: Ctx::new(rivetkit_core::testing::actor_context(
952 "actor-id",
953 "test",
954 Vec::new(),
955 "local",
956 )),
957 rx: rx.into(),
958 _p: PhantomData,
959 };
960
961 assert!(matches!(
962 events.recv().await,
963 Some(RuntimeEvent::ConnClosed(_))
964 ));
965 assert!(wake_rx.await.expect("run wake reply").is_ok());
966 }
967
968 #[tokio::test]
969 async fn run_actor_creates_state_and_replies_with_snapshot() {
970 let (tx, rx) = unbounded_channel();
971 let start = lifecycle_start(Some(cbor(&LifecycleInput { count: 3 })), None, rx.into());
972 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
973
974 let deltas = request_serialize(&tx).await;
975 let state = decode_actor_state(deltas);
976 assert_eq!(state.count, 3);
977 assert_eq!(
978 state.log,
979 [
980 "create_state",
981 "create",
982 "on_create",
983 "on_start",
984 "on_state_change",
985 ]
986 );
987
988 request_sleep(&tx).await;
989 drop(tx);
990 actor.await.expect("join run_actor").expect("run actor");
991 }
992
993 #[tokio::test]
994 async fn run_actor_acknowledges_run_wake_without_a_handler() {
995 let (tx, rx) = unbounded_channel();
996 let start = lifecycle_start(Some(cbor(&LifecycleInput { count: 0 })), None, rx.into());
997 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
998
999 request_run_wake(&tx).await;
1000 drop(tx);
1001 actor.await.expect("join run_actor").expect("run actor");
1002 }
1003
1004 #[tokio::test]
1005 async fn run_actor_rehydrates_snapshot_without_on_create() {
1006 let snapshot = LifecycleState {
1007 count: 8,
1008 log: vec!["snapshot".into()],
1009 };
1010 let (tx, rx) = unbounded_channel();
1011 let start = lifecycle_start(None, Some(cbor(&snapshot)), rx.into());
1012 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1013
1014 let state = decode_actor_state(request_serialize(&tx).await);
1015 assert_eq!(state.count, 8);
1016 assert_eq!(
1017 state.log,
1018 ["snapshot", "create", "on_start", "on_state_change"]
1019 );
1020
1021 request_sleep(&tx).await;
1022 drop(tx);
1023 actor.await.expect("join run_actor").expect("run actor");
1024 }
1025
1026 #[tokio::test]
1027 async fn run_actor_default_fetch_replies_404() {
1028 let (tx, rx) = unbounded_channel();
1029 let start = lifecycle_start(Some(cbor(&LifecycleInput { count: 1 })), None, rx.into());
1030 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1031
1032 let (reply_tx, reply_rx) = oneshot::channel();
1033 tx.send(ActorEvent::HttpRequest {
1034 request: rivetkit_core::Request::default(),
1035 reply: reply_tx.into(),
1036 })
1037 .expect("send http event");
1038
1039 let response = reply_rx.await.expect("http reply").expect("http response");
1040 assert_eq!(response.status().as_u16(), 404);
1041
1042 request_sleep(&tx).await;
1043 drop(tx);
1044 actor.await.expect("join run_actor").expect("run actor");
1045 }
1046
1047 #[tokio::test]
1048 async fn run_actor_missing_unit_input_creates_state() {
1049 let (tx, rx) = unbounded_channel();
1050 let start = unit_creation_start(None, None, rx.into());
1051 let actor = tokio::spawn(run_actor::<UnitCreationActor>(start));
1052
1053 let state = decode_unit_creation_state(request_serialize(&tx).await);
1054 assert_eq!(state.created, 1);
1055
1056 request_sleep(&tx).await;
1057 drop(tx);
1058 actor.await.expect("join run_actor").expect("run actor");
1059 }
1060
1061 #[tokio::test]
1062 async fn run_actor_invalid_input_fails_to_start() {
1063 let (_tx, rx) = unbounded_channel();
1067 let (mut start, _ctx) =
1068 lifecycle_start_with_ctx(Some(vec![0xff, 0xff, 0xff]), None, rx.into());
1069 let (ready_tx, ready_rx) = oneshot::channel();
1070 start.startup_ready = Some(ready_tx);
1071
1072 let error = run_actor::<LifecycleActor>(start)
1073 .await
1074 .expect_err("invalid input should fail actor start");
1075
1076 assert!(
1077 format!("{error:#}").contains("decode actor input from cbor"),
1078 "unexpected error: {error:#}"
1079 );
1080
1081 ready_rx
1085 .await
1086 .expect("startup_ready should be signaled")
1087 .expect_err("startup should report failure");
1088 }
1089
1090 #[tokio::test]
1091 async fn run_actor_default_websocket_rejects() {
1092 let (tx, rx) = unbounded_channel();
1093 let start = lifecycle_start(Some(cbor(&LifecycleInput { count: 1 })), None, rx.into());
1094 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1095
1096 let (reply_tx, reply_rx) = oneshot::channel();
1097 tx.send(ActorEvent::WebSocketOpen {
1098 conn: conn("ws-conn", (), ConnState::default()),
1099 ws: WebSocket::new(),
1100 request: Some(rivetkit_core::Request::default()),
1101 reply: reply_tx.into(),
1102 })
1103 .expect("send websocket event");
1104
1105 let error = reply_rx
1106 .await
1107 .expect("websocket reply")
1108 .expect_err("default websocket should reject");
1109 assert!(error.to_string().contains("websockets not supported"));
1110
1111 request_sleep(&tx).await;
1112 drop(tx);
1113 actor.await.expect("join run_actor").expect("run actor");
1114 }
1115
1116 #[tokio::test]
1117 async fn run_actor_connection_hooks_store_state_and_disconnect() {
1118 let (tx, rx) = unbounded_channel();
1119 let (start, ctx) =
1120 lifecycle_start_with_ctx(Some(cbor(&LifecycleInput { count: 1 })), None, rx.into());
1121 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1122 let params = ConnParams {
1123 allow: true,
1124 value: 41,
1125 };
1126 let conn = conn("conn-hooks", params.clone(), ConnState::default());
1127
1128 let (reply_tx, reply_rx) = oneshot::channel();
1129 tx.send(ActorEvent::ConnectionPreflight {
1130 conn: conn.clone(),
1131 params: cbor(¶ms),
1132 request: None,
1133 reply: reply_tx.into(),
1134 })
1135 .expect("send connection preflight");
1136 reply_rx
1137 .await
1138 .expect("connection preflight reply")
1139 .expect("connection preflight result");
1140 assert_eq!(
1141 decode_cbor::<ConnState>(&conn.state(), "connection state").expect("conn state"),
1142 ConnState { value: 42 }
1143 );
1144
1145 tx.send(ActorEvent::ConnectionClosed { conn })
1146 .expect("send connection closed");
1147 request_sleep(&tx).await;
1148 drop(tx);
1149 actor.await.expect("join run_actor").expect("run actor");
1150
1151 let log = &ctx.state().log;
1152 assert!(log.contains(&"on_before_connect:41".to_owned()));
1153 assert!(log.contains(&"on_connect:conn-hooks:42".to_owned()));
1154 assert!(log.contains(&"on_disconnect:conn-hooks".to_owned()));
1155 }
1156
1157 #[tokio::test]
1158 async fn run_actor_subscribe_hook_allows_and_denies() {
1159 let (tx, rx) = unbounded_channel();
1160 let (start, ctx) =
1161 lifecycle_start_with_ctx(Some(cbor(&LifecycleInput { count: 1 })), None, rx.into());
1162 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1163 let conn = conn(
1164 "subscribe-conn",
1165 ConnParams::default(),
1166 ConnState { value: 91 },
1167 );
1168
1169 request_subscribe(&tx, conn.clone(), "chat.message")
1170 .await
1171 .expect("subscribe should be allowed");
1172 let error = request_subscribe(&tx, conn, "denied")
1173 .await
1174 .expect_err("subscribe should be denied");
1175 assert!(error.to_string().contains("subscribe denied"));
1176
1177 request_sleep(&tx).await;
1178 drop(tx);
1179 actor.await.expect("join run_actor").expect("run actor");
1180
1181 let log = &ctx.state().log;
1182 assert!(log.contains(&"on_subscribe:chat.message:91".to_owned()));
1183 assert!(log.contains(&"on_subscribe:denied:91".to_owned()));
1184 }
1185
1186 #[tokio::test]
1187 async fn run_actor_connection_preflight_rejects_before_connect() {
1188 let (tx, rx) = unbounded_channel();
1189 let (start, ctx) =
1190 lifecycle_start_with_ctx(Some(cbor(&LifecycleInput { count: 1 })), None, rx.into());
1191 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1192 let params = ConnParams {
1193 allow: false,
1194 value: 5,
1195 };
1196
1197 let (reply_tx, reply_rx) = oneshot::channel();
1198 tx.send(ActorEvent::ConnectionPreflight {
1199 conn: conn("conn-reject", params.clone(), ConnState::default()),
1200 params: cbor(¶ms),
1201 request: None,
1202 reply: reply_tx.into(),
1203 })
1204 .expect("send rejected connection preflight");
1205
1206 let error = reply_rx
1207 .await
1208 .expect("connection preflight reply")
1209 .expect_err("connection preflight should reject");
1210 assert!(error.to_string().contains("connection rejected"));
1211
1212 request_sleep(&tx).await;
1213 drop(tx);
1214 actor.await.expect("join run_actor").expect("run actor");
1215 assert!(
1216 !ctx.state()
1217 .log
1218 .iter()
1219 .any(|entry| entry.starts_with("on_connect:conn-reject"))
1220 );
1221 }
1222
1223 #[tokio::test]
1224 async fn run_actor_cancels_run_with_abort_signal() {
1225 let (tx, rx) = unbounded_channel();
1226 let (start, ctx) =
1227 lifecycle_start_with_ctx(Some(cbor(&LifecycleInput { count: 2 })), None, rx.into());
1228 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1229
1230 request_sleep(&tx).await;
1231 drop(tx);
1232 actor.await.expect("join run_actor").expect("run actor");
1233
1234 assert!(ctx.state().log.iter().any(|entry| entry == "run_aborted"));
1235 }
1236
1237 #[tokio::test]
1238 async fn run_actor_destroy_cleanup_fires() {
1239 let (tx, rx) = unbounded_channel();
1240 let (start, ctx) =
1241 lifecycle_start_with_ctx(Some(cbor(&LifecycleInput { count: 2 })), None, rx.into());
1242 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1243
1244 request_destroy(&tx).await;
1245 drop(tx);
1246 actor.await.expect("join run_actor").expect("run actor");
1247
1248 assert!(ctx.state().log.iter().any(|entry| entry == "on_destroy"));
1249 }
1250
1251 #[tokio::test]
1252 async fn run_actor_serializes_state_after_cleanup() {
1253 let (tx, rx) = unbounded_channel();
1257 let start = lifecycle_start(Some(cbor(&LifecycleInput { count: 5 })), None, rx.into());
1258 let actor = tokio::spawn(run_actor::<LifecycleActor>(start));
1259
1260 request_sleep(&tx).await;
1261 let state = decode_actor_state(request_serialize(&tx).await);
1262 assert_eq!(state.count, 5);
1263 assert!(
1264 state.log.iter().any(|entry| entry == "on_sleep"),
1265 "serialize after cleanup lost on_sleep state, got: {:?}",
1266 state.log
1267 );
1268
1269 drop(tx);
1270 actor.await.expect("join run_actor").expect("run actor");
1271 }
1272
1273 #[tokio::test]
1274 async fn run_actor_error_requests_errored_stop() {
1275 let (tx, rx) = unbounded_channel();
1276 let (start, ctx) = unit_start_with_ctx::<FailingRunActor>(rx.into());
1277 ctx.inner().set_started(true);
1280 let actor = tokio::spawn(run_actor::<FailingRunActor>(start));
1281
1282 for _ in 0..1000 {
1286 if ctx.inner().is_destroy_requested() {
1287 break;
1288 }
1289 tokio::task::yield_now().await;
1290 }
1291 assert!(
1292 ctx.inner().is_destroy_requested(),
1293 "run error should request an errored stop"
1294 );
1295
1296 drop(tx);
1297 let error = actor
1298 .await
1299 .expect("join run_actor")
1300 .expect_err("run_actor should propagate the run error");
1301 assert!(format!("{error:#}").contains("boom in run"));
1302 }
1303
1304 #[tokio::test]
1305 async fn run_actor_panic_requests_errored_stop() {
1306 let (tx, rx) = unbounded_channel();
1307 let (start, ctx) = unit_start_with_ctx::<PanickingRunActor>(rx.into());
1308 ctx.inner().set_started(true);
1309 let actor = tokio::spawn(run_actor::<PanickingRunActor>(start));
1310
1311 for _ in 0..1000 {
1314 if ctx.inner().is_destroy_requested() {
1315 break;
1316 }
1317 tokio::task::yield_now().await;
1318 }
1319 assert!(
1320 ctx.inner().is_destroy_requested(),
1321 "run panic should request an errored stop"
1322 );
1323
1324 drop(tx);
1325 let error = actor
1326 .await
1327 .expect("join run_actor")
1328 .expect_err("run_actor should propagate the panic as an error");
1329 assert!(format!("{error:#}").contains("panicked"));
1330 }
1331
1332 #[tokio::test]
1333 async fn run_actor_shutdown_error_is_not_reported_as_crash() {
1334 let (tx, rx) = unbounded_channel();
1335 let (start, ctx) = unit_start_with_ctx::<ShutdownErrRunActor>(rx.into());
1336 ctx.inner().set_started(true);
1337 let actor = tokio::spawn(run_actor::<ShutdownErrRunActor>(start));
1338
1339 drop(tx);
1342 actor
1343 .await
1344 .expect("join run_actor")
1345 .expect_err("run error should still propagate during shutdown");
1346 assert!(
1347 !ctx.inner().is_destroy_requested(),
1348 "shutdown-induced run errors must not be reported as crashes"
1349 );
1350 }
1351
1352 #[tokio::test]
1353 async fn run_actor_dispatches_typed_actions_by_arg_shape() {
1354 let (tx, rx) = unbounded_channel();
1355 let start = action_start(rx.into());
1356 let actor = tokio::spawn(run_actor::<ActionActor>(start));
1357
1358 assert_eq!(
1359 decode_cbor::<u32>(
1360 &request_action(
1361 &tx,
1362 "add",
1363 &encode_positional(&Add { left: 2, right: 3 }).expect("encode add"),
1364 None,
1365 )
1366 .await
1367 .expect("add result"),
1368 "add output",
1369 )
1370 .expect("decode add output"),
1371 5
1372 );
1373 assert_eq!(
1374 decode_cbor::<u32>(
1375 &request_action(
1376 &tx,
1377 "scale",
1378 &encode_positional(&Scale(4, 5)).expect("encode scale"),
1379 None,
1380 )
1381 .await
1382 .expect("scale result"),
1383 "scale output",
1384 )
1385 .expect("decode scale output"),
1386 20
1387 );
1388 assert_eq!(
1389 decode_cbor::<String>(
1390 &request_action(
1391 &tx,
1392 "echo",
1393 &encode_positional(&Echo("hi".to_owned())).expect("encode echo"),
1394 None,
1395 )
1396 .await
1397 .expect("echo result"),
1398 "echo output",
1399 )
1400 .expect("decode echo output"),
1401 "hi"
1402 );
1403 assert_eq!(
1404 decode_cbor::<String>(
1405 &request_action(
1406 &tx,
1407 "ping",
1408 &encode_positional(&Ping).expect("encode ping"),
1409 None,
1410 )
1411 .await
1412 .expect("ping result"),
1413 "ping output",
1414 )
1415 .expect("decode ping output"),
1416 "pong"
1417 );
1418
1419 request_sleep(&tx).await;
1420 drop(tx);
1421 actor.await.expect("join run_actor").expect("run actor");
1422 }
1423
1424 #[tokio::test]
1425 async fn run_actor_dispatches_actions_concurrently() {
1426 let (tx, rx) = unbounded_channel();
1427 let _ = ACTION_BARRIER.set(Arc::new(Barrier::new(2)));
1428 let start = action_start(rx.into());
1429 let actor = tokio::spawn(run_actor::<ActionActor>(start));
1430 let first_args = encode_positional(&WaitForPeer {
1431 label: "first".to_owned(),
1432 })
1433 .expect("encode first wait");
1434 let second_args = encode_positional(&WaitForPeer {
1435 label: "second".to_owned(),
1436 })
1437 .expect("encode second wait");
1438
1439 let first = request_action_rx(&tx, "wait", &first_args, None);
1440 let second = request_action_rx(&tx, "wait", &second_args, None);
1441
1442 let (first, second) = tokio::time::timeout(Duration::from_secs(1), async move {
1443 tokio::join!(first, second)
1444 })
1445 .await
1446 .expect("concurrent handlers should rendezvous");
1447 assert_eq!(
1448 decode_cbor::<String>(&first.expect("first result"), "first wait output")
1449 .expect("decode first wait"),
1450 "first"
1451 );
1452 assert_eq!(
1453 decode_cbor::<String>(&second.expect("second result"), "second wait output")
1454 .expect("decode second wait"),
1455 "second"
1456 );
1457
1458 request_sleep(&tx).await;
1459 drop(tx);
1460 actor.await.expect("join run_actor").expect("run actor");
1461 }
1462
1463 #[tokio::test]
1464 async fn run_actor_action_errors_and_unknown_action_are_structured() {
1465 let (tx, rx) = unbounded_channel();
1466 let start = action_start(rx.into());
1467 let actor = tokio::spawn(run_actor::<ActionActor>(start));
1468
1469 let error = request_action(
1470 &tx,
1471 "fail",
1472 &encode_positional(&Fail).expect("encode fail"),
1473 None,
1474 )
1475 .await
1476 .expect_err("fail action should error");
1477 assert!(error.to_string().contains("intentional action failure"));
1478
1479 let error = request_action(&tx, "missing", &[], None)
1480 .await
1481 .expect_err("missing action should error");
1482 let error = rivet_error::RivetError::extract(&error);
1483 assert_eq!(error.group(), "actor");
1484 assert_eq!(error.code(), "action_not_found");
1485
1486 request_sleep(&tx).await;
1487 drop(tx);
1488 actor.await.expect("join run_actor").expect("run actor");
1489 }
1490
1491 #[tokio::test]
1492 async fn run_actor_action_receives_per_call_connection_state() {
1493 let (tx, rx) = unbounded_channel();
1494 let start = action_start(rx.into());
1495 let actor = tokio::spawn(run_actor::<ActionActor>(start));
1496 let conn = conn(
1497 "action-conn",
1498 ConnParams::default(),
1499 ConnState { value: 77 },
1500 );
1501
1502 let output = request_action(
1503 &tx,
1504 "connValue",
1505 &encode_positional(&ConnValue).expect("encode conn value"),
1506 Some(conn),
1507 )
1508 .await
1509 .expect("conn value result");
1510
1511 assert_eq!(
1512 decode_cbor::<u32>(&output, "conn value output").expect("decode conn value"),
1513 77
1514 );
1515
1516 request_sleep(&tx).await;
1517 drop(tx);
1518 actor.await.expect("join run_actor").expect("run actor");
1519 }
1520
1521 #[tokio::test]
1522 async fn run_actor_dispatches_typed_queue_send() {
1523 let (tx, rx) = unbounded_channel();
1524 let start = action_start(rx.into());
1525 let actor = tokio::spawn(run_actor::<ActionActor>(start));
1526 let conn = conn("queue-conn", ConnParams::default(), ConnState::default());
1527
1528 let result = request_queue_send(
1529 &tx,
1530 "double",
1531 &cbor(&QueueDouble { value: 21 }),
1532 conn.clone(),
1533 )
1534 .await
1535 .expect("queue result");
1536 assert_eq!(result.status, QueueSendStatus::Completed);
1537 assert_eq!(
1538 decode_cbor::<u32>(
1539 result.response.as_deref().expect("queue response"),
1540 "queue response",
1541 )
1542 .expect("decode queue response"),
1543 42
1544 );
1545
1546 let error = request_queue_send(&tx, "missing", &[], conn)
1547 .await
1548 .expect_err("missing queue should error");
1549 let error = rivet_error::RivetError::extract(&error);
1550 assert_eq!(error.group(), "actor");
1551 assert_eq!(error.code(), "not_found");
1552
1553 request_sleep(&tx).await;
1554 drop(tx);
1555 actor.await.expect("join run_actor").expect("run actor");
1556 }
1557
1558 #[tokio::test]
1559 async fn run_actor_can_pull_typed_queue_backlog_until_abort() {
1560 let (done_tx, done_rx) = oneshot::channel();
1561 let drained = QUEUE_PULL_DRAINED.get_or_init(|| parking_lot::Mutex::new(None));
1562 assert!(
1563 drained.lock().replace(done_tx).is_none(),
1564 "queue pull notifier already installed"
1565 );
1566
1567 let (tx, rx) = unbounded_channel();
1568 let start = queue_pull_start(rx.into());
1569 start
1570 .ctx
1571 .queue()
1572 .send("double", &QueueDouble { value: 3 })
1573 .await
1574 .expect("send first queue message");
1575 start
1576 .ctx
1577 .queue()
1578 .send("double", &QueueDouble { value: 4 })
1579 .await
1580 .expect("send second queue message");
1581
1582 let actor = tokio::spawn(run_actor::<QueuePullActor>(start));
1583 let values = done_rx.await.expect("queue drain notification");
1584 assert_eq!(values, vec![3, 4]);
1585
1586 let state = request_serialize(&tx).await;
1587 let [StateDelta::ActorState(bytes)] = state.as_slice() else {
1588 panic!("expected actor state delta");
1589 };
1590 assert_eq!(
1591 decode_cbor::<Vec<u32>>(bytes, "queue pull state").expect("decode state"),
1592 vec![3, 4]
1593 );
1594
1595 request_sleep(&tx).await;
1596 drop(tx);
1597 actor.await.expect("join run_actor").expect("run actor");
1598 }
1599
1600 struct DefaultActor;
1601
1602 impl Actor for DefaultActor {
1603 type State = ();
1604 type Input = DefaultInput;
1605 type Actions = ();
1606 type Events = ();
1607 type Queue = ();
1608 type ConnParams = ();
1609 type ConnState = ();
1610 type Action = action::Raw;
1611 }
1612
1613 #[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
1614 struct DefaultInput {
1615 count: u32,
1616 }
1617
1618 impl Default for DefaultInput {
1619 fn default() -> Self {
1620 Self { count: 7 }
1621 }
1622 }
1623
1624 struct ConnActor;
1625
1626 impl Actor for ConnActor {
1627 type State = ();
1628 type Input = ();
1629 type Actions = ();
1630 type Events = ();
1631 type Queue = ();
1632 type ConnParams = ();
1633 type ConnState = ConnState;
1634 type Action = action::Raw;
1635 }
1636
1637 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
1638 struct ConnParams {
1639 allow: bool,
1640 value: u32,
1641 }
1642
1643 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
1644 struct ConnState {
1645 value: u32,
1646 }
1647
1648 struct UnitCreationActor;
1649
1650 #[async_trait]
1651 impl Actor for UnitCreationActor {
1652 type State = UnitCreationState;
1653 type Input = ();
1654 type Actions = ();
1655 type Events = ();
1656 type Queue = ();
1657 type ConnParams = ();
1658 type ConnState = ();
1659 type Action = action::Raw;
1660
1661 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1662 Ok(UnitCreationState { created: 1 })
1663 }
1664
1665 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1666 Ok(Self)
1667 }
1668 }
1669
1670 #[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
1671 struct UnitCreationState {
1672 created: u32,
1673 }
1674
1675 struct ActionActor;
1676
1677 #[async_trait]
1678 impl Actor for ActionActor {
1679 type State = ();
1680 type Input = ();
1681 type Actions = (Add, Scale, Echo, Ping, Fail, WaitForPeer, ConnValue);
1682 type Events = ();
1683 type Queue = (QueueDouble,);
1684 type ConnParams = ConnParams;
1685 type ConnState = ConnState;
1686 type Action = action::Raw;
1687
1688 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1689 Ok(())
1690 }
1691
1692 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1693 Ok(Self)
1694 }
1695 }
1696
1697 #[derive(Debug, Clone, Serialize, Deserialize)]
1698 struct Add {
1699 left: u32,
1700 right: u32,
1701 }
1702
1703 impl Action for Add {
1704 type Output = u32;
1705
1706 const NAME: &'static str = "add";
1707 }
1708
1709 impl Handles<Add> for ActionActor {
1710 type Future = BoxTestFuture<u32>;
1711
1712 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, action: Add) -> Self::Future {
1713 Box::pin(async move { Ok(action.left + action.right) })
1714 }
1715 }
1716
1717 #[derive(Debug, Clone, Serialize, Deserialize)]
1718 struct Scale(u32, u32);
1719
1720 impl Action for Scale {
1721 type Output = u32;
1722
1723 const NAME: &'static str = "scale";
1724 }
1725
1726 impl Handles<Scale> for ActionActor {
1727 type Future = BoxTestFuture<u32>;
1728
1729 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, action: Scale) -> Self::Future {
1730 Box::pin(async move { Ok(action.0 * action.1) })
1731 }
1732 }
1733
1734 #[derive(Debug, Clone, Serialize, Deserialize)]
1735 struct Echo(String);
1736
1737 impl Action for Echo {
1738 type Output = String;
1739
1740 const NAME: &'static str = "echo";
1741 }
1742
1743 impl Handles<Echo> for ActionActor {
1744 type Future = BoxTestFuture<String>;
1745
1746 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, action: Echo) -> Self::Future {
1747 Box::pin(async move { Ok(action.0) })
1748 }
1749 }
1750
1751 #[derive(Debug, Clone, Serialize, Deserialize)]
1752 struct Ping;
1753
1754 impl Action for Ping {
1755 type Output = String;
1756
1757 const NAME: &'static str = "ping";
1758 }
1759
1760 impl Handles<Ping> for ActionActor {
1761 type Future = BoxTestFuture<String>;
1762
1763 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, _action: Ping) -> Self::Future {
1764 Box::pin(async move { Ok("pong".to_owned()) })
1765 }
1766 }
1767
1768 #[derive(Debug, Clone, Serialize, Deserialize)]
1769 struct Fail;
1770
1771 impl Action for Fail {
1772 type Output = ();
1773
1774 const NAME: &'static str = "fail";
1775 }
1776
1777 impl Handles<Fail> for ActionActor {
1778 type Future = BoxTestFuture<()>;
1779
1780 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, _action: Fail) -> Self::Future {
1781 Box::pin(async move { anyhow::bail!("intentional action failure") })
1782 }
1783 }
1784
1785 #[derive(Debug, Clone, Serialize, Deserialize)]
1786 struct WaitForPeer {
1787 label: String,
1788 }
1789
1790 impl Action for WaitForPeer {
1791 type Output = String;
1792
1793 const NAME: &'static str = "wait";
1794 }
1795
1796 impl Handles<WaitForPeer> for ActionActor {
1797 type Future = BoxTestFuture<String>;
1798
1799 fn handle(self: Arc<Self>, _ctx: Ctx<Self>, action: WaitForPeer) -> Self::Future {
1800 Box::pin(async move {
1801 ACTION_BARRIER
1802 .get()
1803 .expect("action barrier should be installed")
1804 .wait()
1805 .await;
1806 Ok(action.label)
1807 })
1808 }
1809 }
1810
1811 #[derive(Debug, Clone, Serialize, Deserialize)]
1812 struct ConnValue;
1813
1814 impl Action for ConnValue {
1815 type Output = u32;
1816
1817 const NAME: &'static str = "connValue";
1818 }
1819
1820 impl Handles<ConnValue> for ActionActor {
1821 type Future = BoxTestFuture<u32>;
1822
1823 fn handle(self: Arc<Self>, ctx: Ctx<Self>, _action: ConnValue) -> Self::Future {
1824 Box::pin(async move {
1825 let conn = ctx.conn().context("missing action connection")?;
1826 Ok(conn.state()?.value)
1827 })
1828 }
1829 }
1830
1831 #[derive(Debug, Clone, Serialize, Deserialize)]
1832 struct QueueDouble {
1833 value: u32,
1834 }
1835
1836 impl QueueMessage for QueueDouble {
1837 type Reply = u32;
1838
1839 const NAME: &'static str = "double";
1840 }
1841
1842 impl HandlesQueue<QueueDouble> for ActionActor {
1843 type Future = BoxTestFuture<u32>;
1844
1845 fn handle_queue(self: Arc<Self>, _ctx: Ctx<Self>, message: QueueDouble) -> Self::Future {
1846 Box::pin(async move { Ok(message.value * 2) })
1847 }
1848 }
1849
1850 struct QueuePullActor;
1851
1852 #[async_trait]
1853 impl Actor for QueuePullActor {
1854 type State = Vec<u32>;
1855 type Input = ();
1856 type Actions = ();
1857 type Events = ();
1858 type Queue = ();
1859 type ConnParams = ();
1860 type ConnState = ();
1861 type Action = action::Raw;
1862
1863 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1864 Ok(Vec::new())
1865 }
1866
1867 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1868 Ok(Self)
1869 }
1870
1871 async fn run(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
1872 let mut values = Vec::new();
1873
1874 for _ in 0..2 {
1875 let message = ctx
1876 .queue()
1877 .next_typed::<QueueDouble>(QueueNextOpts {
1878 completable: true,
1879 ..Default::default()
1880 })
1881 .await?
1882 .context("expected queued message")?;
1883 let value = message.body().value;
1884 message.complete(value * 2).await?;
1885 values.push(value);
1886 }
1887
1888 ctx.state_mut().extend(values.iter().copied());
1889 if let Some(done) = QUEUE_PULL_DRAINED
1890 .get_or_init(|| parking_lot::Mutex::new(None))
1891 .lock()
1892 .take()
1893 {
1894 let _ = done.send(values);
1895 }
1896
1897 ctx.abort_signal().cancelled().await;
1898 Ok(())
1899 }
1900 }
1901
1902 struct FailingRunActor;
1903
1904 #[async_trait]
1905 impl Actor for FailingRunActor {
1906 type State = ();
1907 type Input = ();
1908 type Actions = ();
1909 type Events = ();
1910 type Queue = ();
1911 type ConnParams = ();
1912 type ConnState = ();
1913 type Action = action::Raw;
1914
1915 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1916 Ok(())
1917 }
1918
1919 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1920 Ok(Self)
1921 }
1922
1923 async fn run(self: Arc<Self>, _ctx: Ctx<Self>) -> Result<()> {
1924 anyhow::bail!("boom in run")
1925 }
1926 }
1927
1928 struct PanickingRunActor;
1929
1930 #[async_trait]
1931 impl Actor for PanickingRunActor {
1932 type State = ();
1933 type Input = ();
1934 type Actions = ();
1935 type Events = ();
1936 type Queue = ();
1937 type ConnParams = ();
1938 type ConnState = ();
1939 type Action = action::Raw;
1940
1941 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1942 Ok(())
1943 }
1944
1945 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1946 Ok(Self)
1947 }
1948
1949 async fn run(self: Arc<Self>, _ctx: Ctx<Self>) -> Result<()> {
1950 panic!("run task panic under test");
1951 }
1952 }
1953
1954 struct ShutdownErrRunActor;
1955
1956 #[async_trait]
1957 impl Actor for ShutdownErrRunActor {
1958 type State = ();
1959 type Input = ();
1960 type Actions = ();
1961 type Events = ();
1962 type Queue = ();
1963 type ConnParams = ();
1964 type ConnState = ();
1965 type Action = action::Raw;
1966
1967 async fn create_state(_ctx: &Ctx<Self>, (): Self::Input) -> Result<Self::State> {
1968 Ok(())
1969 }
1970
1971 async fn create(_ctx: &Ctx<Self>) -> Result<Self> {
1972 Ok(Self)
1973 }
1974
1975 async fn run(self: Arc<Self>, ctx: Ctx<Self>) -> Result<()> {
1976 ctx.abort_signal().cancelled().await;
1977 anyhow::bail!("wait cancelled by shutdown")
1978 }
1979 }
1980
1981 fn unit_start_with_ctx<A: Actor>(rx: ActorEvents) -> (Start<A>, Ctx<A>) {
1982 let ctx = Ctx::new(rivetkit_core::testing::actor_context(
1983 "actor-id",
1984 "test",
1985 Vec::new(),
1986 "local",
1987 ));
1988
1989 let start = Start {
1990 ctx: ctx.clone(),
1991 input: Input {
1992 bytes: None,
1993 _p: PhantomData,
1994 },
1995 is_new: true,
1996 snapshot: Snapshot {
1997 is_new: true,
1998 bytes: None,
1999 },
2000 hibernated: Vec::new(),
2001 events: Events {
2002 ctx: ctx.clone(),
2003 rx,
2004 _p: PhantomData,
2005 },
2006 startup_ready: None,
2007 };
2008
2009 (start, ctx)
2010 }
2011
2012 fn cbor<T: Serialize>(value: &T) -> Vec<u8> {
2013 let mut encoded = Vec::new();
2014 ciborium::into_writer(value, &mut encoded).expect("encode test value as cbor");
2015 encoded
2016 }
2017
2018 fn lifecycle_start(
2019 input: Option<Vec<u8>>,
2020 snapshot: Option<Vec<u8>>,
2021 rx: ActorEvents,
2022 ) -> Start<LifecycleActor> {
2023 lifecycle_start_with_ctx(input, snapshot, rx).0
2024 }
2025
2026 fn lifecycle_start_with_ctx(
2027 input: Option<Vec<u8>>,
2028 snapshot: Option<Vec<u8>>,
2029 rx: ActorEvents,
2030 ) -> (Start<LifecycleActor>, Ctx<LifecycleActor>) {
2031 let ctx = Ctx::new(rivetkit_core::testing::actor_context(
2032 "actor-id",
2033 "test",
2034 Vec::new(),
2035 "local",
2036 ));
2037
2038 let is_new = snapshot.is_none();
2039 let start = Start {
2040 ctx: ctx.clone(),
2041 input: Input {
2042 bytes: input,
2043 _p: PhantomData,
2044 },
2045 is_new,
2046 snapshot: Snapshot {
2047 is_new,
2048 bytes: snapshot,
2049 },
2050 hibernated: Vec::new(),
2051 events: Events {
2052 ctx: ctx.clone(),
2053 rx,
2054 _p: PhantomData,
2055 },
2056 startup_ready: None,
2057 };
2058
2059 (start, ctx)
2060 }
2061
2062 fn unit_creation_start(
2063 input: Option<Vec<u8>>,
2064 snapshot: Option<Vec<u8>>,
2065 rx: ActorEvents,
2066 ) -> Start<UnitCreationActor> {
2067 let ctx = Ctx::new(rivetkit_core::testing::actor_context(
2068 "actor-id",
2069 "unit-creation",
2070 Vec::new(),
2071 "local",
2072 ));
2073
2074 let is_new = snapshot.is_none();
2075 Start {
2076 ctx: ctx.clone(),
2077 input: Input {
2078 bytes: input,
2079 _p: PhantomData,
2080 },
2081 is_new,
2082 snapshot: Snapshot {
2083 is_new,
2084 bytes: snapshot,
2085 },
2086 hibernated: Vec::new(),
2087 events: Events {
2088 ctx,
2089 rx,
2090 _p: PhantomData,
2091 },
2092 startup_ready: None,
2093 }
2094 }
2095
2096 fn action_start(rx: ActorEvents) -> Start<ActionActor> {
2097 let ctx = Ctx::new(rivetkit_core::testing::actor_context(
2098 "actor-id",
2099 "action-test",
2100 Vec::new(),
2101 "local",
2102 ));
2103
2104 Start {
2105 ctx: ctx.clone(),
2106 input: Input {
2107 bytes: None,
2108 _p: PhantomData,
2109 },
2110 is_new: true,
2111 snapshot: Snapshot {
2112 is_new: true,
2113 bytes: None,
2114 },
2115 hibernated: Vec::new(),
2116 events: Events {
2117 ctx,
2118 rx,
2119 _p: PhantomData,
2120 },
2121 startup_ready: None,
2122 }
2123 }
2124
2125 fn queue_pull_start(rx: ActorEvents) -> Start<QueuePullActor> {
2126 let ctx = Ctx::new(rivetkit_core::testing::actor_context(
2127 "actor-id",
2128 "queue-pull-test",
2129 Vec::new(),
2130 "local",
2131 ));
2132
2133 Start {
2134 ctx: ctx.clone(),
2135 input: Input {
2136 bytes: None,
2137 _p: PhantomData,
2138 },
2139 is_new: true,
2140 snapshot: Snapshot {
2141 is_new: true,
2142 bytes: None,
2143 },
2144 hibernated: Vec::new(),
2145 events: Events {
2146 ctx,
2147 rx,
2148 _p: PhantomData,
2149 },
2150 startup_ready: None,
2151 }
2152 }
2153
2154 async fn request_serialize(
2155 tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>,
2156 ) -> Vec<StateDelta> {
2157 let (reply_tx, reply_rx) = oneshot::channel();
2158 tx.send(ActorEvent::SerializeState {
2159 reason: rivetkit_core::SerializeStateReason::Save,
2160 reply: reply_tx.into(),
2161 })
2162 .expect("send serialize event");
2163 reply_rx
2164 .await
2165 .expect("serialize reply")
2166 .expect("serialize result")
2167 }
2168
2169 async fn request_sleep(tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>) {
2170 let (reply_tx, reply_rx) = oneshot::channel();
2171 tx.send(ActorEvent::RunGracefulCleanup {
2172 reason: ShutdownKind::Sleep,
2173 reply: reply_tx.into(),
2174 })
2175 .expect("send sleep cleanup");
2176 reply_rx.await.expect("sleep reply").expect("sleep result");
2177 }
2178
2179 async fn request_run_wake(tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>) {
2180 let (reply_tx, reply_rx) = oneshot::channel();
2181 tx.send(ActorEvent::RunWake {
2182 wake_at: 123,
2183 wake_revision: 1,
2184 reply: reply_tx.into(),
2185 })
2186 .expect("send run wake");
2187 reply_rx
2188 .await
2189 .expect("run wake reply")
2190 .expect("run wake result");
2191 }
2192
2193 async fn request_action(
2194 tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>,
2195 name: &str,
2196 args: &[u8],
2197 conn: Option<ConnHandle>,
2198 ) -> Result<Vec<u8>> {
2199 request_action_rx(tx, name, args, conn).await
2200 }
2201
2202 async fn request_action_rx(
2203 tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>,
2204 name: &str,
2205 args: &[u8],
2206 conn: Option<ConnHandle>,
2207 ) -> Result<Vec<u8>> {
2208 let (reply_tx, reply_rx) = oneshot::channel();
2209 tx.send(ActorEvent::Action {
2210 name: name.to_owned(),
2211 args: args.to_vec(),
2212 conn,
2213 scheduled_fire: None,
2214 reply: reply_tx.into(),
2215 })
2216 .expect("send action event");
2217 reply_rx.await.expect("action reply")
2218 }
2219
2220 async fn request_queue_send(
2221 tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>,
2222 name: &str,
2223 body: &[u8],
2224 conn: ConnHandle,
2225 ) -> Result<QueueSendResult> {
2226 let (reply_tx, reply_rx) = oneshot::channel();
2227 tx.send(ActorEvent::QueueSend {
2228 name: name.to_owned(),
2229 body: body.to_vec(),
2230 conn,
2231 request: rivetkit_core::Request::default(),
2232 wait: true,
2233 timeout_ms: None,
2234 reply: reply_tx.into(),
2235 })
2236 .expect("send queue event");
2237 reply_rx.await.expect("queue reply")
2238 }
2239
2240 async fn request_subscribe(
2241 tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>,
2242 conn: ConnHandle,
2243 event_name: &str,
2244 ) -> Result<()> {
2245 let (reply_tx, reply_rx) = oneshot::channel();
2246 tx.send(ActorEvent::SubscribeRequest {
2247 conn,
2248 event_name: event_name.to_owned(),
2249 reply: reply_tx.into(),
2250 })
2251 .expect("send subscribe event");
2252 reply_rx.await.expect("subscribe reply")
2253 }
2254
2255 async fn request_destroy(tx: &tokio::sync::mpsc::UnboundedSender<ActorEvent>) {
2256 let (reply_tx, reply_rx) = oneshot::channel();
2257 tx.send(ActorEvent::RunGracefulCleanup {
2258 reason: ShutdownKind::Destroy,
2259 reply: reply_tx.into(),
2260 })
2261 .expect("send destroy cleanup");
2262 reply_rx
2263 .await
2264 .expect("destroy reply")
2265 .expect("destroy result");
2266 }
2267
2268 fn decode_actor_state(deltas: Vec<StateDelta>) -> LifecycleState {
2269 let [StateDelta::ActorState(bytes)] = deltas.as_slice() else {
2270 panic!("expected one actor state delta");
2271 };
2272 decode_cbor(bytes, "actor state").expect("decode actor state")
2273 }
2274
2275 fn decode_unit_creation_state(deltas: Vec<StateDelta>) -> UnitCreationState {
2276 let [StateDelta::ActorState(bytes)] = deltas.as_slice() else {
2277 panic!("expected one actor state delta");
2278 };
2279 decode_cbor(bytes, "actor state").expect("decode actor state")
2280 }
2281
2282 fn conn<P: Serialize, S: Serialize>(id: &str, params: P, state: S) -> ConnHandle {
2283 ConnHandle::new(id, cbor(¶ms), cbor(&state), true)
2284 }
2285}