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