1use std::{
2 collections::{BTreeMap, HashMap},
3 sync::Arc,
4};
5
6use crate::async_counter::AsyncCounter;
7use rivet_envoy_protocol as protocol;
8use tokio::sync::oneshot::error::TryRecvError;
9use tokio::sync::{mpsc, oneshot};
10use tokio::task::JoinSet;
11use tracing::Instrument;
12
13use crate::connection::ws_send;
14use crate::context::SharedContext;
15use crate::handle::EnvoyHandle;
16use crate::http::HttpRequest;
17#[cfg(test)]
18use crate::http::{HTTP_BODY_STREAM_CHANNEL_CAPACITY, HttpResponse, ResponseChunk};
19use crate::stringify::stringify_to_rivet_tunnel_message_kind;
20use crate::utils::{
21 BufferMap, id_to_str, spawn_detached, wrapping_add_u16, wrapping_lte_u16, wrapping_sub_u16,
22};
23use crate::websocket::{WebSocketHandler, WebSocketMessage, WebSocketSender, WsOutgoing};
24
25mod http;
26
27pub enum ToActor {
28 Intent {
29 intent: protocol::ActorIntent,
30 error: Option<String>,
31 },
32 Stop {
33 command_idx: i64,
34 reason: protocol::StopActorReason,
35 },
36 Lost,
37 SetAlarm {
38 alarm_ts: Option<i64>,
39 ack_tx: Option<oneshot::Sender<()>>,
40 },
41 ReqStart {
42 message_id: protocol::MessageId,
43 req: protocol::ToEnvoyRequestStart,
44 connection_session: u64,
45 },
46 ConnectionClosed {
47 session: u64,
48 },
49 ReqChunk {
50 message_id: protocol::MessageId,
51 chunk: protocol::ToEnvoyRequestChunk,
52 },
53 ReqAbort {
54 message_id: protocol::MessageId,
55 reason: protocol::HttpStreamAbortReason,
56 },
57 ReqProtocolViolation {
58 message_id: protocol::MessageId,
59 detail: String,
60 },
61 ReqBodyCancelled {
62 message_id: protocol::MessageId,
63 },
64 ReqBodyCancel {
65 message_id: protocol::MessageId,
66 },
67 ResponseBodyWindowUpdate {
68 message_id: protocol::MessageId,
69 consumed_bytes: u64,
70 },
71 ReqComplete {
72 message_id: protocol::MessageId,
73 },
74 WsOpen {
75 message_id: protocol::MessageId,
76 path: String,
77 headers: BTreeMap<String, String>,
78 },
79 WsMsg {
80 message_id: protocol::MessageId,
81 msg: protocol::ToEnvoyWebSocketMessage,
82 },
83 WsClose {
84 message_id: protocol::MessageId,
85 close: protocol::ToEnvoyWebSocketClose,
86 },
87 HwsAck {
88 gateway_id: protocol::GatewayId,
89 request_id: protocol::RequestId,
90 envoy_message_index: u16,
91 },
92}
93
94struct WebSocketRequestState {
95 envoy_message_index: u16,
96}
97
98struct WsEntry {
99 is_hibernatable: bool,
100 rivet_message_index: u16,
101 ws_handler: Option<WebSocketHandler>,
102 outgoing_tx: mpsc::UnboundedSender<WsOutgoing>,
103}
104
105struct ActorContext {
106 shared: Arc<SharedContext>,
107 tx: mpsc::UnboundedSender<ToActor>,
108 actor_id: String,
109 generation: u32,
110 command_idx: i64,
111 event_index: i64,
112 error: Option<String>,
113 http_requests: http::HttpRequests,
114 websocket_requests: BufferMap<WebSocketRequestState>,
115 ws_entries: BufferMap<WsEntry>,
116 hibernating_requests: Vec<protocol::HibernatingRequest>,
117 active_http_request_count: Arc<AsyncCounter>,
118}
119
120struct PendingStop {
121 completion_rx: oneshot::Receiver<anyhow::Result<()>>,
122 stop_code: protocol::StopCode,
123 stop_message: Option<String>,
124}
125
126enum StopProgress {
127 Stopped,
128 Pending(PendingStop),
129}
130
131pub fn create_actor(
132 shared: Arc<SharedContext>,
133 actor_id: String,
134 generation: u32,
135 config: protocol::ActorConfig,
136 hibernating_requests: Vec<protocol::HibernatingRequest>,
137 preloaded_kv: Option<protocol::PreloadedKv>,
138) -> (mpsc::UnboundedSender<ToActor>, Arc<AsyncCounter>) {
139 let (tx, rx) = mpsc::unbounded_channel();
140 let active_http_request_count = Arc::new(AsyncCounter::new());
141 spawn_detached(actor_inner(
142 shared,
143 actor_id,
144 generation,
145 config,
146 hibernating_requests,
147 preloaded_kv,
148 tx.clone(),
149 rx,
150 active_http_request_count.clone(),
151 ));
152 (tx, active_http_request_count)
153}
154
155#[tracing::instrument(
156 skip_all,
157 fields(
158 envoy_key = %shared.envoy_key,
159 actor_id = %actor_id,
160 generation = generation,
161 actor_key = %config.key.as_deref().unwrap_or(""),
162 ),
163)]
164async fn actor_inner(
165 shared: Arc<SharedContext>,
166 actor_id: String,
167 generation: u32,
168 config: protocol::ActorConfig,
169 hibernating_requests: Vec<protocol::HibernatingRequest>,
170 preloaded_kv: Option<protocol::PreloadedKv>,
171 tx: mpsc::UnboundedSender<ToActor>,
172 mut rx: mpsc::UnboundedReceiver<ToActor>,
173 active_http_request_count: Arc<AsyncCounter>,
174) {
175 let handle = EnvoyHandle {
176 shared: shared.clone(),
177 started_rx: tokio::sync::watch::channel(()).1,
179 };
180
181 let mut ctx = ActorContext {
182 shared: shared.clone(),
183 tx,
184 actor_id: actor_id.clone(),
185 generation,
186 command_idx: 0,
187 event_index: 0,
188 error: None,
189 http_requests: http::HttpRequests::new(),
190 websocket_requests: BufferMap::new(),
191 ws_entries: BufferMap::new(),
192 hibernating_requests,
193 active_http_request_count,
194 };
195 let mut http_request_tasks = JoinSet::new();
196 let mut pending_stop: Option<PendingStop> = None;
197 let mut rx_closed = false;
198
199 let start_result = shared
201 .config
202 .callbacks
203 .on_actor_start(
204 handle.clone(),
205 actor_id.clone(),
206 generation,
207 config,
208 preloaded_kv,
209 )
210 .await;
211
212 if let Err(error) = start_result {
213 let error_chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
214 tracing::error!(?error, error_chain = ?error_chain, "actor start failed");
215 send_event(
216 &mut ctx,
217 protocol::Event::EventActorStateUpdate(protocol::EventActorStateUpdate {
218 state: protocol::ActorState::ActorStateStopped(protocol::ActorStateStopped {
219 code: protocol::StopCode::Error,
220 message: Some(format!("{error:#}")),
221 }),
222 }),
223 );
224 return;
225 }
226
227 if let Some(meta_entries) = handle.take_pending_hibernation_restore(&actor_id) {
228 if let Err(error) = handle_hws_restore(&mut ctx, &handle, meta_entries).await {
229 tracing::error!(?error, "actor hibernation restore failed");
230 send_event(
231 &mut ctx,
232 protocol::Event::EventActorStateUpdate(protocol::EventActorStateUpdate {
233 state: protocol::ActorState::ActorStateStopped(protocol::ActorStateStopped {
234 code: protocol::StopCode::Error,
235 message: Some(format!("{error:#}")),
236 }),
237 }),
238 );
239 return;
240 }
241 }
242
243 send_event(
245 &mut ctx,
246 protocol::Event::EventActorStateUpdate(protocol::EventActorStateUpdate {
247 state: protocol::ActorState::ActorStateRunning,
248 }),
249 );
250
251 loop {
252 tokio::select! {
253 maybe_task = async {
254 if http_request_tasks.is_empty() {
255 std::future::pending().await
256 } else {
257 http_request_tasks.join_next().await
258 }
259 } => {
260 if let Some(result) = maybe_task {
261 http::handle_task_result(result);
262 }
263 }
264 msg = async {
265 if rx_closed {
266 std::future::pending::<Option<ToActor>>().await
267 } else {
268 rx.recv().await
269 }
270 } => {
271 let Some(msg) = msg else {
272 if pending_stop.is_some() {
273 rx_closed = true;
274 continue;
275 }
276 break;
277 };
278
279 match msg {
280 ToActor::Intent { intent, error } => {
281 send_event(
282 &mut ctx,
283 protocol::Event::EventActorIntent(protocol::EventActorIntent { intent }),
284 );
285 if error.is_some() {
286 ctx.error = error;
287 }
288 }
289 ToActor::Stop {
290 command_idx,
291 reason,
292 } => {
293 if pending_stop.is_some() {
294 tracing::warn!(
295 command_idx,
296 "ignoring duplicate stop while actor teardown is in progress"
297 );
298 continue;
299 }
300 if command_idx <= ctx.command_idx {
301 tracing::warn!(command_idx, "ignoring already seen command");
302 continue;
303 }
304 ctx.command_idx = command_idx;
305 match begin_stop(&mut ctx, &handle, &mut http_request_tasks, reason).await {
306 StopProgress::Stopped => break,
307 StopProgress::Pending(stop) => pending_stop = Some(stop),
308 }
309 }
310 ToActor::Lost => {
311 if pending_stop.is_some() {
312 tracing::warn!(
313 "ignoring lost signal while actor teardown is in progress"
314 );
315 continue;
316 }
317
318 ctx.error = Some("actor lost due to timeout".to_string());
319
320 match begin_stop(
321 &mut ctx,
322 &handle,
323 &mut http_request_tasks,
324 protocol::StopActorReason::SleepIntent,
325 )
326 .await
327 {
328 StopProgress::Stopped => break,
329 StopProgress::Pending(stop) => pending_stop = Some(stop),
330 }
331 }
332 ToActor::SetAlarm { alarm_ts, ack_tx } => {
333 send_event(
334 &mut ctx,
335 protocol::Event::EventActorSetAlarm(protocol::EventActorSetAlarm { alarm_ts }),
336 );
337 if let Some(ack_tx) = ack_tx {
338 let _ = ack_tx.send(());
339 }
340 }
341 ToActor::ReqStart { message_id, req, connection_session } => {
342 http::handle_req_start(
343 &mut ctx,
344 &handle,
345 &mut http_request_tasks,
346 message_id,
347 req,
348 connection_session,
349 );
350 }
351 ToActor::ConnectionClosed { session } => {
352 http::handle_connection_closed(&mut ctx, session).await;
353 }
354 ToActor::ReqChunk { message_id, chunk } => {
355 http::handle_req_chunk(&mut ctx, message_id, chunk);
356 }
357 ToActor::ReqAbort { message_id, reason } => {
358 http::handle_req_abort(&mut ctx, message_id, reason);
359 }
360 ToActor::ReqProtocolViolation { message_id, detail } => {
361 http::handle_protocol_violation(&mut ctx, message_id, detail).await;
362 }
363 ToActor::ReqBodyCancelled { message_id } => {
364 http::handle_req_body_cancelled(&mut ctx, message_id);
365 }
366 ToActor::ReqBodyCancel { message_id } => {
367 http::handle_req_body_cancel(&mut ctx, message_id);
368 }
369 ToActor::ResponseBodyWindowUpdate {
370 message_id,
371 consumed_bytes,
372 } => {
373 http::handle_response_body_window_update(
374 &mut ctx,
375 message_id,
376 consumed_bytes,
377 )
378 .await;
379 }
380 ToActor::ReqComplete { message_id } => {
381 http::handle_req_complete(&mut ctx, message_id);
382 }
383 ToActor::WsOpen {
384 message_id,
385 path,
386 headers,
387 } => {
388 handle_ws_open(&mut ctx, &handle, message_id, path, headers).await;
389 }
390 ToActor::WsMsg { message_id, msg } => {
391 handle_ws_message(&mut ctx, message_id, msg).await;
392 }
393 ToActor::WsClose { message_id, close } => {
394 handle_ws_close(&mut ctx, message_id, close).await;
395 }
396 ToActor::HwsAck {
397 gateway_id,
398 request_id,
399 envoy_message_index,
400 } => {
401 handle_hws_ack(&mut ctx, gateway_id, request_id, envoy_message_index).await;
402 }
403 }
404 }
405 stop_result = async {
406 let pending = pending_stop
407 .as_mut()
408 .expect("pending stop must exist when waiting for stop completion");
409 (&mut pending.completion_rx).await
410 }, if pending_stop.is_some() => {
411 let pending = pending_stop
412 .take()
413 .expect("pending stop must exist when stop completion resolves");
414 http::abort_and_join_tasks(&mut ctx, &mut http_request_tasks).await;
415 finalize_stop(&mut ctx, pending, stop_result);
416 break;
417 }
418 }
419 }
420
421 http::abort_and_join_tasks(&mut ctx, &mut http_request_tasks).await;
422 tracing::debug!("envoy actor stopped");
423}
424
425fn send_event(ctx: &mut ActorContext, inner: protocol::Event) {
426 let checkpoint = increment_checkpoint(ctx);
427 let _ = crate::envoy::send_to_envoy_tx(
428 &ctx.shared,
429 crate::envoy::ToEnvoyMessage::SendEvents {
430 events: vec![protocol::EventWrapper { checkpoint, inner }],
431 },
432 );
433}
434
435async fn begin_stop(
436 ctx: &mut ActorContext,
437 handle: &EnvoyHandle,
438 _http_request_tasks: &mut JoinSet<()>,
439 reason: protocol::StopActorReason,
440) -> StopProgress {
441 let (mut stop_code, mut stop_message) = if let Some(err) = ctx.error.clone() {
449 (protocol::StopCode::Error, Some(err))
450 } else if matches!(reason, protocol::StopActorReason::Lost) {
451 (
452 protocol::StopCode::Error,
453 Some("envoy connection lost".to_string()),
454 )
455 } else {
456 (protocol::StopCode::Ok, None)
457 };
458 let (stop_tx, mut stop_rx) = oneshot::channel();
459
460 let stop_result = ctx
461 .shared
462 .config
463 .callbacks
464 .on_actor_stop_with_completion(
465 handle.clone(),
466 ctx.actor_id.clone(),
467 ctx.generation,
468 reason.clone(),
469 crate::callbacks::ActorStopHandle::new(stop_tx),
470 )
471 .await;
472
473 if let Err(error) = stop_result {
474 tracing::error!(?error, "actor stop failed");
475 stop_code = protocol::StopCode::Error;
476 if stop_message.is_none() {
477 stop_message = Some(format!("{error:#}"));
478 }
479 send_stopped_event(ctx, stop_code, stop_message);
480 return StopProgress::Stopped;
481 }
482
483 match stop_rx.try_recv() {
484 Ok(stop_result) => {
485 send_stopped_event_for_result(ctx, stop_code, stop_message, stop_result);
486 StopProgress::Stopped
487 }
488 Err(TryRecvError::Empty) => StopProgress::Pending(PendingStop {
489 completion_rx: stop_rx,
490 stop_code,
491 stop_message,
492 }),
493 Err(TryRecvError::Closed) => {
494 send_stopped_event(ctx, stop_code, stop_message);
495 StopProgress::Stopped
496 }
497 }
498}
499
500fn finalize_stop(
501 ctx: &mut ActorContext,
502 pending: PendingStop,
503 stop_result: Result<anyhow::Result<()>, oneshot::error::RecvError>,
504) {
505 match stop_result {
506 Ok(stop_result) => {
507 send_stopped_event_for_result(
508 ctx,
509 pending.stop_code,
510 pending.stop_message,
511 stop_result,
512 );
513 }
514 Err(error) => {
515 tracing::warn!(
516 ?error,
517 "actor stop completion handle dropped before signaling teardown result"
518 );
519 send_stopped_event(ctx, pending.stop_code, pending.stop_message);
520 }
521 }
522}
523
524fn send_stopped_event_for_result(
525 ctx: &mut ActorContext,
526 mut stop_code: protocol::StopCode,
527 mut stop_message: Option<String>,
528 stop_result: anyhow::Result<()>,
529) {
530 if let Err(error) = stop_result {
531 tracing::error!(?error, "actor stop completion failed");
532 stop_code = protocol::StopCode::Error;
533 if stop_message.is_none() {
534 stop_message = Some(format!("{error:#}"));
535 }
536 }
537
538 send_stopped_event(ctx, stop_code, stop_message);
539}
540
541fn send_stopped_event(
542 ctx: &mut ActorContext,
543 stop_code: protocol::StopCode,
544 stop_message: Option<String>,
545) {
546 send_event(
547 ctx,
548 protocol::Event::EventActorStateUpdate(protocol::EventActorStateUpdate {
549 state: protocol::ActorState::ActorStateStopped(protocol::ActorStateStopped {
550 code: stop_code,
551 message: stop_message,
552 }),
553 }),
554 );
555}
556
557fn spawn_ws_outgoing_task(
558 shared: Arc<SharedContext>,
559 gateway_id: protocol::GatewayId,
560 request_id: protocol::RequestId,
561 mut outgoing_rx: mpsc::UnboundedReceiver<WsOutgoing>,
562) {
563 let ws_task = async move {
564 let mut idx: u16 = 0;
565 while let Some(msg) = outgoing_rx.recv().await {
566 idx += 1;
567 match msg {
568 WsOutgoing::Message { data, binary } => {
569 ws_send(
570 &shared,
571 protocol::ToRivet::ToRivetTunnelMessage(protocol::ToRivetTunnelMessage {
572 message_id: protocol::MessageId {
573 gateway_id,
574 request_id,
575 message_index: idx,
576 },
577 message_kind:
578 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketMessage(
579 protocol::ToRivetWebSocketMessage { data, binary },
580 ),
581 }),
582 )
583 .await;
584 }
585 WsOutgoing::Flush { tx } => {
586 let _ = tx.send(());
587 }
588 WsOutgoing::Close { code, reason } => {
589 ws_send(
590 &shared,
591 protocol::ToRivet::ToRivetTunnelMessage(protocol::ToRivetTunnelMessage {
592 message_id: protocol::MessageId {
593 gateway_id,
594 request_id,
595 message_index: 0,
596 },
597 message_kind: protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
598 protocol::ToRivetWebSocketClose {
599 code,
600 reason,
601 hibernate: false,
602 },
603 ),
604 }),
605 )
606 .await;
607 break;
608 }
609 }
610 }
611 };
612 spawn_detached(ws_task.in_current_span());
613}
614
615async fn handle_ws_open(
616 ctx: &mut ActorContext,
617 handle: &EnvoyHandle,
618 message_id: protocol::MessageId,
619 path: String,
620 headers: BTreeMap<String, String>,
621) {
622 let restored_ws = ctx
623 .ws_entries
624 .remove(&[&message_id.gateway_id, &message_id.request_id]);
625 let is_restoring_hibernatable = restored_ws
626 .as_ref()
627 .map(|ws| ws.is_hibernatable)
628 .unwrap_or(false);
629
630 if !is_restoring_hibernatable {
631 ctx.websocket_requests.insert(
632 &[&message_id.gateway_id, &message_id.request_id],
633 WebSocketRequestState {
634 envoy_message_index: 0,
635 },
636 );
637 }
638
639 let mut full_headers: HashMap<String, String> = headers.into_iter().collect();
640 full_headers.insert("Upgrade".to_string(), "websocket".to_string());
641 full_headers.insert("Connection".to_string(), "Upgrade".to_string());
642
643 let request = HttpRequest {
644 method: "GET".to_string(),
645 path: path.clone(),
646 headers: full_headers.clone(),
647 body: None,
648 body_stream: None,
649 };
650
651 let is_hibernatable = if is_restoring_hibernatable {
652 true
653 } else {
654 match ctx
655 .shared
656 .config
657 .callbacks
658 .can_hibernate(
659 &ctx.actor_id,
660 &message_id.gateway_id,
661 &message_id.request_id,
662 &request,
663 )
664 .await
665 {
666 Ok(is_hibernatable) => is_hibernatable,
667 Err(error) => {
668 tracing::error!(?error, "error checking websocket hibernation");
669
670 send_actor_message(
671 ctx,
672 message_id.gateway_id,
673 message_id.request_id,
674 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
675 protocol::ToRivetWebSocketClose {
676 code: Some(1011),
677 reason: Some("Server Error".to_string()),
678 hibernate: false,
679 },
680 ),
681 )
682 .await;
683
684 ctx.websocket_requests
685 .remove(&[&message_id.gateway_id, &message_id.request_id]);
686 return;
687 }
688 }
689 };
690
691 let (outgoing_tx, outgoing_rx) = mpsc::unbounded_channel::<WsOutgoing>();
693 let sender = WebSocketSender {
694 tx: outgoing_tx.clone(),
695 };
696
697 let ws_result = if is_restoring_hibernatable {
698 ctx.shared
699 .config
700 .callbacks
701 .websocket(
702 handle.clone(),
703 ctx.actor_id.clone(),
704 message_id.gateway_id,
705 message_id.request_id,
706 request,
707 path,
708 full_headers,
709 true,
710 true,
711 sender,
712 )
713 .await
714 } else {
715 ctx.shared
716 .config
717 .callbacks
718 .websocket(
719 handle.clone(),
720 ctx.actor_id.clone(),
721 message_id.gateway_id,
722 message_id.request_id,
723 request,
724 path,
725 full_headers,
726 is_hibernatable,
727 false,
728 sender,
729 )
730 .await
731 };
732
733 match ws_result {
734 Ok(ws_handler) => {
735 ctx.ws_entries.insert(
736 &[&message_id.gateway_id, &message_id.request_id],
737 WsEntry {
738 is_hibernatable,
739 rivet_message_index: message_id.message_index,
740 ws_handler: Some(ws_handler),
741 outgoing_tx,
742 },
743 );
744
745 spawn_ws_outgoing_task(
746 ctx.shared.clone(),
747 message_id.gateway_id,
748 message_id.request_id,
749 outgoing_rx,
750 );
751
752 send_actor_message(
756 ctx,
757 message_id.gateway_id,
758 message_id.request_id,
759 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketOpen(
760 protocol::ToRivetWebSocketOpen {
761 can_hibernate: is_hibernatable,
762 },
763 ),
764 )
765 .await;
766
767 if let Some(ws) = ctx
769 .ws_entries
770 .get_mut(&[&message_id.gateway_id, &message_id.request_id])
771 {
772 if let Some(handler) = &mut ws.ws_handler {
773 if let Some(on_open) = handler.on_open.take() {
774 let sender = WebSocketSender {
775 tx: ws.outgoing_tx.clone(),
776 };
777
778 on_open(sender).await;
779 }
780 }
781 }
782 }
783 Err(error) => {
784 tracing::error!(?error, "error handling websocket open");
785
786 send_actor_message(
787 ctx,
788 message_id.gateway_id,
789 message_id.request_id,
790 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
791 protocol::ToRivetWebSocketClose {
792 code: Some(1011),
793 reason: Some("Server Error".to_string()),
794 hibernate: false,
795 },
796 ),
797 )
798 .await;
799
800 ctx.websocket_requests
801 .remove(&[&message_id.gateway_id, &message_id.request_id]);
802 ctx.ws_entries
803 .remove(&[&message_id.gateway_id, &message_id.request_id]);
804 }
805 }
806}
807
808async fn handle_ws_message(
809 ctx: &mut ActorContext,
810 message_id: protocol::MessageId,
811 msg: protocol::ToEnvoyWebSocketMessage,
812) {
813 let ws = ctx
814 .ws_entries
815 .get_mut(&[&message_id.gateway_id, &message_id.request_id]);
816
817 if let Some(ws) = ws {
818 if ws.is_hibernatable {
820 let previous_index = ws.rivet_message_index;
821 let received_index = message_id.message_index;
822
823 if wrapping_lte_u16(received_index, previous_index) {
824 tracing::info!(
825 request_id = id_to_str(&message_id.request_id),
826 previous_index,
827 received_index,
828 "received duplicate hibernating websocket message"
829 );
830 return;
831 }
832
833 let expected_index = wrapping_add_u16(previous_index, 1);
834 if received_index != expected_index {
835 tracing::warn!(
836 request_id = id_to_str(&message_id.request_id),
837 previous_index,
838 expected_index,
839 received_index,
840 gap = wrapping_sub_u16(wrapping_sub_u16(received_index, previous_index), 1),
841 "hibernatable websocket message index out of sequence, closing connection"
842 );
843
844 send_actor_message(
845 ctx,
846 message_id.gateway_id,
847 message_id.request_id,
848 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
849 protocol::ToRivetWebSocketClose {
850 code: Some(1008),
851 reason: Some("ws.message_index_skip".to_string()),
852 hibernate: false,
853 },
854 ),
855 )
856 .await;
857 return;
858 }
859
860 ws.rivet_message_index = received_index;
861 }
862
863 if let Some(handler) = &ws.ws_handler {
864 let sender = WebSocketSender {
865 tx: ws.outgoing_tx.clone(),
866 };
867 let ws_msg = WebSocketMessage {
868 data: msg.data,
869 binary: msg.binary,
870 gateway_id: message_id.gateway_id,
871 request_id: message_id.request_id,
872 message_index: message_id.message_index,
873 sender,
874 };
875 (handler.on_message)(ws_msg).await;
876 }
877 } else {
878 tracing::warn!("received message for unknown ws");
879 }
880}
881
882async fn handle_ws_close(
883 ctx: &mut ActorContext,
884 message_id: protocol::MessageId,
885 close: protocol::ToEnvoyWebSocketClose,
886) {
887 let ws = ctx
888 .ws_entries
889 .remove(&[&message_id.gateway_id, &message_id.request_id]);
890
891 if let Some(ws) = ws {
892 if let Some(handler) = &ws.ws_handler {
893 let code = close.code.unwrap_or(1000);
894 let reason = close.reason.unwrap_or_default();
895 (handler.on_close)(code, reason).await;
896 }
897 ctx.websocket_requests
898 .remove(&[&message_id.gateway_id, &message_id.request_id]);
899 } else {
900 tracing::warn!("received close for unknown ws");
901 }
902}
903
904async fn handle_hws_restore(
905 ctx: &mut ActorContext,
906 handle: &EnvoyHandle,
907 meta_entries: Vec<crate::tunnel::HibernatingWebSocketMetadata>,
908) -> anyhow::Result<()> {
909 tracing::debug!(
910 requests = ctx.hibernating_requests.len(),
911 "restoring hibernating requests"
912 );
913
914 let hibernating_requests = std::mem::take(&mut ctx.hibernating_requests);
915
916 for hib_req in &hibernating_requests {
917 let meta = meta_entries.iter().find(|entry| {
918 entry.gateway_id == hib_req.gateway_id && entry.request_id == hib_req.request_id
919 });
920
921 if let Some(meta) = meta {
922 ctx.websocket_requests.insert(
923 &[&hib_req.gateway_id, &hib_req.request_id],
924 WebSocketRequestState {
925 envoy_message_index: meta.envoy_message_index,
926 },
927 );
928
929 let mut full_headers = meta.headers.clone();
930 full_headers.insert("Upgrade".to_string(), "websocket".to_string());
931 full_headers.insert("Connection".to_string(), "Upgrade".to_string());
932
933 let request = HttpRequest {
934 method: "GET".to_string(),
935 path: meta.path.clone(),
936 headers: full_headers.clone(),
937 body: None,
938 body_stream: None,
939 };
940
941 let (hws_outgoing_tx, hws_outgoing_rx) = mpsc::unbounded_channel();
942 let hws_sender = WebSocketSender {
943 tx: hws_outgoing_tx.clone(),
944 };
945
946 let ws_result = ctx
947 .shared
948 .config
949 .callbacks
950 .websocket(
951 handle.clone(),
952 ctx.actor_id.clone(),
953 hib_req.gateway_id,
954 hib_req.request_id,
955 request,
956 meta.path.clone(),
957 full_headers,
958 true,
959 true,
960 hws_sender,
961 )
962 .await;
963
964 match ws_result {
965 Ok(ws_handler) => {
966 ctx.ws_entries.insert(
967 &[&hib_req.gateway_id, &hib_req.request_id],
968 WsEntry {
969 is_hibernatable: true,
970 rivet_message_index: meta.rivet_message_index,
971 ws_handler: Some(ws_handler),
972 outgoing_tx: hws_outgoing_tx,
973 },
974 );
975 if !handle
976 .rebind_hibernating_websocket(
977 ctx.actor_id.clone(),
978 ctx.generation,
979 hib_req.gateway_id,
980 hib_req.request_id,
981 )
982 .await
983 {
984 tracing::warn!(
985 request_id = id_to_str(&hib_req.request_id),
986 "hibernating websocket route disappeared before restore"
987 );
988 ctx.websocket_requests
989 .remove(&[&hib_req.gateway_id, &hib_req.request_id]);
990 ctx.ws_entries
991 .remove(&[&hib_req.gateway_id, &hib_req.request_id]);
992 continue;
993 }
994 send_actor_message(
997 ctx,
998 hib_req.gateway_id,
999 hib_req.request_id,
1000 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketOpen(
1001 protocol::ToRivetWebSocketOpen {
1002 can_hibernate: true,
1003 },
1004 ),
1005 )
1006 .await;
1007 spawn_ws_outgoing_task(
1011 ctx.shared.clone(),
1012 hib_req.gateway_id,
1013 hib_req.request_id,
1014 hws_outgoing_rx,
1015 );
1016 if let Some(ws) = ctx
1017 .ws_entries
1018 .get_mut(&[&hib_req.gateway_id, &hib_req.request_id])
1019 {
1020 if let Some(handler) = &mut ws.ws_handler {
1021 if let Some(on_open) = handler.on_open.take() {
1022 let sender = WebSocketSender {
1023 tx: ws.outgoing_tx.clone(),
1024 };
1025
1026 on_open(sender).await;
1027 }
1028 }
1029 }
1030 tracing::info!(
1031 request_id = id_to_str(&hib_req.request_id),
1032 "connection successfully restored"
1033 );
1034 }
1035 Err(error) => {
1036 tracing::error!(
1037 request_id = id_to_str(&hib_req.request_id),
1038 ?error,
1039 "error creating websocket during restore"
1040 );
1041
1042 send_actor_message(
1043 ctx,
1044 hib_req.gateway_id,
1045 hib_req.request_id,
1046 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
1047 protocol::ToRivetWebSocketClose {
1048 code: Some(1011),
1049 reason: Some("ws.restore_error".to_string()),
1050 hibernate: false,
1051 },
1052 ),
1053 )
1054 .await;
1055
1056 ctx.websocket_requests
1057 .remove(&[&hib_req.gateway_id, &hib_req.request_id]);
1058 }
1059 }
1060 } else {
1061 tracing::warn!(
1062 request_id = id_to_str(&hib_req.request_id),
1063 "closing websocket that is not persisted"
1064 );
1065
1066 send_actor_message(
1067 ctx,
1068 hib_req.gateway_id,
1069 hib_req.request_id,
1070 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketClose(
1071 protocol::ToRivetWebSocketClose {
1072 code: Some(1000),
1073 reason: Some("ws.meta_not_found_during_restore".to_string()),
1074 hibernate: false,
1075 },
1076 ),
1077 )
1078 .await;
1079 }
1080 }
1081
1082 for meta in &meta_entries {
1084 let is_connected = hibernating_requests
1085 .iter()
1086 .any(|req| req.gateway_id == meta.gateway_id && req.request_id == meta.request_id);
1087
1088 if !is_connected {
1089 tracing::warn!(
1090 request_id = id_to_str(&meta.request_id),
1091 "removing stale persisted websocket"
1092 );
1093
1094 let full_headers = meta.headers.clone();
1095 let request = HttpRequest {
1096 method: "GET".to_string(),
1097 path: meta.path.clone(),
1098 headers: full_headers.clone(),
1099 body: None,
1100 body_stream: None,
1101 };
1102
1103 let (stale_tx, _) = mpsc::unbounded_channel();
1104 let stale_sender = WebSocketSender { tx: stale_tx };
1105
1106 let ws_result = ctx
1107 .shared
1108 .config
1109 .callbacks
1110 .websocket(
1111 handle.clone(),
1112 ctx.actor_id.clone(),
1113 meta.gateway_id,
1114 meta.request_id,
1115 request,
1116 meta.path.clone(),
1117 full_headers,
1118 true,
1119 true,
1120 stale_sender,
1121 )
1122 .await;
1123
1124 if let Ok(handler) = ws_result {
1125 (handler.on_close)(1000, "ws.stale_metadata".to_string()).await;
1126 }
1127 }
1128 }
1129
1130 ctx.hibernating_requests = hibernating_requests;
1131 tracing::info!("restored hibernatable websockets");
1132 Ok(())
1133}
1134
1135async fn handle_hws_ack(
1136 ctx: &mut ActorContext,
1137 gateway_id: protocol::GatewayId,
1138 request_id: protocol::RequestId,
1139 envoy_message_index: u16,
1140) {
1141 tracing::debug!(
1142 request_id = id_to_str(&request_id),
1143 index = envoy_message_index,
1144 "ack ws msg"
1145 );
1146
1147 send_actor_message(
1148 ctx,
1149 gateway_id,
1150 request_id,
1151 protocol::ToRivetTunnelMessageKind::ToRivetWebSocketMessageAck(
1152 protocol::ToRivetWebSocketMessageAck {
1153 index: envoy_message_index,
1154 },
1155 ),
1156 )
1157 .await;
1158}
1159
1160fn increment_checkpoint(ctx: &mut ActorContext) -> protocol::ActorCheckpoint {
1161 let index = ctx.event_index;
1162 ctx.event_index += 1;
1163 protocol::ActorCheckpoint {
1164 actor_id: ctx.actor_id.clone(),
1165 generation: ctx.generation,
1166 index,
1167 }
1168}
1169
1170async fn send_actor_message(
1171 ctx: &mut ActorContext,
1172 gateway_id: protocol::GatewayId,
1173 request_id: protocol::RequestId,
1174 message_kind: protocol::ToRivetTunnelMessageKind,
1175) {
1176 let req = ctx.websocket_requests.get_mut(&[&gateway_id, &request_id]);
1177 let envoy_message_index = if let Some(req) = req {
1178 let idx = req.envoy_message_index;
1179 req.envoy_message_index += 1;
1180 idx
1181 } else {
1182 tracing::warn!(
1183 gateway_id = id_to_str(&gateway_id),
1184 request_id = id_to_str(&request_id),
1185 "missing pending request for send message"
1186 );
1187 return;
1188 };
1189
1190 let msg = protocol::ToRivetTunnelMessage {
1191 message_id: protocol::MessageId {
1192 gateway_id,
1193 request_id,
1194 message_index: envoy_message_index,
1195 },
1196 message_kind: message_kind.clone(),
1197 };
1198
1199 let buffer_msg = msg.clone();
1200 let failed = ws_send(&ctx.shared, protocol::ToRivet::ToRivetTunnelMessage(msg)).await;
1201
1202 if failed {
1203 if tracing::enabled!(tracing::Level::DEBUG) {
1204 tracing::debug!(
1205 request_id = id_to_str(&request_id),
1206 message = stringify_to_rivet_tunnel_message_kind(&message_kind),
1207 "buffering tunnel message, socket not connected to engine"
1208 );
1209 }
1210 let _ = crate::envoy::send_to_envoy_tx(
1211 &ctx.shared,
1212 crate::envoy::ToEnvoyMessage::SendOrBufferTunnelMsg { msg: buffer_msg },
1213 );
1214 }
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219 use std::collections::HashMap;
1220 use std::future::pending;
1221 use std::sync::Mutex;
1222 use std::sync::atomic::{AtomicBool, Ordering};
1223 use std::time::{Duration, Instant};
1224
1225 use tokio::sync::Notify;
1226 use tokio::sync::oneshot;
1227 use vbare::OwnedVersionedData;
1228
1229 use super::*;
1230 use crate::config::{BoxFuture, EnvoyCallbacks, WebSocketHandler, WebSocketSender};
1231 use crate::context::WsTxMessage;
1232 use crate::envoy::ToEnvoyMessage;
1233
1234 struct DropSignal(Option<oneshot::Sender<()>>);
1235
1236 impl Drop for DropSignal {
1237 fn drop(&mut self) {
1238 if let Some(tx) = self.0.take() {
1239 let _ = tx.send(());
1240 }
1241 }
1242 }
1243
1244 pub(super) struct TestCallbacks {
1245 fetch_started_tx: Mutex<Option<oneshot::Sender<()>>>,
1246 fetch_dropped_tx: Mutex<Option<oneshot::Sender<()>>>,
1247 release_fetch: Arc<Notify>,
1248 complete_fetch: AtomicBool,
1249 }
1250
1251 impl TestCallbacks {
1252 pub(super) fn idle() -> Self {
1253 Self {
1254 fetch_started_tx: Mutex::new(None),
1255 fetch_dropped_tx: Mutex::new(None),
1256 release_fetch: Arc::new(Notify::new()),
1257 complete_fetch: AtomicBool::new(true),
1258 }
1259 }
1260
1261 fn completing(fetch_started_tx: oneshot::Sender<()>, release_fetch: Arc<Notify>) -> Self {
1262 Self {
1263 fetch_started_tx: Mutex::new(Some(fetch_started_tx)),
1264 fetch_dropped_tx: Mutex::new(None),
1265 release_fetch,
1266 complete_fetch: AtomicBool::new(true),
1267 }
1268 }
1269
1270 pub(super) fn hanging(
1271 fetch_started_tx: oneshot::Sender<()>,
1272 fetch_dropped_tx: oneshot::Sender<()>,
1273 ) -> Self {
1274 Self {
1275 fetch_started_tx: Mutex::new(Some(fetch_started_tx)),
1276 fetch_dropped_tx: Mutex::new(Some(fetch_dropped_tx)),
1277 release_fetch: Arc::new(Notify::new()),
1278 complete_fetch: AtomicBool::new(false),
1279 }
1280 }
1281 }
1282
1283 struct DeferredStopCallbacks {
1284 stop_handle_tx: Mutex<Option<oneshot::Sender<crate::config::ActorStopHandle>>>,
1285 }
1286
1287 pub(super) struct StreamingCallbacks {
1288 pub(super) body_tx: Mutex<Option<oneshot::Sender<mpsc::Sender<ResponseChunk>>>>,
1289 }
1290
1291 pub(super) struct StreamingRequestCallbacks {
1292 pub(super) request_tx: Mutex<Option<oneshot::Sender<HttpRequest>>>,
1293 }
1294
1295 impl EnvoyCallbacks for TestCallbacks {
1296 fn on_actor_start(
1297 &self,
1298 _handle: EnvoyHandle,
1299 _actor_id: String,
1300 _generation: u32,
1301 _config: protocol::ActorConfig,
1302 _preloaded_kv: Option<protocol::PreloadedKv>,
1303 ) -> BoxFuture<anyhow::Result<()>> {
1304 Box::pin(async { Ok(()) })
1305 }
1306
1307 fn on_actor_stop(
1308 &self,
1309 _handle: EnvoyHandle,
1310 _actor_id: String,
1311 _generation: u32,
1312 _reason: protocol::StopActorReason,
1313 ) -> BoxFuture<anyhow::Result<()>> {
1314 Box::pin(async { Ok(()) })
1315 }
1316
1317 fn on_shutdown(&self) {}
1318
1319 fn fetch(
1320 &self,
1321 _handle: EnvoyHandle,
1322 _actor_id: String,
1323 _gateway_id: protocol::GatewayId,
1324 _request_id: protocol::RequestId,
1325 request: HttpRequest,
1326 ) -> BoxFuture<anyhow::Result<HttpResponse>> {
1327 let fetch_started_tx = self
1328 .fetch_started_tx
1329 .lock()
1330 .expect("fetch_started mutex poisoned")
1331 .take();
1332 let fetch_dropped_tx = self
1333 .fetch_dropped_tx
1334 .lock()
1335 .expect("fetch_dropped mutex poisoned")
1336 .take();
1337 let release_fetch = self.release_fetch.clone();
1338 let complete_fetch = self.complete_fetch.load(Ordering::Acquire);
1339
1340 Box::pin(async move {
1341 let _request = request;
1342 if let Some(tx) = fetch_started_tx {
1343 let _ = tx.send(());
1344 }
1345
1346 let _drop_signal = DropSignal(fetch_dropped_tx);
1347
1348 if complete_fetch {
1349 release_fetch.notified().await;
1350 Ok(HttpResponse {
1351 status: 200,
1352 headers: HashMap::new(),
1353 body: Some(Vec::new()),
1354 body_stream: None,
1355 })
1356 } else {
1357 pending::<()>().await;
1358 unreachable!("pending future should never resolve");
1359 }
1360 })
1361 }
1362
1363 fn websocket(
1364 &self,
1365 _handle: EnvoyHandle,
1366 _actor_id: String,
1367 _gateway_id: protocol::GatewayId,
1368 _request_id: protocol::RequestId,
1369 _request: HttpRequest,
1370 _path: String,
1371 _headers: HashMap<String, String>,
1372 _is_hibernatable: bool,
1373 _is_restoring_hibernatable: bool,
1374 _sender: WebSocketSender,
1375 ) -> BoxFuture<anyhow::Result<WebSocketHandler>> {
1376 Box::pin(async {
1377 Ok(WebSocketHandler {
1378 on_message: Box::new(|_| Box::pin(async {})),
1379 on_close: Box::new(|_, _| Box::pin(async {})),
1380 on_open: None,
1381 })
1382 })
1383 }
1384
1385 fn can_hibernate(
1386 &self,
1387 _actor_id: &str,
1388 _gateway_id: &protocol::GatewayId,
1389 _request_id: &protocol::RequestId,
1390 _request: &HttpRequest,
1391 ) -> BoxFuture<anyhow::Result<bool>> {
1392 Box::pin(async { Ok(false) })
1393 }
1394 }
1395
1396 impl EnvoyCallbacks for StreamingRequestCallbacks {
1397 fn on_actor_start(
1398 &self,
1399 _handle: EnvoyHandle,
1400 _actor_id: String,
1401 _generation: u32,
1402 _config: protocol::ActorConfig,
1403 _preloaded_kv: Option<protocol::PreloadedKv>,
1404 ) -> BoxFuture<anyhow::Result<()>> {
1405 Box::pin(async { Ok(()) })
1406 }
1407
1408 fn on_shutdown(&self) {}
1409
1410 fn fetch(
1411 &self,
1412 _handle: EnvoyHandle,
1413 _actor_id: String,
1414 _gateway_id: protocol::GatewayId,
1415 _request_id: protocol::RequestId,
1416 request: HttpRequest,
1417 ) -> BoxFuture<anyhow::Result<HttpResponse>> {
1418 let request_tx = self
1419 .request_tx
1420 .lock()
1421 .expect("streaming request mutex poisoned")
1422 .take();
1423 Box::pin(async move {
1424 let Some(request_tx) = request_tx else {
1425 anyhow::bail!("streaming request sender missing");
1426 };
1427 request_tx
1428 .send(request)
1429 .map_err(|_| anyhow::anyhow!("streaming request receiver dropped"))?;
1430 pending::<()>().await;
1431 unreachable!("pending request callback should not resolve")
1432 })
1433 }
1434
1435 fn websocket(
1436 &self,
1437 _handle: EnvoyHandle,
1438 _actor_id: String,
1439 _gateway_id: protocol::GatewayId,
1440 _request_id: protocol::RequestId,
1441 _request: HttpRequest,
1442 _path: String,
1443 _headers: HashMap<String, String>,
1444 _is_hibernatable: bool,
1445 _is_restoring_hibernatable: bool,
1446 _sender: WebSocketSender,
1447 ) -> BoxFuture<anyhow::Result<WebSocketHandler>> {
1448 Box::pin(async { anyhow::bail!("websocket should not be called in HTTP stream test") })
1449 }
1450
1451 fn can_hibernate(
1452 &self,
1453 _actor_id: &str,
1454 _gateway_id: &protocol::GatewayId,
1455 _request_id: &protocol::RequestId,
1456 _request: &HttpRequest,
1457 ) -> BoxFuture<anyhow::Result<bool>> {
1458 Box::pin(async { Ok(false) })
1459 }
1460 }
1461
1462 impl EnvoyCallbacks for DeferredStopCallbacks {
1463 fn on_actor_start(
1464 &self,
1465 _handle: EnvoyHandle,
1466 _actor_id: String,
1467 _generation: u32,
1468 _config: protocol::ActorConfig,
1469 _preloaded_kv: Option<protocol::PreloadedKv>,
1470 ) -> BoxFuture<anyhow::Result<()>> {
1471 Box::pin(async { Ok(()) })
1472 }
1473
1474 fn on_actor_stop_with_completion(
1475 &self,
1476 _handle: EnvoyHandle,
1477 _actor_id: String,
1478 _generation: u32,
1479 _reason: protocol::StopActorReason,
1480 stop_handle: crate::config::ActorStopHandle,
1481 ) -> BoxFuture<anyhow::Result<()>> {
1482 let stop_handle_tx = self
1483 .stop_handle_tx
1484 .lock()
1485 .expect("stop handle mutex poisoned")
1486 .take();
1487
1488 Box::pin(async move {
1489 let Some(tx) = stop_handle_tx else {
1490 anyhow::bail!("stop handle sender missing");
1491 };
1492
1493 tx.send(stop_handle)
1494 .map_err(|_| anyhow::anyhow!("failed to publish stop handle"))?;
1495 Ok(())
1496 })
1497 }
1498
1499 fn on_shutdown(&self) {}
1500
1501 fn fetch(
1502 &self,
1503 _handle: EnvoyHandle,
1504 _actor_id: String,
1505 _gateway_id: protocol::GatewayId,
1506 _request_id: protocol::RequestId,
1507 _request: HttpRequest,
1508 ) -> BoxFuture<anyhow::Result<HttpResponse>> {
1509 Box::pin(async { anyhow::bail!("fetch should not be called in deferred stop test") })
1510 }
1511
1512 fn websocket(
1513 &self,
1514 _handle: EnvoyHandle,
1515 _actor_id: String,
1516 _gateway_id: protocol::GatewayId,
1517 _request_id: protocol::RequestId,
1518 _request: HttpRequest,
1519 _path: String,
1520 _headers: HashMap<String, String>,
1521 _is_hibernatable: bool,
1522 _is_restoring_hibernatable: bool,
1523 _sender: WebSocketSender,
1524 ) -> BoxFuture<anyhow::Result<WebSocketHandler>> {
1525 Box::pin(async {
1526 anyhow::bail!("websocket should not be called in deferred stop test")
1527 })
1528 }
1529
1530 fn can_hibernate(
1531 &self,
1532 _actor_id: &str,
1533 _gateway_id: &protocol::GatewayId,
1534 _request_id: &protocol::RequestId,
1535 _request: &HttpRequest,
1536 ) -> BoxFuture<anyhow::Result<bool>> {
1537 Box::pin(async { Ok(false) })
1538 }
1539 }
1540
1541 impl EnvoyCallbacks for StreamingCallbacks {
1542 fn on_actor_start(
1543 &self,
1544 _handle: EnvoyHandle,
1545 _actor_id: String,
1546 _generation: u32,
1547 _config: protocol::ActorConfig,
1548 _preloaded_kv: Option<protocol::PreloadedKv>,
1549 ) -> BoxFuture<anyhow::Result<()>> {
1550 Box::pin(async { Ok(()) })
1551 }
1552
1553 fn on_actor_stop(
1554 &self,
1555 _handle: EnvoyHandle,
1556 _actor_id: String,
1557 _generation: u32,
1558 _reason: protocol::StopActorReason,
1559 ) -> BoxFuture<anyhow::Result<()>> {
1560 Box::pin(async { Ok(()) })
1561 }
1562
1563 fn on_shutdown(&self) {}
1564
1565 fn fetch(
1566 &self,
1567 _handle: EnvoyHandle,
1568 _actor_id: String,
1569 _gateway_id: protocol::GatewayId,
1570 _request_id: protocol::RequestId,
1571 _request: HttpRequest,
1572 ) -> BoxFuture<anyhow::Result<HttpResponse>> {
1573 let body_tx = self
1574 .body_tx
1575 .lock()
1576 .expect("streaming body mutex poisoned")
1577 .take();
1578
1579 Box::pin(async move {
1580 let (tx, rx) = mpsc::channel(HTTP_BODY_STREAM_CHANNEL_CAPACITY);
1581 if let Some(body_tx) = body_tx {
1582 let _ = body_tx.send(tx);
1583 }
1584 Ok(HttpResponse {
1585 status: 200,
1586 headers: HashMap::new(),
1587 body: None,
1588 body_stream: Some(rx.into()),
1589 })
1590 })
1591 }
1592
1593 fn websocket(
1594 &self,
1595 _handle: EnvoyHandle,
1596 _actor_id: String,
1597 _gateway_id: protocol::GatewayId,
1598 _request_id: protocol::RequestId,
1599 _request: HttpRequest,
1600 _path: String,
1601 _headers: HashMap<String, String>,
1602 _is_hibernatable: bool,
1603 _is_restoring_hibernatable: bool,
1604 _sender: WebSocketSender,
1605 ) -> BoxFuture<anyhow::Result<WebSocketHandler>> {
1606 Box::pin(async { anyhow::bail!("websocket should not be called in streaming test") })
1607 }
1608
1609 fn can_hibernate(
1610 &self,
1611 _actor_id: &str,
1612 _gateway_id: &protocol::GatewayId,
1613 _request_id: &protocol::RequestId,
1614 _request: &HttpRequest,
1615 ) -> BoxFuture<anyhow::Result<bool>> {
1616 Box::pin(async { Ok(false) })
1617 }
1618 }
1619
1620 pub(super) fn build_shared_context(
1621 callbacks: Arc<dyn EnvoyCallbacks>,
1622 ) -> (Arc<SharedContext>, mpsc::UnboundedReceiver<ToEnvoyMessage>) {
1623 let (envoy_tx, envoy_rx) = mpsc::unbounded_channel();
1624 let shared = Arc::new(SharedContext {
1625 config: crate::config::EnvoyConfig {
1626 version: 1,
1627 endpoint: "http://127.0.0.1:1".to_string(),
1628 token: None,
1629 namespace: "test".to_string(),
1630 pool_name: "test".to_string(),
1631 prepopulate_actor_names: HashMap::new(),
1632 metadata: None,
1633 not_global: true,
1634 debug_latency_ms: None,
1635 callbacks,
1636 },
1637 envoy_key: "test-envoy".to_string(),
1638 envoy_tx,
1639 actors: Arc::new(std::sync::Mutex::new(HashMap::new())),
1640 actors_notify: Arc::new(tokio::sync::Notify::new()),
1641 live_tunnel_requests: Arc::new(std::sync::Mutex::new(HashMap::new())),
1642 pending_hibernation_restores: Arc::new(std::sync::Mutex::new(HashMap::new())),
1643 ws_tx: Arc::new(tokio::sync::Mutex::new(
1644 None::<mpsc::UnboundedSender<WsTxMessage>>,
1645 )),
1646 http_ws_tx: Arc::new(tokio::sync::Mutex::new(None)),
1647 connection_session: std::sync::atomic::AtomicU64::new(0),
1648 next_connection_session: std::sync::atomic::AtomicU64::new(0),
1649 connection_session_tx: tokio::sync::watch::channel(0).0,
1650 protocol_metadata: Arc::new(tokio::sync::Mutex::new(None)),
1651 shutting_down: std::sync::atomic::AtomicBool::new(false),
1652 last_ping_ts: std::sync::atomic::AtomicI64::new(0),
1653 stopped_tx: tokio::sync::watch::channel(true).0,
1654 });
1655 (shared, envoy_rx)
1656 }
1657
1658 pub(super) fn actor_config() -> protocol::ActorConfig {
1659 protocol::ActorConfig {
1660 name: "test".to_string(),
1661 key: Some("test-key".to_string()),
1662 create_ts: 0,
1663 input: None,
1664 }
1665 }
1666
1667 pub(super) fn request_start() -> protocol::ToEnvoyRequestStart {
1668 protocol::ToEnvoyRequestStart {
1669 actor_id: "test-actor".to_string(),
1670 actor_generation: Some(1),
1671 method: "GET".to_string(),
1672 path: "/test".to_string(),
1673 headers: HashMap::new(),
1674 body: None,
1675 stream: false,
1676 response_stream: true,
1677 }
1678 }
1679
1680 pub(super) fn message_id() -> protocol::MessageId {
1681 protocol::MessageId {
1682 gateway_id: [1, 2, 3, 4],
1683 request_id: [5, 6, 7, 8],
1684 message_index: 0,
1685 }
1686 }
1687
1688 pub(super) async fn wait_for_zero(active_http_request_count: &Arc<AsyncCounter>) {
1689 assert!(
1690 active_http_request_count
1691 .wait_zero(Instant::now() + Duration::from_secs(2))
1692 .await,
1693 "timed out waiting for active HTTP request count to reach zero"
1694 );
1695 }
1696
1697 pub(super) async fn recv_ws_tunnel_msg(
1698 ws_rx: &mut mpsc::UnboundedReceiver<WsTxMessage>,
1699 ) -> protocol::ToRivetTunnelMessage {
1700 tokio::time::timeout(Duration::from_secs(2), async {
1701 loop {
1702 let Some(msg) = ws_rx.recv().await else {
1703 panic!("websocket channel closed before tunnel message");
1704 };
1705 let WsTxMessage::Send(bytes) = msg else {
1706 continue;
1707 };
1708 let message =
1709 protocol::versioned::ToRivet::deserialize(&bytes, protocol::PROTOCOL_VERSION)
1710 .expect("failed to decode ToRivet message");
1711 if let protocol::ToRivet::ToRivetTunnelMessage(msg) = message {
1712 return msg;
1713 }
1714 }
1715 })
1716 .await
1717 .expect("timed out waiting for tunnel message")
1718 }
1719
1720 pub(super) async fn wait_for_stopped_event(
1721 envoy_rx: &mut mpsc::UnboundedReceiver<ToEnvoyMessage>,
1722 ) {
1723 tokio::time::timeout(Duration::from_secs(2), async {
1724 loop {
1725 let Some(msg) = envoy_rx.recv().await else {
1726 panic!("envoy channel closed before stopped event");
1727 };
1728
1729 if let ToEnvoyMessage::SendEvents { events } = msg {
1730 if events.iter().any(|event| {
1731 matches!(
1732 event.inner,
1733 protocol::Event::EventActorStateUpdate(
1734 protocol::EventActorStateUpdate {
1735 state: protocol::ActorState::ActorStateStopped(_),
1736 }
1737 )
1738 )
1739 }) {
1740 return;
1741 }
1742 }
1743 }
1744 })
1745 .await
1746 .expect("timed out waiting for stopped event");
1747 }
1748
1749 async fn assert_alarm_before_stopped_event(
1750 envoy_rx: &mut mpsc::UnboundedReceiver<ToEnvoyMessage>,
1751 expected_alarm_ts: Option<i64>,
1752 ) {
1753 tokio::time::timeout(Duration::from_secs(2), async {
1754 let mut saw_alarm = false;
1755 loop {
1756 let Some(msg) = envoy_rx.recv().await else {
1757 panic!("envoy channel closed before stopped event");
1758 };
1759
1760 if let ToEnvoyMessage::SendEvents { events } = msg {
1761 for event in events {
1762 match event.inner {
1763 protocol::Event::EventActorSetAlarm(alarm) => {
1764 if alarm.alarm_ts == expected_alarm_ts {
1765 saw_alarm = true;
1766 }
1767 }
1768 protocol::Event::EventActorStateUpdate(
1769 protocol::EventActorStateUpdate {
1770 state: protocol::ActorState::ActorStateStopped(_),
1771 },
1772 ) => {
1773 assert!(saw_alarm, "stopped event arrived before alarm update");
1774 return;
1775 }
1776 _ => {}
1777 }
1778 }
1779 }
1780 }
1781 })
1782 .await
1783 .expect("timed out waiting for stopped event");
1784 }
1785
1786 async fn assert_no_stopped_event(envoy_rx: &mut mpsc::UnboundedReceiver<ToEnvoyMessage>) {
1787 let result = tokio::time::timeout(Duration::from_millis(100), async {
1788 loop {
1789 let Some(msg) = envoy_rx.recv().await else {
1790 panic!("envoy channel closed while waiting for non-stopped event");
1791 };
1792
1793 if let ToEnvoyMessage::SendEvents { events } = msg {
1794 if events.iter().any(|event| {
1795 matches!(
1796 event.inner,
1797 protocol::Event::EventActorStateUpdate(
1798 protocol::EventActorStateUpdate {
1799 state: protocol::ActorState::ActorStateStopped(_),
1800 }
1801 )
1802 )
1803 }) {
1804 panic!("received stopped event before teardown completion");
1805 }
1806 }
1807 }
1808 })
1809 .await;
1810
1811 assert!(
1812 result.is_err(),
1813 "stopped event arrived before teardown completion"
1814 );
1815 }
1816
1817 #[tokio::test]
1818 async fn active_http_request_count_tracks_in_flight_fetches() {
1819 let (fetch_started_tx, fetch_started_rx) = oneshot::channel();
1820 let release_fetch = Arc::new(Notify::new());
1821 let callbacks = Arc::new(TestCallbacks::completing(
1822 fetch_started_tx,
1823 release_fetch.clone(),
1824 ));
1825 let (shared, mut envoy_rx) = build_shared_context(callbacks);
1826 let (actor_tx, active_http_request_count) = create_actor(
1827 shared,
1828 "actor-1".to_string(),
1829 1,
1830 actor_config(),
1831 Vec::new(),
1832 None,
1833 );
1834
1835 actor_tx
1836 .send(ToActor::ReqStart {
1837 message_id: message_id(),
1838 req: request_start(),
1839 connection_session: 1,
1840 })
1841 .expect("failed to send request start");
1842
1843 tokio::time::timeout(Duration::from_secs(2), fetch_started_rx)
1844 .await
1845 .expect("timed out waiting for fetch start")
1846 .expect("fetch start sender dropped");
1847 assert_eq!(active_http_request_count.load(), 1);
1848
1849 release_fetch.notify_waiters();
1850 wait_for_zero(&active_http_request_count).await;
1851
1852 actor_tx
1853 .send(ToActor::Stop {
1854 command_idx: 1,
1855 reason: protocol::StopActorReason::StopIntent,
1856 })
1857 .expect("failed to send stop");
1858 wait_for_stopped_event(&mut envoy_rx).await;
1859 }
1860
1861 #[tokio::test]
1862 async fn actor_stop_aborts_in_flight_http_requests_before_stopped_event() {
1863 let (fetch_started_tx, fetch_started_rx) = oneshot::channel();
1864 let (fetch_dropped_tx, fetch_dropped_rx) = oneshot::channel();
1865 let callbacks = Arc::new(TestCallbacks::hanging(fetch_started_tx, fetch_dropped_tx));
1866 let (shared, mut envoy_rx) = build_shared_context(callbacks);
1867 let (actor_tx, active_http_request_count) = create_actor(
1868 shared,
1869 "actor-2".to_string(),
1870 1,
1871 actor_config(),
1872 Vec::new(),
1873 None,
1874 );
1875
1876 actor_tx
1877 .send(ToActor::ReqStart {
1878 message_id: message_id(),
1879 req: request_start(),
1880 connection_session: 1,
1881 })
1882 .expect("failed to send request start");
1883
1884 tokio::time::timeout(Duration::from_secs(2), fetch_started_rx)
1885 .await
1886 .expect("timed out waiting for fetch start")
1887 .expect("fetch start sender dropped");
1888 assert_eq!(active_http_request_count.load(), 1);
1889
1890 actor_tx
1891 .send(ToActor::Stop {
1892 command_idx: 1,
1893 reason: protocol::StopActorReason::StopIntent,
1894 })
1895 .expect("failed to send stop");
1896
1897 tokio::time::timeout(Duration::from_secs(2), fetch_dropped_rx)
1898 .await
1899 .expect("timed out waiting for fetch abort")
1900 .expect("fetch drop sender dropped");
1901 wait_for_stopped_event(&mut envoy_rx).await;
1902 assert_eq!(active_http_request_count.load(), 0);
1903 }
1904
1905 #[tokio::test]
1906 async fn actor_stop_waits_for_completion_handle_before_stopped_event() {
1907 let (stop_handle_tx, stop_handle_rx) = oneshot::channel();
1908 let callbacks = Arc::new(DeferredStopCallbacks {
1909 stop_handle_tx: Mutex::new(Some(stop_handle_tx)),
1910 });
1911 let (shared, mut envoy_rx) = build_shared_context(callbacks);
1912 let (actor_tx, _active_http_request_count) = create_actor(
1913 shared,
1914 "actor-3".to_string(),
1915 1,
1916 actor_config(),
1917 Vec::new(),
1918 None,
1919 );
1920
1921 actor_tx
1922 .send(ToActor::Stop {
1923 command_idx: 1,
1924 reason: protocol::StopActorReason::StopIntent,
1925 })
1926 .expect("failed to send stop");
1927
1928 let stop_handle = tokio::time::timeout(Duration::from_secs(2), stop_handle_rx)
1929 .await
1930 .expect("timed out waiting for stop handle")
1931 .expect("stop handle sender dropped");
1932 assert_no_stopped_event(&mut envoy_rx).await;
1933
1934 assert!(stop_handle.complete(), "stop handle should complete once");
1935 wait_for_stopped_event(&mut envoy_rx).await;
1936 }
1937
1938 #[tokio::test]
1939 async fn actor_stop_flushes_acknowledged_alarm_before_completion() {
1940 let (stop_handle_tx, stop_handle_rx) = oneshot::channel();
1941 let callbacks = Arc::new(DeferredStopCallbacks {
1942 stop_handle_tx: Mutex::new(Some(stop_handle_tx)),
1943 });
1944 let (shared, mut envoy_rx) = build_shared_context(callbacks);
1945 let (actor_tx, _active_http_request_count) = create_actor(
1946 shared,
1947 "actor-4".to_string(),
1948 1,
1949 actor_config(),
1950 Vec::new(),
1951 None,
1952 );
1953
1954 actor_tx
1955 .send(ToActor::Stop {
1956 command_idx: 1,
1957 reason: protocol::StopActorReason::StopIntent,
1958 })
1959 .expect("failed to send stop");
1960
1961 let stop_handle = tokio::time::timeout(Duration::from_secs(2), stop_handle_rx)
1962 .await
1963 .expect("timed out waiting for stop handle")
1964 .expect("stop handle sender dropped");
1965
1966 let (alarm_ack_tx, alarm_ack_rx) = oneshot::channel();
1967 actor_tx
1968 .send(ToActor::SetAlarm {
1969 alarm_ts: Some(123),
1970 ack_tx: Some(alarm_ack_tx),
1971 })
1972 .expect("failed to send alarm");
1973
1974 tokio::time::timeout(Duration::from_secs(2), alarm_ack_rx)
1975 .await
1976 .expect("timed out waiting for alarm ack")
1977 .expect("alarm ack sender dropped");
1978
1979 assert!(stop_handle.complete(), "stop handle should complete once");
1980 assert_alarm_before_stopped_event(&mut envoy_rx, Some(123)).await;
1981 }
1982}
1983
1984#[cfg(test)]
1985#[path = "../tests/support/actor_http_stream.rs"]
1986mod http_stream_tests;