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