Skip to main content

rivetkit/
start.rs

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	// Run the whole startup phase (input decode, state creation, create,
204	// on_create, on_start) as one fallible unit so a failure is forwarded to
205	// the runtime handshake as the real cause instead of being dropped. Without
206	// this, an input decode error would drop `startup_ready`, surfacing only a
207	// generic closed-channel error rather than the actual failure. The failure
208	// itself is logged by rivetkit-core when it drains the run handle.
209	let startup = async {
210		let state = match snapshot.decode()? {
211			Some(state) => state,
212			// Absent input falls back to the input type's default, matching
213			// rivetkit-typescript where createState receives undefined input.
214			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
273/// Reports a `run` failure to the engine as an errored stop. The event loop in
274/// `run_actor` does not observe the run task until shutdown, so the report
275/// must happen here for the engine to learn about the crash promptly. Errors
276/// that surface after the abort signal fires are shutdown-induced (cancelled
277/// waits during sleep or destroy) and are not crashes.
278fn 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>(&params)?;
389				actor
390					.clone()
391					.on_before_connect(ctx.clone(), &params)
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			// Keep the loop alive so the finalize-phase `SerializeState` can capture state the hook wrote; core ends it by closing the channel.
437		}
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		// Non-empty bytes that are not valid CBOR for the input type must fail
1064		// the actor start rather than silently defaulting, and the failure must
1065		// be forwarded to the startup handshake instead of dropped.
1066		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		// The startup handshake is signaled with the error rather than dropped,
1082		// so the runtime handshake surfaces the real cause. rivetkit-core owns
1083		// logging the failure when it drains the run handle.
1084		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(&params),
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(&params),
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		// Core sends `SerializeState` during finalize, after the cleanup hook, to
1254		// capture state the hook wrote. The event loop must stay alive to handle
1255		// it instead of ending on the cleanup event.
1256		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		// The harness has no ActorTask to complete startup, so mark the
1278		// lifecycle started directly; stop requests are rejected before then.
1279		ctx.inner().set_started(true);
1280		let actor = tokio::spawn(run_actor::<FailingRunActor>(start));
1281
1282		// The errored stop is requested inside the spawned run task right
1283		// after `run` returns, so yielding the current-thread runtime drives
1284		// it to completion deterministically.
1285		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		// See run_actor_error_requests_errored_stop for why yielding drives
1312		// the spawned run task deterministically.
1313		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		// Closing the events channel starts the shutdown join path, which
1340		// cancels the abort signal before joining the run task.
1341		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(&params), cbor(&state), true)
2284	}
2285}