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