1use parking_lot::Mutex;
2use rivet_envoy_client::async_counter::AsyncCounter;
3use rivet_envoy_client::handle::EnvoyHandle;
4use std::future::Future;
5use std::sync::Arc;
6#[cfg(test)]
7use std::sync::atomic::AtomicUsize as TestAtomicUsize;
8use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
9#[cfg(not(feature = "wasm-runtime"))]
10use tokio::runtime::Handle;
11use tokio::sync::Notify;
12use tokio::task::JoinHandle;
13use tracing::Instrument;
14
15use crate::actor::config::ActorConfig;
16use crate::actor::context::ActorContext;
17#[cfg(not(feature = "wasm-runtime"))]
18use crate::actor::context::ActorWorkRegion;
19use crate::actor::task_types::ShutdownKind;
20#[cfg(not(feature = "wasm-runtime"))]
21use crate::actor::work_registry::ActorWorkPolicy;
22#[cfg(feature = "wasm-runtime")]
23use crate::actor::work_registry::LocalShutdownTask;
24use crate::actor::work_registry::{ActorWorkKind, CountGuard, RegionGuard, WorkRegistry};
25#[cfg(feature = "wasm-runtime")]
26use crate::runtime::RuntimeSpawner;
27#[cfg(test)]
28use crate::time::sleep_until;
29use crate::time::{Instant, sleep};
30#[cfg(test)]
31use crate::types::ActorKey;
32#[cfg(feature = "wasm-runtime")]
33use futures::channel::oneshot as futures_oneshot;
34#[cfg(feature = "wasm-runtime")]
35use futures::future::{AbortHandle, Abortable};
36
37pub(crate) struct SleepState {
44 pub(super) config: Mutex<ActorConfig>,
47 pub(super) envoy_handle: Mutex<Option<EnvoyHandle>>,
48 pub(super) generation: Mutex<Option<u32>>,
49 pub(super) http_request_counter: Mutex<Option<Arc<AsyncCounter>>>,
50 pub(super) stop_error: Mutex<Option<String>>,
54 #[cfg(test)]
55 sleep_request_count: TestAtomicUsize,
56 #[cfg(test)]
57 destroy_request_count: TestAtomicUsize,
58 pub(super) lifecycle_started: AtomicBool,
59 pub(super) run_handler_active_count: AtomicUsize,
60 pub(super) sleep_timer: Mutex<Option<JoinHandle<()>>>,
62 pub(super) work: WorkRegistry,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub(crate) enum CanSleep {
67 Yes,
68 NotReady,
69 NoSleep,
70 ActiveHttpRequests,
71 ActiveKeepAwake,
72 ActiveInternalKeepAwake,
73 ActiveRunHandler,
74 ActiveDisconnectCallbacks,
75 ActiveConnections,
76 ActiveWebSocketCallbacks,
77}
78
79impl SleepState {
80 pub fn new(config: ActorConfig) -> Self {
81 Self {
82 config: Mutex::new(config),
83 envoy_handle: Mutex::new(None),
84 generation: Mutex::new(None),
85 http_request_counter: Mutex::new(None),
86 stop_error: Mutex::new(None),
87 #[cfg(test)]
88 sleep_request_count: TestAtomicUsize::new(0),
89 #[cfg(test)]
90 destroy_request_count: TestAtomicUsize::new(0),
91 lifecycle_started: AtomicBool::new(false),
92 run_handler_active_count: AtomicUsize::new(0),
93 sleep_timer: Mutex::new(None),
94 work: WorkRegistry::new(),
95 }
96 }
97}
98
99impl Default for SleepState {
100 fn default() -> Self {
101 Self::new(ActorConfig::default())
102 }
103}
104
105impl std::fmt::Debug for SleepState {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("SleepState")
108 .field(
109 "lifecycle_started",
110 &self.lifecycle_started.load(Ordering::SeqCst),
111 )
112 .field(
113 "run_handler_active_count",
114 &self.run_handler_active_count.load(Ordering::SeqCst),
115 )
116 .field("keep_awake_count", &self.work.keep_awake.load())
117 .field(
118 "internal_keep_awake_count",
119 &self.work.internal_keep_awake.load(),
120 )
121 .field(
122 "websocket_callback_count",
123 &self.work.websocket_callback.load(),
124 )
125 .field(
126 "disconnect_callback_count",
127 &self.work.disconnect_callback.load(),
128 )
129 .finish()
130 }
131}
132
133impl ActorContext {
134 #[cfg(test)]
135 pub(crate) fn new_for_sleep_tests(actor_id: impl Into<String>) -> Self {
136 Self::new(actor_id, "sleep-test", ActorKey::default(), "local")
137 }
138
139 pub(crate) fn configure_sleep_state(&self, config: ActorConfig) {
140 *self.0.sleep.config.lock() = config;
141 }
142
143 pub(crate) fn configure_sleep_envoy(&self, envoy_handle: EnvoyHandle, generation: Option<u32>) {
144 *self.0.sleep.envoy_handle.lock() = Some(envoy_handle);
145 *self.0.sleep.generation.lock() = generation;
146 *self.0.sleep.http_request_counter.lock() =
147 self.lookup_http_request_counter(self.actor_id());
148 }
149
150 pub(crate) fn sleep_envoy_handle(&self) -> Option<EnvoyHandle> {
151 self.0.sleep.envoy_handle.lock().clone()
152 }
153
154 pub(crate) fn sleep_generation(&self) -> Option<u32> {
155 *self.0.sleep.generation.lock()
156 }
157
158 pub(crate) fn request_sleep_from_envoy(&self) {
159 #[cfg(test)]
160 self.0
161 .sleep
162 .sleep_request_count
163 .fetch_add(1, Ordering::SeqCst);
164 let envoy_handle = self.0.sleep.envoy_handle.lock().clone();
165 let generation = *self.0.sleep.generation.lock();
166 if let Some(envoy_handle) = envoy_handle {
167 envoy_handle.sleep_actor(self.actor_id().to_owned(), generation, None);
168 }
169 }
170
171 pub(crate) fn request_stop_from_envoy(&self) {
172 #[cfg(test)]
173 self.0
174 .sleep
175 .destroy_request_count
176 .fetch_add(1, Ordering::SeqCst);
177 let envoy_handle = self.0.sleep.envoy_handle.lock().clone();
178 let generation = *self.0.sleep.generation.lock();
179 let error = self.0.sleep.stop_error.lock().take();
180 let Some(envoy_handle) = envoy_handle else {
181 return;
182 };
183 match error {
187 Some(error) => {
188 envoy_handle.sleep_actor(self.actor_id().to_owned(), generation, Some(error))
189 }
190 None => envoy_handle.stop_actor(self.actor_id().to_owned(), generation),
191 }
192 }
193
194 pub(crate) fn set_lifecycle_started(&self, started: bool) {
195 let previous = self
196 .0
197 .sleep
198 .lifecycle_started
199 .swap(started, Ordering::SeqCst);
200 if previous != started {
201 self.reset_sleep_timer();
202 }
203 }
204
205 pub(crate) fn lifecycle_started(&self) -> bool {
206 self.0.sleep.lifecycle_started.load(Ordering::SeqCst)
207 }
208
209 #[doc(hidden)]
210 pub fn begin_run_handler(&self) {
211 let previous = self
212 .0
213 .sleep
214 .run_handler_active_count
215 .fetch_add(1, Ordering::SeqCst);
216 if previous == 0 {
217 self.reset_sleep_timer();
218 }
219 }
220
221 #[doc(hidden)]
222 pub fn end_run_handler(&self) {
223 match self.0.sleep.run_handler_active_count.fetch_update(
224 Ordering::SeqCst,
225 Ordering::SeqCst,
226 |count| count.checked_sub(1),
227 ) {
228 Ok(1) => self.reset_sleep_timer(),
229 Ok(_) => {}
230 Err(_) => {
231 tracing::warn!(
232 actor_id = %self.actor_id(),
233 "run handler active counter underflow"
234 );
235 }
236 }
237 }
238
239 #[doc(hidden)]
240 pub fn run_handler_active(&self) -> bool {
241 self.0.sleep.run_handler_active_count.load(Ordering::SeqCst) > 0
242 }
243
244 #[cfg(test)]
245 pub(crate) fn sleep_request_count(&self) -> usize {
246 self.0.sleep.sleep_request_count.load(Ordering::SeqCst)
247 }
248
249 pub(crate) async fn can_arm_sleep_timer(&self) -> CanSleep {
250 let config = self.sleep_state_config();
251 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst) {
252 return CanSleep::NotReady;
253 }
254 if config.no_sleep {
255 return CanSleep::NoSleep;
256 }
257 if self.active_http_request_count() > 0 {
258 return CanSleep::ActiveHttpRequests;
259 }
260 if self.sleep_keep_awake_count() > 0 {
261 return CanSleep::ActiveKeepAwake;
262 }
263 if self.sleep_internal_keep_awake_count() > 0 {
264 return CanSleep::ActiveInternalKeepAwake;
265 }
266 if self.run_handler_active() && self.active_queue_wait_count() == 0 {
269 return CanSleep::ActiveRunHandler;
270 }
271 if self.pending_disconnect_count() > 0 {
272 return CanSleep::ActiveDisconnectCallbacks;
273 }
274 if !self.conns().is_empty() {
275 return CanSleep::ActiveConnections;
276 }
277 if self.websocket_callback_count() > 0 {
278 return CanSleep::ActiveWebSocketCallbacks;
279 }
280
281 CanSleep::Yes
282 }
283
284 pub(crate) fn can_finalize_shutdown(&self, reason: ShutdownKind) -> bool {
285 self.0.sleep.work.core_dispatched_hooks.load() == 0
286 && (matches!(reason, ShutdownKind::Destroy) || !self.run_handler_active())
289 && self.shutdown_task_count() == 0
290 && self.sleep_keep_awake_count() == 0
291 && self.sleep_internal_keep_awake_count() == 0
292 && self.active_http_request_count() == 0
293 && self.websocket_callback_count() == 0
294 && self.pending_disconnect_count() == 0
295 }
296
297 pub(crate) fn reset_sleep_timer_state(&self) {
305 self.cancel_sleep_timer();
306
307 #[cfg(not(feature = "wasm-runtime"))]
308 let Ok(runtime) = Handle::try_current() else {
309 tracing::debug!(
310 actor_id = %self.actor_id(),
311 "sleep activity reset skipped without tokio runtime"
312 );
313 return;
314 };
315
316 tracing::debug!(
317 actor_id = %self.actor_id(),
318 sleep_timeout_ms = self.0.sleep.config.lock().sleep_timeout.as_millis() as u64,
319 "sleep activity reset"
320 );
321
322 let ctx = self.clone();
323 let task_body = async move {
324 let can_sleep = ctx.can_sleep().await;
325 if can_sleep != CanSleep::Yes {
326 tracing::debug!(
327 actor_id = %ctx.actor_id(),
328 reason = ?can_sleep,
329 "sleep idle timer skipped"
330 );
331 return;
332 }
333
334 let timeout = ctx.sleep_config().sleep_timeout;
335 sleep(timeout).await;
336
337 let can_sleep = ctx.can_sleep().await;
338 if can_sleep == CanSleep::Yes {
339 tracing::debug!(
340 actor_id = %ctx.actor_id(),
341 sleep_timeout_ms = timeout.as_millis() as u64,
342 "sleep idle timer elapsed"
343 );
344 if let Err(err) = ctx.sleep() {
345 tracing::debug!(
346 actor_id = %ctx.actor_id(),
347 ?err,
348 "sleep idle timer request suppressed"
349 );
350 }
351 } else {
352 tracing::warn!(
353 actor_id = %ctx.actor_id(),
354 reason = ?can_sleep,
355 "sleep idle timer elapsed but actor stayed awake"
356 );
357 }
358 };
359
360 #[cfg(not(feature = "wasm-runtime"))]
361 let task = runtime.spawn(task_body);
362
363 #[cfg(feature = "wasm-runtime")]
364 let task = RuntimeSpawner::spawn(task_body);
365
366 *self.0.sleep.sleep_timer.lock() = Some(task);
367 }
368
369 pub(crate) fn cancel_sleep_timer(&self) {
370 let timer = self.0.sleep.sleep_timer.lock().take();
371 if let Some(timer) = timer {
372 timer.abort();
373 }
374 }
375
376 pub(crate) async fn wait_for_internal_keep_awake_idle(&self, deadline: Instant) -> bool {
377 self.0
378 .sleep
379 .work
380 .internal_keep_awake
381 .wait_zero(deadline)
382 .await
383 }
384
385 #[cfg(test)]
386 pub(crate) async fn wait_for_sleep_idle_window(&self, deadline: Instant) -> bool {
387 loop {
388 let activity = self.sleep_activity_notify();
389 let activity_notified = activity.notified();
390 tokio::pin!(activity_notified);
391 activity_notified.as_mut().enable();
392 let idle = self.0.sleep.work.idle_notify.notified();
393 tokio::pin!(idle);
394 idle.as_mut().enable();
395
396 if self.can_finalize_shutdown(ShutdownKind::Sleep) {
397 return true;
398 }
399
400 tokio::select! {
401 _ = &mut activity_notified => {}
402 _ = &mut idle => {}
403 _ = sleep_until(deadline) => return false,
404 }
405 }
406 }
407
408 #[cfg(test)]
409 pub(crate) async fn wait_for_shutdown_tasks(&self, deadline: Instant) -> bool {
410 loop {
411 let activity = self.sleep_activity_notify();
412 let notified = activity.notified();
413 tokio::pin!(notified);
414 notified.as_mut().enable();
415
416 let shutdown_count = self.shutdown_task_count();
417 let websocket_count = self.websocket_callback_count();
418 if shutdown_count == 0 && websocket_count == 0 {
419 return true;
420 }
421
422 tokio::select! {
423 drained = self.0.sleep.work.shutdown_counter.wait_zero(deadline), if shutdown_count > 0 => {
424 if !drained {
425 return false;
426 }
427 }
428 drained = self.0.sleep.work.websocket_callback.wait_zero(deadline), if websocket_count > 0 => {
429 if !drained {
430 return false;
431 }
432 }
433 _ = &mut notified => {}
434 _ = sleep_until(deadline) => return false,
435 }
436 }
437 }
438
439 pub async fn wait_for_tracked_shutdown_work(&self) -> bool {
440 let shutdown_deadline = self.shutdown_deadline_token();
441 tokio::select! {
442 _ = self.wait_for_tracked_shutdown_work_drained() => true,
443 _ = shutdown_deadline.cancelled() => false,
444 }
445 }
446
447 pub async fn wait_for_tracked_shutdown_work_unbounded(&self) {
448 self.wait_for_tracked_shutdown_work_drained().await;
449 }
450
451 async fn wait_for_tracked_shutdown_work_drained(&self) {
452 loop {
453 let shutdown_count = self.shutdown_task_count();
454 let websocket_count = self.websocket_callback_count();
455 if shutdown_count == 0 && websocket_count == 0 {
456 return;
457 }
458
459 tokio::select! {
460 _ = self.0.sleep.work.shutdown_counter.wait_zero_unbounded(), if shutdown_count > 0 => {}
461 _ = self.0.sleep.work.websocket_callback.wait_zero_unbounded(), if websocket_count > 0 => {}
462 }
463 }
464 }
465
466 pub(crate) async fn wait_for_http_requests_drained(&self, deadline: Instant) -> bool {
467 let Some(counter) = self.http_request_counter() else {
468 return true;
469 };
470 counter.wait_zero(deadline).await
471 }
472
473 pub(crate) async fn wait_for_http_requests_idle(&self) {
474 loop {
475 let idle = self.0.sleep.work.idle_notify.notified();
476 tokio::pin!(idle);
477 idle.as_mut().enable();
478
479 if self.active_http_request_count() == 0 {
480 return;
481 }
482
483 idle.await;
484 }
485 }
486
487 pub(crate) fn keep_awake_region_state(&self) -> RegionGuard {
488 self.0.sleep.work.keep_awake_guard()
489 }
490
491 pub(crate) fn sleep_keep_awake_count(&self) -> usize {
492 self.0.sleep.work.keep_awake.load()
493 }
494
495 pub(crate) fn internal_keep_awake_region(&self) -> RegionGuard {
496 self.0.sleep.work.internal_keep_awake_guard()
497 }
498
499 pub(crate) fn sleep_internal_keep_awake_count(&self) -> usize {
500 self.0.sleep.work.internal_keep_awake.load()
501 }
502
503 fn active_queue_wait_count(&self) -> usize {
504 self.0.active_queue_wait_count.load(Ordering::SeqCst) as usize
505 }
506
507 pub(crate) fn websocket_callback_region_state(&self) -> RegionGuard {
508 self.0.sleep.work.websocket_callback_guard()
509 }
510
511 pub(crate) fn websocket_callback_count(&self) -> usize {
512 self.0.sleep.work.websocket_callback.load()
513 }
514
515 pub(crate) fn disconnect_callback_region_state(&self) -> RegionGuard {
516 self.0.sleep.work.disconnect_callback_guard()
517 }
518
519 #[cfg(not(feature = "wasm-runtime"))]
520 pub(crate) fn spawn_work_inner<F>(&self, kind: ActorWorkKind, fut: F) -> bool
521 where
522 F: Future<Output = ()> + Send + 'static,
523 {
524 if Handle::try_current().is_err() {
525 tracing::warn!(
526 kind = kind.label(),
527 "actor work spawned without tokio runtime"
528 );
529 return false;
530 }
531
532 let policy = kind.policy();
533 if policy.aborts_at_shutdown_deadline {
534 let mut shutdown_tasks = self.0.sleep.work.shutdown_tasks.lock();
535 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
536 tracing::warn!(
537 kind = kind.label(),
538 "actor work spawned after teardown; aborting immediately"
539 );
540 return false;
541 }
542 while shutdown_tasks.try_join_next().is_some() {}
548 let region = self.begin_work_region(kind);
549 shutdown_tasks.spawn(self.build_spawned_work_task(kind, policy, region, fut));
550 } else {
551 let mut unabortable_shutdown_tasks =
552 self.0.sleep.work.unabortable_shutdown_tasks.lock();
553 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
554 tracing::warn!(
555 kind = kind.label(),
556 "actor work spawned after teardown; aborting immediately"
557 );
558 return false;
559 }
560 while unabortable_shutdown_tasks.try_join_next().is_some() {}
561 let region = self.begin_work_region(kind);
562 unabortable_shutdown_tasks
563 .spawn(self.build_spawned_work_task(kind, policy, region, fut));
564 }
565 self.reset_sleep_timer();
566 true
567 }
568
569 #[cfg(not(feature = "wasm-runtime"))]
570 fn build_spawned_work_task<F>(
571 &self,
572 kind: ActorWorkKind,
573 policy: ActorWorkPolicy,
574 region: ActorWorkRegion,
575 fut: F,
576 ) -> impl Future<Output = ()> + Send + 'static
577 where
578 F: Future<Output = ()> + Send + 'static,
579 {
580 let ctx = self.clone();
581 async move {
582 let _region = region;
583 if policy.aborts_at_shutdown_deadline {
584 let shutdown_deadline = ctx.shutdown_deadline_token();
585 tokio::select! {
586 _ = fut => {}
587 _ = shutdown_deadline.cancelled() => {
588 tracing::warn!(
589 actor_id = %ctx.actor_id(),
590 kind = kind.label(),
591 reason = "shutdown_deadline_elapsed",
592 "actor work cancelled by shutdown deadline"
593 );
594 }
595 }
596 } else {
597 fut.await;
598 }
599 ctx.reset_sleep_timer();
600 }
601 .in_current_span()
602 }
603
604 #[cfg(feature = "wasm-runtime")]
605 pub(crate) fn spawn_work_inner<F>(&self, kind: ActorWorkKind, fut: F) -> bool
606 where
607 F: Future<Output = ()> + 'static,
608 {
609 let mut local_shutdown_tasks = self.0.sleep.work.local_shutdown_tasks.lock();
610 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
611 tracing::warn!(
612 kind = kind.label(),
613 "actor work spawned after teardown; aborting immediately"
614 );
615 return false;
616 }
617
618 local_shutdown_tasks.retain_mut(|task| matches!(task.complete_rx.try_recv(), Ok(None)));
621
622 let policy = kind.policy();
623 let region = self.begin_work_region(kind);
624 let ctx = self.clone();
625 let (complete_tx, complete_rx) = futures_oneshot::channel();
626 let (abort_handle, abort_registration) = AbortHandle::new_pair();
627 local_shutdown_tasks.push(LocalShutdownTask {
628 abort_handle,
629 complete_rx,
630 aborts_at_shutdown_deadline: policy.aborts_at_shutdown_deadline,
631 });
632 drop(local_shutdown_tasks);
633 let ctx_for_task = ctx.clone();
634 wasm_bindgen_futures::spawn_local(
635 async move {
636 let task = async move {
637 let _region = region;
638 if policy.aborts_at_shutdown_deadline {
639 let shutdown_deadline = ctx_for_task.shutdown_deadline_token();
640 tokio::select! {
641 _ = fut => {}
642 _ = shutdown_deadline.cancelled() => {
643 tracing::warn!(
644 actor_id = %ctx_for_task.actor_id(),
645 kind = kind.label(),
646 reason = "shutdown_deadline_elapsed",
647 "actor work cancelled by shutdown deadline"
648 );
649 }
650 }
651 } else {
652 fut.await;
653 }
654 let _ = complete_tx.send(());
655 ctx_for_task.reset_sleep_timer();
656 };
657 if Abortable::new(task, abort_registration).await.is_err() {
658 ctx.reset_sleep_timer();
659 }
660 }
661 .in_current_span(),
662 );
663 self.reset_sleep_timer();
664 true
665 }
666
667 #[cfg(not(feature = "wasm-runtime"))]
668 pub(crate) fn track_shutdown_task<F>(&self, fut: F) -> bool
669 where
670 F: Future<Output = ()> + Send + 'static,
671 {
672 if Handle::try_current().is_err() {
673 tracing::warn!("shutdown task spawned without tokio runtime; running fallback");
674 return false;
675 }
676
677 let mut shutdown_tasks = self.0.sleep.work.shutdown_tasks.lock();
678 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
679 tracing::warn!("shutdown task spawned after teardown; aborting immediately");
680 return false;
681 }
682 let counter = self.0.sleep.work.shutdown_counter.clone();
683 counter.increment();
684 let guard = CountGuard::from_incremented(counter);
685 let ctx = self.clone();
686 shutdown_tasks.spawn(
689 async move {
690 {
691 let _guard = guard;
692 fut.await;
693 }
694 ctx.reset_sleep_timer();
695 }
696 .in_current_span(),
697 );
698 drop(shutdown_tasks);
699 self.reset_sleep_timer();
700 true
701 }
702
703 #[cfg(feature = "wasm-runtime")]
704 pub(crate) fn track_shutdown_task<F>(&self, fut: F) -> bool
705 where
706 F: Future<Output = ()> + 'static,
707 {
708 let mut local_shutdown_tasks = self.0.sleep.work.local_shutdown_tasks.lock();
709 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
710 tracing::warn!("shutdown task spawned after teardown; aborting immediately");
711 return false;
712 }
713 let counter = self.0.sleep.work.shutdown_counter.clone();
714 counter.increment();
715 let guard = CountGuard::from_incremented(counter);
716 let ctx = self.clone();
717 let (complete_tx, complete_rx) = futures_oneshot::channel();
721 let (abort_handle, abort_registration) = AbortHandle::new_pair();
722 local_shutdown_tasks.push(LocalShutdownTask {
723 abort_handle,
724 complete_rx,
725 aborts_at_shutdown_deadline: true,
726 });
727 drop(local_shutdown_tasks);
728 let ctx_for_task = ctx.clone();
729 wasm_bindgen_futures::spawn_local(
730 async move {
731 let task = async move {
732 {
733 let _guard = guard;
734 fut.await;
735 }
736 let _ = complete_tx.send(());
737 ctx_for_task.reset_sleep_timer();
738 };
739 if Abortable::new(task, abort_registration).await.is_err() {
740 ctx.reset_sleep_timer();
741 }
742 }
743 .in_current_span(),
744 );
745 self.reset_sleep_timer();
746 true
747 }
748
749 pub(crate) fn shutdown_task_count(&self) -> usize {
750 self.0.sleep.work.shutdown_counter.load()
751 }
752
753 pub(crate) fn mark_shutdown_deadline_reached(&self) {
754 self.0
755 .sleep
756 .work
757 .shutdown_deadline_reached
758 .store(true, Ordering::Release);
759 }
760
761 pub(crate) fn begin_core_dispatched_hook(&self) {
762 self.0.sleep.work.core_dispatched_hooks.increment();
763 self.reset_sleep_timer();
764 }
765
766 pub fn mark_core_dispatched_hook_completed(&self) {
767 self.0.sleep.work.core_dispatched_hooks.decrement();
768 self.reset_sleep_timer();
769 }
770
771 pub(crate) fn core_dispatched_hook_count(&self) -> usize {
772 self.0.sleep.work.core_dispatched_hooks.load()
773 }
774
775 pub(crate) async fn teardown_sleep_state(&self) {
776 let abort_remaining = self
777 .0
778 .sleep
779 .work
780 .shutdown_deadline_reached
781 .swap(false, Ordering::AcqRel);
782 if abort_remaining {
786 self.0
787 .sleep
788 .work
789 .teardown_started
790 .store(true, Ordering::Release);
791 }
792
793 #[cfg(feature = "wasm-runtime")]
794 {
795 loop {
796 let local_shutdown_tasks = {
797 let mut guard = self.0.sleep.work.local_shutdown_tasks.lock();
798 let taken = std::mem::take(&mut *guard);
799 if taken.is_empty() {
800 self.0
801 .sleep
802 .work
803 .teardown_started
804 .store(true, Ordering::Release);
805 return;
806 }
807 taken
808 };
809
810 if abort_remaining {
811 for task in local_shutdown_tasks {
812 if task.aborts_at_shutdown_deadline {
813 task.abort_handle.abort();
814 }
815 if task.complete_rx.await.is_err() {
816 tracing::debug!("aborted shutdown task during teardown");
817 }
818 }
819 self.0
820 .sleep
821 .work
822 .teardown_started
823 .store(true, Ordering::Release);
824 return;
825 }
826
827 for task in local_shutdown_tasks {
828 if task.complete_rx.await.is_err() {
829 tracing::debug!("shutdown task completion dropped during teardown");
830 }
831 }
832 }
833 }
834
835 #[cfg(not(feature = "wasm-runtime"))]
836 loop {
837 let mut abortable_shutdown_tasks = {
838 let mut guard = self.0.sleep.work.shutdown_tasks.lock();
839 let taken = std::mem::take(&mut *guard);
840 let mut unabortable_guard = self.0.sleep.work.unabortable_shutdown_tasks.lock();
841 let unabortable_taken = std::mem::take(&mut *unabortable_guard);
842 if taken.is_empty() && unabortable_taken.is_empty() {
843 self.0
844 .sleep
845 .work
846 .teardown_started
847 .store(true, Ordering::Release);
848 return;
849 }
850 (taken, unabortable_taken)
851 };
852
853 abortable_shutdown_tasks.0.shutdown().await;
854 while let Some(result) = abortable_shutdown_tasks.0.join_next().await {
855 if let Err(error) = result
856 && !error.is_cancelled()
857 {
858 tracing::error!(?error, "shutdown task join failed during teardown");
859 }
860 }
861 while let Some(result) = abortable_shutdown_tasks.1.join_next().await {
862 if let Err(error) = result
863 && !error.is_cancelled()
864 {
865 tracing::error!(?error, "shutdown task join failed during teardown");
866 }
867 }
868 }
869 }
870
871 pub(crate) fn sleep_state_config(&self) -> ActorConfig {
872 self.0.sleep.config.lock().clone()
873 }
874
875 pub(crate) fn active_http_request_count(&self) -> usize {
876 self.http_request_counter()
877 .map(|counter| counter.load())
878 .unwrap_or(0)
879 }
880
881 pub(crate) fn sleep_activity_notify(&self) -> Arc<Notify> {
882 self.0.sleep.work.activity_notify.clone()
883 }
884
885 fn http_request_counter(&self) -> Option<Arc<AsyncCounter>> {
886 if let Some(counter) = self.0.sleep.http_request_counter.lock().clone() {
887 return Some(counter);
888 }
889
890 let counter = self.lookup_http_request_counter(self.actor_id())?;
891 *self.0.sleep.http_request_counter.lock() = Some(counter.clone());
892 Some(counter)
893 }
894
895 fn lookup_http_request_counter(&self, actor_id: &str) -> Option<Arc<AsyncCounter>> {
896 let envoy_handle = self.0.sleep.envoy_handle.lock().clone();
897 let generation = *self.0.sleep.generation.lock();
898 let envoy_handle = envoy_handle?;
899 let counter = envoy_handle.http_request_counter(actor_id, generation)?;
900 counter.register_zero_notify(&self.0.sleep.work.idle_notify);
901 let ctx = self.clone();
906 counter.register_change_callback(Arc::new(move || {
907 ctx.0
908 .metrics
909 .set_http_requests_active(ctx.active_http_request_count());
910 ctx.reset_sleep_timer();
911 }));
912 self.0.metrics.set_http_requests_active(counter.load());
913 Some(counter)
914 }
915}
916
917#[cfg(test)]
919#[path = "../../tests/sleep.rs"]
920mod tests;