1use std::collections::{BTreeMap, BTreeSet};
2use std::future::Future;
3use std::sync::Weak;
4use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
5use std::sync::{Arc, OnceLock};
6use std::time::Duration;
7
8use crate::time::{Instant, SystemTime, UNIX_EPOCH};
9
10use anyhow::{Context as AnyhowContext, Result};
11use futures::future::BoxFuture;
12use parking_lot::{Mutex, RwLock};
13use rivet_envoy_client::handle::EnvoyHandle;
14use rivet_envoy_client::tunnel::HibernatingWebSocketMetadata;
15use rivet_error::ActorSpecifier;
16use scc::HashMap as SccHashMap;
17use tokio::runtime::Handle;
18use tokio::sync::{Mutex as AsyncMutex, Notify, OnceCell, broadcast, mpsc, oneshot};
19use tokio::task::JoinHandle;
20use tokio_util::sync::CancellationToken;
21
22use crate::ActorConfig;
23use crate::actor::connection::{
24 ConnHandle, ConnHandles, HibernatableConnectionMetadata, PendingHibernationChanges,
25 hibernatable_id_from_slice,
26};
27use crate::actor::diagnostics::ActorDiagnostics;
28use crate::actor::lifecycle_hooks::Reply;
29use crate::actor::messages::{ActorEvent, Request, StateDelta};
30use crate::actor::metrics::ActorMetrics;
31use crate::actor::preload::PreloadedKv;
32use crate::actor::queue::{QueueInspectorUpdateCallback, QueueMetadata, QueueWaitActivityCallback};
33use crate::actor::schedule::{InternalKeepAwakeCallback, LocalAlarmCallback};
34use crate::actor::sleep::{CanSleep, SleepState};
35use crate::actor::state::{PendingSave, PersistedActor, RequestSaveOpts};
36use crate::actor::task::LifecycleEvent;
37use crate::actor::task_types::UserTaskKind;
38use crate::actor::work_registry::{ActorWorkKind, CountGuard, RegionGuard};
39use crate::error::{ActorLifecycle as ActorLifecycleError, ActorRuntime};
40use crate::inspector::{Inspector, InspectorSnapshot};
41use crate::kv::Kv;
42use crate::sqlite::SqliteDb;
43use crate::types::{ActorKey, ConnId, ListOpts, format_actor_key};
44
45#[derive(Clone)]
53pub struct ActorContext(pub(crate) Arc<ActorContextInner>);
54
55pub(crate) struct ActorContextInner {
56 pub(super) kv: Kv,
57 sql: SqliteDb,
58 pub(super) current_state: RwLock<Vec<u8>>,
61 pub(super) persisted: RwLock<PersistedActor>,
62 pub(super) last_pushed_alarm: RwLock<Option<i64>>,
63 pub(super) state_save_interval: Duration,
64 pub(super) state_dirty: AtomicBool,
65 pub(super) state_revision: AtomicU64,
66 pub(super) save_request_revision: AtomicU64,
67 pub(super) save_completed_revision: AtomicU64,
68 pub(super) save_completion: Notify,
69 pub(super) save_requested: AtomicBool,
70 pub(super) save_requested_immediate: AtomicBool,
71 pub(super) save_requested_within_deadline: Mutex<Option<crate::time::Instant>>,
73 pub(super) last_save_at: Mutex<Option<crate::time::Instant>>,
74 pub(super) pending_save: Mutex<Option<PendingSave>>,
75 pub(super) tracked_persist: Mutex<Option<JoinHandle<()>>>,
76 pub(super) save_guard: AsyncMutex<()>,
77 pub(super) in_flight_state_writes: AtomicUsize,
78 pub(super) state_write_completion: Notify,
79 pub(super) on_state_change_in_flight: AtomicUsize,
80 pub(super) on_state_change_idle: Notify,
81 pub(super) request_save_hooks: RwLock<Vec<Arc<dyn Fn(RequestSaveOpts) + Send + Sync>>>,
84 pub(super) schedule_generation: Mutex<Option<u32>>,
87 pub(super) schedule_envoy_handle: Mutex<Option<EnvoyHandle>>,
88 pub(super) client_endpoint: OnceLock<String>,
89 pub(super) client_token: OnceLock<String>,
90 pub(super) client_namespace: OnceLock<String>,
91 pub(super) client_pool_name: OnceLock<String>,
92 pub(super) schedule_internal_keep_awake: Mutex<Option<InternalKeepAwakeCallback>>,
93 pub(super) schedule_local_alarm_callback: Mutex<Option<LocalAlarmCallback>>,
94 pub(super) schedule_local_alarm_task: Mutex<Option<JoinHandle<()>>>,
96 pub(super) schedule_pending_alarm_writes: Mutex<Vec<oneshot::Receiver<()>>>,
99 pub(super) schedule_local_alarm_epoch: AtomicU64,
100 pub(super) schedule_alarm_dispatch_enabled: AtomicBool,
101 pub(super) schedule_dirty_since_push: AtomicBool,
102 #[cfg(test)]
103 pub(super) schedule_driver_alarm_cancel_count: AtomicUsize,
104 pub(super) queue_config: Mutex<ActorConfig>,
107 pub(super) queue_abort_signal: Mutex<CancellationToken>,
108 pub(super) queue_initialize: OnceCell<()>,
109 pub(super) queue_preloaded_kv: Mutex<Option<PreloadedKv>>,
111 pub(super) queue_preloaded_message_entries: Mutex<Option<Vec<(Vec<u8>, Vec<u8>)>>>,
112 pub(super) queue_metadata: AsyncMutex<QueueMetadata>,
113 pub(super) queue_receive_lock: AsyncMutex<()>,
114 pub(super) queue_completion_waiters: SccHashMap<u64, oneshot::Sender<Option<Vec<u8>>>>,
115 pub(super) queue_notify: Notify,
116 pub(super) active_queue_wait_count: AtomicU32,
117 pub(super) queue_wait_activity_callback: Mutex<Option<QueueWaitActivityCallback>>,
119 pub(super) queue_inspector_update_callback: Mutex<Option<QueueInspectorUpdateCallback>>,
120 pub(super) connection_config: RwLock<ActorConfig>,
124 pub(super) connections: RwLock<BTreeMap<ConnId, ConnHandle>>,
125 pub(super) pending_hibernation_updates: RwLock<BTreeSet<ConnId>>,
126 pub(super) pending_hibernation_removals: RwLock<BTreeSet<ConnId>>,
127 pub(super) connection_disconnect_state: Mutex<()>,
128 pub(super) sleep: SleepState,
129 activity: ActivityState,
130 sleep_requested: AtomicBool,
131 destroy_requested: AtomicBool,
132 destroy_completed: AtomicBool,
133 destroy_completion_notify: Notify,
134 abort_signal: Mutex<CancellationToken>,
135 shutdown_deadline: CancellationToken,
136 inspector: RwLock<Option<Inspector>>,
139 inspector_attach_count: RwLock<Option<Arc<AtomicU32>>>,
140 inspector_overlay_tx: RwLock<Option<broadcast::Sender<Arc<Vec<u8>>>>>,
141 actor_events: RwLock<Option<mpsc::UnboundedSender<ActorEvent>>>,
142 pub(super) lifecycle_events: RwLock<Option<mpsc::UnboundedSender<LifecycleEvent>>>,
143 hibernated_connection_liveness_override: RwLock<Option<BTreeSet<(Vec<u8>, Vec<u8>)>>>,
144 pub(super) metrics: ActorMetrics,
145 diagnostics: ActorDiagnostics,
146 actor_id: String,
147 name: String,
148 key: ActorKey,
149 region: String,
150}
151
152#[derive(Debug, Default)]
153pub(crate) struct ActivityState {
154 dirty: AtomicBool,
155}
156
157impl ActivityState {
158 fn mark_dirty(&self) -> bool {
159 !self.dirty.swap(true, Ordering::AcqRel)
160 }
161
162 fn take_dirty(&self) -> bool {
163 self.dirty.swap(false, Ordering::AcqRel)
164 }
165}
166
167impl ActorContext {
168 pub fn new(
169 actor_id: impl Into<String>,
170 name: impl Into<String>,
171 key: ActorKey,
172 region: impl Into<String>,
173 ) -> Self {
174 Self::build(
175 actor_id.into(),
176 name.into(),
177 key,
178 region.into(),
179 None,
180 String::new(),
181 ActorConfig::default(),
182 Kv::default(),
183 SqliteDb::default(),
184 )
185 }
186
187 pub fn new_with_kv(
188 actor_id: impl Into<String>,
189 name: impl Into<String>,
190 key: ActorKey,
191 region: impl Into<String>,
192 kv: Kv,
193 ) -> Self {
194 Self::build(
195 actor_id.into(),
196 name.into(),
197 key,
198 region.into(),
199 None,
200 String::new(),
201 ActorConfig::default(),
202 kv,
203 SqliteDb::default(),
204 )
205 }
206
207 #[cfg(test)]
208 pub(crate) fn new_for_state_tests(kv: Kv, config: ActorConfig) -> Self {
209 Self::build(
210 "state-test".to_owned(),
211 "state-test".to_owned(),
212 Vec::new(),
213 "local".to_owned(),
214 None,
215 String::new(),
216 config,
217 kv,
218 SqliteDb::default(),
219 )
220 }
221
222 pub(crate) fn build(
223 actor_id: String,
224 name: String,
225 key: ActorKey,
226 region: String,
227 _generation: Option<u32>,
228 _envoy_key: String,
229 config: ActorConfig,
230 kv: Kv,
231 sql: SqliteDb,
232 ) -> Self {
233 let metrics = ActorMetrics::new(name.clone());
234 #[cfg(feature = "sqlite-local")]
235 let mut sql = sql;
236 #[cfg(feature = "sqlite-local")]
237 sql.set_vfs_metrics(Arc::new(metrics.clone()));
238 let diagnostics = ActorDiagnostics::new(actor_id.clone());
239 let state_save_interval = config.state_save_interval;
240 let abort_signal = CancellationToken::new();
241 let shutdown_deadline = CancellationToken::new();
242 let sleep = SleepState::new(config.clone());
243 let ctx = Self(Arc::new(ActorContextInner {
244 kv,
245 sql,
246 current_state: RwLock::new(Vec::new()),
247 persisted: RwLock::new(PersistedActor::default()),
248 last_pushed_alarm: RwLock::new(None),
249 state_save_interval,
250 state_dirty: AtomicBool::new(false),
251 state_revision: AtomicU64::new(0),
252 save_request_revision: AtomicU64::new(0),
253 save_completed_revision: AtomicU64::new(0),
254 save_completion: Notify::new(),
255 save_requested: AtomicBool::new(false),
256 save_requested_immediate: AtomicBool::new(false),
257 save_requested_within_deadline: Mutex::new(None),
258 last_save_at: Mutex::new(None),
259 pending_save: Mutex::new(None),
260 tracked_persist: Mutex::new(None),
261 save_guard: AsyncMutex::new(()),
262 in_flight_state_writes: AtomicUsize::new(0),
263 state_write_completion: Notify::new(),
264 on_state_change_in_flight: AtomicUsize::new(0),
265 on_state_change_idle: Notify::new(),
266 request_save_hooks: RwLock::new(Vec::new()),
267 schedule_generation: Mutex::new(None),
268 schedule_envoy_handle: Mutex::new(None),
269 client_endpoint: OnceLock::new(),
270 client_token: OnceLock::new(),
271 client_namespace: OnceLock::new(),
272 client_pool_name: OnceLock::new(),
273 schedule_internal_keep_awake: Mutex::new(None),
274 schedule_local_alarm_callback: Mutex::new(None),
275 schedule_local_alarm_task: Mutex::new(None),
276 schedule_pending_alarm_writes: Mutex::new(Vec::new()),
277 schedule_local_alarm_epoch: AtomicU64::new(0),
278 schedule_alarm_dispatch_enabled: AtomicBool::new(true),
279 schedule_dirty_since_push: AtomicBool::new(true),
282 #[cfg(test)]
283 schedule_driver_alarm_cancel_count: AtomicUsize::new(0),
284 queue_config: Mutex::new(config.clone()),
285 queue_abort_signal: Mutex::new(abort_signal.clone()),
286 queue_initialize: OnceCell::new(),
287 queue_preloaded_kv: Mutex::new(None),
288 queue_preloaded_message_entries: Mutex::new(None),
289 queue_metadata: AsyncMutex::new(QueueMetadata::default()),
290 queue_receive_lock: AsyncMutex::new(()),
291 queue_completion_waiters: SccHashMap::new(),
292 queue_notify: Notify::new(),
293 active_queue_wait_count: AtomicU32::new(0),
294 queue_wait_activity_callback: Mutex::new(None),
295 queue_inspector_update_callback: Mutex::new(None),
296 connection_config: RwLock::new(config),
297 connections: RwLock::new(BTreeMap::new()),
298 pending_hibernation_updates: RwLock::new(BTreeSet::new()),
299 pending_hibernation_removals: RwLock::new(BTreeSet::new()),
300 connection_disconnect_state: Mutex::new(()),
301 sleep,
302 activity: ActivityState::default(),
303 sleep_requested: AtomicBool::new(false),
304 destroy_requested: AtomicBool::new(false),
305 destroy_completed: AtomicBool::new(false),
306 destroy_completion_notify: Notify::new(),
307 abort_signal: Mutex::new(abort_signal),
308 shutdown_deadline,
309 inspector: RwLock::new(None),
310 inspector_attach_count: RwLock::new(None),
311 inspector_overlay_tx: RwLock::new(None),
312 actor_events: RwLock::new(None),
313 lifecycle_events: RwLock::new(None),
314 hibernated_connection_liveness_override: RwLock::new(None),
315 metrics,
316 diagnostics,
317 actor_id,
318 name,
319 key,
320 region,
321 }));
322 ctx.configure_sleep_hooks();
323 ctx
324 }
325
326 #[deprecated(
327 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
328 )]
329 pub async fn kv_batch_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
330 self.0.kv.batch_get(keys).await
331 }
332
333 #[deprecated(
334 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
335 )]
336 pub async fn kv_batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<()> {
337 self.0.kv.batch_put(entries).await
338 }
339
340 #[deprecated(
341 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
342 )]
343 pub async fn kv_batch_delete(&self, keys: &[&[u8]]) -> Result<()> {
344 self.0.kv.batch_delete(keys).await
345 }
346
347 #[deprecated(
348 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
349 )]
350 pub async fn kv_delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
351 self.0.kv.delete_range(start, end).await
352 }
353
354 #[deprecated(
355 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
356 )]
357 pub async fn kv_list_prefix(
358 &self,
359 prefix: &[u8],
360 opts: ListOpts,
361 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
362 self.0.kv.list_prefix(prefix, opts).await
363 }
364
365 #[deprecated(
366 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
367 )]
368 pub async fn kv_list_range(
369 &self,
370 start: &[u8],
371 end: &[u8],
372 opts: ListOpts,
373 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
374 self.0.kv.list_range(start, end, opts).await
375 }
376
377 #[deprecated(
378 note = "Actor KV is deprecated. Use embedded SQLite (`sql()`) or actor state instead."
379 )]
380 pub fn kv(&self) -> &Kv {
381 &self.0.kv
382 }
383
384 pub(crate) fn kv_internal(&self) -> &Kv {
389 &self.0.kv
390 }
391
392 pub fn sql(&self) -> &SqliteDb {
393 &self.0.sql
394 }
395
396 pub async fn db_exec(&self, sql: &str) -> Result<Vec<u8>> {
397 self.0.sql.exec_rows_cbor(sql).await
398 }
399
400 pub async fn db_query(&self, sql: &str, params: Option<&[u8]>) -> Result<Vec<u8>> {
401 self.0.sql.query_rows_cbor(sql, params).await
402 }
403
404 pub async fn db_execute(&self, sql: &str, params: Option<&[u8]>) -> Result<Vec<u8>> {
405 self.0.sql.execute_rows_cbor(sql, params).await
406 }
407
408 pub async fn db_run(&self, sql: &str, params: Option<&[u8]>) -> Result<()> {
409 self.0.sql.run_cbor(sql, params).await?;
410 Ok(())
411 }
412
413 pub fn set_alarm(&self, timestamp_ms: Option<i64>) -> Result<()> {
414 self.set_schedule_alarm(timestamp_ms)
415 }
416
417 pub fn init_alarms(&self) {
423 self.sync_future_alarm_logged();
424 }
425
426 pub fn queue(&self) -> &Self {
427 self
428 }
429
430 pub fn sleep(&self) -> Result<()> {
431 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst) {
436 let already_stopping = self.0.sleep_requested.load(Ordering::SeqCst)
437 || self.0.destroy_requested.load(Ordering::SeqCst);
438 return if already_stopping {
439 Err(ActorLifecycleError::Stopping.build()).context("actor is already shutting down")
440 } else {
441 Err(ActorLifecycleError::Starting.build())
442 .context("cannot request sleep before actor startup completes")
443 };
444 }
445 if self.0.sleep_requested.swap(true, Ordering::SeqCst) {
446 return Ok(());
447 }
448 self.cancel_sleep_timer();
449 if Handle::try_current().is_ok() {
450 let ctx = self.clone();
451 let tracked = self.track_shutdown_task(async move {
452 ctx.record_user_task_started(UserTaskKind::SleepFinalize);
453 let started_at = Instant::now();
454 ctx.request_sleep_from_envoy();
455 ctx.record_user_task_finished(UserTaskKind::SleepFinalize, started_at.elapsed());
456 });
457 if tracked {
458 return Ok(());
459 }
460 }
461
462 self.request_sleep_from_envoy();
463 Ok(())
464 }
465
466 pub fn destroy(&self) -> Result<()> {
467 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst)
471 && !self.0.sleep_requested.load(Ordering::SeqCst)
472 && !self.0.destroy_requested.load(Ordering::SeqCst)
473 {
474 return Err(ActorLifecycleError::Starting.build())
475 .context("cannot request destroy before actor startup completes");
476 }
477 if self.0.destroy_requested.swap(true, Ordering::SeqCst) {
478 return Err(ActorLifecycleError::Stopping.build())
479 .context("destroy already requested for this generation");
480 }
481 #[cfg(not(feature = "wasm-runtime"))]
486 self.mark_destroy_requested();
487 #[cfg(feature = "wasm-runtime")]
488 self.mark_destroy_requested_without_spawn();
489
490 let ctx = self.clone();
491 if Handle::try_current().is_ok() {
492 let tracked = self.track_shutdown_task(async move {
493 ctx.record_user_task_started(UserTaskKind::DestroyRequest);
494 let started_at = Instant::now();
495 ctx.request_destroy_from_envoy();
496 ctx.record_user_task_finished(UserTaskKind::DestroyRequest, started_at.elapsed());
497 });
498 if tracked {
499 return Ok(());
500 }
501 }
502
503 self.request_destroy_from_envoy();
504 Ok(())
505 }
506
507 pub fn mark_destroy_requested(&self) {
508 self.cancel_sleep_timer();
509 self.flush_on_shutdown();
510 self.0.destroy_requested.store(true, Ordering::SeqCst);
511 self.0.destroy_completed.store(false, Ordering::SeqCst);
512 }
513
514 #[cfg(feature = "wasm-runtime")]
515 fn mark_destroy_requested_without_spawn(&self) {
516 self.cancel_sleep_timer();
517 self.0.destroy_requested.store(true, Ordering::SeqCst);
518 self.0.destroy_completed.store(false, Ordering::SeqCst);
519 }
520
521 #[doc(hidden)]
522 pub fn cancel_actor_abort_signal(&self) {
523 self.0.abort_signal.lock().cancel();
524 }
525
526 pub(crate) fn reset_abort_signal_for_start(&self) {
527 let mut abort_signal = self.0.abort_signal.lock();
528 if !abort_signal.is_cancelled() {
529 return;
530 }
531
532 let next_signal = CancellationToken::new();
536 *abort_signal = next_signal.clone();
537 *self.0.queue_abort_signal.lock() = next_signal;
538 }
539
540 #[doc(hidden)]
541 pub fn actor_abort_signal(&self) -> CancellationToken {
542 self.0.abort_signal.lock().clone()
543 }
544
545 #[doc(hidden)]
546 pub fn actor_aborted(&self) -> bool {
547 self.0.abort_signal.lock().is_cancelled()
548 }
549
550 #[doc(hidden)]
555 pub fn shutdown_deadline_token(&self) -> CancellationToken {
556 self.0.shutdown_deadline.clone()
557 }
558
559 #[doc(hidden)]
560 pub fn cancel_shutdown_deadline(&self) {
561 self.0.shutdown_deadline.cancel();
562 }
563
564 #[deprecated(note = "no-op: use `keep_awake` or `wait_until` instead")]
568 pub fn set_prevent_sleep(&self, _enabled: bool) {}
569
570 #[deprecated(note = "no-op: always returns false")]
571 pub fn prevent_sleep(&self) -> bool {
572 false
573 }
574
575 #[cfg(not(feature = "wasm-runtime"))]
576 pub fn wait_until(&self, future: impl Future<Output = ()> + Send + 'static) {
577 self.spawn_work(ActorWorkKind::WaitUntil, future);
578 }
579
580 #[cfg(not(feature = "wasm-runtime"))]
581 pub fn register_task(&self, future: impl Future<Output = ()> + Send + 'static) {
582 self.spawn_work(ActorWorkKind::RegisteredTask, future);
583 }
584
585 #[cfg(feature = "wasm-runtime")]
586 pub fn wait_until(&self, future: impl Future<Output = ()> + 'static) {
587 self.spawn_work(ActorWorkKind::WaitUntil, future);
588 }
589
590 #[cfg(feature = "wasm-runtime")]
591 pub fn register_task(&self, future: impl Future<Output = ()> + 'static) {
592 self.spawn_work(ActorWorkKind::RegisteredTask, future);
593 }
594
595 pub async fn keep_awake<F>(&self, future: F) -> F::Output
596 where
597 F: Future,
598 {
599 self.track_work(ActorWorkKind::KeepAwake, future).await
600 }
601
602 pub fn keep_awake_region(&self) -> KeepAwakeRegion {
603 KeepAwakeRegion {
604 region: Some(self.begin_work_region(ActorWorkKind::KeepAwake)),
605 }
606 }
607
608 pub async fn internal_keep_awake<F>(&self, future: F) -> F::Output
609 where
610 F: Future,
611 {
612 self.track_work(ActorWorkKind::InternalKeepAwake, future)
613 .await
614 }
615
616 pub fn keep_awake_count(&self) -> usize {
617 self.sleep_keep_awake_count()
618 }
619
620 pub fn internal_keep_awake_count(&self) -> usize {
621 self.sleep_internal_keep_awake_count()
622 }
623
624 pub async fn track_work<F>(&self, kind: ActorWorkKind, future: F) -> F::Output
625 where
626 F: Future,
627 {
628 let _region = self.begin_work_region(kind);
629 future.await
630 }
631
632 #[cfg(not(feature = "wasm-runtime"))]
633 pub fn spawn_work<F>(&self, kind: ActorWorkKind, future: F)
634 where
635 F: Future<Output = ()> + Send + 'static,
636 {
637 self.spawn_work_inner(kind, future);
638 }
639
640 #[cfg(feature = "wasm-runtime")]
641 pub fn spawn_work<F>(&self, kind: ActorWorkKind, future: F)
642 where
643 F: Future<Output = ()> + 'static,
644 {
645 self.spawn_work_inner(kind, future);
646 }
647
648 pub fn begin_work_region(&self, kind: ActorWorkKind) -> ActorWorkRegion {
649 ActorWorkRegion {
650 guard: Some(ActorWorkGuard::new(self.clone(), kind)),
651 }
652 }
653
654 pub fn actor_id(&self) -> &str {
655 &self.0.actor_id
656 }
657
658 pub fn name(&self) -> &str {
659 &self.0.name
660 }
661
662 pub fn key(&self) -> &ActorKey {
663 &self.0.key
664 }
665
666 pub(crate) fn actor_specifier(&self) -> Option<ActorSpecifier> {
667 Some(
668 ActorSpecifier::new(self.actor_id().to_owned(), self.sleep_generation()? as u64)
669 .with_key(format_actor_key(self.key())),
670 )
671 }
672
673 pub(crate) fn attach_actor_to_error(&self, error: anyhow::Error) -> anyhow::Error {
674 match self.actor_specifier() {
675 Some(actor) => error.context(actor),
676 None => error,
677 }
678 }
679
680 pub fn region(&self) -> &str {
681 &self.0.region
682 }
683
684 pub fn has_state(&self) -> bool {
685 self.0.connection_config.read().has_state
686 }
687
688 #[doc(hidden)]
689 pub fn record_startup_create_state(&self, duration: Duration) {
690 self.0.metrics.observe_create_state(duration);
691 }
692
693 #[doc(hidden)]
694 pub fn record_startup_create_vars(&self, duration: Duration) {
695 self.0.metrics.observe_create_vars(duration);
696 }
697
698 pub fn broadcast(&self, name: &str, args: &[u8]) {
699 for connection in self.conns() {
700 if connection.is_subscribed(name) {
701 connection.send(name, args);
702 }
703 }
704 }
705
706 #[must_use]
711 pub fn conns(&self) -> ConnHandles<'_> {
712 self.iter_connections()
713 }
714
715 pub fn client_endpoint(&self) -> Option<&str> {
716 self.0.client_endpoint.get().map(String::as_str)
717 }
718
719 pub fn client_token(&self) -> Option<&str> {
720 self.0.client_token.get().map(String::as_str)
721 }
722
723 pub fn client_namespace(&self) -> Option<&str> {
724 self.0.client_namespace.get().map(String::as_str)
725 }
726
727 pub fn client_pool_name(&self) -> Option<&str> {
728 self.0.client_pool_name.get().map(String::as_str)
729 }
730
731 pub fn ack_hibernatable_websocket_message(
732 &self,
733 gateway_id: &[u8],
734 request_id: &[u8],
735 server_message_index: u16,
736 ) -> Result<()> {
737 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
738 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
739 let envoy_handle = self.sleep_envoy_handle().ok_or_else(|| {
740 ActorRuntime::NotConfigured {
741 component: "hibernatable websocket ack".to_owned(),
742 }
743 .build()
744 })?;
745 envoy_handle.send_hibernatable_ws_message_ack(gateway_id, request_id, server_message_index);
746 Ok(())
747 }
748
749 pub(crate) fn load_persisted_actor(&self, persisted: PersistedActor) {
750 self.load_persisted(persisted);
751 }
752
753 pub(crate) fn persisted_actor(&self) -> PersistedActor {
754 self.persisted()
755 }
756
757 pub async fn drain_overdue_scheduled_events(&self) -> Result<()> {
762 for event in self.due_scheduled_events(now_timestamp_ms()) {
763 self.dispatch_scheduled_action(
764 &event.event_id,
765 event.action,
766 event.args.unwrap_or_default(),
767 )
768 .await;
769 }
770
771 self.sync_alarm_logged();
772 Ok(())
773 }
774
775 pub(crate) fn metrics(&self) -> &ActorMetrics {
776 &self.0.metrics
777 }
778
779 pub(crate) fn record_user_task_started(&self, kind: UserTaskKind) {
780 self.0.metrics.begin_user_task(kind);
781 }
782
783 pub(crate) fn record_user_task_finished(&self, kind: UserTaskKind, duration: Duration) {
784 self.0.metrics.end_user_task(kind, duration);
785 }
786
787 pub(crate) fn record_shutdown_wait(
788 &self,
789 reason: crate::actor::task_types::ShutdownKind,
790 duration: Duration,
791 ) {
792 self.0.metrics.observe_shutdown_wait(reason, duration);
793 }
794
795 pub(crate) fn record_shutdown_timeout(&self, reason: crate::actor::task_types::ShutdownKind) {
796 self.0.metrics.inc_shutdown_timeout(reason);
797 }
798
799 pub(crate) fn record_direct_subsystem_shutdown_warning(
800 &self,
801 subsystem: &str,
802 operation: &str,
803 ) {
804 self.0
805 .metrics
806 .inc_direct_subsystem_shutdown_warning(subsystem, operation);
807 }
808
809 pub(crate) fn warn_work_sent_to_stopping_instance(&self, operation: &'static str) {
810 if let Some(suppression) = self.0.diagnostics.record("work_sent_to_stopping_instance") {
811 tracing::warn!(
812 actor_id = %suppression.actor_id,
813 operation,
814 per_actor_suppressed = suppression.per_actor_suppressed,
815 global_suppressed = suppression.global_suppressed,
816 "work sent to stopping actor instance"
817 );
818 }
819 }
820
821 pub(crate) fn warn_self_call_risk(&self, operation: &'static str) {
822 if let Some(suppression) = self.0.diagnostics.record("self_call_risk") {
823 tracing::warn!(
824 actor_id = %suppression.actor_id,
825 operation,
826 per_actor_suppressed = suppression.per_actor_suppressed,
827 global_suppressed = suppression.global_suppressed,
828 "actor dispatch may be parked behind the current instance"
829 );
830 }
831 }
832
833 #[cfg(test)]
834 pub(crate) fn add_conn(&self, conn: ConnHandle) {
835 self.insert_existing(conn);
836 self.record_connections_updated();
837 self.reset_sleep_timer();
838 }
839
840 pub(crate) fn remove_conn(&self, conn_id: &str) -> Option<ConnHandle> {
841 let removed = self.remove_existing(conn_id);
842 if removed.is_some() {
843 self.record_connections_updated();
844 self.reset_sleep_timer();
845 }
846 removed
847 }
848
849 pub(crate) fn configure_connection_runtime(&self, config: ActorConfig) {
850 self.configure_sleep_state(config.clone());
851 self.configure_connection_storage(config);
852 }
853
854 pub(crate) fn configure_queue_preload(&self, preloaded_kv: Option<PreloadedKv>) {
855 self.configure_preload(preloaded_kv);
856 }
857
858 pub(crate) fn configure_actor_events(&self, sender: Option<mpsc::UnboundedSender<ActorEvent>>) {
859 *self.0.actor_events.write() = sender;
860 }
861
862 pub(crate) fn try_send_actor_event(
863 &self,
864 event: ActorEvent,
865 operation: &'static str,
866 ) -> Result<()> {
867 let sender = self.0.actor_events.read().clone().ok_or_else(|| {
868 ActorRuntime::NotConfigured {
869 component: "actor event inbox".to_owned(),
870 }
871 .build()
872 })?;
873 tracing::debug!(
874 actor_id = %self.actor_id(),
875 operation,
876 event = event.kind(),
877 "actor event enqueued"
878 );
879 sender.send(event).map_err(|_| {
880 ActorRuntime::NotConfigured {
881 component: "actor event inbox".to_owned(),
882 }
883 .build()
884 })
885 }
886
887 #[doc(hidden)]
888 pub fn configure_envoy(&self, envoy_handle: EnvoyHandle, generation: Option<u32>) {
889 let _ = self
890 .0
891 .client_endpoint
892 .set(envoy_handle.endpoint().to_owned());
893 if let Some(token) = envoy_handle.token() {
894 let _ = self.0.client_token.set(token.to_owned());
895 }
896 let _ = self
897 .0
898 .client_namespace
899 .set(envoy_handle.namespace().to_owned());
900 let _ = self
901 .0
902 .client_pool_name
903 .set(envoy_handle.pool_name().to_owned());
904 self.configure_sleep_envoy(envoy_handle.clone(), generation);
905 self.configure_schedule_envoy(envoy_handle, generation);
906 }
907
908 pub(crate) async fn connect_conn<F>(
909 &self,
910 params: Vec<u8>,
911 is_hibernatable: bool,
912 hibernation: Option<HibernatableConnectionMetadata>,
913 request: Option<Request>,
914 create_state: F,
915 ) -> Result<ConnHandle>
916 where
917 F: Future<Output = Result<Vec<u8>>> + Send,
918 {
919 let conn = self
920 .connect_with_state(params, is_hibernatable, hibernation, request, create_state)
921 .await?;
922 self.record_connections_updated();
923 self.reset_sleep_timer();
924 Ok(conn)
925 }
926
927 pub(crate) async fn connect_conn_with_prepare<F, P>(
928 &self,
929 params: Vec<u8>,
930 is_hibernatable: bool,
931 hibernation: Option<HibernatableConnectionMetadata>,
932 request: Option<Request>,
933 create_state: F,
934 prepare_connection: P,
935 ) -> Result<ConnHandle>
936 where
937 F: Future<Output = Result<Vec<u8>>> + Send,
938 P: FnOnce(&ConnHandle) -> Result<()>,
939 {
940 let conn = self
941 .connect_with_state_and_prepare(
942 params,
943 is_hibernatable,
944 hibernation,
945 request,
946 create_state,
947 prepare_connection,
948 )
949 .await?;
950 self.record_connections_updated();
951 self.reset_sleep_timer();
952 Ok(conn)
953 }
954
955 pub async fn connect_conn_with_request<F>(
956 &self,
957 params: Vec<u8>,
958 request: Option<Request>,
959 create_state: F,
960 ) -> Result<ConnHandle>
961 where
962 F: Future<Output = Result<Vec<u8>>> + Send,
963 {
964 self.connect_conn(params, false, None, request, create_state)
965 .await
966 }
967
968 pub(crate) fn reconnect_hibernatable_conn(
969 &self,
970 gateway_id: &[u8],
971 request_id: &[u8],
972 ) -> Result<ConnHandle> {
973 self.reconnect_hibernatable(gateway_id, request_id)
974 }
975
976 pub async fn disconnect_conn(&self, id: ConnId) -> Result<()> {
977 self.disconnect_transport_only(|conn| conn.id() == id).await
978 }
979
980 pub async fn disconnect_conns<F>(&self, predicate: F) -> Result<()>
981 where
982 F: FnMut(&ConnHandle) -> bool,
983 {
984 self.disconnect_transport_only(predicate).await
985 }
986
987 pub(crate) fn request_hibernation_transport_save(&self, conn_id: &str) {
988 self.queue_hibernation_update(conn_id.to_owned());
989 self.request_save(RequestSaveOpts::default());
990 }
991
992 pub(crate) fn request_hibernation_transport_removal(&self, conn_id: impl Into<String>) {
993 self.queue_hibernation_removal_inner(conn_id.into());
994 self.request_save(RequestSaveOpts::default());
995 }
996
997 pub fn queue_hibernation_removal(&self, conn_id: impl Into<String>) {
998 self.request_hibernation_transport_removal(conn_id);
999 }
1000
1001 pub fn has_pending_hibernation_changes(&self) -> bool {
1002 self.has_pending_hibernation_changes_inner()
1003 }
1004
1005 pub fn take_pending_hibernation_changes(&self) -> Vec<ConnId> {
1006 self.pending_hibernation_removals()
1007 }
1008
1009 pub fn dirty_hibernatable_conns(&self) -> Vec<ConnHandle> {
1010 self.dirty_hibernatable_conns_inner()
1011 }
1012
1013 pub(crate) fn hibernated_connection_is_live(
1014 &self,
1015 gateway_id: &[u8],
1016 request_id: &[u8],
1017 ) -> Result<bool> {
1018 let gateway_id = hibernatable_id_from_slice("gateway_id", gateway_id)?;
1019 let request_id = hibernatable_id_from_slice("request_id", request_id)?;
1020
1021 if let Some(override_pairs) = self
1022 .0
1023 .hibernated_connection_liveness_override
1024 .read()
1025 .as_ref()
1026 {
1027 return Ok(override_pairs.contains(&(gateway_id.to_vec(), request_id.to_vec())));
1028 }
1029
1030 let Some(envoy_handle) = self.sleep_envoy_handle() else {
1031 return Ok(false);
1032 };
1033 let is_live = envoy_handle.hibernatable_connection_is_live(
1034 self.actor_id(),
1035 self.sleep_generation(),
1036 gateway_id,
1037 request_id,
1038 );
1039 Ok(is_live)
1040 }
1041
1042 #[cfg(test)]
1043 pub(crate) fn set_hibernated_connection_liveness_override<I>(&self, pairs: I)
1044 where
1045 I: IntoIterator<Item = (Vec<u8>, Vec<u8>)>,
1046 {
1047 *self.0.hibernated_connection_liveness_override.write() = Some(pairs.into_iter().collect());
1048 }
1049
1050 fn prepare_state_deltas(
1051 &self,
1052 deltas: Vec<StateDelta>,
1053 ) -> Result<(Vec<StateDelta>, PendingHibernationChanges)> {
1054 fn finish_with_error(
1055 ctx: &ActorContext,
1056 pending: PendingHibernationChanges,
1057 error: anyhow::Error,
1058 ) -> Result<(Vec<StateDelta>, PendingHibernationChanges)> {
1059 ctx.restore_pending_hibernation_changes(pending);
1060 Err(error)
1061 }
1062
1063 let mut next_deltas = Vec::new();
1064 let mut explicit_updates = std::collections::BTreeMap::new();
1065 let mut explicit_removals = std::collections::BTreeSet::new();
1066
1067 for delta in deltas {
1068 match delta {
1069 StateDelta::ConnHibernation { conn, bytes } => {
1070 if let Some(handle) = self.connection(&conn) {
1071 handle.set_state_initial(bytes.clone());
1072 }
1073 explicit_updates.insert(conn, bytes);
1074 }
1075 StateDelta::ConnHibernationRemoved(conn) => {
1076 explicit_removals.insert(conn);
1077 }
1078 other => next_deltas.push(other),
1079 }
1080 }
1081
1082 let pending = self.take_pending_hibernation_changes_inner();
1083 let mut removal_ids = pending.removed.clone();
1084 removal_ids.extend(explicit_removals.iter().cloned());
1085
1086 let explicit_update_ids: std::collections::BTreeSet<_> =
1087 explicit_updates.keys().cloned().collect();
1088
1089 for (conn, bytes) in explicit_updates {
1090 if removal_ids.contains(&conn) {
1091 continue;
1092 }
1093 let encoded = match self.encode_hibernation_delta(&conn, bytes) {
1094 Ok(encoded) => encoded,
1095 Err(error) => {
1096 return finish_with_error(self, pending, error);
1097 }
1098 };
1099 next_deltas.push(StateDelta::ConnHibernation {
1100 conn,
1101 bytes: encoded,
1102 });
1103 }
1104
1105 for conn in &pending.updated {
1106 if removal_ids.contains(conn)
1107 || explicit_removals.contains(conn)
1108 || explicit_update_ids.contains(conn)
1109 {
1110 continue;
1111 }
1112 let Some(handle) = self.connection(conn) else {
1113 continue;
1114 };
1115 if !handle.is_hibernatable() || handle.hibernation().is_none() {
1116 continue;
1117 }
1118 let encoded = match self.encode_hibernation_delta(conn, handle.state()) {
1119 Ok(encoded) => encoded,
1120 Err(error) => {
1121 return finish_with_error(self, pending, error);
1122 }
1123 };
1124 next_deltas.push(StateDelta::ConnHibernation {
1125 conn: conn.clone(),
1126 bytes: encoded,
1127 });
1128 }
1129
1130 for conn in removal_ids {
1131 next_deltas.push(StateDelta::ConnHibernationRemoved(conn));
1132 }
1133
1134 Ok((next_deltas, pending))
1135 }
1136
1137 #[cfg(test)]
1138 pub(crate) async fn restore_hibernatable_connections(&self) -> Result<Vec<ConnHandle>> {
1139 self.restore_hibernatable_connections_with_preload(None)
1140 .await
1141 }
1142
1143 pub(crate) async fn restore_hibernatable_connections_with_preload(
1144 &self,
1145 preloaded_kv: Option<&PreloadedKv>,
1146 ) -> Result<Vec<ConnHandle>> {
1147 let restored = self.restore_persisted(preloaded_kv).await?;
1148 if !restored.is_empty() {
1149 if let Some(envoy_handle) = self.sleep_envoy_handle() {
1150 let meta_entries: Vec<_> = restored
1151 .iter()
1152 .filter_map(|conn| {
1153 let hibernation = conn.hibernation()?;
1154 Some(HibernatingWebSocketMetadata {
1155 gateway_id: hibernation.gateway_id,
1156 request_id: hibernation.request_id,
1157 envoy_message_index: hibernation.client_message_index,
1158 rivet_message_index: hibernation.server_message_index,
1159 path: hibernation.request_path,
1160 headers: hibernation.request_headers.into_iter().collect(),
1161 })
1162 })
1163 .collect();
1164 envoy_handle.restore_hibernating_requests(self.actor_id().to_owned(), meta_entries);
1165 }
1166 self.record_connections_updated();
1167 self.reset_sleep_timer();
1168 }
1169 Ok(restored)
1170 }
1171
1172 pub(crate) fn configure_inspector(&self, inspector: Option<Inspector>) {
1173 *self.0.inspector.write() = inspector;
1174 }
1175
1176 pub(crate) fn inspector(&self) -> Option<Inspector> {
1177 self.0.inspector.read().clone()
1178 }
1179
1180 pub fn inspector_snapshot(&self) -> InspectorSnapshot {
1181 self.inspector()
1182 .map(|inspector| inspector.snapshot())
1183 .unwrap_or_default()
1184 }
1185
1186 pub(crate) fn configure_inspector_runtime(
1187 &self,
1188 attach_count: Arc<AtomicU32>,
1189 overlay_tx: broadcast::Sender<Arc<Vec<u8>>>,
1190 ) {
1191 *self.0.inspector_attach_count.write() = Some(attach_count);
1192 *self.0.inspector_overlay_tx.write() = Some(overlay_tx);
1193 }
1194
1195 pub(crate) fn inspector_attach(&self) -> Option<InspectorAttachGuard> {
1196 InspectorAttachGuard::new(self.clone())
1197 }
1198
1199 #[cfg(test)]
1200 pub(crate) fn inspector_attach_count(&self) -> u32 {
1201 self.inspector_attach_count_arc()
1202 .map(|attach_count| attach_count.load(Ordering::SeqCst))
1203 .unwrap_or(0)
1204 }
1205
1206 pub(crate) fn subscribe_inspector(&self) -> Option<broadcast::Receiver<Arc<Vec<u8>>>> {
1207 self.0
1208 .inspector_overlay_tx
1209 .read()
1210 .clone()
1211 .map(|overlay_tx| overlay_tx.subscribe())
1212 }
1213
1214 pub(crate) fn downgrade(&self) -> Weak<ActorContextInner> {
1215 Arc::downgrade(&self.0)
1216 }
1217
1218 pub(crate) fn from_weak(weak: &Weak<ActorContextInner>) -> Option<Self> {
1219 weak.upgrade().map(Self)
1220 }
1221
1222 #[doc(hidden)]
1223 pub fn set_started(&self, started: bool) {
1224 self.set_lifecycle_started(started);
1225 self.reset_sleep_timer();
1226 }
1227
1228 #[doc(hidden)]
1229 pub fn started(&self) -> bool {
1230 self.lifecycle_started()
1231 }
1232
1233 pub(crate) fn destroy_requested(&self) -> bool {
1234 self.0.destroy_requested.load(Ordering::SeqCst)
1235 }
1236
1237 pub fn is_destroy_requested(&self) -> bool {
1238 self.destroy_requested()
1239 }
1240
1241 pub(crate) async fn wait_for_destroy_completion(&self) {
1242 if self.0.destroy_completed.load(Ordering::SeqCst) {
1243 return;
1244 }
1245
1246 loop {
1247 let notified = self.0.destroy_completion_notify.notified();
1248 if self.0.destroy_completed.load(Ordering::SeqCst) {
1249 return;
1250 }
1251 notified.await;
1252 if self.0.destroy_completed.load(Ordering::SeqCst) {
1253 return;
1254 }
1255 }
1256 }
1257
1258 pub async fn wait_for_destroy_completion_public(&self) {
1259 self.wait_for_destroy_completion().await;
1260 }
1261
1262 pub(crate) fn mark_destroy_completed(&self) {
1263 self.0.destroy_completed.store(true, Ordering::SeqCst);
1264 self.0.destroy_completion_notify.notify_waiters();
1265 }
1266
1267 pub(crate) async fn can_sleep(&self) -> CanSleep {
1268 self.can_arm_sleep_timer().await
1269 }
1270
1271 pub(crate) fn pending_disconnect_count(&self) -> usize {
1272 self.0.sleep.work.disconnect_callback.load()
1273 }
1274
1275 pub async fn with_disconnect_callback<F, Fut, T>(&self, run: F) -> T
1276 where
1277 F: FnOnce() -> Fut,
1278 Fut: Future<Output = T>,
1279 {
1280 self.track_work(ActorWorkKind::DisconnectCallback, run())
1281 .await
1282 }
1283
1284 pub(crate) fn configure_lifecycle_events(
1285 &self,
1286 sender: Option<mpsc::UnboundedSender<LifecycleEvent>>,
1287 ) {
1288 *self.0.lifecycle_events.write() = sender;
1289 }
1290
1291 pub(crate) fn notify_inspector_serialize_requested(&self) {
1292 self.try_send_lifecycle_event(
1293 LifecycleEvent::InspectorSerializeRequested,
1294 "inspector_serialize_requested",
1295 );
1296 }
1297
1298 pub(crate) fn notify_activity_dirty(&self) -> bool {
1299 if self.0.lifecycle_events.read().is_none() {
1300 return false;
1301 }
1302 if self.0.activity.mark_dirty() {
1303 self.sleep_activity_notify().notify_one();
1304 }
1305 true
1306 }
1307
1308 pub(crate) fn acknowledge_activity_dirty(&self) -> bool {
1309 self.0.activity.take_dirty()
1310 }
1311
1312 pub(crate) fn reset_sleep_timer(&self) {
1316 if self.notify_activity_dirty() {
1317 return;
1318 }
1319
1320 #[cfg(feature = "wasm-runtime")]
1321 return;
1322
1323 #[cfg(not(feature = "wasm-runtime"))]
1324 self.reset_sleep_timer_state();
1325 }
1326
1327 fn notify_inspector_attachments_changed(&self) {
1328 self.try_send_lifecycle_event(
1329 LifecycleEvent::InspectorAttachmentsChanged,
1330 "inspector_attachments_changed",
1331 );
1332 }
1333
1334 pub(crate) fn configure_sleep(&self, config: ActorConfig) {
1335 self.configure_sleep_state(config.clone());
1336 self.configure_queue(config);
1337 self.reset_sleep_timer();
1338 }
1339
1340 pub(crate) fn sleep_config(&self) -> ActorConfig {
1341 self.sleep_state_config()
1342 }
1343
1344 pub(crate) fn sleep_requested(&self) -> bool {
1345 self.0.sleep_requested.load(Ordering::SeqCst)
1346 }
1347
1348 pub(crate) fn clear_sleep_requested(&self) {
1349 self.0.sleep_requested.store(false, Ordering::SeqCst);
1350 }
1351
1352 pub(crate) async fn internal_keep_awake_task(
1353 &self,
1354 future: BoxFuture<'static, Result<()>>,
1355 ) -> Result<()> {
1356 self.internal_keep_awake(future).await
1357 }
1358
1359 pub fn websocket_callback_region(&self) -> WebSocketCallbackRegion {
1360 WebSocketCallbackRegion {
1361 region: Some(self.begin_work_region(ActorWorkKind::WebSocketCallback)),
1362 }
1363 }
1364
1365 pub(crate) async fn with_websocket_callback<F, Fut, T>(&self, run: F) -> T
1366 where
1367 F: FnOnce() -> Fut,
1368 Fut: Future<Output = T>,
1369 {
1370 let _guard = self.websocket_callback_region();
1371 run().await
1372 }
1373
1374 fn idle_work_region(&self, kind: ActorWorkKind) -> Option<RegionGuard> {
1375 if !kind.policy().blocks_idle_sleep {
1376 return None;
1377 }
1378 let region = match kind {
1379 ActorWorkKind::Action => self.internal_keep_awake_region(),
1380 ActorWorkKind::KeepAwake => self.keep_awake_region_state(),
1381 ActorWorkKind::InternalKeepAwake => self.internal_keep_awake_region(),
1382 ActorWorkKind::WaitUntil => return None,
1383 ActorWorkKind::RegisteredTask => return None,
1384 ActorWorkKind::WebSocketCallback => self.websocket_callback_region_state(),
1385 ActorWorkKind::DisconnectCallback => self.disconnect_callback_region_state(),
1386 };
1387 Some(region.with_log_fields(kind.label(), Some(self.actor_id().to_owned())))
1388 }
1389
1390 fn shutdown_work_region(&self) -> CountGuard {
1391 let counter = self.0.sleep.work.shutdown_counter.clone();
1392 counter.increment();
1393 CountGuard::from_incremented(counter)
1394 }
1395
1396 fn configure_sleep_hooks(&self) {
1397 let keep_awake_ctx = self.downgrade();
1398 self.0
1399 .sleep
1400 .work
1401 .keep_awake
1402 .register_change_callback(Arc::new(move || {
1403 if let Some(ctx) = ActorContext::from_weak(&keep_awake_ctx) {
1404 ctx.0
1405 .metrics
1406 .set_keep_awake_active(ctx.sleep_keep_awake_count());
1407 }
1408 }));
1409
1410 let internal_keep_awake_metric_ctx = self.downgrade();
1411 self.0
1412 .sleep
1413 .work
1414 .internal_keep_awake
1415 .register_change_callback(Arc::new(move || {
1416 if let Some(ctx) = ActorContext::from_weak(&internal_keep_awake_metric_ctx) {
1417 ctx.0
1418 .metrics
1419 .set_internal_keep_awake_active(ctx.sleep_internal_keep_awake_count());
1420 }
1421 }));
1422
1423 let shutdown_tasks_ctx = self.downgrade();
1424 self.0
1425 .sleep
1426 .work
1427 .shutdown_counter
1428 .register_change_callback(Arc::new(move || {
1429 if let Some(ctx) = ActorContext::from_weak(&shutdown_tasks_ctx) {
1430 ctx.0
1431 .metrics
1432 .set_shutdown_tasks_active(ctx.shutdown_task_count());
1433 }
1434 }));
1435
1436 let internal_keep_awake_ctx = self.downgrade();
1437 self.set_internal_keep_awake(Some(Arc::new(move |future| {
1438 let ctx = ActorContext::from_weak(&internal_keep_awake_ctx);
1439 Box::pin(async move {
1440 let Some(ctx) = ctx else {
1441 return Err(ActorRuntime::NotConfigured {
1442 component: "actor context".to_owned(),
1443 }
1444 .build());
1445 };
1446 ctx.internal_keep_awake_task(future).await
1447 })
1448 })));
1449
1450 let queue_ctx = self.downgrade();
1451 self.set_wait_activity_callback(Some(Arc::new(move || {
1452 if let Some(ctx) = ActorContext::from_weak(&queue_ctx) {
1453 ctx.reset_sleep_timer();
1454 }
1455 })));
1456
1457 let queue_ctx = self.downgrade();
1458 self.set_inspector_update_callback(Some(Arc::new(move |queue_size| {
1459 if let Some(ctx) = ActorContext::from_weak(&queue_ctx) {
1460 ctx.record_queue_updated(queue_size);
1461 }
1462 })));
1463 }
1464
1465 pub(crate) fn record_state_updated(&self) {
1466 if let Some(inspector) = self.inspector() {
1467 inspector.record_state_updated();
1468 }
1469 }
1470
1471 pub(crate) fn record_connections_updated(&self) {
1472 let Some(inspector) = self.inspector() else {
1473 return;
1474 };
1475 let active_connections = self.active_connection_count();
1476 inspector.record_connections_updated(active_connections);
1477 }
1478
1479 fn record_queue_updated(&self, queue_size: u32) {
1480 if let Some(inspector) = self.inspector() {
1481 inspector.record_queue_updated(queue_size);
1482 }
1483 }
1484
1485 pub(crate) async fn save_state_with_revision(
1486 &self,
1487 deltas: Vec<StateDelta>,
1488 save_request_revision: u64,
1489 ) -> Result<()> {
1490 let (deltas, pending_hibernation_changes) = match self.prepare_state_deltas(deltas) {
1491 Ok(prepared) => prepared,
1492 Err(error) => return Err(error),
1493 };
1494 if let Err(error) = self.apply_state_deltas(deltas, save_request_revision).await {
1495 self.restore_pending_hibernation_changes(pending_hibernation_changes);
1496 return Err(error);
1497 }
1498 self.record_state_updated();
1499 Ok(())
1500 }
1501
1502 async fn dispatch_scheduled_action(&self, event_id: &str, action: String, args: Vec<u8>) {
1503 self.cancel_scheduled_event(event_id);
1504 let ctx = self.clone();
1505 let event_id = event_id.to_owned();
1506 let internal_keep_awake_region = self.begin_work_region(ActorWorkKind::InternalKeepAwake);
1507
1508 self.track_shutdown_task(async move {
1509 let _internal_keep_awake_region = internal_keep_awake_region;
1510 ctx.record_user_task_started(UserTaskKind::ScheduledAction);
1511 let started_at = Instant::now();
1512 let action_name = action.clone();
1513 let (reply_tx, reply_rx) = oneshot::channel();
1514
1515 match ctx.try_send_actor_event(
1516 ActorEvent::Action {
1517 name: action.clone(),
1518 args,
1519 conn: None,
1520 reply: Reply::from(reply_tx),
1521 },
1522 "scheduled_action",
1523 ) {
1524 Ok(()) => match reply_rx.await {
1525 Ok(Ok(_)) => {}
1526 Ok(Err(error)) => {
1527 tracing::error!(
1528 ?error,
1529 event_id,
1530 action_name,
1531 "scheduled event execution failed"
1532 );
1533 }
1534 Err(error) => {
1535 tracing::error!(
1536 ?error,
1537 event_id,
1538 action_name,
1539 "scheduled event reply dropped"
1540 );
1541 }
1542 },
1543 Err(error) => {
1544 tracing::error!(
1545 ?error,
1546 event_id,
1547 action_name,
1548 "failed to enqueue scheduled event"
1549 );
1550 }
1551 }
1552
1553 ctx.record_user_task_finished(UserTaskKind::ScheduledAction, started_at.elapsed());
1554 });
1555 }
1556
1557 fn inspector_attach_count_arc(&self) -> Option<Arc<AtomicU32>> {
1558 self.0.inspector_attach_count.read().clone()
1559 }
1560
1561 fn try_send_lifecycle_event(&self, event: LifecycleEvent, operation: &'static str) {
1562 let Some(sender) = self.0.lifecycle_events.read().clone() else {
1563 return;
1564 };
1565
1566 if sender.send(event).is_err() {
1567 tracing::warn!(operation, "failed to enqueue actor lifecycle event");
1568 }
1569 }
1570}
1571
1572fn now_timestamp_ms() -> i64 {
1573 let duration = SystemTime::now()
1574 .duration_since(UNIX_EPOCH)
1575 .unwrap_or_default();
1576 i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
1577}
1578
1579struct ActorWorkGuard {
1580 ctx: ActorContext,
1581 kind: ActorWorkKind,
1582 started_at: Option<Instant>,
1583 idle_region: Option<RegionGuard>,
1584 shutdown_region: Option<CountGuard>,
1585}
1586
1587#[must_use]
1588pub struct ActorWorkRegion {
1589 guard: Option<ActorWorkGuard>,
1590}
1591
1592impl ActorWorkGuard {
1593 fn new(ctx: ActorContext, kind: ActorWorkKind) -> Self {
1594 let policy = kind.policy();
1595 let idle_region = ctx.idle_work_region(kind);
1596 let shutdown_region = if policy.drains_shutdown_grace {
1597 Some(ctx.shutdown_work_region())
1598 } else {
1599 None
1600 };
1601 let started_at = if let Some(user_task_kind) = policy.user_task_kind {
1602 ctx.record_user_task_started(user_task_kind);
1603 Some(Instant::now())
1604 } else {
1605 None
1606 };
1607 ctx.reset_sleep_timer();
1608 Self {
1609 ctx,
1610 kind,
1611 started_at,
1612 idle_region,
1613 shutdown_region,
1614 }
1615 }
1616}
1617
1618impl Drop for ActorWorkGuard {
1619 fn drop(&mut self) {
1620 if let Some(started_at) = self.started_at.take()
1621 && let Some(user_task_kind) = self.kind.policy().user_task_kind
1622 {
1623 self.ctx
1624 .record_user_task_finished(user_task_kind, started_at.elapsed());
1625 }
1626 self.idle_region.take();
1627 self.shutdown_region.take();
1628 self.ctx.reset_sleep_timer();
1629 }
1630}
1631
1632impl Drop for ActorWorkRegion {
1633 fn drop(&mut self) {
1634 self.guard.take();
1635 }
1636}
1637
1638#[must_use]
1639#[derive(Debug)]
1640pub(crate) struct InspectorAttachGuard {
1641 ctx: ActorContext,
1642}
1643
1644impl InspectorAttachGuard {
1645 fn new(ctx: ActorContext) -> Option<Self> {
1646 let attach_count = ctx.inspector_attach_count_arc()?;
1647 let previous = attach_count.fetch_add(1, Ordering::SeqCst);
1648 let current = previous.saturating_add(1);
1649 tracing::debug!(
1650 actor_id = %ctx.actor_id(),
1651 previous_count = previous,
1652 current_count = current,
1653 "inspector attached"
1654 );
1655 if previous == 0 {
1656 ctx.notify_inspector_attachments_changed();
1657 }
1658 Some(Self { ctx })
1659 }
1660}
1661
1662impl Drop for InspectorAttachGuard {
1663 fn drop(&mut self) {
1664 let Some(attach_count) = self.ctx.inspector_attach_count_arc() else {
1665 return;
1666 };
1667 let Ok(previous) =
1668 attach_count.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |current| {
1669 current.checked_sub(1)
1670 })
1671 else {
1672 return;
1673 };
1674 let current = previous.saturating_sub(1);
1675 tracing::debug!(
1676 actor_id = %self.ctx.actor_id(),
1677 previous_count = previous,
1678 current_count = current,
1679 "inspector detached"
1680 );
1681 if previous == 1 {
1682 self.ctx.notify_inspector_attachments_changed();
1683 }
1684 }
1685}
1686
1687pub struct WebSocketCallbackRegion {
1688 region: Option<ActorWorkRegion>,
1689}
1690
1691pub struct KeepAwakeRegion {
1692 region: Option<ActorWorkRegion>,
1693}
1694
1695impl Drop for WebSocketCallbackRegion {
1696 fn drop(&mut self) {
1697 self.region.take();
1698 }
1699}
1700
1701impl Drop for KeepAwakeRegion {
1702 fn drop(&mut self) {
1703 self.region.take();
1705 }
1706}
1707
1708impl std::fmt::Debug for ActorContext {
1709 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1710 f.debug_struct("ActorContext")
1711 .field("actor_id", &self.0.actor_id)
1712 .field("name", &self.0.name)
1713 .field("key", &self.0.key)
1714 .field("region", &self.0.region)
1715 .finish()
1716 }
1717}
1718
1719#[cfg(test)]
1721#[path = "../../tests/context.rs"]
1722pub(crate) mod tests;