Skip to main content

rivetkit_core/actor/
messages.rs

1use std::collections::HashMap;
2use std::ops::{Deref, DerefMut};
3use std::sync::Arc;
4
5use anyhow::Result;
6use parking_lot::Mutex;
7use rivet_envoy_client::config::{HttpRequestBodyStream, ResponseChunk};
8use serde::{Deserialize, Serialize};
9use tokio::sync::mpsc;
10use tokio_util::sync::CancellationToken;
11
12use crate::actor::connection::ConnHandle;
13use crate::actor::lifecycle_hooks::Reply;
14use crate::actor::schedule::ScheduledFireInfo;
15use crate::actor::task_types::ShutdownKind;
16use crate::error::ProtocolError;
17use crate::telemetry::ActorInvocationTelemetry;
18use crate::types::ConnId;
19use crate::websocket::WebSocket;
20
21#[derive(Clone, Debug)]
22pub struct Request {
23	inner: http::Request<Vec<u8>>,
24	body_stream: Option<Arc<Mutex<Option<HttpRequestBodyStream>>>>,
25	cancel_token: CancellationToken,
26}
27
28impl Request {
29	pub fn new(body: Vec<u8>) -> Self {
30		Self {
31			inner: http::Request::new(body),
32			body_stream: None,
33			cancel_token: CancellationToken::new(),
34		}
35	}
36
37	pub fn from_parts(
38		method: &str,
39		uri: &str,
40		headers: HashMap<String, String>,
41		body: Vec<u8>,
42	) -> Result<Self> {
43		Self::from_parts_with_stream(method, uri, headers, body, None)
44	}
45
46	pub fn from_parts_with_stream(
47		method: &str,
48		uri: &str,
49		headers: HashMap<String, String>,
50		body: Vec<u8>,
51		body_stream: Option<HttpRequestBodyStream>,
52	) -> Result<Self> {
53		let method = method
54			.parse::<http::Method>()
55			.map_err(|error| invalid_http_request("method", format!("{method}: {error}")))?;
56		let uri = uri
57			.parse::<http::Uri>()
58			.map_err(|error| invalid_http_request("uri", format!("{uri}: {error}")))?;
59		let mut request = http::Request::builder()
60			.method(method)
61			.uri(uri)
62			.body(body)?;
63
64		for (name, value) in headers {
65			let header_name: http::header::HeaderName = name
66				.parse()
67				.map_err(|error| invalid_http_request("header name", format!("{name}: {error}")))?;
68			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
69				invalid_http_request("header value", format!("{name}: {error}"))
70			})?;
71			request.headers_mut().insert(header_name, header_value);
72		}
73
74		Ok(Self {
75			inner: request,
76			body_stream: body_stream.map(|rx| Arc::new(Mutex::new(Some(rx)))),
77			cancel_token: CancellationToken::new(),
78		})
79	}
80
81	pub fn to_parts(&self) -> (String, String, HashMap<String, String>, Vec<u8>) {
82		(
83			self.method().to_string(),
84			self.uri().to_string(),
85			self.headers()
86				.iter()
87				.map(|(name, value)| {
88					(
89						name.to_string(),
90						String::from_utf8_lossy(value.as_bytes()).into_owned(),
91					)
92				})
93				.collect(),
94			self.body().clone(),
95		)
96	}
97
98	pub fn has_body_stream(&self) -> bool {
99		self.body_stream
100			.as_ref()
101			.is_some_and(|body_stream| body_stream.lock().is_some())
102	}
103
104	pub fn take_body_stream(&self) -> Option<HttpRequestBodyStream> {
105		self.body_stream
106			.as_ref()
107			.and_then(|body_stream| body_stream.lock().take())
108	}
109
110	pub fn cancellation_token(&self) -> CancellationToken {
111		self.cancel_token.clone()
112	}
113
114	/// Replaces the request token with a child of `parent`, so cancelling the
115	/// parent (for example the actor abort signal) also cancels this request.
116	/// Call it before any clone of the token is handed out.
117	pub(crate) fn set_cancellation_parent(&mut self, parent: &CancellationToken) {
118		self.cancel_token = parent.child_token();
119	}
120
121	pub async fn into_buffered(mut self) -> Result<Self> {
122		if let Some(body_stream) = &self.body_stream {
123			let mut body_stream = body_stream.lock().take();
124			if let Some(mut body_stream) = body_stream.take() {
125				while let Some(chunk) = body_stream.recv().await? {
126					self.inner.body_mut().extend_from_slice(&chunk);
127				}
128			}
129		}
130		self.body_stream = None;
131		Ok(self)
132	}
133
134	pub fn into_inner(self) -> http::Request<Vec<u8>> {
135		self.inner
136	}
137
138	pub fn into_body(self) -> Vec<u8> {
139		self.inner.into_body()
140	}
141}
142
143impl Default for Request {
144	fn default() -> Self {
145		Self::new(Vec::new())
146	}
147}
148
149impl Deref for Request {
150	type Target = http::Request<Vec<u8>>;
151
152	fn deref(&self) -> &Self::Target {
153		&self.inner
154	}
155}
156
157impl DerefMut for Request {
158	fn deref_mut(&mut self) -> &mut Self::Target {
159		&mut self.inner
160	}
161}
162
163impl From<http::Request<Vec<u8>>> for Request {
164	fn from(value: http::Request<Vec<u8>>) -> Self {
165		Self {
166			inner: value,
167			body_stream: None,
168			cancel_token: CancellationToken::new(),
169		}
170	}
171}
172
173impl From<Request> for http::Request<Vec<u8>> {
174	fn from(value: Request) -> Self {
175		value.inner
176	}
177}
178
179#[derive(Clone, Debug)]
180pub struct Response(http::Response<Vec<u8>>);
181
182impl Response {
183	pub fn new(body: Vec<u8>) -> Self {
184		Self(http::Response::new(body))
185	}
186
187	pub fn from_parts(
188		status: u16,
189		headers: HashMap<String, String>,
190		body: Vec<u8>,
191	) -> Result<Self> {
192		let mut response = http::Response::new(body);
193		*response.status_mut() = status
194			.try_into()
195			.map_err(|error| invalid_http_response("status", format!("{status}: {error}")))?;
196
197		for (name, value) in headers {
198			let header_name: http::header::HeaderName = name.parse().map_err(|error| {
199				invalid_http_response("header name", format!("{name}: {error}"))
200			})?;
201			let header_value: http::header::HeaderValue = value.parse().map_err(|error| {
202				invalid_http_response("header value", format!("{name}: {error}"))
203			})?;
204			response.headers_mut().insert(header_name, header_value);
205		}
206
207		Ok(Self(response))
208	}
209
210	pub fn to_parts(&self) -> (u16, HashMap<String, String>, Vec<u8>) {
211		(
212			self.status().as_u16(),
213			self.headers()
214				.iter()
215				.map(|(name, value)| {
216					(
217						name.to_string(),
218						String::from_utf8_lossy(value.as_bytes()).into_owned(),
219					)
220				})
221				.collect(),
222			self.body().clone(),
223		)
224	}
225
226	pub fn into_inner(self) -> http::Response<Vec<u8>> {
227		self.0
228	}
229
230	pub fn into_body(self) -> Vec<u8> {
231		self.0.into_body()
232	}
233}
234
235fn invalid_http_request(field: &str, reason: String) -> anyhow::Error {
236	ProtocolError::InvalidHttpRequest {
237		field: field.to_owned(),
238		reason,
239	}
240	.build()
241}
242
243fn invalid_http_response(field: &str, reason: String) -> anyhow::Error {
244	ProtocolError::InvalidHttpResponse {
245		field: field.to_owned(),
246		reason,
247	}
248	.build()
249}
250
251impl Default for Response {
252	fn default() -> Self {
253		Self::new(Vec::new())
254	}
255}
256
257impl Deref for Response {
258	type Target = http::Response<Vec<u8>>;
259
260	fn deref(&self) -> &Self::Target {
261		&self.0
262	}
263}
264
265impl DerefMut for Response {
266	fn deref_mut(&mut self) -> &mut Self::Target {
267		&mut self.0
268	}
269}
270
271impl From<http::Response<Vec<u8>>> for Response {
272	fn from(value: http::Response<Vec<u8>>) -> Self {
273		Self(value)
274	}
275}
276
277impl From<Response> for http::Response<Vec<u8>> {
278	fn from(value: Response) -> Self {
279		value.0
280	}
281}
282
283pub struct StreamingResponse {
284	status: u16,
285	headers: HashMap<String, String>,
286	body_stream: mpsc::Receiver<ResponseChunk>,
287}
288
289impl StreamingResponse {
290	pub fn from_parts(
291		status: u16,
292		headers: HashMap<String, String>,
293		body_stream: mpsc::Receiver<ResponseChunk>,
294	) -> Result<Self> {
295		let status_code: http::StatusCode = status
296			.try_into()
297			.map_err(|error| invalid_http_response("status", format!("{status}: {error}")))?;
298		for (name, value) in &headers {
299			let _: http::header::HeaderName = name.parse().map_err(|error| {
300				invalid_http_response("header name", format!("{name}: {error}"))
301			})?;
302			let _: http::header::HeaderValue = value.parse().map_err(|error| {
303				invalid_http_response("header value", format!("{name}: {error}"))
304			})?;
305		}
306
307		Ok(Self {
308			status: status_code.as_u16(),
309			headers,
310			body_stream,
311		})
312	}
313
314	pub fn into_parts(self) -> (u16, HashMap<String, String>, mpsc::Receiver<ResponseChunk>) {
315		(self.status, self.headers, self.body_stream)
316	}
317}
318
319pub enum ActorHttpResponse {
320	Buffered(Response),
321	Stream(StreamingResponse),
322}
323
324impl ActorHttpResponse {
325	pub fn status(&self) -> u16 {
326		match self {
327			Self::Buffered(response) => response.status().as_u16(),
328			Self::Stream(response) => response.status,
329		}
330	}
331}
332
333impl From<Response> for ActorHttpResponse {
334	fn from(value: Response) -> Self {
335		Self::Buffered(value)
336	}
337}
338
339impl From<StreamingResponse> for ActorHttpResponse {
340	fn from(value: StreamingResponse) -> Self {
341		Self::Stream(value)
342	}
343}
344
345#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
346pub enum StateDelta {
347	ActorState(Vec<u8>),
348	ConnHibernation { conn: ConnId, bytes: Vec<u8> },
349	ConnHibernationRemoved(ConnId),
350}
351
352/// One workflow-engine KV write that must commit atomically with its actor
353/// state snapshot.
354#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
355pub struct WorkflowKvWrite {
356	pub key: Vec<u8>,
357	pub value: Vec<u8>,
358}
359
360impl StateDelta {
361	pub(crate) fn payload_len(&self) -> usize {
362		match self {
363			Self::ActorState(bytes) | Self::ConnHibernation { bytes, .. } => bytes.len(),
364			Self::ConnHibernationRemoved(_) => 0,
365		}
366	}
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
370pub enum SerializeStateReason {
371	Save,
372	Inspector,
373}
374
375impl SerializeStateReason {
376	pub(crate) fn label(self) -> &'static str {
377		match self {
378			Self::Save => "save",
379			Self::Inspector => "inspector",
380		}
381	}
382}
383
384#[derive(Clone, Debug, PartialEq, Eq)]
385pub enum QueueSendStatus {
386	Completed,
387	TimedOut,
388}
389
390impl QueueSendStatus {
391	pub(crate) fn as_str(&self) -> &'static str {
392		match self {
393			Self::Completed => "completed",
394			Self::TimedOut => "timedOut",
395		}
396	}
397}
398
399#[derive(Clone, Debug, PartialEq, Eq)]
400pub struct QueueSendResult {
401	pub status: QueueSendStatus,
402	pub response: Option<Vec<u8>>,
403}
404
405#[derive(Debug)]
406pub enum ActorEvent {
407	Action {
408		name: String,
409		args: Vec<u8>,
410		conn: Option<ConnHandle>,
411		scheduled_fire: Option<ScheduledFireInfo>,
412		/// Telemetry of the invocation this action runs as, for the host
413		/// runtime to bind onto the context it hands the action. Absent when
414		/// the dispatch opened no invocation.
415		invocation_telemetry: Option<ActorInvocationTelemetry>,
416		reply: Reply<Vec<u8>>,
417	},
418	HttpRequest {
419		request: Request,
420		/// Telemetry of the invocation this request runs as. See `Action`.
421		invocation_telemetry: Option<crate::ActorInvocationTelemetry>,
422		reply: Reply<ActorHttpResponse>,
423	},
424	QueueSend {
425		name: String,
426		body: Vec<u8>,
427		conn: ConnHandle,
428		request: Request,
429		wait: bool,
430		timeout_ms: Option<u64>,
431		/// Telemetry of the invocation this send runs as. See `Action`.
432		invocation_telemetry: Option<crate::ActorInvocationTelemetry>,
433		reply: Reply<QueueSendResult>,
434	},
435	WebSocketOpen {
436		conn: ConnHandle,
437		ws: WebSocket,
438		request: Option<Request>,
439		reply: Reply<()>,
440	},
441	ConnectionPreflight {
442		conn: ConnHandle,
443		params: Vec<u8>,
444		request: Option<Request>,
445		reply: Reply<()>,
446	},
447	ConnectionOpen {
448		conn: ConnHandle,
449		request: Option<Request>,
450		reply: Reply<()>,
451	},
452	ConnectionClosed {
453		conn: ConnHandle,
454	},
455	SubscribeRequest {
456		conn: ConnHandle,
457		event_name: String,
458		reply: Reply<()>,
459	},
460	SerializeState {
461		reason: SerializeStateReason,
462		reply: Reply<Vec<StateDelta>>,
463	},
464	RunGracefulCleanup {
465		reason: ShutdownKind,
466		reply: Reply<()>,
467	},
468	DisconnectConn {
469		conn_id: ConnId,
470		reply: Reply<()>,
471	},
472	
473	
474	
475	WorkflowHistoryRequested {
476		reply: Reply<Option<Vec<u8>>>,
477	},
478	WorkflowReplayRequested {
479		entry_id: Option<String>,
480		reply: Reply<Option<Vec<u8>>>,
481	},
482	/// The persisted logical run deadline became due. Foreign runtimes ensure
483	/// their run callback is active without restarting the actor runtime itself.
484	RunWake {
485		wake_at: i64,
486		wake_revision: u64,
487		reply: Reply<()>,
488	},
489}
490
491impl ActorEvent {
492	pub(crate) fn kind(&self) -> &'static str {
493		match self {
494			Self::Action { .. } => "action",
495			Self::HttpRequest { .. } => "http_request",
496			Self::QueueSend { .. } => "queue_send",
497			Self::WebSocketOpen { .. } => "websocket_open",
498			Self::ConnectionPreflight { .. } => "connection_preflight",
499			Self::ConnectionOpen { .. } => "connection_open",
500			Self::ConnectionClosed { .. } => "connection_closed",
501			Self::SubscribeRequest { .. } => "subscribe_request",
502			Self::SerializeState { reason, .. } => match reason {
503				SerializeStateReason::Save => "serialize_state_save",
504				SerializeStateReason::Inspector => "serialize_state_inspector",
505			},
506			Self::RunGracefulCleanup { reason, .. } => match reason {
507				ShutdownKind::Sleep => "run_sleep_cleanup",
508				ShutdownKind::Destroy => "run_destroy_cleanup",
509			},
510			Self::DisconnectConn { .. } => "disconnect_conn",
511			
512			
513			
514			Self::WorkflowHistoryRequested { .. } => "workflow_history_requested",
515			Self::WorkflowReplayRequested { .. } => "workflow_replay_requested",
516			Self::RunWake { .. } => "run_wake",
517		}
518	}
519}
520
521// Test shim keeps moved tests in crate-root tests/ with private-module access.
522