1use std::collections::{BTreeMap, BTreeSet};
2use std::future::Future;
3use std::sync::Weak;
4#[cfg(any(test, feature = "test-support"))]
5use std::sync::atomic::AtomicI64;
6use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
7use std::sync::{Arc, OnceLock};
8use std::time::Duration;
9
10use crate::time::Instant;
11
12use anyhow::{Context as AnyhowContext, Result};
13use futures::future::BoxFuture;
14use parking_lot::{Mutex, RwLock};
15use rivet_envoy_client::handle::EnvoyHandle;
16use rivet_envoy_client::tunnel::HibernatingWebSocketMetadata;
17use rivet_error::ActorSpecifier;
18use scc::HashMap as SccHashMap;
19use scc::HashSet as SccHashSet;
20use tokio::runtime::Handle;
21use tokio::sync::{Mutex as AsyncMutex, Notify, OnceCell, broadcast, mpsc, oneshot};
22use tokio::task::JoinHandle;
23use tokio_util::sync::CancellationToken;
24
25#[cfg(not(feature = "wasm-runtime"))]
26type LocalAlarmTask = JoinHandle<()>;
27#[cfg(feature = "wasm-runtime")]
28type LocalAlarmTask = futures::future::AbortHandle;
29
30#[cfg(feature = "sqlite-local")]
31use crate::actor::actor_runtime_socket::{
32 ActorRuntimeSocketEndpoint, ActorRuntimeSocketEndpointInfo,
33};
34use crate::actor::connection::{
35 ConnHandle, ConnHandles, HibernatableConnectionMetadata, PendingHibernationChanges,
36 hibernatable_id_from_slice,
37};
38use crate::actor::diagnostics::ActorDiagnostics;
39use crate::actor::internal_storage;
40use crate::actor::kv::LegacyActorKv;
41use crate::actor::lifecycle_hooks::Reply;
42use crate::actor::messages::{ActorEvent, Request, StateDelta, WorkflowKvWrite};
43use crate::actor::metrics::ActorMetrics;
44use crate::actor::queue::{QueueInspectorUpdateCallback, QueueMetadata, QueueWaitActivityCallback};
45use crate::actor::schedule::{InternalKeepAwakeCallback, LocalAlarmCallback};
46use crate::actor::sleep::{CanSleep, SleepState};
47use crate::actor::state::{PendingSave, PersistedActor, RequestSaveOpts, StateTransactionSlot};
48use crate::actor::task::LifecycleEvent;
49use crate::actor::task_types::UserTaskKind;
50use crate::actor::work_registry::{ActorWorkKind, CountGuard, RegionGuard};
51use crate::error::{ActorLifecycle as ActorLifecycleError, ActorRuntime};
52use crate::inspector::{Inspector, InspectorSnapshot};
53use crate::sqlite::SqliteDb;
54use crate::telemetry::{
55 ActorInvocationTelemetry, ActorInvocationTraceContext, ActorTelemetryIdentity,
56 IncomingTraceContext, OutboundCallInvocation, WorkflowRunInvocation, WorkflowRunOutcome,
57 WorkflowStepSpan,
58};
59use crate::types::{ActorKey, ConnId, ListOpts, format_actor_key};
60use crate::{ActorConfig, ActorTracingConfig};
61
62#[derive(Clone)]
70pub struct ActorContext(
71 pub(crate) Arc<ActorContextInner>,
72 pub(crate) Option<ActorInvocationTelemetry>,
74);
75
76#[derive(Clone)]
77pub struct ActorKv {
78 sql: SqliteDb,
79}
80
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
83pub(crate) struct StartupCommit {
84 pub(crate) pending: usize,
86 pub(crate) failed: bool,
88 pub(crate) returned: bool,
91}
92
93pub(crate) struct ActorContextInner {
94 pub(super) legacy_kv: LegacyActorKv,
95 user_kv: ActorKv,
96 sql: SqliteDb,
97 new_actor_storage: AtomicBool,
100 start_received_at: parking_lot::Mutex<Option<crate::time::Instant>>,
104 #[cfg(feature = "sqlite-local")]
105 actor_runtime_socket: ActorRuntimeSocketEndpoint,
106 pub(super) current_state: RwLock<Vec<u8>>,
109 pub(super) persisted: RwLock<PersistedActor>,
110 pub(super) last_pushed_alarm: RwLock<Option<i64>>,
111 pub(super) run_wake_at: RwLock<Option<i64>>,
112 pub(super) run_wake_revision: AtomicU64,
113 pub(super) state_save_interval: Duration,
114 pub(super) state_dirty: AtomicBool,
115 pub(super) state_revision: AtomicU64,
116 pub(super) save_request_revision: AtomicU64,
117 pub(super) save_completed_revision: AtomicU64,
118 pub(super) save_failed_revision: AtomicU64,
121 pub(super) save_completion: Notify,
122 pub(super) save_requested: AtomicBool,
123 pub(super) save_requested_immediate: AtomicBool,
124 pub(super) save_requested_within_deadline: Mutex<Option<crate::time::Instant>>,
126 pub(super) last_save_at: Mutex<Option<crate::time::Instant>>,
127 pub(super) pending_save: Mutex<Option<PendingSave>>,
128 pub(super) tracked_persist: Mutex<Option<JoinHandle<()>>>,
129 pub(super) save_guard: Arc<AsyncMutex<()>>,
130 pub(super) state_transaction: Mutex<StateTransactionSlot>,
132 pub(super) state_transaction_epoch: AtomicU64,
133 pub(super) in_flight_state_writes: AtomicUsize,
134 pub(super) startup_commit: tokio::sync::watch::Sender<StartupCommit>,
137 pub(super) state_write_completion: Notify,
138 pub(super) on_state_change_in_flight: AtomicUsize,
139 pub(super) on_state_change_idle: Notify,
140 pub(super) request_save_hooks: RwLock<Vec<Arc<dyn Fn(RequestSaveOpts) + Send + Sync>>>,
143 pub(super) schedule_generation: Mutex<Option<u32>>,
146 pub(super) schedule_envoy_handle: Mutex<Option<EnvoyHandle>>,
147 pub(super) client_endpoint: OnceLock<String>,
148 pub(super) client_token: OnceLock<String>,
149 pub(super) client_namespace: OnceLock<String>,
150 pub(super) client_pool_name: OnceLock<String>,
151 pub(super) schedule_internal_keep_awake: Mutex<Option<InternalKeepAwakeCallback>>,
152 pub(super) schedule_local_alarm_callback: Mutex<Option<LocalAlarmCallback>>,
153 pub(super) schedule_local_alarm_task: Mutex<Option<LocalAlarmTask>>,
155 pub(super) schedule_pending_alarm_writes: Mutex<Vec<oneshot::Receiver<()>>>,
158 pub(super) schedule_local_alarm_epoch: AtomicU64,
159 pub(super) schedule_alarm_push_epoch: AtomicU64,
160 pub(super) schedule_alarm_persist_lock: AsyncMutex<()>,
162 pub(super) schedule_alarm_dispatch_enabled: AtomicBool,
163 pub(super) schedule_dirty_since_push: AtomicBool,
164 pub(super) schedule_mutation_lock: AsyncMutex<()>,
165 pub(super) schedule_running: SccHashSet<String>,
166 pub(super) schedule_history_insert_count: AtomicUsize,
167 pub(super) max_schedules: u32,
168 #[cfg(any(test, feature = "test-support"))]
169 pub(super) schedule_now_override: AtomicI64,
170
171
172 pub(super) queue_config: Mutex<ActorConfig>,
175 pub(super) queue_abort_signal: Mutex<CancellationToken>,
176 pub(super) queue_initialize: OnceCell<()>,
177 pub(super) queue_metadata: AsyncMutex<QueueMetadata>,
178 pub(super) queue_receive_lock: AsyncMutex<()>,
179 pub(super) queue_completion_waiters: SccHashMap<u64, oneshot::Sender<Option<Vec<u8>>>>,
180 pub(super) queue_notify: Notify,
181 pub(super) active_queue_wait_count: AtomicU32,
182 pub(super) queue_wait_activity_callback: Mutex<Option<QueueWaitActivityCallback>>,
184 pub(super) queue_inspector_update_callback: Mutex<Option<QueueInspectorUpdateCallback>>,
185 pub(super) connection_config: RwLock<ActorConfig>,
189 pub(super) connections: RwLock<BTreeMap<ConnId, ConnHandle>>,
190 pub(super) pending_hibernation_updates: RwLock<BTreeSet<ConnId>>,
191 pub(super) pending_hibernation_removals: RwLock<BTreeSet<ConnId>>,
192 pub(super) connection_disconnect_state: Mutex<()>,
193 pub(super) sleep: SleepState,
194 activity: ActivityState,
195 sleep_requested: AtomicBool,
196 destroy_requested: AtomicBool,
197 destroy_completed: AtomicBool,
198 destroy_completion_notify: Notify,
199 abort_signal: Mutex<CancellationToken>,
200 shutdown_deadline: CancellationToken,
201 inspector: RwLock<Option<Inspector>>,
204 inspector_attach_count: RwLock<Option<Arc<AtomicU32>>>,
205 inspector_overlay_tx: RwLock<Option<broadcast::Sender<Arc<Vec<u8>>>>>,
206 actor_events: RwLock<Option<mpsc::UnboundedSender<ActorEvent>>>,
207 pub(super) lifecycle_events: RwLock<Option<mpsc::UnboundedSender<LifecycleEvent>>>,
208 hibernated_connection_liveness_override: RwLock<Option<BTreeSet<(Vec<u8>, Vec<u8>)>>>,
209 pub(super) metrics: ActorMetrics,
210 diagnostics: ActorDiagnostics,
211 telemetry_identity: Arc<ActorTelemetryIdentity>,
212 tracing_config: Arc<ActorTracingConfig>,
213 actor_id: String,
214 name: String,
215 key: ActorKey,
216 region: String,
217}
218
219#[derive(Debug, Default)]
220pub(crate) struct ActivityState {
221 dirty: AtomicBool,
222}
223
224impl ActivityState {
225 fn mark_dirty(&self) -> bool {
226 !self.dirty.swap(true, Ordering::AcqRel)
227 }
228
229 fn take_dirty(&self) -> bool {
230 self.dirty.swap(false, Ordering::AcqRel)
231 }
232}
233
234impl ActorKv {
235 pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
236 let mut values = self.batch_get(&[key]).await?;
237 Ok(values.pop().flatten())
238 }
239
240 pub async fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
241 self.batch_put(&[(key, value)]).await
242 }
243
244 pub async fn delete(&self, key: &[u8]) -> Result<()> {
245 self.batch_delete(&[key]).await
246 }
247
248 pub async fn batch_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
249 internal_storage::user_kv_batch_get(&self.sql, keys).await
250 }
251
252 pub async fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<()> {
253 internal_storage::user_kv_batch_put(&self.sql, entries).await
254 }
255
256 pub async fn batch_delete(&self, keys: &[&[u8]]) -> Result<()> {
257 internal_storage::user_kv_batch_delete(&self.sql, keys).await
258 }
259
260 pub async fn delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
261 internal_storage::user_kv_delete_range(&self.sql, start, end).await
262 }
263
264 pub async fn list_prefix(
265 &self,
266 prefix: &[u8],
267 opts: ListOpts,
268 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
269 internal_storage::user_kv_list_prefix(&self.sql, prefix, opts).await
270 }
271
272 pub async fn list_range(
273 &self,
274 start: &[u8],
275 end: &[u8],
276 opts: ListOpts,
277 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
278 internal_storage::user_kv_list_range(&self.sql, start, end, opts).await
279 }
280}
281
282impl ActorContext {
283 pub fn with_invocation_telemetry(
286 mut self,
287 telemetry: Option<ActorInvocationTelemetry>,
288 ) -> Self {
289 self.1 = telemetry;
290 self
291 }
292
293 #[doc(hidden)]
298 pub fn with_application_span(
299 &self,
300 traceparent: Option<&str>,
301 tracestate: Option<&str>,
302 ) -> Self {
303 Self(
304 self.0.clone(),
305 self.1
306 .as_ref()
307 .map(|telemetry| telemetry.with_application_span(traceparent, tracestate)),
308 )
309 }
310
311 pub(crate) fn invocation_telemetry(&self) -> Option<&ActorInvocationTelemetry> {
312 self.1.as_ref()
313 }
314
315 pub fn invocation_sql(&self) -> SqliteDb {
317 self.0.sql.clone().with_invocation_telemetry(self.1.clone())
318 }
319
320 #[doc(hidden)]
327 pub fn start_call_span(
328 &self,
329 actor_name: &str,
330 action_name: &str,
331 ) -> Option<OutboundCallInvocation> {
332 self.1
333 .as_ref()?
334 .start_outbound_call(actor_name, action_name)
335 }
336
337 #[doc(hidden)]
340 pub async fn start_workflow_span(&self) -> WorkflowRunInvocation {
341 let previous = if tracing::enabled!(target: "rivetkit::telemetry", tracing::Level::INFO) {
342 internal_storage::load_workflow_trace(&self.0.sql)
343 .await
344 .unwrap_or_else(|error| {
345 tracing::warn!(
346 actor_id = %self.actor_id(),
347 ?error,
348 "failed to load the previous workflow run span, so this run will not link to it"
349 );
350 IncomingTraceContext::default()
351 })
352 } else {
353 IncomingTraceContext::default()
354 };
355 WorkflowRunInvocation::start(self, previous)
356 }
357
358 #[doc(hidden)]
360 pub async fn finish_workflow_span(
361 &self,
362 run: WorkflowRunInvocation,
363 outcome: WorkflowRunOutcome,
364 ) {
365 let Some(trace_context) = run.finish(outcome) else {
366 return;
367 };
368 if let Err(error) =
369 internal_storage::persist_workflow_trace(&self.0.sql, trace_context).await
370 {
371 tracing::warn!(
372 actor_id = %self.actor_id(),
373 ?error,
374 "failed to persist the workflow run span, so the next run will not link to it"
375 );
376 }
377 }
378
379 #[doc(hidden)]
382 pub fn start_workflow_step_span(
383 &self,
384 step_name: &str,
385 attempt: u32,
386 ) -> Option<WorkflowStepSpan> {
387 WorkflowStepSpan::start(self, step_name, attempt)
388 }
389
390 pub fn invocation_trace_context(&self) -> Option<ActorInvocationTraceContext> {
393 self.1.as_ref()?.trace_context()
394 }
395
396 #[doc(hidden)]
398 pub fn is_same_instance(&self, other: &Self) -> bool {
399 Arc::ptr_eq(&self.0, &other.0)
400 }
401
402 #[doc(hidden)]
407 pub fn instance_key(&self) -> usize {
408 Arc::as_ptr(&self.0) as usize
409 }
410
411
412
413
414
415
416
417 pub(crate) fn build(
418 actor_id: String,
419 name: String,
420 key: ActorKey,
421 region: String,
422 _generation: Option<u32>,
423 _envoy_key: String,
424 config: ActorConfig,
425 legacy_kv: LegacyActorKv,
426 sql: SqliteDb,
427 ) -> Self {
428 #[cfg(feature = "sqlite-local")]
429 let mut sql = sql;
430 #[cfg(feature = "sqlite-local")]
431 sql.set_profiling_config(config.sqlite_profiling.clone());
432 let metrics =
433 ActorMetrics::new_with_sqlite_profiling(name.clone(), config.sqlite_profiling.clone());
434 #[cfg(feature = "sqlite-local")]
435 sql.set_vfs_metrics(Arc::new(metrics.clone()));
436 let diagnostics = ActorDiagnostics::new(actor_id.clone());
437 #[cfg(feature = "sqlite-local")]
438 let actor_runtime_socket =
439 ActorRuntimeSocketEndpoint::new(config.enable_actor_runtime_socket, sql.clone());
440 let state_save_interval = config.state_save_interval;
441 let tracing_config = Arc::clone(&config.tracing);
442 let max_schedules = config.max_schedules;
443 let abort_signal = CancellationToken::new();
444 let shutdown_deadline = CancellationToken::new();
445 let sleep = SleepState::new(config.clone());
446 let user_kv = ActorKv { sql: sql.clone() };
447 let inner = Arc::new(ActorContextInner {
448 legacy_kv,
449 user_kv,
450 sql,
451 new_actor_storage: AtomicBool::new(false),
452 start_received_at: parking_lot::Mutex::new(None),
453 #[cfg(feature = "sqlite-local")]
454 actor_runtime_socket,
455 current_state: RwLock::new(Vec::new()),
456 persisted: RwLock::new(PersistedActor::default()),
457 last_pushed_alarm: RwLock::new(None),
458 run_wake_at: RwLock::new(None),
459 run_wake_revision: AtomicU64::new(0),
460 state_save_interval,
461 state_dirty: AtomicBool::new(false),
462 state_revision: AtomicU64::new(0),
463 save_request_revision: AtomicU64::new(0),
464 save_completed_revision: AtomicU64::new(0),
465 save_failed_revision: AtomicU64::new(0),
466 save_completion: Notify::new(),
467 save_requested: AtomicBool::new(false),
468 save_requested_immediate: AtomicBool::new(false),
469 save_requested_within_deadline: Mutex::new(None),
470 last_save_at: Mutex::new(None),
471 pending_save: Mutex::new(None),
472 tracked_persist: Mutex::new(None),
473 save_guard: Arc::new(AsyncMutex::new(())),
474 state_transaction: Mutex::new(StateTransactionSlot::Idle),
475 state_transaction_epoch: AtomicU64::new(0),
476 in_flight_state_writes: AtomicUsize::new(0),
477 startup_commit: tokio::sync::watch::Sender::new(StartupCommit::default()),
478 state_write_completion: Notify::new(),
479 on_state_change_in_flight: AtomicUsize::new(0),
480 on_state_change_idle: Notify::new(),
481 request_save_hooks: RwLock::new(Vec::new()),
482 schedule_generation: Mutex::new(None),
483 schedule_envoy_handle: Mutex::new(None),
484 client_endpoint: OnceLock::new(),
485 client_token: OnceLock::new(),
486 client_namespace: OnceLock::new(),
487 client_pool_name: OnceLock::new(),
488 schedule_internal_keep_awake: Mutex::new(None),
489 schedule_local_alarm_callback: Mutex::new(None),
490 schedule_local_alarm_task: Mutex::new(None),
491 schedule_pending_alarm_writes: Mutex::new(Vec::new()),
492 schedule_local_alarm_epoch: AtomicU64::new(0),
493 schedule_alarm_push_epoch: AtomicU64::new(0),
494 schedule_alarm_persist_lock: AsyncMutex::new(()),
495 schedule_alarm_dispatch_enabled: AtomicBool::new(true),
496 schedule_dirty_since_push: AtomicBool::new(true),
499 schedule_mutation_lock: AsyncMutex::new(()),
500 schedule_running: SccHashSet::new(),
501 schedule_history_insert_count: AtomicUsize::new(0),
502 max_schedules,
503 #[cfg(any(test, feature = "test-support"))]
504 schedule_now_override: AtomicI64::new(i64::MIN),
505
506
507 queue_config: Mutex::new(config.clone()),
508 queue_abort_signal: Mutex::new(abort_signal.clone()),
509 queue_initialize: OnceCell::new(),
510 queue_metadata: AsyncMutex::new(QueueMetadata::default()),
511 queue_receive_lock: AsyncMutex::new(()),
512 queue_completion_waiters: SccHashMap::new(),
513 queue_notify: Notify::new(),
514 active_queue_wait_count: AtomicU32::new(0),
515 queue_wait_activity_callback: Mutex::new(None),
516 queue_inspector_update_callback: Mutex::new(None),
517 connection_config: RwLock::new(config),
518 connections: RwLock::new(BTreeMap::new()),
519 pending_hibernation_updates: RwLock::new(BTreeSet::new()),
520 pending_hibernation_removals: RwLock::new(BTreeSet::new()),
521 connection_disconnect_state: Mutex::new(()),
522 sleep,
523 activity: ActivityState::default(),
524 sleep_requested: AtomicBool::new(false),
525 destroy_requested: AtomicBool::new(false),
526 destroy_completed: AtomicBool::new(false),
527 destroy_completion_notify: Notify::new(),
528 abort_signal: Mutex::new(abort_signal),
529 shutdown_deadline,
530 inspector: RwLock::new(None),
531 inspector_attach_count: RwLock::new(None),
532 inspector_overlay_tx: RwLock::new(None),
533 actor_events: RwLock::new(None),
534 lifecycle_events: RwLock::new(None),
535 hibernated_connection_liveness_override: RwLock::new(None),
536 metrics,
537 diagnostics,
538 tracing_config,
539 telemetry_identity: Arc::new(ActorTelemetryIdentity {
540 actor_id: actor_id.clone(),
541 actor_name: name.clone(),
542 actor_key: format_actor_key(&key),
543 }),
544 actor_id,
545 name,
546 key,
547 region,
548 });
549 let ctx = Self(inner, None);
550 ctx.configure_sleep_hooks();
551 ctx
552 }
553
554 #[deprecated(
555 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
556 )]
557 pub async fn kv_batch_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
558 self.0.user_kv.batch_get(keys).await
559 }
560
561 #[deprecated(
562 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
563 )]
564 pub async fn kv_batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<()> {
565 self.0.user_kv.batch_put(entries).await
566 }
567
568 #[deprecated(
569 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
570 )]
571 pub async fn kv_batch_delete(&self, keys: &[&[u8]]) -> Result<()> {
572 self.0.user_kv.batch_delete(keys).await
573 }
574
575 #[deprecated(
576 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
577 )]
578 pub async fn kv_delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
579 self.0.user_kv.delete_range(start, end).await
580 }
581
582 #[deprecated(
583 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
584 )]
585 pub async fn kv_list_prefix(
586 &self,
587 prefix: &[u8],
588 opts: ListOpts,
589 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
590 self.0.user_kv.list_prefix(prefix, opts).await
591 }
592
593 #[deprecated(
594 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
595 )]
596 pub async fn kv_list_range(
597 &self,
598 start: &[u8],
599 end: &[u8],
600 opts: ListOpts,
601 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
602 self.0.user_kv.list_range(start, end, opts).await
603 }
604
605 #[deprecated(
606 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
607 )]
608 pub fn kv(&self) -> &ActorKv {
609 &self.0.user_kv
610 }
611
612 pub(crate) fn legacy_kv(&self) -> &LegacyActorKv {
615 &self.0.legacy_kv
616 }
617
618 pub fn sql(&self) -> &SqliteDb {
619 &self.0.sql
620 }
621
622 pub(crate) fn set_new_actor_storage(&self, new_actor_storage: bool) {
624 self.0
625 .new_actor_storage
626 .store(new_actor_storage, Ordering::Release);
627 }
628
629 pub(crate) fn set_start_received_at(&self, received_at: crate::time::Instant) {
631 *self.0.start_received_at.lock() = Some(received_at);
632 }
633
634 pub(crate) fn take_start_received_at(&self) -> Option<crate::time::Instant> {
636 self.0.start_received_at.lock().take()
637 }
638
639 pub(crate) fn take_new_actor_storage(&self) -> bool {
642 self.0.new_actor_storage.swap(false, Ordering::AcqRel)
643 }
644
645 #[cfg(feature = "sqlite-local")]
646 pub async fn provision_actor_runtime_socket(&self) -> Result<ActorRuntimeSocketEndpointInfo> {
647 self.0.actor_runtime_socket.provision().await
648 }
649
650 #[cfg(feature = "sqlite-local")]
651 pub(crate) async fn shutdown_actor_runtime_socket(&self) {
652 self.0.actor_runtime_socket.shutdown().await;
653 }
654
655 pub async fn db_exec(&self, sql: &str) -> Result<Vec<u8>> {
656 self.0.sql.exec_rows_cbor(sql).await
657 }
658
659 pub async fn db_query(&self, sql: &str, params: Option<&[u8]>) -> Result<Vec<u8>> {
660 self.0.sql.query_rows_cbor(sql, params).await
661 }
662
663 pub async fn db_execute(&self, sql: &str, params: Option<&[u8]>) -> Result<Vec<u8>> {
664 self.0.sql.execute_rows_cbor(sql, params).await
665 }
666
667 pub async fn db_run(&self, sql: &str, params: Option<&[u8]>) -> Result<()> {
668 self.0.sql.run_cbor(sql, params).await?;
669 Ok(())
670 }
671
672 pub fn set_alarm(&self, timestamp_ms: Option<i64>) -> Result<()> {
673 self.set_schedule_alarm(timestamp_ms)
674 }
675
676 pub async fn init_alarms(&self) {
682 if let Err(error) = self.recover_interrupted_schedule_history().await {
683 tracing::error!(?error, "failed to recover interrupted schedule history");
684 }
685 self.sync_future_alarm_logged().await;
686 }
687
688 pub fn queue(&self) -> &Self {
689 self
690 }
691
692 pub fn sleep(&self) -> Result<()> {
693 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst) {
698 let already_stopping = self.0.sleep_requested.load(Ordering::SeqCst)
699 || self.0.destroy_requested.load(Ordering::SeqCst);
700 return if already_stopping {
701 Err(ActorLifecycleError::Stopping.build()).context("actor is already shutting down")
702 } else {
703 Err(ActorLifecycleError::Starting.build())
704 .context("cannot request sleep before actor startup completes")
705 };
706 }
707 if self.0.sleep_requested.swap(true, Ordering::SeqCst) {
708 return Ok(());
709 }
710 self.cancel_sleep_timer();
711 if Handle::try_current().is_ok() {
712 let ctx = self.clone();
713 let tracked = self.track_shutdown_task(async move {
714 ctx.record_user_task_started(UserTaskKind::SleepFinalize);
715 let started_at = Instant::now();
716 ctx.request_sleep_from_envoy();
717 ctx.record_user_task_finished(UserTaskKind::SleepFinalize, started_at.elapsed());
718 });
719 if tracked {
720 return Ok(());
721 }
722 }
723
724 self.request_sleep_from_envoy();
725 Ok(())
726 }
727
728 pub fn destroy(&self) -> Result<()> {
729 self.request_stop(None)
730 }
731
732 pub fn stop_with_error(&self, message: impl Into<String>) -> Result<()> {
740 self.request_stop(Some(truncate_stop_error_message(message.into())))
741 }
742
743 fn request_stop(&self, error: Option<String>) -> Result<()> {
744 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst)
748 && !self.0.sleep_requested.load(Ordering::SeqCst)
749 && !self.0.destroy_requested.load(Ordering::SeqCst)
750 {
751 return Err(ActorLifecycleError::Starting.build())
752 .context("cannot request stop before actor startup completes");
753 }
754
755 if let Some(error) = error {
756 if self.0.destroy_requested.load(Ordering::SeqCst) {
759 return Err(ActorLifecycleError::Stopping.build())
760 .context("destroy already requested for this generation");
761 }
762 let queued_error = self.0.sleep.stop_error.lock().replace(error);
767 if !self.0.sleep_requested.swap(true, Ordering::SeqCst) {
768 self.mark_errored_stop_requested();
769 }
770 if queued_error.is_some() {
778 return Ok(());
779 }
780 } else {
781 if self.0.destroy_requested.swap(true, Ordering::SeqCst) {
782 return Err(ActorLifecycleError::Stopping.build())
783 .context("destroy already requested for this generation");
784 }
785 *self.0.sleep.stop_error.lock() = None;
790 #[cfg(not(feature = "wasm-runtime"))]
795 self.mark_destroy_requested();
796 #[cfg(feature = "wasm-runtime")]
797 self.mark_destroy_requested_without_spawn();
798 }
799
800 let ctx = self.clone();
801 if Handle::try_current().is_ok() {
802 let tracked = self.track_shutdown_task(async move {
803 ctx.record_user_task_started(UserTaskKind::DestroyRequest);
804 let started_at = Instant::now();
805 ctx.request_stop_from_envoy();
806 ctx.record_user_task_finished(UserTaskKind::DestroyRequest, started_at.elapsed());
807 });
808 if tracked {
809 return Ok(());
810 }
811 }
812
813 self.request_stop_from_envoy();
814 Ok(())
815 }
816
817 pub fn mark_destroy_requested(&self) {
818 self.cancel_sleep_timer();
819 self.flush_on_shutdown();
820 self.0.destroy_requested.store(true, Ordering::SeqCst);
821 self.0.destroy_completed.store(false, Ordering::SeqCst);
822 }
823
824 fn mark_errored_stop_requested(&self) {
829 self.cancel_sleep_timer();
830 #[cfg(not(feature = "wasm-runtime"))]
831 self.flush_on_shutdown();
832 }
833
834 #[cfg(feature = "wasm-runtime")]
835 fn mark_destroy_requested_without_spawn(&self) {
836 self.cancel_sleep_timer();
837 self.0.destroy_requested.store(true, Ordering::SeqCst);
838 self.0.destroy_completed.store(false, Ordering::SeqCst);
839 }
840
841 #[doc(hidden)]
842 pub fn cancel_actor_abort_signal(&self) {
843 self.0.abort_signal.lock().cancel();
844 }
845
846 pub(crate) fn reset_abort_signal_for_start(&self) {
847 let mut abort_signal = self.0.abort_signal.lock();
848 if !abort_signal.is_cancelled() {
849 return;
850 }
851
852 let next_signal = CancellationToken::new();
856 *abort_signal = next_signal.clone();
857 *self.0.queue_abort_signal.lock() = next_signal;
858 }
859
860 #[doc(hidden)]
861 pub fn actor_abort_signal(&self) -> CancellationToken {
862 self.0.abort_signal.lock().clone()
863 }
864
865 #[doc(hidden)]
866 pub fn actor_aborted(&self) -> bool {
867 self.0.abort_signal.lock().is_cancelled()
868 }
869
870 #[doc(hidden)]
875 pub fn shutdown_deadline_token(&self) -> CancellationToken {
876 self.0.shutdown_deadline.clone()
877 }
878
879 #[doc(hidden)]
880 pub fn cancel_shutdown_deadline(&self) {
881 self.0.shutdown_deadline.cancel();
882 }
883
884 #[deprecated(note = "no-op: use `keep_awake` or `wait_until` instead")]
888 pub fn set_prevent_sleep(&self, _enabled: bool) {}
889
890 #[deprecated(note = "no-op: always returns false")]
891 pub fn prevent_sleep(&self) -> bool {
892 false
893 }
894
895 #[cfg(not(feature = "wasm-runtime"))]
900 pub fn wait_until(&self, future: impl Future<Output = ()> + Send + 'static) {
901 let invocation = self
902 .1
903 .as_ref()
904 .and_then(crate::ActorInvocationTelemetry::hold_open);
905 self.spawn_work(ActorWorkKind::WaitUntil, async move {
906 future.await;
907 drop(invocation);
908 });
909 }
910
911 #[cfg(not(feature = "wasm-runtime"))]
912 pub fn register_task(&self, future: impl Future<Output = ()> + Send + 'static) {
913 self.spawn_work(ActorWorkKind::RegisteredTask, future);
914 }
915
916 #[cfg(feature = "wasm-runtime")]
917 pub fn wait_until(&self, future: impl Future<Output = ()> + 'static) {
918 let invocation = self
919 .1
920 .as_ref()
921 .and_then(crate::ActorInvocationTelemetry::hold_open);
922 self.spawn_work(ActorWorkKind::WaitUntil, async move {
923 future.await;
924 drop(invocation);
925 });
926 }
927
928 #[cfg(feature = "wasm-runtime")]
929 pub fn register_task(&self, future: impl Future<Output = ()> + 'static) {
930 self.spawn_work(ActorWorkKind::RegisteredTask, future);
931 }
932
933 pub async fn keep_awake<F>(&self, future: F) -> F::Output
934 where
935 F: Future,
936 {
937 self.track_work(ActorWorkKind::KeepAwake, future).await
938 }
939
940 pub fn keep_awake_region(&self) -> KeepAwakeRegion {
941 KeepAwakeRegion {
942 region: Some(self.begin_work_region(ActorWorkKind::KeepAwake)),
943 }
944 }
945
946 pub async fn internal_keep_awake<F>(&self, future: F) -> F::Output
947 where
948 F: Future,
949 {
950 self.track_work(ActorWorkKind::InternalKeepAwake, future)
951 .await
952 }
953
954 pub fn keep_awake_count(&self) -> usize {
955 self.sleep_keep_awake_count()
956 }
957
958 pub fn internal_keep_awake_count(&self) -> usize {
959 self.sleep_internal_keep_awake_count()
960 }
961
962 pub async fn track_work<F>(&self, kind: ActorWorkKind, future: F) -> F::Output
963 where
964 F: Future,
965 {
966 let _region = self.begin_work_region(kind);
967 future.await
968 }
969
970 #[cfg(not(feature = "wasm-runtime"))]
971 pub fn spawn_work<F>(&self, kind: ActorWorkKind, future: F)
972 where
973 F: Future<Output = ()> + Send + 'static,
974 {
975 self.spawn_work_inner(kind, future);
976 }
977
978 #[cfg(feature = "wasm-runtime")]
979 pub fn spawn_work<F>(&self, kind: ActorWorkKind, future: F)
980 where
981 F: Future<Output = ()> + 'static,
982 {
983 self.spawn_work_inner(kind, future);
984 }
985
986 pub fn begin_work_region(&self, kind: ActorWorkKind) -> ActorWorkRegion {
987 ActorWorkRegion {
988 guard: Some(ActorWorkGuard::new(self.clone(), kind)),
989 }
990 }
991
992 pub fn actor_id(&self) -> &str {
993 &self.0.actor_id
994 }
995
996 pub fn name(&self) -> &str {
997 &self.0.name
998 }
999
1000 pub fn key(&self) -> &ActorKey {
1001 &self.0.key
1002 }
1003
1004 pub(crate) fn actor_specifier(&self) -> Option<ActorSpecifier> {
1005 Some(
1006 ActorSpecifier::new(self.actor_id().to_owned(), self.sleep_generation()? as u64)
1007 .with_key(format_actor_key(self.key())),
1008 )
1009 }
1010
1011 pub(crate) fn attach_actor_to_error(&self, error: anyhow::Error) -> anyhow::Error {
1012 match self.actor_specifier() {
1013 Some(actor) => error.context(actor),
1014 None => error,
1015 }
1016 }
1017
1018 pub fn region(&self) -> &str {
1019 &self.0.region
1020 }
1021
1022 pub fn has_state(&self) -> bool {
1023 self.0.connection_config.read().has_state
1024 }
1025
1026 #[doc(hidden)]
1027 pub fn record_startup_create_state(&self, duration: Duration) {
1028 self.0.metrics.observe_create_state(duration);
1029 }
1030
1031 #[doc(hidden)]
1034 pub async fn time_runtime_callback<T>(
1035 &self,
1036 callback: &'static str,
1037 future: impl std::future::Future<Output = T>,
1038 ) -> T {
1039 let started_at = Instant::now();
1040 let result = future.await;
1041 self.0
1042 .metrics
1043 .observe_runtime_callback(callback, started_at.elapsed());
1044 result
1045 }
1046
1047 #[doc(hidden)]
1048 pub fn record_startup_create_vars(&self, duration: Duration) {
1049 self.0.metrics.observe_create_vars(duration);
1050 }
1051
1052 pub fn broadcast(&self, name: &str, args: &[u8]) {
1053 for connection in self.conns() {
1054 if connection.is_subscribed(name) {
1055 connection.send(name, args);
1056 }
1057 }
1058 }
1059
1060 #[must_use]
1065 pub fn conns(&self) -> ConnHandles<'_> {
1066 self.iter_connections()
1067 }
1068
1069 pub fn client_endpoint(&self) -> Option<&str> {
1070 self.0.client_endpoint.get().map(String::as_str)
1071 }
1072
1073 pub fn client_token(&self) -> Option<&str> {
1074 self.0.client_token.get().map(String::as_str)
1075 }
1076
1077 pub fn client_namespace(&self) -> Option<&str> {
1078 self.0.client_namespace.get().map(String::as_str)
1079 }
1080
1081 pub fn client_pool_name(&self) -> Option<&str> {
1082 self.0.client_pool_name.get().map(String::as_str)
1083 }
1084
1085 pub fn ack_hibernatable_websocket_message(
1086 &self,
1087 gateway_id: &[u8],
1088 request_id: &[u8],
1089 server_message_index: u16,
1090 ) -> Result<()> {
1091 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
1092 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
1093 let envoy_handle = self.sleep_envoy_handle().ok_or_else(|| {
1094 ActorRuntime::NotConfigured {
1095 component: "hibernatable websocket ack".to_owned(),
1096 }
1097 .build()
1098 })?;
1099 envoy_handle.send_hibernatable_ws_message_ack(gateway_id, request_id, server_message_index);
1100 Ok(())
1101 }
1102
1103 pub(crate) fn load_persisted_actor(&self, persisted: PersistedActor) {
1104 self.load_persisted(persisted);
1105 }
1106
1107 pub(crate) fn persisted_actor(&self) -> PersistedActor {
1108 self.persisted()
1109 }
1110
1111 pub async fn drain_overdue_scheduled_events(&self) -> Result<()> {
1116 for dispatch in self.take_due_schedule_dispatches().await? {
1117 self.dispatch_scheduled_action(dispatch).await;
1118 }
1119 Ok(())
1120 }
1121
1122 pub(crate) fn metrics(&self) -> &ActorMetrics {
1123 &self.0.metrics
1124 }
1125
1126 pub(crate) fn telemetry_identity(&self) -> Arc<ActorTelemetryIdentity> {
1129 self.0.telemetry_identity.clone()
1130 }
1131
1132 pub(crate) fn tracing_config(&self) -> &ActorTracingConfig {
1133 &self.0.tracing_config
1134 }
1135
1136 pub(crate) fn record_user_task_started(&self, kind: UserTaskKind) {
1137 self.0.metrics.begin_user_task(kind);
1138 }
1139
1140 pub(crate) fn record_user_task_finished(&self, kind: UserTaskKind, duration: Duration) {
1141 self.0.metrics.end_user_task(kind, duration);
1142 }
1143
1144 pub(crate) fn record_shutdown_wait(
1145 &self,
1146 reason: crate::actor::task_types::ShutdownKind,
1147 duration: Duration,
1148 ) {
1149 self.0.metrics.observe_shutdown_wait(reason, duration);
1150 }
1151
1152 pub(crate) fn record_shutdown_timeout(&self, reason: crate::actor::task_types::ShutdownKind) {
1153 self.0.metrics.inc_shutdown_timeout(reason);
1154 }
1155
1156 pub(crate) fn record_direct_subsystem_shutdown_warning(
1157 &self,
1158 subsystem: &str,
1159 operation: &str,
1160 ) {
1161 self.0
1162 .metrics
1163 .inc_direct_subsystem_shutdown_warning(subsystem, operation);
1164 }
1165
1166 pub(crate) fn warn_work_sent_to_stopping_instance(&self, operation: &'static str) {
1167 if let Some(suppression) = self.0.diagnostics.record("work_sent_to_stopping_instance") {
1168 tracing::warn!(
1169 actor_id = %suppression.actor_id,
1170 operation,
1171 per_actor_suppressed = suppression.per_actor_suppressed,
1172 global_suppressed = suppression.global_suppressed,
1173 "work sent to stopping actor instance"
1174 );
1175 }
1176 }
1177
1178 pub(crate) fn warn_self_call_risk(&self, operation: &'static str) {
1179 if let Some(suppression) = self.0.diagnostics.record("self_call_risk") {
1180 tracing::warn!(
1181 actor_id = %suppression.actor_id,
1182 operation,
1183 per_actor_suppressed = suppression.per_actor_suppressed,
1184 global_suppressed = suppression.global_suppressed,
1185 "actor dispatch may be parked behind the current instance"
1186 );
1187 }
1188 }
1189
1190
1191
1192 pub(crate) fn remove_conn(&self, conn_id: &str) -> Option<ConnHandle> {
1193 let removed = self.remove_existing(conn_id);
1194 if removed.is_some() {
1195 self.record_connections_updated();
1196 self.reset_sleep_timer();
1197 }
1198 removed
1199 }
1200
1201 pub(crate) fn configure_connection_runtime(&self, config: ActorConfig) {
1202 self.configure_sleep_state(config.clone());
1203 self.configure_connection_storage(config);
1204 }
1205
1206 pub(crate) fn configure_actor_events(&self, sender: Option<mpsc::UnboundedSender<ActorEvent>>) {
1207 *self.0.actor_events.write() = sender;
1208 }
1209
1210 pub(crate) fn try_send_actor_event(
1211 &self,
1212 event: ActorEvent,
1213 operation: &'static str,
1214 ) -> Result<()> {
1215 let sender = self.0.actor_events.read().clone().ok_or_else(|| {
1216 ActorRuntime::NotConfigured {
1217 component: "actor event inbox".to_owned(),
1218 }
1219 .build()
1220 })?;
1221 tracing::debug!(
1222 actor_id = %self.actor_id(),
1223 operation,
1224 event = event.kind(),
1225 "actor event enqueued"
1226 );
1227 sender.send(event).map_err(|_| {
1228 ActorRuntime::NotConfigured {
1229 component: "actor event inbox".to_owned(),
1230 }
1231 .build()
1232 })
1233 }
1234
1235 #[doc(hidden)]
1236 pub fn configure_envoy(&self, envoy_handle: EnvoyHandle, generation: Option<u32>) {
1237 let _ = self
1238 .0
1239 .client_endpoint
1240 .set(envoy_handle.endpoint().to_owned());
1241 if let Some(token) = envoy_handle.token() {
1242 let _ = self.0.client_token.set(token.to_owned());
1243 }
1244 let _ = self
1245 .0
1246 .client_namespace
1247 .set(envoy_handle.namespace().to_owned());
1248 let _ = self
1249 .0
1250 .client_pool_name
1251 .set(envoy_handle.pool_name().to_owned());
1252 self.configure_sleep_envoy(envoy_handle.clone(), generation);
1253 self.configure_schedule_envoy(envoy_handle, generation);
1254 }
1255
1256 pub(crate) async fn connect_conn<F>(
1257 &self,
1258 params: Vec<u8>,
1259 is_hibernatable: bool,
1260 hibernation: Option<HibernatableConnectionMetadata>,
1261 request: Option<Request>,
1262 create_state: F,
1263 ) -> Result<ConnHandle>
1264 where
1265 F: Future<Output = Result<Vec<u8>>> + Send,
1266 {
1267 let conn = self
1268 .connect_with_state(params, is_hibernatable, hibernation, request, create_state)
1269 .await?;
1270 self.record_connections_updated();
1271 self.reset_sleep_timer();
1272 Ok(conn)
1273 }
1274
1275 pub(crate) async fn connect_conn_with_prepare<F, P>(
1276 &self,
1277 params: Vec<u8>,
1278 is_hibernatable: bool,
1279 hibernation: Option<HibernatableConnectionMetadata>,
1280 request: Option<Request>,
1281 create_state: F,
1282 prepare_connection: P,
1283 ) -> Result<ConnHandle>
1284 where
1285 F: Future<Output = Result<Vec<u8>>> + Send,
1286 P: FnOnce(&ConnHandle) -> Result<()>,
1287 {
1288 let conn = self
1289 .connect_with_state_and_prepare(
1290 params,
1291 is_hibernatable,
1292 hibernation,
1293 request,
1294 create_state,
1295 prepare_connection,
1296 )
1297 .await?;
1298 self.record_connections_updated();
1299 self.reset_sleep_timer();
1300 Ok(conn)
1301 }
1302
1303 pub async fn connect_conn_with_request<F>(
1304 &self,
1305 params: Vec<u8>,
1306 request: Option<Request>,
1307 create_state: F,
1308 ) -> Result<ConnHandle>
1309 where
1310 F: Future<Output = Result<Vec<u8>>> + Send,
1311 {
1312 self.connect_conn(params, false, None, request, create_state)
1313 .await
1314 }
1315
1316 pub(crate) fn reconnect_hibernatable_conn(
1317 &self,
1318 gateway_id: &[u8],
1319 request_id: &[u8],
1320 ) -> Result<ConnHandle> {
1321 self.reconnect_hibernatable(gateway_id, request_id)
1322 }
1323
1324 pub async fn disconnect_conn(&self, id: ConnId) -> Result<()> {
1325 self.disconnect_transport_only(|conn| conn.id() == id).await
1326 }
1327
1328 pub async fn disconnect_conns<F>(&self, predicate: F) -> Result<()>
1329 where
1330 F: FnMut(&ConnHandle) -> bool,
1331 {
1332 self.disconnect_transport_only(predicate).await
1333 }
1334
1335 pub(crate) fn request_hibernation_transport_save(&self, conn_id: &str) {
1336 self.queue_hibernation_update(conn_id.to_owned());
1337 self.request_save(RequestSaveOpts::default());
1338 }
1339
1340 pub(crate) fn request_hibernation_transport_removal(&self, conn_id: impl Into<String>) {
1341 self.queue_hibernation_removal_inner(conn_id.into());
1342 self.request_save(RequestSaveOpts::default());
1343 }
1344
1345 pub fn queue_hibernation_removal(&self, conn_id: impl Into<String>) {
1346 self.request_hibernation_transport_removal(conn_id);
1347 }
1348
1349 pub fn has_pending_hibernation_changes(&self) -> bool {
1350 self.has_pending_hibernation_changes_inner()
1351 }
1352
1353 pub fn take_pending_hibernation_changes(&self) -> Vec<ConnId> {
1354 self.pending_hibernation_removals()
1355 }
1356
1357 pub fn dirty_hibernatable_conns(&self) -> Vec<ConnHandle> {
1358 self.dirty_hibernatable_conns_inner()
1359 }
1360
1361 pub(crate) fn hibernated_connection_is_live(
1362 &self,
1363 gateway_id: &[u8],
1364 request_id: &[u8],
1365 ) -> Result<bool> {
1366 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
1367 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
1368
1369 if let Some(override_pairs) = self
1370 .0
1371 .hibernated_connection_liveness_override
1372 .read()
1373 .as_ref()
1374 {
1375 return Ok(override_pairs.contains(&(gateway_id.to_vec(), request_id.to_vec())));
1376 }
1377
1378 let Some(envoy_handle) = self.sleep_envoy_handle() else {
1379 return Ok(false);
1380 };
1381 let is_live = envoy_handle.hibernatable_connection_is_live(
1382 self.actor_id(),
1383 self.sleep_generation(),
1384 gateway_id,
1385 request_id,
1386 );
1387 Ok(is_live)
1388 }
1389
1390
1391
1392 pub(super) fn prepare_state_deltas(
1393 &self,
1394 deltas: Vec<StateDelta>,
1395 ) -> Result<(Vec<StateDelta>, PendingHibernationChanges)> {
1396 fn finish_with_error(
1397 ctx: &ActorContext,
1398 pending: PendingHibernationChanges,
1399 error: anyhow::Error,
1400 ) -> Result<(Vec<StateDelta>, PendingHibernationChanges)> {
1401 ctx.restore_pending_hibernation_changes(pending);
1402 Err(error)
1403 }
1404
1405 let mut next_deltas = Vec::new();
1406 let mut explicit_updates = std::collections::BTreeMap::new();
1407 let mut explicit_removals = std::collections::BTreeSet::new();
1408
1409 for delta in deltas {
1410 match delta {
1411 StateDelta::ConnHibernation { conn, bytes } => {
1412 explicit_updates.insert(conn, bytes);
1413 }
1414 StateDelta::ConnHibernationRemoved(conn) => {
1415 explicit_removals.insert(conn);
1416 }
1417 other => next_deltas.push(other),
1418 }
1419 }
1420
1421 let mut pending = self.take_pending_hibernation_changes_inner();
1422 let mut removal_ids = pending.removed.clone();
1423 removal_ids.extend(explicit_removals.iter().cloned());
1424
1425 let explicit_update_ids: std::collections::BTreeSet<_> =
1426 explicit_updates.keys().cloned().collect();
1427 pending.updated.extend(explicit_update_ids.iter().cloned());
1428 pending.removed.extend(explicit_removals.iter().cloned());
1429
1430 for (conn, bytes) in explicit_updates {
1431 if removal_ids.contains(&conn) {
1432 continue;
1433 }
1434 let encoded = match self.encode_hibernation_delta(&conn, bytes) {
1435 Ok(encoded) => encoded,
1436 Err(error) => {
1437 return finish_with_error(self, pending, error);
1438 }
1439 };
1440 next_deltas.push(StateDelta::ConnHibernation {
1441 conn,
1442 bytes: encoded,
1443 });
1444 }
1445
1446 for conn in &pending.updated {
1447 if removal_ids.contains(conn)
1448 || explicit_removals.contains(conn)
1449 || explicit_update_ids.contains(conn)
1450 {
1451 continue;
1452 }
1453 let Some(handle) = self.connection(conn) else {
1454 continue;
1455 };
1456 if !handle.is_hibernatable() || handle.hibernation().is_none() {
1457 continue;
1458 }
1459 let encoded = match self.encode_hibernation_delta(conn, handle.state()) {
1460 Ok(encoded) => encoded,
1461 Err(error) => {
1462 return finish_with_error(self, pending, error);
1463 }
1464 };
1465 next_deltas.push(StateDelta::ConnHibernation {
1466 conn: conn.clone(),
1467 bytes: encoded,
1468 });
1469 }
1470
1471 for conn in removal_ids {
1472 next_deltas.push(StateDelta::ConnHibernationRemoved(conn));
1473 }
1474
1475 Ok((next_deltas, pending))
1476 }
1477
1478 pub(crate) async fn restore_hibernatable_connections(&self) -> Result<Vec<ConnHandle>> {
1479 let restored = self.restore_persisted().await?;
1480 if !restored.is_empty() {
1481 if let Some(envoy_handle) = self.sleep_envoy_handle() {
1482 let meta_entries: Vec<_> = restored
1483 .iter()
1484 .filter_map(|conn| {
1485 let hibernation = conn.hibernation()?;
1486 Some(HibernatingWebSocketMetadata {
1487 gateway_id: hibernation.gateway_id,
1488 request_id: hibernation.request_id,
1489 envoy_message_index: hibernation.client_message_index,
1490 rivet_message_index: hibernation.server_message_index,
1491 path: hibernation.request_path,
1492 headers: hibernation.request_headers.into_iter().collect(),
1493 })
1494 })
1495 .collect();
1496 envoy_handle.restore_hibernating_requests(self.actor_id().to_owned(), meta_entries);
1497 }
1498 self.record_connections_updated();
1499 self.reset_sleep_timer();
1500 }
1501 Ok(restored)
1502 }
1503
1504 pub(crate) fn configure_inspector(&self, inspector: Option<Inspector>) {
1505 *self.0.inspector.write() = inspector;
1506 }
1507
1508 pub(crate) fn inspector(&self) -> Option<Inspector> {
1509 self.0.inspector.read().clone()
1510 }
1511
1512 pub fn inspector_snapshot(&self) -> InspectorSnapshot {
1513 self.inspector()
1514 .map(|inspector| inspector.snapshot())
1515 .unwrap_or_default()
1516 }
1517
1518 pub(crate) fn configure_inspector_runtime(
1519 &self,
1520 attach_count: Arc<AtomicU32>,
1521 overlay_tx: broadcast::Sender<Arc<Vec<u8>>>,
1522 ) {
1523 *self.0.inspector_attach_count.write() = Some(attach_count);
1524 *self.0.inspector_overlay_tx.write() = Some(overlay_tx);
1525 }
1526
1527 pub(crate) fn inspector_attach(&self) -> Option<InspectorAttachGuard> {
1528 InspectorAttachGuard::new(self.clone())
1529 }
1530
1531
1532
1533 pub(crate) fn subscribe_inspector(&self) -> Option<broadcast::Receiver<Arc<Vec<u8>>>> {
1534 self.0
1535 .inspector_overlay_tx
1536 .read()
1537 .clone()
1538 .map(|overlay_tx| overlay_tx.subscribe())
1539 }
1540
1541 pub(crate) fn downgrade(&self) -> Weak<ActorContextInner> {
1542 Arc::downgrade(&self.0)
1543 }
1544
1545 pub(crate) fn from_weak(weak: &Weak<ActorContextInner>) -> Option<Self> {
1546 weak.upgrade().map(|inner| Self(inner, None))
1547 }
1548
1549 #[doc(hidden)]
1550 pub fn set_started(&self, started: bool) {
1551 self.set_lifecycle_started(started);
1552 self.reset_sleep_timer();
1553 }
1554
1555 #[doc(hidden)]
1556 pub fn started(&self) -> bool {
1557 self.lifecycle_started()
1558 }
1559
1560 pub(crate) fn destroy_requested(&self) -> bool {
1561 self.0.destroy_requested.load(Ordering::SeqCst)
1562 }
1563
1564 pub fn is_destroy_requested(&self) -> bool {
1565 self.destroy_requested()
1566 }
1567
1568 pub(crate) async fn wait_for_destroy_completion(&self) {
1569 if self.0.destroy_completed.load(Ordering::SeqCst) {
1570 return;
1571 }
1572
1573 loop {
1574 let notified = self.0.destroy_completion_notify.notified();
1575 if self.0.destroy_completed.load(Ordering::SeqCst) {
1576 return;
1577 }
1578 notified.await;
1579 if self.0.destroy_completed.load(Ordering::SeqCst) {
1580 return;
1581 }
1582 }
1583 }
1584
1585 pub async fn wait_for_destroy_completion_public(&self) {
1586 self.wait_for_destroy_completion().await;
1587 }
1588
1589 pub(crate) fn mark_destroy_completed(&self) {
1590 self.0.destroy_completed.store(true, Ordering::SeqCst);
1591 self.0.destroy_completion_notify.notify_waiters();
1592 }
1593
1594 pub(crate) async fn can_sleep(&self) -> CanSleep {
1595 self.can_arm_sleep_timer().await
1596 }
1597
1598 pub(crate) fn pending_disconnect_count(&self) -> usize {
1599 self.0.sleep.work.disconnect_callback.load()
1600 }
1601
1602 pub async fn with_disconnect_callback<F, Fut, T>(&self, run: F) -> T
1603 where
1604 F: FnOnce() -> Fut,
1605 Fut: Future<Output = T>,
1606 {
1607 self.track_work(ActorWorkKind::DisconnectCallback, run())
1608 .await
1609 }
1610
1611 pub(crate) fn configure_lifecycle_events(
1612 &self,
1613 sender: Option<mpsc::UnboundedSender<LifecycleEvent>>,
1614 ) {
1615 *self.0.lifecycle_events.write() = sender;
1616 }
1617
1618 pub(crate) fn notify_inspector_serialize_requested(&self) {
1619 self.try_send_lifecycle_event(
1620 LifecycleEvent::InspectorSerializeRequested,
1621 "inspector_serialize_requested",
1622 );
1623 }
1624
1625 pub(crate) fn notify_activity_dirty(&self) -> bool {
1626 if self.0.lifecycle_events.read().is_none() {
1627 return false;
1628 }
1629 if self.0.activity.mark_dirty() {
1630 self.sleep_activity_notify().notify_one();
1631 }
1632 true
1633 }
1634
1635 pub(crate) fn acknowledge_activity_dirty(&self) -> bool {
1636 self.0.activity.take_dirty()
1637 }
1638
1639 pub(crate) fn reset_sleep_timer(&self) {
1643 if self.notify_activity_dirty() {
1644 return;
1645 }
1646
1647 #[cfg(feature = "wasm-runtime")]
1648 return;
1649
1650 #[cfg(not(feature = "wasm-runtime"))]
1651 self.reset_sleep_timer_state();
1652 }
1653
1654 fn notify_inspector_attachments_changed(&self) {
1655 self.try_send_lifecycle_event(
1656 LifecycleEvent::InspectorAttachmentsChanged,
1657 "inspector_attachments_changed",
1658 );
1659 }
1660
1661 pub(crate) fn configure_sleep(&self, config: ActorConfig) {
1662 self.configure_sleep_state(config.clone());
1663 self.configure_queue(config);
1664 self.reset_sleep_timer();
1665 }
1666
1667 pub(crate) fn sleep_config(&self) -> ActorConfig {
1668 self.sleep_state_config()
1669 }
1670
1671 pub(crate) fn sleep_requested(&self) -> bool {
1672 self.0.sleep_requested.load(Ordering::SeqCst)
1673 }
1674
1675 pub(crate) fn clear_sleep_requested(&self) {
1676 self.0.sleep_requested.store(false, Ordering::SeqCst);
1677 }
1678
1679 pub(crate) async fn internal_keep_awake_task(
1680 &self,
1681 future: BoxFuture<'static, Result<()>>,
1682 ) -> Result<()> {
1683 self.internal_keep_awake(future).await
1684 }
1685
1686 pub fn websocket_callback_region(&self) -> WebSocketCallbackRegion {
1687 WebSocketCallbackRegion {
1688 region: Some(self.begin_work_region(ActorWorkKind::WebSocketCallback)),
1689 }
1690 }
1691
1692 pub(crate) async fn with_websocket_callback<F, Fut, T>(&self, run: F) -> T
1693 where
1694 F: FnOnce() -> Fut,
1695 Fut: Future<Output = T>,
1696 {
1697 let _guard = self.websocket_callback_region();
1698 run().await
1699 }
1700
1701 fn idle_work_region(&self, kind: ActorWorkKind) -> Option<RegionGuard> {
1702 if !kind.policy().blocks_idle_sleep {
1703 return None;
1704 }
1705 let region = match kind {
1706 ActorWorkKind::Action => self.internal_keep_awake_region(),
1707 ActorWorkKind::KeepAwake => self.keep_awake_region_state(),
1708 ActorWorkKind::InternalKeepAwake => self.internal_keep_awake_region(),
1709 ActorWorkKind::WaitUntil => return None,
1710 ActorWorkKind::RegisteredTask => return None,
1711 ActorWorkKind::WebSocketCallback => self.websocket_callback_region_state(),
1712 ActorWorkKind::DisconnectCallback => self.disconnect_callback_region_state(),
1713 };
1714 Some(region.with_log_fields(kind.label(), Some(self.actor_id().to_owned())))
1715 }
1716
1717 fn shutdown_work_region(&self) -> CountGuard {
1718 let counter = self.0.sleep.work.shutdown_counter.clone();
1719 counter.increment();
1720 CountGuard::from_incremented(counter)
1721 }
1722
1723 fn configure_sleep_hooks(&self) {
1724 let keep_awake_ctx = self.downgrade();
1725 self.0
1726 .sleep
1727 .work
1728 .keep_awake
1729 .register_change_callback(Arc::new(move || {
1730 if let Some(ctx) = ActorContext::from_weak(&keep_awake_ctx) {
1731 ctx.0
1732 .metrics
1733 .set_keep_awake_active(ctx.sleep_keep_awake_count());
1734 }
1735 }));
1736
1737 let internal_keep_awake_metric_ctx = self.downgrade();
1738 self.0
1739 .sleep
1740 .work
1741 .internal_keep_awake
1742 .register_change_callback(Arc::new(move || {
1743 if let Some(ctx) = ActorContext::from_weak(&internal_keep_awake_metric_ctx) {
1744 ctx.0
1745 .metrics
1746 .set_internal_keep_awake_active(ctx.sleep_internal_keep_awake_count());
1747 }
1748 }));
1749
1750 let shutdown_tasks_ctx = self.downgrade();
1751 self.0
1752 .sleep
1753 .work
1754 .shutdown_counter
1755 .register_change_callback(Arc::new(move || {
1756 if let Some(ctx) = ActorContext::from_weak(&shutdown_tasks_ctx) {
1757 ctx.0
1758 .metrics
1759 .set_shutdown_tasks_active(ctx.shutdown_task_count());
1760 }
1761 }));
1762
1763 let internal_keep_awake_ctx = self.downgrade();
1764 self.set_internal_keep_awake(Some(Arc::new(move |future| {
1765 let ctx = ActorContext::from_weak(&internal_keep_awake_ctx);
1766 Box::pin(async move {
1767 let Some(ctx) = ctx else {
1768 return Err(ActorRuntime::NotConfigured {
1769 component: "actor context".to_owned(),
1770 }
1771 .build());
1772 };
1773 ctx.internal_keep_awake_task(future).await
1774 })
1775 })));
1776
1777 let queue_ctx = self.downgrade();
1778 self.set_wait_activity_callback(Some(Arc::new(move || {
1779 if let Some(ctx) = ActorContext::from_weak(&queue_ctx) {
1780 ctx.reset_sleep_timer();
1781 }
1782 })));
1783
1784 let queue_ctx = self.downgrade();
1785 self.set_inspector_update_callback(Some(Arc::new(move |queue_size| {
1786 if let Some(ctx) = ActorContext::from_weak(&queue_ctx) {
1787 ctx.record_queue_updated(queue_size);
1788 }
1789 })));
1790 }
1791
1792 pub(crate) fn record_state_updated(&self) {
1793 if let Some(inspector) = self.inspector() {
1794 inspector.record_state_updated();
1795 }
1796 }
1797
1798 pub(crate) fn record_connections_updated(&self) {
1799 let Some(inspector) = self.inspector() else {
1800 return;
1801 };
1802 let active_connections = self.active_connection_count();
1803 inspector.record_connections_updated(active_connections);
1804 }
1805
1806 fn record_queue_updated(&self, queue_size: u32) {
1807 if let Some(inspector) = self.inspector() {
1808 inspector.record_queue_updated(queue_size);
1809 }
1810 }
1811
1812 pub(crate) fn record_schedules_updated(&self) {
1813 if let Some(inspector) = self.inspector() {
1814 inspector.record_schedules_updated();
1815 }
1816 }
1817
1818 pub(crate) async fn save_state_with_revision(
1819 &self,
1820 deltas: Vec<StateDelta>,
1821 save_request_revision: u64,
1822 ) -> Result<()> {
1823 let (deltas, pending_hibernation_changes) = match self.prepare_state_deltas(deltas) {
1824 Ok(prepared) => prepared,
1825 Err(error) => return Err(error),
1826 };
1827 if let Err(error) = self.apply_state_deltas(deltas, save_request_revision).await {
1828 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1829 return Err(error);
1830 }
1831 self.record_state_updated();
1832 Ok(())
1833 }
1834
1835 pub(crate) fn state_transaction_epoch(&self) -> u64 {
1836 self.0.state_transaction_epoch.load(Ordering::SeqCst)
1837 }
1838
1839 pub(crate) async fn save_state_with_revision_at_transaction_epoch(
1840 &self,
1841 deltas: Vec<StateDelta>,
1842 save_request_revision: u64,
1843 state_transaction_epoch: u64,
1844 ) -> Result<bool> {
1845 let (deltas, pending_hibernation_changes) = match self.prepare_state_deltas(deltas) {
1846 Ok(prepared) => prepared,
1847 Err(error) => return Err(error),
1848 };
1849 match self
1850 .apply_state_deltas_inner(
1851 deltas,
1852 None,
1853 save_request_revision,
1854 Some(state_transaction_epoch),
1855 )
1856 .await
1857 {
1858 Ok(true) => {
1859 self.record_state_updated();
1860 Ok(true)
1861 }
1862 Ok(false) => {
1863 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1864 Ok(false)
1865 }
1866 Err(error) => {
1867 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1868 Err(error)
1869 }
1870 }
1871 }
1872
1873
1874
1875 pub(crate) async fn save_state_and_workflow_batch_at_transaction_epoch(
1876 &self,
1877 deltas: Vec<StateDelta>,
1878 workflow_writes: Vec<WorkflowKvWrite>,
1879 save_request_revision: u64,
1880 state_transaction_epoch: u64,
1881 ) -> Result<bool> {
1882 self.save_state_and_workflow_batch_with_revision_inner(
1883 deltas,
1884 workflow_writes,
1885 save_request_revision,
1886 Some(state_transaction_epoch),
1887 )
1888 .await
1889 }
1890
1891 async fn save_state_and_workflow_batch_with_revision_inner(
1892 &self,
1893 deltas: Vec<StateDelta>,
1894 workflow_writes: Vec<WorkflowKvWrite>,
1895 save_request_revision: u64,
1896 expected_state_transaction_epoch: Option<u64>,
1897 ) -> Result<bool> {
1898 let (deltas, pending_hibernation_changes) = match self.prepare_state_deltas(deltas) {
1899 Ok(prepared) => prepared,
1900 Err(error) => return Err(error),
1901 };
1902 match self
1903 .apply_state_deltas_inner(
1904 deltas,
1905 Some(workflow_writes),
1906 save_request_revision,
1907 expected_state_transaction_epoch,
1908 )
1909 .await
1910 {
1911 Ok(true) => {
1912 self.record_state_updated();
1913 Ok(true)
1914 }
1915 Ok(false) => {
1916 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1917 Ok(false)
1918 }
1919 Err(error) => {
1920 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1921 Err(error)
1922 }
1923 }
1924 }
1925
1926 async fn dispatch_scheduled_action(
1927 &self,
1928 dispatch: crate::actor::schedule::DueScheduleDispatch,
1929 ) {
1930 let ctx = self.clone();
1931 let event_id = dispatch.event_id;
1932 let action = dispatch.action;
1933 let args = dispatch.args;
1934 let scheduled_fire = dispatch.fire;
1935 let recurring_name = scheduled_fire.name.clone();
1936 let history_id = dispatch.history_id;
1937 let internal_keep_awake_region = self.begin_work_region(ActorWorkKind::InternalKeepAwake);
1938
1939 self.track_shutdown_task(async move {
1940 let _internal_keep_awake_region = internal_keep_awake_region;
1941 ctx.record_user_task_started(UserTaskKind::ScheduledAction);
1942 let user_task_started_at = Instant::now();
1943 let action_name = action.clone();
1944 let invocation = crate::telemetry::ActorInvocation::start_scheduled(
1945 &ctx,
1946 &action_name,
1947 dispatch.trace_context,
1948 );
1949 let invocation_telemetry = invocation.telemetry();
1950 let (reply_tx, reply_rx) = oneshot::channel();
1951
1952 let mut dispatch_error = None;
1953 match ctx.try_send_actor_event(
1954 ActorEvent::Action {
1955 name: action.clone(),
1956 args,
1957 conn: None,
1958 scheduled_fire: Some(scheduled_fire),
1959 invocation_telemetry: Some(invocation_telemetry),
1960 reply: Reply::from(reply_tx),
1961 },
1962 "scheduled_action",
1963 ) {
1964 Ok(()) => match reply_rx.await {
1965 Ok(Ok(_)) => {}
1966 Ok(Err(error)) => {
1967 dispatch_error = Some(error);
1968 tracing::error!(
1969 error = ?dispatch_error.as_ref().expect("just assigned"),
1970 event_id,
1971 action_name,
1972 "scheduled event execution failed"
1973 );
1974 }
1975 Err(_) => {
1976 dispatch_error = Some(ActorLifecycleError::DroppedReply.build());
1977 tracing::error!(
1978 error = ?dispatch_error.as_ref().expect("just assigned"),
1979 event_id,
1980 action_name,
1981 "scheduled event reply dropped"
1982 );
1983 }
1984 },
1985 Err(error) => {
1986 dispatch_error = Some(error);
1987 tracing::error!(
1988 error = ?dispatch_error.as_ref().expect("just assigned"),
1989 event_id,
1990 action_name,
1991 "failed to enqueue scheduled event"
1992 );
1993 }
1994 }
1995 invocation.finish(dispatch_error.as_ref());
1996
1997 ctx.finish_schedule_dispatch(&event_id, history_id, dispatch_error.as_ref())
1998 .await;
1999 if let (Some(name), Some(error)) = (recurring_name, dispatch_error.as_ref()) {
2000 let structured = rivet_error::RivetError::extract(error);
2001 if structured.group() == "actor" && structured.code() == "action_not_found" {
2002 if let Err(delete_error) = ctx.cron_delete_if_action(&name, &action_name).await
2003 {
2004 tracing::error!(
2005 ?delete_error,
2006 %name,
2007 "failed to delete recurring schedule for missing action"
2008 );
2009 }
2010 }
2011 }
2012
2013 ctx.record_user_task_finished(
2014 UserTaskKind::ScheduledAction,
2015 user_task_started_at.elapsed(),
2016 );
2017 });
2018 }
2019
2020 fn inspector_attach_count_arc(&self) -> Option<Arc<AtomicU32>> {
2021 self.0.inspector_attach_count.read().clone()
2022 }
2023
2024 fn try_send_lifecycle_event(&self, event: LifecycleEvent, operation: &'static str) {
2025 let Some(sender) = self.0.lifecycle_events.read().clone() else {
2026 return;
2027 };
2028
2029 if sender.send(event).is_err() {
2030 tracing::warn!(operation, "failed to enqueue actor lifecycle event");
2031 }
2032 }
2033}
2034
2035const MAX_STOP_ERROR_MESSAGE_LEN: usize = 2048;
2039
2040fn truncate_stop_error_message(mut message: String) -> String {
2041 if message.len() <= MAX_STOP_ERROR_MESSAGE_LEN {
2042 return message;
2043 }
2044 let mut end = MAX_STOP_ERROR_MESSAGE_LEN;
2045 while !message.is_char_boundary(end) {
2046 end -= 1;
2047 }
2048 message.truncate(end);
2049 message.push_str("... (truncated)");
2050 message
2051}
2052
2053struct ActorWorkGuard {
2054 ctx: ActorContext,
2055 kind: ActorWorkKind,
2056 started_at: Option<Instant>,
2057 idle_region: Option<RegionGuard>,
2058 shutdown_region: Option<CountGuard>,
2059}
2060
2061#[must_use]
2062pub struct ActorWorkRegion {
2063 guard: Option<ActorWorkGuard>,
2064}
2065
2066impl ActorWorkGuard {
2067 fn new(ctx: ActorContext, kind: ActorWorkKind) -> Self {
2068 let policy = kind.policy();
2069 let idle_region = ctx.idle_work_region(kind);
2070 let shutdown_region = if policy.drains_shutdown_grace {
2071 Some(ctx.shutdown_work_region())
2072 } else {
2073 None
2074 };
2075 let started_at = if let Some(user_task_kind) = policy.user_task_kind {
2076 ctx.record_user_task_started(user_task_kind);
2077 Some(Instant::now())
2078 } else {
2079 None
2080 };
2081 ctx.reset_sleep_timer();
2082 Self {
2083 ctx,
2084 kind,
2085 started_at,
2086 idle_region,
2087 shutdown_region,
2088 }
2089 }
2090}
2091
2092impl Drop for ActorWorkGuard {
2093 fn drop(&mut self) {
2094 if let Some(started_at) = self.started_at.take()
2095 && let Some(user_task_kind) = self.kind.policy().user_task_kind
2096 {
2097 self.ctx
2098 .record_user_task_finished(user_task_kind, started_at.elapsed());
2099 }
2100 self.idle_region.take();
2101 self.shutdown_region.take();
2102 self.ctx.reset_sleep_timer();
2103 }
2104}
2105
2106impl Drop for ActorWorkRegion {
2107 fn drop(&mut self) {
2108 self.guard.take();
2109 }
2110}
2111
2112#[must_use]
2113#[derive(Debug)]
2114pub(crate) struct InspectorAttachGuard {
2115 ctx: ActorContext,
2116}
2117
2118impl InspectorAttachGuard {
2119 fn new(ctx: ActorContext) -> Option<Self> {
2120 let attach_count = ctx.inspector_attach_count_arc()?;
2121 let previous = attach_count.fetch_add(1, Ordering::SeqCst);
2122 let current = previous.saturating_add(1);
2123 tracing::debug!(
2124 actor_id = %ctx.actor_id(),
2125 previous_count = previous,
2126 current_count = current,
2127 "inspector attached"
2128 );
2129 if previous == 0 {
2130 ctx.notify_inspector_attachments_changed();
2131 }
2132 Some(Self { ctx })
2133 }
2134}
2135
2136impl Drop for InspectorAttachGuard {
2137 fn drop(&mut self) {
2138 let Some(attach_count) = self.ctx.inspector_attach_count_arc() else {
2139 return;
2140 };
2141 let Ok(previous) = attach_count.try_update(Ordering::SeqCst, Ordering::SeqCst, |current| {
2142 current.checked_sub(1)
2143 }) else {
2144 return;
2145 };
2146 let current = previous.saturating_sub(1);
2147 tracing::debug!(
2148 actor_id = %self.ctx.actor_id(),
2149 previous_count = previous,
2150 current_count = current,
2151 "inspector detached"
2152 );
2153 if previous == 1 {
2154 self.ctx.notify_inspector_attachments_changed();
2155 }
2156 }
2157}
2158
2159pub struct WebSocketCallbackRegion {
2160 region: Option<ActorWorkRegion>,
2161}
2162
2163pub struct KeepAwakeRegion {
2164 region: Option<ActorWorkRegion>,
2165}
2166
2167impl Drop for WebSocketCallbackRegion {
2168 fn drop(&mut self) {
2169 self.region.take();
2170 }
2171}
2172
2173impl Drop for KeepAwakeRegion {
2174 fn drop(&mut self) {
2175 self.region.take();
2177 }
2178}
2179
2180impl std::fmt::Debug for ActorContext {
2181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2182 f.debug_struct("ActorContext")
2183 .field("actor_id", &self.0.actor_id)
2184 .field("name", &self.0.name)
2185 .field("key", &self.0.key)
2186 .field("region", &self.0.region)
2187 .finish()
2188 }
2189}
2190
2191