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().into(),
363 parameters: self.params().into(),
364 state: state.into(),
365 subscriptions: self
366 .subscriptions()
367 .into_iter()
368 .map(|event_name| PersistedSubscription {
369 event_name: event_name.into(),
370 })
371 .collect(),
372 gateway_id: hibernation.gateway_id,
373 request_id: hibernation.request_id,
374 server_message_index: hibernation.server_message_index,
375 client_message_index: hibernation.client_message_index,
376 request_path: hibernation.request_path.into(),
377 request_headers: hibernation
378 .request_headers
379 .into_iter()
380 .map(|(name, value)| (name.into(), value.into()))
381 .collect(),
382 })
383 }
384
385 pub(crate) fn from_persisted(persisted: PersistedConnection) -> Self {
386 let conn = Self::new(
387 persisted.id.to_string(),
388 persisted.parameters.to_vec(),
389 persisted.state.to_vec(),
390 true,
391 );
392 conn.configure_hibernation(Some(HibernatableConnectionMetadata {
393 gateway_id: persisted.gateway_id,
394 request_id: persisted.request_id,
395 server_message_index: persisted.server_message_index,
396 client_message_index: persisted.client_message_index,
397 request_path: persisted.request_path.to_string(),
398 request_headers: persisted
399 .request_headers
400 .into_iter()
401 .map(|(name, value)| (name.to_string(), value.to_string()))
402 .collect(),
403 }));
404 for subscription in persisted.subscriptions {
405 conn.subscribe(subscription.event_name);
406 }
407 conn.clear_hibernation_dirty();
408 conn
409 }
410
411 pub(crate) fn try_send(&self, name: &str, args: &[u8]) -> Result<()> {
412 let event_sender = self.event_sender()?;
413 event_sender(OutgoingEvent {
414 name: name.to_owned(),
415 args: args.to_vec(),
416 })
417 }
418
419 fn event_sender(&self) -> Result<EventSendCallback> {
420 self.0
421 .event_sender
422 .read()
423 .clone()
424 .ok_or_else(|| connection_not_configured("event sender"))
425 }
426
427 fn disconnect_handler(&self) -> Result<DisconnectCallback> {
428 self.0
429 .disconnect_handler
430 .read()
431 .clone()
432 .ok_or_else(|| connection_not_configured("disconnect handler"))
433 }
434
435 pub(crate) fn managed_disconnect_handler(&self) -> Result<DisconnectCallback> {
436 self.disconnect_handler()
437 }
438
439 pub(crate) async fn disconnect_transport_only(&self) -> Result<()> {
440 let Some(handler) = self.transport_disconnect_handler() else {
441 return Ok(());
442 };
443 handler(None).await
444 }
445
446 fn transport_disconnect_handler(&self) -> Option<DisconnectCallback> {
447 self.0.transport_disconnect_handler.read().clone()
448 }
449}
450
451impl Default for ConnHandle {
452 fn default() -> Self {
453 Self::new("", Vec::new(), Vec::new(), false)
454 }
455}
456
457impl fmt::Debug for ConnHandle {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 f.debug_struct("ConnHandle")
460 .field("id", &self.0.id)
461 .field("is_hibernatable", &self.0.is_hibernatable)
462 .field("subscriptions", &self.subscriptions())
463 .finish()
464 }
465}
466
467#[derive(Default)]
468pub(crate) struct PendingHibernationChanges {
469 pub updated: BTreeSet<ConnId>,
470 pub removed: BTreeSet<ConnId>,
471}
472
473#[must_use = "connection iterators hold a read lock until dropped"]
479pub struct ConnHandles<'a> {
480 guard: RwLockReadGuard<'a, BTreeMap<ConnId, ConnHandle>>,
481 next_after: Option<ConnId>,
482}
483
484impl<'a> ConnHandles<'a> {
485 fn new(guard: RwLockReadGuard<'a, BTreeMap<ConnId, ConnHandle>>) -> Self {
486 Self {
487 guard,
488 next_after: None,
489 }
490 }
491
492 pub fn len(&self) -> usize {
493 self.guard.len()
494 }
495
496 pub fn is_empty(&self) -> bool {
497 self.guard.is_empty()
498 }
499}
500
501impl Iterator for ConnHandles<'_> {
502 type Item = ConnHandle;
503
504 fn next(&mut self) -> Option<Self::Item> {
505 let (conn_id, conn) = match self.next_after.as_ref() {
506 Some(conn_id) => self
507 .guard
508 .range((Excluded(conn_id.clone()), Unbounded))
509 .next()?,
510 None => self.guard.iter().next()?,
511 };
512 self.next_after = Some(conn_id.clone());
513 Some(conn.clone())
514 }
515}
516
517impl ActorContext {
518 pub(crate) fn configure_connection_storage(&self, config: ActorConfig) {
519 *self.0.connection_config.write() = config;
520 }
521
522 pub(crate) fn iter_connections(&self) -> ConnHandles<'_> {
523 ConnHandles::new(self.0.connections.read())
524 }
525
526 pub(crate) fn active_connection_count(&self) -> u32 {
527 self.0
528 .connections
529 .read()
530 .len()
531 .try_into()
532 .unwrap_or(u32::MAX)
533 }
534
535 pub(crate) fn insert_existing(&self, conn: ConnHandle) {
536 let conn_id = conn.id().to_owned();
537 let is_hibernatable = conn.is_hibernatable();
538 let active_count = {
539 let mut connections = self.0.connections.write();
540 connections.insert(conn_id.clone(), conn);
541 connections.len()
542 };
543 self.0.metrics.set_active_connections(active_count);
544 tracing::debug!(
545 actor_id = %self.actor_id(),
546 conn_id = %conn_id,
547 is_hibernatable,
548 active_count,
549 "connection added"
550 );
551 }
552
553 pub(crate) fn remove_existing(&self, conn_id: &str) -> Option<ConnHandle> {
554 let (removed, active_count) = {
555 let mut connections = self.0.connections.write();
556 let removed = connections.remove(conn_id);
557 (removed, connections.len())
558 };
559 self.0.metrics.set_active_connections(active_count);
560 tracing::debug!(
561 actor_id = %self.actor_id(),
562 conn_id,
563 removed = removed.is_some(),
564 active_count,
565 "connection removed"
566 );
567 removed
568 }
569
570 fn remove_existing_for_disconnect(&self, conn_id: &str) -> Option<ConnHandle> {
571 let _disconnect_state = self.0.connection_disconnect_state.lock();
572 let (removed, active_count) = {
573 let mut connections = self.0.connections.write();
574 let removed = connections.remove(conn_id)?;
575
576 if removed.is_hibernatable() {
577 self.0.pending_hibernation_updates.write().remove(conn_id);
578 self.0
579 .pending_hibernation_removals
580 .write()
581 .insert(conn_id.to_owned());
582 }
583
584 (removed, connections.len())
585 };
586 self.0.metrics.set_active_connections(active_count);
587 tracing::debug!(
588 actor_id = %self.actor_id(),
589 conn_id,
590 is_hibernatable = removed.is_hibernatable(),
591 active_count,
592 "connection removed for disconnect"
593 );
594 Some(removed)
595 }
596
597 pub(crate) fn queue_hibernation_update(&self, conn_id: impl Into<ConnId>) {
598 let _disconnect_state = self.0.connection_disconnect_state.lock();
599 let conn_id = conn_id.into();
600 self.0
601 .pending_hibernation_updates
602 .write()
603 .insert(conn_id.clone());
604 self.0.pending_hibernation_removals.write().remove(&conn_id);
605 tracing::debug!(
606 actor_id = %self.actor_id(),
607 conn_id = %conn_id,
608 "hibernatable connection transport queued for save"
609 );
610 }
611
612 pub(crate) fn dirty_hibernatable_conns_inner(&self) -> Vec<ConnHandle> {
613 let _disconnect_state = self.0.connection_disconnect_state.lock();
614 let update_ids: Vec<_> = self
615 .0
616 .pending_hibernation_updates
617 .read()
618 .iter()
619 .cloned()
620 .collect();
621 let connections = self.0.connections.read();
622 update_ids
623 .into_iter()
624 .filter_map(|conn_id| connections.get(&conn_id).cloned())
625 .filter(|conn| conn.is_hibernatable() && conn.hibernation().is_some())
626 .collect()
627 }
628
629 pub(crate) fn queue_hibernation_removal_inner(&self, conn_id: impl Into<ConnId>) {
630 let _disconnect_state = self.0.connection_disconnect_state.lock();
631 let conn_id = conn_id.into();
632 self.0.pending_hibernation_updates.write().remove(&conn_id);
633 self.0
634 .pending_hibernation_removals
635 .write()
636 .insert(conn_id.clone());
637 tracing::debug!(
638 actor_id = %self.actor_id(),
639 conn_id = %conn_id,
640 "hibernatable connection transport queued for removal"
641 );
642 }
643
644 pub(crate) fn take_pending_hibernation_changes_inner(&self) -> PendingHibernationChanges {
645 let _disconnect_state = self.0.connection_disconnect_state.lock();
646 PendingHibernationChanges {
647 updated: std::mem::take(&mut *self.0.pending_hibernation_updates.write()),
648 removed: std::mem::take(&mut *self.0.pending_hibernation_removals.write()),
649 }
650 }
651
652 pub(crate) fn pending_hibernation_removals(&self) -> Vec<ConnId> {
653 let _disconnect_state = self.0.connection_disconnect_state.lock();
654 self.0
655 .pending_hibernation_removals
656 .read()
657 .iter()
658 .cloned()
659 .collect()
660 }
661
662 pub(crate) fn has_pending_hibernation_changes_inner(&self) -> bool {
663 let _disconnect_state = self.0.connection_disconnect_state.lock();
664 let has_updates = !self.0.pending_hibernation_updates.read().is_empty();
665 let has_removals = !self.0.pending_hibernation_removals.read().is_empty();
666 has_updates || has_removals
667 }
668
669 pub(crate) fn restore_pending_hibernation_changes(&self, pending: PendingHibernationChanges) {
670 let _disconnect_state = self.0.connection_disconnect_state.lock();
671 if !pending.updated.is_empty() {
672 self.0
673 .pending_hibernation_updates
674 .write()
675 .extend(pending.updated);
676 }
677 if !pending.removed.is_empty() {
678 self.0
679 .pending_hibernation_removals
680 .write()
681 .extend(pending.removed);
682 }
683 }
684
685 pub(crate) async fn connect_with_state<F>(
686 &self,
687 params: Vec<u8>,
688 is_hibernatable: bool,
689 hibernation: Option<HibernatableConnectionMetadata>,
690 request: Option<Request>,
691 create_state: F,
692 ) -> Result<ConnHandle>
693 where
694 F: std::future::Future<Output = Result<Vec<u8>>> + Send,
695 {
696 self.connect_with_state_and_prepare(
697 params,
698 is_hibernatable,
699 hibernation,
700 request,
701 create_state,
702 |_| Ok(()),
703 )
704 .await
705 }
706
707 pub(crate) async fn connect_with_state_and_prepare<F, P>(
708 &self,
709 params: Vec<u8>,
710 is_hibernatable: bool,
711 hibernation: Option<HibernatableConnectionMetadata>,
712 request: Option<Request>,
713 create_state: F,
714 prepare_connection: P,
715 ) -> Result<ConnHandle>
716 where
717 F: std::future::Future<Output = Result<Vec<u8>>> + Send,
718 P: FnOnce(&ConnHandle) -> Result<()>,
719 {
720 let config = self.connection_config();
721
722 let state = timeout(config.create_conn_state_timeout, create_state)
723 .await
724 .with_context(|| {
725 timeout_message("create_conn_state", config.create_conn_state_timeout)
726 })??;
727
728 let conn = ConnHandle::new(
729 Uuid::new_v4().to_string(),
730 params.clone(),
731 state,
732 is_hibernatable,
733 );
734 conn.configure_hibernation(hibernation);
735 self.prepare_managed_conn(&conn);
736
737 if let Err(error) = prepare_connection(&conn) {
738 return Err(error);
739 }
740
741 self.emit_connection_preflight(&conn, params.clone(), request.clone())
742 .await?;
743 self.insert_existing(conn.clone());
744
745 let opening = DisconnectOnDrop::new(conn.clone());
747 if let Err(error) = self.emit_connection_open(&conn, request).await {
748 opening.disarm();
749 self.remove_existing(conn.id());
750 return Err(error);
751 }
752 opening.disarm();
753 self.0.metrics.inc_connections_total();
754 self.record_connections_updated();
755 self.reset_sleep_timer();
756
757 Ok(conn)
758 }
759
760 pub(crate) fn encode_hibernation_delta(
761 &self,
762 conn_id: &str,
763 bytes: Vec<u8>,
764 ) -> Result<Vec<u8>> {
765 let conn = self.connection(conn_id).ok_or_else(|| {
766 ConnectionNotFound {
767 conn_id: conn_id.to_owned(),
768 }
769 .build()
770 })?;
771 let persisted = conn.persisted_with_state(bytes).ok_or_else(|| {
772 ConnectionNotHibernatable {
773 conn_id: conn_id.to_owned(),
774 }
775 .build()
776 })?;
777 encode_persisted_connection(&persisted).context("encode persisted connection")
778 }
779
780 pub(crate) async fn restore_persisted(&self) -> Result<Vec<ConnHandle>> {
781 let persisted_connections = internal_storage::load_connections(self.sql())
782 .await
783 .context("load hibernatable connections from sqlite")?;
784 let mut restored = Vec::new();
785
786 for persisted in persisted_connections {
787 let conn = ConnHandle::from_persisted(persisted);
788 self.prepare_managed_conn(&conn);
789 self.insert_existing(conn.clone());
790 tracing::debug!(
791 actor_id = %self.actor_id(),
792 conn_id = conn.id(),
793 "hibernatable connection restored"
794 );
795 restored.push(conn);
796 }
797
798 Ok(restored)
799 }
800
801 pub(crate) fn reconnect_hibernatable(
802 &self,
803 gateway_id: &[u8],
804 request_id: &[u8],
805 ) -> Result<ConnHandle> {
806 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
807 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
808 let Some(conn) = self
809 .iter_connections()
810 .find(|conn| match conn.hibernation() {
811 Some(hibernation) => {
812 hibernation.gateway_id == gateway_id && hibernation.request_id == request_id
813 }
814 None => false,
815 })
816 else {
817 return Err(ConnectionRestoreNotFound.build());
818 };
819
820 self.record_connections_updated();
821 self.reset_sleep_timer();
822 tracing::debug!(
823 actor_id = %self.actor_id(),
824 conn_id = conn.id(),
825 "hibernatable connection transport restored"
826 );
827 Ok(conn)
828 }
829
830 fn prepare_managed_conn(&self, conn: &ConnHandle) {
831 let ctx = self.downgrade();
832 let conn_id = conn.id().to_owned();
833
834 conn.configure_state_change_handler(Some(Arc::new({
835 let ctx = ctx.clone();
836 move |conn| {
837 let Some(ctx) = ActorContext::from_weak(&ctx) else {
838 tracing::warn!(
839 conn_id = conn.id(),
840 "skipping hibernatable connection state save without actor context"
841 );
842 return;
843 };
844 ctx.queue_hibernation_update(conn.id().to_owned());
845 ctx.request_save(RequestSaveOpts::default());
846 }
847 })));
848
849 conn.configure_disconnect_handler(Some(Arc::new(move |reason| {
850 let ctx = ctx.clone();
851 let conn_id = conn_id.clone();
852 Box::pin(async move {
853 let ctx = ActorContext::from_weak(&ctx).ok_or_else(|| {
854 ActorRuntime::NotConfigured {
855 component: "actor context".to_owned(),
856 }
857 .build()
858 })?;
859 ctx.with_disconnect_callback(|| async {
860 ctx.disconnect_managed(&conn_id, reason).await
861 })
862 .await
863 })
864 })));
865 }
866
867 fn connection_config(&self) -> ActorConfig {
868 self.0.connection_config.read().clone()
869 }
870
871
872
873 async fn disconnect_managed(&self, conn_id: &str, reason: Option<String>) -> Result<()> {
874 let Some(conn) = self.remove_existing_for_disconnect(conn_id) else {
875 tracing::debug!(
876 actor_id = %self.actor_id(),
877 conn_id,
878 reason = ?reason.as_deref(),
879 "connection disconnect skipped because connection was already removed"
880 );
881 return Ok(());
882 };
883 conn.clear_subscriptions();
884
885 self.try_send_actor_event(ActorEvent::ConnectionClosed { conn }, "connection_closed")
886 .with_context(|| disconnect_message(conn_id, reason.as_deref()))?;
887
888 self.record_connections_updated();
889 self.reset_sleep_timer();
890 tracing::debug!(
891 actor_id = %self.actor_id(),
892 conn_id,
893 reason = ?reason.as_deref(),
894 "connection disconnected"
895 );
896 Ok(())
897 }
898
899 async fn emit_connection_open(
900 &self,
901 conn: &ConnHandle,
902 request: Option<Request>,
903 ) -> Result<()> {
904 let config = self.connection_config();
905 let (reply_tx, reply_rx) = oneshot::channel();
906 self.try_send_actor_event(
907 ActorEvent::ConnectionOpen {
908 conn: conn.clone(),
909 request,
910 reply: Reply::from(reply_tx),
911 },
912 "connection_open",
913 )?;
914 timeout(config.on_connect_timeout, reply_rx)
915 .await
916 .with_context(|| timeout_message("connection_open", config.on_connect_timeout))?
917 .context("receive connection_open reply")??;
918 Ok(())
919 }
920
921 async fn emit_connection_preflight(
922 &self,
923 conn: &ConnHandle,
924 params: Vec<u8>,
925 request: Option<Request>,
926 ) -> Result<()> {
927 let config = self.connection_config();
928 let timeout_duration = config
929 .on_before_connect_timeout
930 .saturating_add(config.create_conn_state_timeout);
931 let (reply_tx, reply_rx) = oneshot::channel();
932 self.try_send_actor_event(
933 ActorEvent::ConnectionPreflight {
934 conn: conn.clone(),
935 params,
936 request,
937 reply: Reply::from(reply_tx),
938 },
939 "connection_preflight",
940 )?;
941 timeout(timeout_duration, reply_rx)
942 .await
943 .with_context(|| timeout_message("connection_preflight", timeout_duration))?
944 .context("receive connection_preflight reply")??;
945 Ok(())
946 }
947
948 pub(crate) fn connection(&self, conn_id: &str) -> Option<ConnHandle> {
949 self.0.connections.read().get(conn_id).cloned()
950 }
951
952 pub(crate) async fn disconnect_transport_only<F>(&self, mut predicate: F) -> Result<()>
953 where
954 F: FnMut(&ConnHandle) -> bool,
955 {
956 let connections: Vec<_> = self
957 .iter_connections()
958 .filter(|conn| predicate(conn))
959 .collect();
960 let mut disconnected_ids = Vec::new();
961 let mut failures = Vec::new();
962
963 for conn in &connections {
964 match conn.disconnect_transport_only().await {
965 Ok(()) => {
966 tracing::debug!(
967 actor_id = %self.actor_id(),
968 conn_id = conn.id(),
969 "connection transport disconnect completed"
970 );
971 disconnected_ids.push(conn.id().to_owned());
972 }
973 Err(error) => {
974 tracing::error!(
975 conn_id = %conn.id(),
976 ?error,
977 "failed transport-only connection disconnect"
978 );
979 failures.push((conn.id().to_owned(), format!("{error:#}")));
980 }
981 }
982 }
983
984 let mut removed_any = false;
985 for conn_id in disconnected_ids {
986 let Some(conn) = self.remove_existing_for_disconnect(&conn_id) else {
987 tracing::debug!(
988 actor_id = %self.actor_id(),
989 conn_id = %conn_id,
990 "connection transport removal skipped because connection was already removed"
991 );
992 continue;
993 };
994 conn.clear_subscriptions();
995 removed_any = true;
996 tracing::debug!(
997 actor_id = %self.actor_id(),
998 conn_id = %conn_id,
999 "connection transport removed"
1000 );
1001 }
1002
1003 if removed_any {
1004 self.record_connections_updated();
1005 self.reset_sleep_timer();
1006 }
1007
1008 if failures.is_empty() {
1009 return Ok(());
1010 }
1011
1012 let count = failures.len();
1013 Err(ConnectionDisconnectFailed {
1014 count,
1015 details: failures
1016 .into_iter()
1017 .map(|(conn_id, error)| format!("{conn_id}: {error}"))
1018 .collect::<Vec<_>>()
1019 .join("; "),
1020 }
1021 .build())
1022 }
1023}
1024
1025fn connection_not_configured(component: &str) -> anyhow::Error {
1026 ConnectionNotConfigured {
1027 component: component.to_owned(),
1028 }
1029 .build()
1030}
1031
1032fn timeout_message(callback_name: &str, timeout: Duration) -> String {
1033 format!(
1034 "`{callback_name}` timed out after {} ms",
1035 timeout.as_millis()
1036 )
1037}
1038
1039fn disconnect_message(conn_id: &str, reason: Option<&str>) -> String {
1040 match reason {
1041 Some(reason) => format!("disconnect connection `{conn_id}` with reason `{reason}`"),
1042 None => format!("disconnect connection `{conn_id}`"),
1043 }
1044}
1045
1046