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