1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::ops::Bound::{Excluded, Unbounded};
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::time::Duration;
7
8use anyhow::{Context, Result};
9use futures::future::BoxFuture;
10use parking_lot::{RwLock, RwLockReadGuard};
11use rivet_error::RivetError;
12use rivetkit_actor_persist::{generated::v4 as persist_v4, versioned as persist_versioned};
13use serde::Serialize;
14use uuid::Uuid;
15
16use tokio::sync::oneshot;
17
18use crate::actor::config::ActorConfig;
19use crate::actor::context::ActorContext;
20use crate::actor::keys::CONN_PREFIX;
21use crate::actor::lifecycle_hooks::Reply;
22use crate::actor::messages::{ActorEvent, Request};
23use crate::actor::persist::{
24 decode_latest_with_embedded_version, encode_latest_with_embedded_version,
25};
26use crate::actor::preload::PreloadedKv;
27use crate::actor::state::RequestSaveOpts;
28use crate::error::ActorRuntime;
29use crate::time::timeout;
30use crate::types::ConnId;
31use crate::types::ListOpts;
32
33pub(crate) type EventSendCallback = Arc<dyn Fn(OutgoingEvent) -> Result<()> + Send + Sync>;
34pub(crate) type DisconnectCallback =
35 Arc<dyn Fn(Option<String>) -> BoxFuture<'static, Result<()>> + Send + Sync>;
36type StateChangeCallback = Arc<dyn Fn(&ConnHandle) + Send + Sync>;
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub(crate) struct OutgoingEvent {
40 pub name: String,
41 pub args: Vec<u8>,
42}
43
44#[derive(Clone, Debug, Default, PartialEq, Eq)]
45pub(crate) struct HibernatableConnectionMetadata {
46 pub gateway_id: [u8; 4],
47 pub request_id: [u8; 4],
48 pub server_message_index: u16,
49 pub client_message_index: u16,
50 pub request_path: String,
51 pub request_headers: BTreeMap<String, String>,
52}
53
54pub(crate) type PersistedSubscription = persist_v4::Subscription;
55pub(crate) type PersistedConnection = persist_v4::Conn;
56
57#[derive(RivetError, Serialize)]
58#[error(
59 "actor",
60 "invalid_request",
61 "Invalid hibernatable websocket connection ID",
62 "Hibernatable websocket {field} must be exactly 4 bytes, got {actual_len}."
63)]
64struct InvalidHibernatableConnectionId {
65 field: String,
66 actual_len: usize,
67}
68
69#[derive(RivetError, Serialize)]
70#[error(
71 "connection",
72 "not_configured",
73 "Connection callback is not configured",
74 "Connection {component} is not configured."
75)]
76struct ConnectionNotConfigured {
77 component: String,
78}
79
80#[derive(RivetError, Serialize)]
81#[error(
82 "connection",
83 "not_found",
84 "Connection was not found",
85 "Connection '{conn_id}' was not found."
86)]
87struct ConnectionNotFound {
88 conn_id: String,
89}
90
91#[derive(RivetError, Serialize)]
92#[error(
93 "connection",
94 "not_hibernatable",
95 "Connection is not hibernatable",
96 "Connection '{conn_id}' is not hibernatable."
97)]
98struct ConnectionNotHibernatable {
99 conn_id: String,
100}
101
102#[derive(RivetError, Serialize)]
103#[error(
104 "connection",
105 "restore_not_found",
106 "Hibernatable connection restore target was not found"
107)]
108struct ConnectionRestoreNotFound;
109
110#[derive(RivetError, Serialize)]
111#[error(
112 "connection",
113 "disconnect_failed",
114 "Connection disconnect failed",
115 "Disconnect transport failed for {count} connection(s): {details}"
116)]
117struct ConnectionDisconnectFailed {
118 count: usize,
119 details: String,
120}
121
122pub(crate) fn hibernatable_id_from_slice(field: &'static str, bytes: &[u8]) -> Result<[u8; 4]> {
123 bytes.try_into().map_err(|_| {
124 InvalidHibernatableConnectionId {
125 field: field.to_owned(),
126 actual_len: bytes.len(),
127 }
128 .build()
129 })
130}
131
132pub(crate) fn encode_persisted_connection(connection: &PersistedConnection) -> Result<Vec<u8>> {
133 encode_latest_with_embedded_version::<persist_versioned::Conn>(
134 connection.clone(),
135 rivetkit_actor_persist::CURRENT_VERSION,
136 "persisted connection",
137 )
138}
139
140pub(crate) fn decode_persisted_connection(payload: &[u8]) -> Result<PersistedConnection> {
141 let connection = decode_latest_with_embedded_version::<persist_versioned::Conn>(
142 payload,
143 "persisted connection",
144 )?;
145 Ok(connection)
146}
147
148#[derive(Clone)]
149pub struct ConnHandle(Arc<ConnHandleInner>);
150
151struct ConnHandleInner {
152 id: ConnId,
153 params: Vec<u8>,
154 state: RwLock<Vec<u8>>,
157 is_hibernatable: bool,
158 dirty: AtomicBool,
159 subscriptions: RwLock<BTreeSet<String>>,
160 hibernation: RwLock<Option<HibernatableConnectionMetadata>>,
161 state_change_handler: RwLock<Option<StateChangeCallback>>,
162 event_sender: RwLock<Option<EventSendCallback>>,
163 transport_disconnect_handler: RwLock<Option<DisconnectCallback>>,
164 disconnect_handler: RwLock<Option<DisconnectCallback>>,
165}
166
167impl ConnHandle {
168 pub fn new(
169 id: impl Into<ConnId>,
170 params: Vec<u8>,
171 state: Vec<u8>,
172 is_hibernatable: bool,
173 ) -> Self {
174 Self(Arc::new(ConnHandleInner {
175 id: id.into(),
176 params,
177 state: RwLock::new(state),
178 is_hibernatable,
179 dirty: AtomicBool::new(false),
180 subscriptions: RwLock::new(BTreeSet::new()),
181 hibernation: RwLock::new(None),
182 state_change_handler: RwLock::new(None),
183 event_sender: RwLock::new(None),
184 transport_disconnect_handler: RwLock::new(None),
185 disconnect_handler: RwLock::new(None),
186 }))
187 }
188
189 pub fn id(&self) -> &str {
190 &self.0.id
191 }
192
193 pub fn params(&self) -> Vec<u8> {
194 self.0.params.clone()
195 }
196
197 pub fn state(&self) -> Vec<u8> {
198 self.0.state.read().clone()
199 }
200
201 pub fn set_state(&self, state: Vec<u8>) {
202 self.set_state_inner(state, true);
203 }
204
205 #[doc(hidden)]
206 pub fn set_state_initial(&self, state: Vec<u8>) {
207 self.set_state_inner(state, false);
208 }
209
210 fn set_state_inner(&self, state: Vec<u8>, mark_dirty: bool) {
211 *self.0.state.write() = state;
212 if mark_dirty {
213 self.mark_hibernation_dirty();
214 }
215 }
216
217 fn mark_hibernation_dirty(&self) {
218 if !self.is_hibernatable() {
219 return;
220 }
221 self.0.dirty.store(true, Ordering::SeqCst);
222 let handler = self.0.state_change_handler.read().clone();
223 if let Some(handler) = handler {
224 handler(self);
225 }
226 }
227
228 pub(crate) fn clear_hibernation_dirty(&self) {
229 self.0.dirty.store(false, Ordering::SeqCst);
230 }
231
232 pub fn is_hibernatable(&self) -> bool {
233 self.0.is_hibernatable
234 }
235
236 pub fn send(&self, name: &str, args: &[u8]) {
237 if let Err(error) = self.try_send(name, args) {
238 tracing::error!(
239 ?error,
240 conn_id = self.id(),
241 event_name = name,
242 "failed to send event to connection"
243 );
244 }
245 }
246
247 pub async fn disconnect(&self, reason: Option<&str>) -> Result<()> {
248 if let Some(handler) = self.transport_disconnect_handler() {
249 handler(reason.map(str::to_owned)).await?;
250 }
251 let handler = self.disconnect_handler()?;
252 handler(reason.map(str::to_owned)).await
253 }
254
255 pub(crate) fn configure_event_sender(&self, event_sender: Option<EventSendCallback>) {
256 *self.0.event_sender.write() = event_sender;
257 }
258
259 pub(crate) fn configure_disconnect_handler(
260 &self,
261 disconnect_handler: Option<DisconnectCallback>,
262 ) {
263 *self.0.disconnect_handler.write() = disconnect_handler;
264 }
265
266 pub(crate) fn configure_transport_disconnect_handler(
267 &self,
268 disconnect_handler: Option<DisconnectCallback>,
269 ) {
270 *self.0.transport_disconnect_handler.write() = disconnect_handler;
271 }
272
273 pub(crate) fn subscribe(&self, event_name: impl Into<String>) -> bool {
274 self.0.subscriptions.write().insert(event_name.into())
275 }
276
277 pub(crate) fn unsubscribe(&self, event_name: &str) -> bool {
278 self.0.subscriptions.write().remove(event_name)
279 }
280
281 pub(crate) fn is_subscribed(&self, event_name: &str) -> bool {
282 self.0.subscriptions.read().contains(event_name)
283 }
284
285 pub(crate) fn subscriptions(&self) -> Vec<String> {
286 self.0.subscriptions.read().iter().cloned().collect()
287 }
288
289 pub(crate) fn clear_subscriptions(&self) {
290 self.0.subscriptions.write().clear();
291 }
292
293 pub(crate) fn configure_hibernation(
294 &self,
295 hibernation: Option<HibernatableConnectionMetadata>,
296 ) {
297 *self.0.hibernation.write() = hibernation;
298 }
299
300 pub(crate) fn hibernation(&self) -> Option<HibernatableConnectionMetadata> {
301 self.0.hibernation.read().clone()
302 }
303
304 pub(crate) fn configure_state_change_handler(&self, handler: Option<StateChangeCallback>) {
305 *self.0.state_change_handler.write() = handler;
306 }
307
308 pub(crate) fn set_server_message_index(
309 &self,
310 message_index: u16,
311 ) -> Option<HibernatableConnectionMetadata> {
312 let mut hibernation = self.0.hibernation.write();
313 let hibernation = hibernation.as_mut()?;
314 hibernation.server_message_index = message_index;
315 Some(hibernation.clone())
316 }
317
318 pub(crate) fn persisted_with_state(&self, state: Vec<u8>) -> Option<PersistedConnection> {
319 let hibernation = self.0.hibernation.read().clone()?;
320
321 Some(PersistedConnection {
322 id: self.id().to_owned(),
323 parameters: self.params(),
324 state,
325 subscriptions: self
326 .subscriptions()
327 .into_iter()
328 .map(|event_name| PersistedSubscription { event_name })
329 .collect(),
330 gateway_id: hibernation.gateway_id,
331 request_id: hibernation.request_id,
332 server_message_index: hibernation.server_message_index,
333 client_message_index: hibernation.client_message_index,
334 request_path: hibernation.request_path,
335 request_headers: hibernation.request_headers.into_iter().collect(),
336 })
337 }
338
339 pub(crate) fn from_persisted(persisted: PersistedConnection) -> Self {
340 let conn = Self::new(
341 persisted.id.clone(),
342 persisted.parameters,
343 persisted.state,
344 true,
345 );
346 conn.configure_hibernation(Some(HibernatableConnectionMetadata {
347 gateway_id: persisted.gateway_id,
348 request_id: persisted.request_id,
349 server_message_index: persisted.server_message_index,
350 client_message_index: persisted.client_message_index,
351 request_path: persisted.request_path,
352 request_headers: persisted.request_headers.into_iter().collect(),
353 }));
354 for subscription in persisted.subscriptions {
355 conn.subscribe(subscription.event_name);
356 }
357 conn.clear_hibernation_dirty();
358 conn
359 }
360
361 pub(crate) fn try_send(&self, name: &str, args: &[u8]) -> Result<()> {
362 let event_sender = self.event_sender()?;
363 event_sender(OutgoingEvent {
364 name: name.to_owned(),
365 args: args.to_vec(),
366 })
367 }
368
369 fn event_sender(&self) -> Result<EventSendCallback> {
370 self.0
371 .event_sender
372 .read()
373 .clone()
374 .ok_or_else(|| connection_not_configured("event sender"))
375 }
376
377 fn disconnect_handler(&self) -> Result<DisconnectCallback> {
378 self.0
379 .disconnect_handler
380 .read()
381 .clone()
382 .ok_or_else(|| connection_not_configured("disconnect handler"))
383 }
384
385 pub(crate) fn managed_disconnect_handler(&self) -> Result<DisconnectCallback> {
386 self.disconnect_handler()
387 }
388
389 pub(crate) async fn disconnect_transport_only(&self) -> Result<()> {
390 let Some(handler) = self.transport_disconnect_handler() else {
391 return Ok(());
392 };
393 handler(None).await
394 }
395
396 fn transport_disconnect_handler(&self) -> Option<DisconnectCallback> {
397 self.0.transport_disconnect_handler.read().clone()
398 }
399}
400
401impl Default for ConnHandle {
402 fn default() -> Self {
403 Self::new("", Vec::new(), Vec::new(), false)
404 }
405}
406
407impl fmt::Debug for ConnHandle {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 f.debug_struct("ConnHandle")
410 .field("id", &self.0.id)
411 .field("is_hibernatable", &self.0.is_hibernatable)
412 .field("subscriptions", &self.subscriptions())
413 .finish()
414 }
415}
416
417#[derive(Default)]
418pub(crate) struct PendingHibernationChanges {
419 pub updated: BTreeSet<ConnId>,
420 pub removed: BTreeSet<ConnId>,
421}
422
423#[must_use = "connection iterators hold a read lock until dropped"]
429pub struct ConnHandles<'a> {
430 guard: RwLockReadGuard<'a, BTreeMap<ConnId, ConnHandle>>,
431 next_after: Option<ConnId>,
432}
433
434impl<'a> ConnHandles<'a> {
435 fn new(guard: RwLockReadGuard<'a, BTreeMap<ConnId, ConnHandle>>) -> Self {
436 Self {
437 guard,
438 next_after: None,
439 }
440 }
441
442 pub fn len(&self) -> usize {
443 self.guard.len()
444 }
445
446 pub fn is_empty(&self) -> bool {
447 self.guard.is_empty()
448 }
449}
450
451impl Iterator for ConnHandles<'_> {
452 type Item = ConnHandle;
453
454 fn next(&mut self) -> Option<Self::Item> {
455 let (conn_id, conn) = match self.next_after.as_ref() {
456 Some(conn_id) => self
457 .guard
458 .range((Excluded(conn_id.clone()), Unbounded))
459 .next()?,
460 None => self.guard.iter().next()?,
461 };
462 self.next_after = Some(conn_id.clone());
463 Some(conn.clone())
464 }
465}
466
467impl ActorContext {
468 pub(crate) fn configure_connection_storage(&self, config: ActorConfig) {
469 *self.0.connection_config.write() = config;
470 }
471
472 pub(crate) fn iter_connections(&self) -> ConnHandles<'_> {
473 ConnHandles::new(self.0.connections.read())
474 }
475
476 pub(crate) fn active_connection_count(&self) -> u32 {
477 self.0
478 .connections
479 .read()
480 .len()
481 .try_into()
482 .unwrap_or(u32::MAX)
483 }
484
485 pub(crate) fn insert_existing(&self, conn: ConnHandle) {
486 let conn_id = conn.id().to_owned();
487 let is_hibernatable = conn.is_hibernatable();
488 let active_count = {
489 let mut connections = self.0.connections.write();
490 connections.insert(conn_id.clone(), conn);
491 connections.len()
492 };
493 self.0.metrics.set_active_connections(active_count);
494 tracing::debug!(
495 actor_id = %self.actor_id(),
496 conn_id = %conn_id,
497 is_hibernatable,
498 active_count,
499 "connection added"
500 );
501 }
502
503 pub(crate) fn remove_existing(&self, conn_id: &str) -> Option<ConnHandle> {
504 let (removed, active_count) = {
505 let mut connections = self.0.connections.write();
506 let removed = connections.remove(conn_id);
507 (removed, connections.len())
508 };
509 self.0.metrics.set_active_connections(active_count);
510 tracing::debug!(
511 actor_id = %self.actor_id(),
512 conn_id,
513 removed = removed.is_some(),
514 active_count,
515 "connection removed"
516 );
517 removed
518 }
519
520 fn remove_existing_for_disconnect(&self, conn_id: &str) -> Option<ConnHandle> {
521 let _disconnect_state = self.0.connection_disconnect_state.lock();
522 let (removed, active_count) = {
523 let mut connections = self.0.connections.write();
524 let removed = connections.remove(conn_id)?;
525
526 if removed.is_hibernatable() {
527 self.0.pending_hibernation_updates.write().remove(conn_id);
528 self.0
529 .pending_hibernation_removals
530 .write()
531 .insert(conn_id.to_owned());
532 }
533
534 (removed, connections.len())
535 };
536 self.0.metrics.set_active_connections(active_count);
537 tracing::debug!(
538 actor_id = %self.actor_id(),
539 conn_id,
540 is_hibernatable = removed.is_hibernatable(),
541 active_count,
542 "connection removed for disconnect"
543 );
544 Some(removed)
545 }
546
547 pub(crate) fn queue_hibernation_update(&self, conn_id: impl Into<ConnId>) {
548 let _disconnect_state = self.0.connection_disconnect_state.lock();
549 let conn_id = conn_id.into();
550 self.0
551 .pending_hibernation_updates
552 .write()
553 .insert(conn_id.clone());
554 self.0.pending_hibernation_removals.write().remove(&conn_id);
555 tracing::debug!(
556 actor_id = %self.actor_id(),
557 conn_id = %conn_id,
558 "hibernatable connection transport queued for save"
559 );
560 }
561
562 pub(crate) fn dirty_hibernatable_conns_inner(&self) -> Vec<ConnHandle> {
563 let _disconnect_state = self.0.connection_disconnect_state.lock();
564 let update_ids: Vec<_> = self
565 .0
566 .pending_hibernation_updates
567 .read()
568 .iter()
569 .cloned()
570 .collect();
571 let connections = self.0.connections.read();
572 update_ids
573 .into_iter()
574 .filter_map(|conn_id| connections.get(&conn_id).cloned())
575 .filter(|conn| conn.is_hibernatable() && conn.hibernation().is_some())
576 .collect()
577 }
578
579 pub(crate) fn queue_hibernation_removal_inner(&self, conn_id: impl Into<ConnId>) {
580 let _disconnect_state = self.0.connection_disconnect_state.lock();
581 let conn_id = conn_id.into();
582 self.0.pending_hibernation_updates.write().remove(&conn_id);
583 self.0
584 .pending_hibernation_removals
585 .write()
586 .insert(conn_id.clone());
587 tracing::debug!(
588 actor_id = %self.actor_id(),
589 conn_id = %conn_id,
590 "hibernatable connection transport queued for removal"
591 );
592 }
593
594 pub(crate) fn take_pending_hibernation_changes_inner(&self) -> PendingHibernationChanges {
595 let _disconnect_state = self.0.connection_disconnect_state.lock();
596 PendingHibernationChanges {
597 updated: std::mem::take(&mut *self.0.pending_hibernation_updates.write()),
598 removed: std::mem::take(&mut *self.0.pending_hibernation_removals.write()),
599 }
600 }
601
602 pub(crate) fn pending_hibernation_removals(&self) -> Vec<ConnId> {
603 let _disconnect_state = self.0.connection_disconnect_state.lock();
604 self.0
605 .pending_hibernation_removals
606 .read()
607 .iter()
608 .cloned()
609 .collect()
610 }
611
612 pub(crate) fn has_pending_hibernation_changes_inner(&self) -> bool {
613 let _disconnect_state = self.0.connection_disconnect_state.lock();
614 let has_updates = !self.0.pending_hibernation_updates.read().is_empty();
615 let has_removals = !self.0.pending_hibernation_removals.read().is_empty();
616 has_updates || has_removals
617 }
618
619 pub(crate) fn restore_pending_hibernation_changes(&self, pending: PendingHibernationChanges) {
620 let _disconnect_state = self.0.connection_disconnect_state.lock();
621 if !pending.updated.is_empty() {
622 self.0
623 .pending_hibernation_updates
624 .write()
625 .extend(pending.updated);
626 }
627 if !pending.removed.is_empty() {
628 self.0
629 .pending_hibernation_removals
630 .write()
631 .extend(pending.removed);
632 }
633 }
634
635 pub(crate) async fn connect_with_state<F>(
636 &self,
637 params: Vec<u8>,
638 is_hibernatable: bool,
639 hibernation: Option<HibernatableConnectionMetadata>,
640 request: Option<Request>,
641 create_state: F,
642 ) -> Result<ConnHandle>
643 where
644 F: std::future::Future<Output = Result<Vec<u8>>> + Send,
645 {
646 self.connect_with_state_and_prepare(
647 params,
648 is_hibernatable,
649 hibernation,
650 request,
651 create_state,
652 |_| Ok(()),
653 )
654 .await
655 }
656
657 pub(crate) async fn connect_with_state_and_prepare<F, P>(
658 &self,
659 params: Vec<u8>,
660 is_hibernatable: bool,
661 hibernation: Option<HibernatableConnectionMetadata>,
662 request: Option<Request>,
663 create_state: F,
664 prepare_connection: P,
665 ) -> Result<ConnHandle>
666 where
667 F: std::future::Future<Output = Result<Vec<u8>>> + Send,
668 P: FnOnce(&ConnHandle) -> Result<()>,
669 {
670 let config = self.connection_config();
671
672 let state = timeout(config.create_conn_state_timeout, create_state)
673 .await
674 .with_context(|| {
675 timeout_message("create_conn_state", config.create_conn_state_timeout)
676 })??;
677
678 let conn = ConnHandle::new(
679 Uuid::new_v4().to_string(),
680 params.clone(),
681 state,
682 is_hibernatable,
683 );
684 conn.configure_hibernation(hibernation);
685 self.prepare_managed_conn(&conn);
686
687 if let Err(error) = prepare_connection(&conn) {
688 return Err(error);
689 }
690
691 self.emit_connection_preflight(&conn, params.clone(), request.clone())
692 .await?;
693 self.insert_existing(conn.clone());
694
695 if let Err(error) = self.emit_connection_open(&conn, request).await {
696 self.remove_existing(conn.id());
697 return Err(error);
698 }
699 self.0.metrics.inc_connections_total();
700 self.record_connections_updated();
701 self.reset_sleep_timer();
702
703 Ok(conn)
704 }
705
706 pub(crate) fn encode_hibernation_delta(
707 &self,
708 conn_id: &str,
709 bytes: Vec<u8>,
710 ) -> Result<Vec<u8>> {
711 let conn = self.connection(conn_id).ok_or_else(|| {
712 ConnectionNotFound {
713 conn_id: conn_id.to_owned(),
714 }
715 .build()
716 })?;
717 let persisted = conn.persisted_with_state(bytes).ok_or_else(|| {
718 ConnectionNotHibernatable {
719 conn_id: conn_id.to_owned(),
720 }
721 .build()
722 })?;
723 encode_persisted_connection(&persisted).context("encode persisted connection")
724 }
725
726 pub(crate) async fn restore_persisted(
727 &self,
728 preloaded_kv: Option<&PreloadedKv>,
729 ) -> Result<Vec<ConnHandle>> {
730 let entries =
731 if let Some(entries) = preloaded_kv.and_then(|kv| kv.prefix_entries(&CONN_PREFIX)) {
732 entries
733 } else {
734 self.0
735 .kv
736 .list_prefix(
737 &CONN_PREFIX,
738 ListOpts {
739 reverse: false,
740 limit: None,
741 },
742 )
743 .await?
744 };
745 let mut restored = Vec::new();
746
747 for (_key, value) in entries {
748 match decode_persisted_connection(&value) {
749 Ok(persisted) => {
750 let conn = ConnHandle::from_persisted(persisted);
751 self.prepare_managed_conn(&conn);
752 self.insert_existing(conn.clone());
753 tracing::debug!(
754 actor_id = %self.actor_id(),
755 conn_id = conn.id(),
756 "hibernatable connection restored"
757 );
758 restored.push(conn);
759 }
760 Err(error) => {
761 tracing::error!(?error, "failed to decode persisted connection");
762 }
763 }
764 }
765
766 Ok(restored)
767 }
768
769 pub(crate) fn reconnect_hibernatable(
770 &self,
771 gateway_id: &[u8],
772 request_id: &[u8],
773 ) -> Result<ConnHandle> {
774 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
775 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
776 let Some(conn) = self
777 .iter_connections()
778 .find(|conn| match conn.hibernation() {
779 Some(hibernation) => {
780 hibernation.gateway_id == gateway_id && hibernation.request_id == request_id
781 }
782 None => false,
783 })
784 else {
785 return Err(ConnectionRestoreNotFound.build());
786 };
787
788 self.record_connections_updated();
789 self.reset_sleep_timer();
790 tracing::debug!(
791 actor_id = %self.actor_id(),
792 conn_id = conn.id(),
793 "hibernatable connection transport restored"
794 );
795 Ok(conn)
796 }
797
798 fn prepare_managed_conn(&self, conn: &ConnHandle) {
799 let ctx = self.downgrade();
800 let conn_id = conn.id().to_owned();
801
802 conn.configure_state_change_handler(Some(Arc::new({
803 let ctx = ctx.clone();
804 move |conn| {
805 let Some(ctx) = ActorContext::from_weak(&ctx) else {
806 tracing::warn!(
807 conn_id = conn.id(),
808 "skipping hibernatable connection state save without actor context"
809 );
810 return;
811 };
812 ctx.queue_hibernation_update(conn.id().to_owned());
813 ctx.request_save(RequestSaveOpts::default());
814 }
815 })));
816
817 conn.configure_disconnect_handler(Some(Arc::new(move |reason| {
818 let ctx = ctx.clone();
819 let conn_id = conn_id.clone();
820 Box::pin(async move {
821 let ctx = ActorContext::from_weak(&ctx).ok_or_else(|| {
822 ActorRuntime::NotConfigured {
823 component: "actor context".to_owned(),
824 }
825 .build()
826 })?;
827 ctx.with_disconnect_callback(|| async {
828 ctx.disconnect_managed(&conn_id, reason).await
829 })
830 .await
831 })
832 })));
833 }
834
835 fn connection_config(&self) -> ActorConfig {
836 self.0.connection_config.read().clone()
837 }
838
839 #[cfg(test)]
840 pub(crate) fn connection_config_for_tests(&self) -> ActorConfig {
841 self.connection_config()
842 }
843
844 async fn disconnect_managed(&self, conn_id: &str, reason: Option<String>) -> Result<()> {
845 let Some(conn) = self.remove_existing_for_disconnect(conn_id) else {
846 tracing::debug!(
847 actor_id = %self.actor_id(),
848 conn_id,
849 reason = ?reason.as_deref(),
850 "connection disconnect skipped because connection was already removed"
851 );
852 return Ok(());
853 };
854 conn.clear_subscriptions();
855
856 self.try_send_actor_event(ActorEvent::ConnectionClosed { conn }, "connection_closed")
857 .with_context(|| disconnect_message(conn_id, reason.as_deref()))?;
858
859 self.record_connections_updated();
860 self.reset_sleep_timer();
861 tracing::debug!(
862 actor_id = %self.actor_id(),
863 conn_id,
864 reason = ?reason.as_deref(),
865 "connection disconnected"
866 );
867 Ok(())
868 }
869
870 async fn emit_connection_open(
871 &self,
872 conn: &ConnHandle,
873 request: Option<Request>,
874 ) -> Result<()> {
875 let config = self.connection_config();
876 let (reply_tx, reply_rx) = oneshot::channel();
877 self.try_send_actor_event(
878 ActorEvent::ConnectionOpen {
879 conn: conn.clone(),
880 request,
881 reply: Reply::from(reply_tx),
882 },
883 "connection_open",
884 )?;
885 timeout(config.on_connect_timeout, reply_rx)
886 .await
887 .with_context(|| timeout_message("connection_open", config.on_connect_timeout))?
888 .context("receive connection_open reply")??;
889 Ok(())
890 }
891
892 async fn emit_connection_preflight(
893 &self,
894 conn: &ConnHandle,
895 params: Vec<u8>,
896 request: Option<Request>,
897 ) -> Result<()> {
898 let config = self.connection_config();
899 let timeout_duration = config
900 .on_before_connect_timeout
901 .saturating_add(config.create_conn_state_timeout);
902 let (reply_tx, reply_rx) = oneshot::channel();
903 self.try_send_actor_event(
904 ActorEvent::ConnectionPreflight {
905 conn: conn.clone(),
906 params,
907 request,
908 reply: Reply::from(reply_tx),
909 },
910 "connection_preflight",
911 )?;
912 timeout(timeout_duration, reply_rx)
913 .await
914 .with_context(|| timeout_message("connection_preflight", timeout_duration))?
915 .context("receive connection_preflight reply")??;
916 Ok(())
917 }
918
919 pub(crate) fn connection(&self, conn_id: &str) -> Option<ConnHandle> {
920 self.0.connections.read().get(conn_id).cloned()
921 }
922
923 pub(crate) async fn disconnect_transport_only<F>(&self, mut predicate: F) -> Result<()>
924 where
925 F: FnMut(&ConnHandle) -> bool,
926 {
927 let connections: Vec<_> = self
928 .iter_connections()
929 .filter(|conn| predicate(conn))
930 .collect();
931 let mut disconnected_ids = Vec::new();
932 let mut failures = Vec::new();
933
934 for conn in &connections {
935 match conn.disconnect_transport_only().await {
936 Ok(()) => {
937 tracing::debug!(
938 actor_id = %self.actor_id(),
939 conn_id = conn.id(),
940 "connection transport disconnect completed"
941 );
942 disconnected_ids.push(conn.id().to_owned());
943 }
944 Err(error) => {
945 tracing::error!(
946 conn_id = %conn.id(),
947 ?error,
948 "failed transport-only connection disconnect"
949 );
950 failures.push((conn.id().to_owned(), format!("{error:#}")));
951 }
952 }
953 }
954
955 let mut removed_any = false;
956 for conn_id in disconnected_ids {
957 let Some(conn) = self.remove_existing_for_disconnect(&conn_id) else {
958 tracing::debug!(
959 actor_id = %self.actor_id(),
960 conn_id = %conn_id,
961 "connection transport removal skipped because connection was already removed"
962 );
963 continue;
964 };
965 conn.clear_subscriptions();
966 removed_any = true;
967 tracing::debug!(
968 actor_id = %self.actor_id(),
969 conn_id = %conn_id,
970 "connection transport removed"
971 );
972 }
973
974 if removed_any {
975 self.record_connections_updated();
976 self.reset_sleep_timer();
977 }
978
979 if failures.is_empty() {
980 return Ok(());
981 }
982
983 let count = failures.len();
984 Err(ConnectionDisconnectFailed {
985 count,
986 details: failures
987 .into_iter()
988 .map(|(conn_id, error)| format!("{conn_id}: {error}"))
989 .collect::<Vec<_>>()
990 .join("; "),
991 }
992 .build())
993 }
994}
995
996fn connection_not_configured(component: &str) -> anyhow::Error {
997 ConnectionNotConfigured {
998 component: component.to_owned(),
999 }
1000 .build()
1001}
1002
1003fn timeout_message(callback_name: &str, timeout: Duration) -> String {
1004 format!(
1005 "`{callback_name}` timed out after {} ms",
1006 timeout.as_millis()
1007 )
1008}
1009
1010fn disconnect_message(conn_id: &str, reason: Option<&str>) -> String {
1011 match reason {
1012 Some(reason) => format!("disconnect connection `{conn_id}` with reason `{reason}`"),
1013 None => format!("disconnect connection `{conn_id}`"),
1014 }
1015}
1016
1017#[cfg(test)]
1019#[path = "../../tests/connection.rs"]
1020mod tests;