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