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 #[doc(hidden)]
230 pub fn run_handler_active(&self) -> bool {
231 self.0.sleep.run_handler_active_count.load(Ordering::SeqCst) > 0
232 }
233
234 #[cfg(test)]
235 pub(crate) fn sleep_request_count(&self) -> usize {
236 self.0.sleep.sleep_request_count.load(Ordering::SeqCst)
237 }
238
239 pub(crate) async fn can_arm_sleep_timer(&self) -> CanSleep {
240 let config = self.sleep_state_config();
241 if !self.0.sleep.lifecycle_started.load(Ordering::SeqCst) {
242 return CanSleep::NotReady;
243 }
244 if config.no_sleep {
245 return CanSleep::NoSleep;
246 }
247 if self.active_http_request_count() > 0 {
248 return CanSleep::ActiveHttpRequests;
249 }
250 if self.sleep_keep_awake_count() > 0 {
251 return CanSleep::ActiveKeepAwake;
252 }
253 if self.sleep_internal_keep_awake_count() > 0 {
254 return CanSleep::ActiveInternalKeepAwake;
255 }
256 if self.run_handler_active() && self.active_queue_wait_count() == 0 {
259 return CanSleep::ActiveRunHandler;
260 }
261 if self.pending_disconnect_count() > 0 {
262 return CanSleep::ActiveDisconnectCallbacks;
263 }
264 if !self.conns().is_empty() {
265 return CanSleep::ActiveConnections;
266 }
267 if self.websocket_callback_count() > 0 {
268 return CanSleep::ActiveWebSocketCallbacks;
269 }
270
271 CanSleep::Yes
272 }
273
274 pub(crate) fn can_finalize_shutdown(&self, reason: ShutdownKind) -> bool {
275 self.0.sleep.work.core_dispatched_hooks.load() == 0
276 && (matches!(reason, ShutdownKind::Destroy) || !self.run_handler_active())
279 && self.shutdown_task_count() == 0
280 && self.sleep_keep_awake_count() == 0
281 && self.sleep_internal_keep_awake_count() == 0
282 && self.active_http_request_count() == 0
283 && self.websocket_callback_count() == 0
284 && self.pending_disconnect_count() == 0
285 }
286
287 pub(crate) fn reset_sleep_timer_state(&self) {
295 self.cancel_sleep_timer();
296
297 #[cfg(not(feature = "wasm-runtime"))]
298 let Ok(runtime) = Handle::try_current() else {
299 tracing::debug!(
300 actor_id = %self.actor_id(),
301 "sleep activity reset skipped without tokio runtime"
302 );
303 return;
304 };
305
306 tracing::debug!(
307 actor_id = %self.actor_id(),
308 sleep_timeout_ms = self.0.sleep.config.lock().sleep_timeout.as_millis() as u64,
309 "sleep activity reset"
310 );
311
312 let ctx = self.clone();
313 let task_body = async move {
314 let can_sleep = ctx.can_sleep().await;
315 if can_sleep != CanSleep::Yes {
316 tracing::debug!(
317 actor_id = %ctx.actor_id(),
318 reason = ?can_sleep,
319 "sleep idle timer skipped"
320 );
321 return;
322 }
323
324 let timeout = ctx.sleep_config().sleep_timeout;
325 sleep(timeout).await;
326
327 let can_sleep = ctx.can_sleep().await;
328 if can_sleep == CanSleep::Yes {
329 tracing::debug!(
330 actor_id = %ctx.actor_id(),
331 sleep_timeout_ms = timeout.as_millis() as u64,
332 "sleep idle timer elapsed"
333 );
334 if let Err(err) = ctx.sleep() {
335 tracing::debug!(
336 actor_id = %ctx.actor_id(),
337 ?err,
338 "sleep idle timer request suppressed"
339 );
340 }
341 } else {
342 tracing::warn!(
343 actor_id = %ctx.actor_id(),
344 reason = ?can_sleep,
345 "sleep idle timer elapsed but actor stayed awake"
346 );
347 }
348 };
349
350 #[cfg(not(feature = "wasm-runtime"))]
351 let task = runtime.spawn(task_body);
352
353 #[cfg(feature = "wasm-runtime")]
354 let task = RuntimeSpawner::spawn(task_body);
355
356 *self.0.sleep.sleep_timer.lock() = Some(task);
357 }
358
359 pub(crate) fn cancel_sleep_timer(&self) {
360 let timer = self.0.sleep.sleep_timer.lock().take();
361 if let Some(timer) = timer {
362 timer.abort();
363 }
364 }
365
366 pub(crate) async fn wait_for_internal_keep_awake_idle(&self, deadline: Instant) -> bool {
367 self.0
368 .sleep
369 .work
370 .internal_keep_awake
371 .wait_zero(deadline)
372 .await
373 }
374
375 #[cfg(test)]
376 pub(crate) async fn wait_for_sleep_idle_window(&self, deadline: Instant) -> bool {
377 loop {
378 let activity = self.sleep_activity_notify();
379 let activity_notified = activity.notified();
380 tokio::pin!(activity_notified);
381 activity_notified.as_mut().enable();
382 let idle = self.0.sleep.work.idle_notify.notified();
383 tokio::pin!(idle);
384 idle.as_mut().enable();
385
386 if self.can_finalize_shutdown(ShutdownKind::Sleep) {
387 return true;
388 }
389
390 tokio::select! {
391 _ = &mut activity_notified => {}
392 _ = &mut idle => {}
393 _ = sleep_until(deadline) => return false,
394 }
395 }
396 }
397
398 #[cfg(test)]
399 pub(crate) async fn wait_for_shutdown_tasks(&self, deadline: Instant) -> bool {
400 loop {
401 let activity = self.sleep_activity_notify();
402 let notified = activity.notified();
403 tokio::pin!(notified);
404 notified.as_mut().enable();
405
406 let shutdown_count = self.shutdown_task_count();
407 let websocket_count = self.websocket_callback_count();
408 if shutdown_count == 0 && websocket_count == 0 {
409 return true;
410 }
411
412 tokio::select! {
413 drained = self.0.sleep.work.shutdown_counter.wait_zero(deadline), if shutdown_count > 0 => {
414 if !drained {
415 return false;
416 }
417 }
418 drained = self.0.sleep.work.websocket_callback.wait_zero(deadline), if websocket_count > 0 => {
419 if !drained {
420 return false;
421 }
422 }
423 _ = &mut notified => {}
424 _ = sleep_until(deadline) => return false,
425 }
426 }
427 }
428
429 pub async fn wait_for_tracked_shutdown_work(&self) -> bool {
430 let shutdown_deadline = self.shutdown_deadline_token();
431 tokio::select! {
432 _ = self.wait_for_tracked_shutdown_work_drained() => true,
433 _ = shutdown_deadline.cancelled() => false,
434 }
435 }
436
437 pub async fn wait_for_tracked_shutdown_work_unbounded(&self) {
438 self.wait_for_tracked_shutdown_work_drained().await;
439 }
440
441 async fn wait_for_tracked_shutdown_work_drained(&self) {
442 loop {
443 let shutdown_count = self.shutdown_task_count();
444 let websocket_count = self.websocket_callback_count();
445 if shutdown_count == 0 && websocket_count == 0 {
446 return;
447 }
448
449 tokio::select! {
450 _ = self.0.sleep.work.shutdown_counter.wait_zero_unbounded(), if shutdown_count > 0 => {}
451 _ = self.0.sleep.work.websocket_callback.wait_zero_unbounded(), if websocket_count > 0 => {}
452 }
453 }
454 }
455
456 pub(crate) async fn wait_for_http_requests_drained(&self, deadline: Instant) -> bool {
457 let Some(counter) = self.http_request_counter() else {
458 return true;
459 };
460 counter.wait_zero(deadline).await
461 }
462
463 pub(crate) async fn wait_for_http_requests_idle(&self) {
464 loop {
465 let idle = self.0.sleep.work.idle_notify.notified();
466 tokio::pin!(idle);
467 idle.as_mut().enable();
468
469 if self.active_http_request_count() == 0 {
470 return;
471 }
472
473 idle.await;
474 }
475 }
476
477 pub(crate) fn keep_awake_region_state(&self) -> RegionGuard {
478 self.0.sleep.work.keep_awake_guard()
479 }
480
481 pub(crate) fn sleep_keep_awake_count(&self) -> usize {
482 self.0.sleep.work.keep_awake.load()
483 }
484
485 pub(crate) fn internal_keep_awake_region(&self) -> RegionGuard {
486 self.0.sleep.work.internal_keep_awake_guard()
487 }
488
489 pub(crate) fn sleep_internal_keep_awake_count(&self) -> usize {
490 self.0.sleep.work.internal_keep_awake.load()
491 }
492
493 fn active_queue_wait_count(&self) -> usize {
494 self.0.active_queue_wait_count.load(Ordering::SeqCst) as usize
495 }
496
497 pub(crate) fn websocket_callback_region_state(&self) -> RegionGuard {
498 self.0.sleep.work.websocket_callback_guard()
499 }
500
501 pub(crate) fn websocket_callback_count(&self) -> usize {
502 self.0.sleep.work.websocket_callback.load()
503 }
504
505 pub(crate) fn disconnect_callback_region_state(&self) -> RegionGuard {
506 self.0.sleep.work.disconnect_callback_guard()
507 }
508
509 #[cfg(not(feature = "wasm-runtime"))]
510 pub(crate) fn spawn_work_inner<F>(&self, kind: ActorWorkKind, fut: F) -> bool
511 where
512 F: Future<Output = ()> + Send + 'static,
513 {
514 if Handle::try_current().is_err() {
515 tracing::warn!(
516 kind = kind.label(),
517 "actor work spawned without tokio runtime"
518 );
519 return false;
520 }
521
522 let policy = kind.policy();
523 if policy.aborts_at_shutdown_deadline {
524 let mut shutdown_tasks = self.0.sleep.work.shutdown_tasks.lock();
525 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
526 tracing::warn!(
527 kind = kind.label(),
528 "actor work spawned after teardown; aborting immediately"
529 );
530 return false;
531 }
532 while shutdown_tasks.try_join_next().is_some() {}
538 let region = self.begin_work_region(kind);
539 shutdown_tasks.spawn(self.build_spawned_work_task(kind, policy, region, fut));
540 } else {
541 let mut unabortable_shutdown_tasks =
542 self.0.sleep.work.unabortable_shutdown_tasks.lock();
543 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
544 tracing::warn!(
545 kind = kind.label(),
546 "actor work spawned after teardown; aborting immediately"
547 );
548 return false;
549 }
550 while unabortable_shutdown_tasks.try_join_next().is_some() {}
551 let region = self.begin_work_region(kind);
552 unabortable_shutdown_tasks
553 .spawn(self.build_spawned_work_task(kind, policy, region, fut));
554 }
555 self.reset_sleep_timer();
556 true
557 }
558
559 #[cfg(not(feature = "wasm-runtime"))]
560 fn build_spawned_work_task<F>(
561 &self,
562 kind: ActorWorkKind,
563 policy: ActorWorkPolicy,
564 region: ActorWorkRegion,
565 fut: F,
566 ) -> impl Future<Output = ()> + Send + 'static
567 where
568 F: Future<Output = ()> + Send + 'static,
569 {
570 let ctx = self.clone();
571 async move {
572 let _region = region;
573 if policy.aborts_at_shutdown_deadline {
574 let shutdown_deadline = ctx.shutdown_deadline_token();
575 tokio::select! {
576 _ = fut => {}
577 _ = shutdown_deadline.cancelled() => {
578 tracing::warn!(
579 actor_id = %ctx.actor_id(),
580 kind = kind.label(),
581 reason = "shutdown_deadline_elapsed",
582 "actor work cancelled by shutdown deadline"
583 );
584 }
585 }
586 } else {
587 fut.await;
588 }
589 ctx.reset_sleep_timer();
590 }
591 .in_current_span()
592 }
593
594 #[cfg(feature = "wasm-runtime")]
595 pub(crate) fn spawn_work_inner<F>(&self, kind: ActorWorkKind, fut: F) -> bool
596 where
597 F: Future<Output = ()> + 'static,
598 {
599 let mut local_shutdown_tasks = self.0.sleep.work.local_shutdown_tasks.lock();
600 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
601 tracing::warn!(
602 kind = kind.label(),
603 "actor work spawned after teardown; aborting immediately"
604 );
605 return false;
606 }
607
608 local_shutdown_tasks.retain_mut(|task| matches!(task.complete_rx.try_recv(), Ok(None)));
611
612 let policy = kind.policy();
613 let region = self.begin_work_region(kind);
614 let ctx = self.clone();
615 let (complete_tx, complete_rx) = futures_oneshot::channel();
616 let (abort_handle, abort_registration) = AbortHandle::new_pair();
617 local_shutdown_tasks.push(LocalShutdownTask {
618 abort_handle,
619 complete_rx,
620 aborts_at_shutdown_deadline: policy.aborts_at_shutdown_deadline,
621 });
622 drop(local_shutdown_tasks);
623 let ctx_for_task = ctx.clone();
624 wasm_bindgen_futures::spawn_local(
625 async move {
626 let task = async move {
627 let _region = region;
628 if policy.aborts_at_shutdown_deadline {
629 let shutdown_deadline = ctx_for_task.shutdown_deadline_token();
630 tokio::select! {
631 _ = fut => {}
632 _ = shutdown_deadline.cancelled() => {
633 tracing::warn!(
634 actor_id = %ctx_for_task.actor_id(),
635 kind = kind.label(),
636 reason = "shutdown_deadline_elapsed",
637 "actor work cancelled by shutdown deadline"
638 );
639 }
640 }
641 } else {
642 fut.await;
643 }
644 let _ = complete_tx.send(());
645 ctx_for_task.reset_sleep_timer();
646 };
647 if Abortable::new(task, abort_registration).await.is_err() {
648 ctx.reset_sleep_timer();
649 }
650 }
651 .in_current_span(),
652 );
653 self.reset_sleep_timer();
654 true
655 }
656
657 #[cfg(not(feature = "wasm-runtime"))]
658 pub(crate) fn track_shutdown_task<F>(&self, fut: F) -> bool
659 where
660 F: Future<Output = ()> + Send + 'static,
661 {
662 if Handle::try_current().is_err() {
663 tracing::warn!("shutdown task spawned without tokio runtime; running fallback");
664 return false;
665 }
666
667 let mut shutdown_tasks = self.0.sleep.work.shutdown_tasks.lock();
668 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
669 tracing::warn!("shutdown task spawned after teardown; aborting immediately");
670 return false;
671 }
672 let counter = self.0.sleep.work.shutdown_counter.clone();
673 counter.increment();
674 let guard = CountGuard::from_incremented(counter);
675 let ctx = self.clone();
676 shutdown_tasks.spawn(
679 async move {
680 {
681 let _guard = guard;
682 fut.await;
683 }
684 ctx.reset_sleep_timer();
685 }
686 .in_current_span(),
687 );
688 drop(shutdown_tasks);
689 self.reset_sleep_timer();
690 true
691 }
692
693 #[cfg(feature = "wasm-runtime")]
694 pub(crate) fn track_shutdown_task<F>(&self, fut: F) -> bool
695 where
696 F: Future<Output = ()> + 'static,
697 {
698 let mut local_shutdown_tasks = self.0.sleep.work.local_shutdown_tasks.lock();
699 if self.0.sleep.work.teardown_started.load(Ordering::Acquire) {
700 tracing::warn!("shutdown task spawned after teardown; aborting immediately");
701 return false;
702 }
703 let counter = self.0.sleep.work.shutdown_counter.clone();
704 counter.increment();
705 let guard = CountGuard::from_incremented(counter);
706 let ctx = self.clone();
707 let (complete_tx, complete_rx) = futures_oneshot::channel();
711 let (abort_handle, abort_registration) = AbortHandle::new_pair();
712 local_shutdown_tasks.push(LocalShutdownTask {
713 abort_handle,
714 complete_rx,
715 aborts_at_shutdown_deadline: true,
716 });
717 drop(local_shutdown_tasks);
718 let ctx_for_task = ctx.clone();
719 wasm_bindgen_futures::spawn_local(
720 async move {
721 let task = async move {
722 {
723 let _guard = guard;
724 fut.await;
725 }
726 let _ = complete_tx.send(());
727 ctx_for_task.reset_sleep_timer();
728 };
729 if Abortable::new(task, abort_registration).await.is_err() {
730 ctx.reset_sleep_timer();
731 }
732 }
733 .in_current_span(),
734 );
735 self.reset_sleep_timer();
736 true
737 }
738
739 pub(crate) fn shutdown_task_count(&self) -> usize {
740 self.0.sleep.work.shutdown_counter.load()
741 }
742
743 pub(crate) fn mark_shutdown_deadline_reached(&self) {
744 self.0
745 .sleep
746 .work
747 .shutdown_deadline_reached
748 .store(true, Ordering::Release);
749 }
750
751 pub(crate) fn begin_core_dispatched_hook(&self) {
752 self.0.sleep.work.core_dispatched_hooks.increment();
753 self.reset_sleep_timer();
754 }
755
756 pub fn mark_core_dispatched_hook_completed(&self) {
757 self.0.sleep.work.core_dispatched_hooks.decrement();
758 self.reset_sleep_timer();
759 }
760
761 pub(crate) fn core_dispatched_hook_count(&self) -> usize {
762 self.0.sleep.work.core_dispatched_hooks.load()
763 }
764
765 pub(crate) async fn teardown_sleep_state(&self) {
766 let abort_remaining = self
767 .0
768 .sleep
769 .work
770 .shutdown_deadline_reached
771 .swap(false, Ordering::AcqRel);
772 if abort_remaining {
776 self.0
777 .sleep
778 .work
779 .teardown_started
780 .store(true, Ordering::Release);
781 }
782
783 #[cfg(feature = "wasm-runtime")]
784 {
785 loop {
786 let local_shutdown_tasks = {
787 let mut guard = self.0.sleep.work.local_shutdown_tasks.lock();
788 let taken = std::mem::take(&mut *guard);
789 if taken.is_empty() {
790 self.0
791 .sleep
792 .work
793 .teardown_started
794 .store(true, Ordering::Release);
795 return;
796 }
797 taken
798 };
799
800 if abort_remaining {
801 for task in local_shutdown_tasks {
802 if task.aborts_at_shutdown_deadline {
803 task.abort_handle.abort();
804 }
805 if task.complete_rx.await.is_err() {
806 tracing::debug!("aborted shutdown task during teardown");
807 }
808 }
809 self.0
810 .sleep
811 .work
812 .teardown_started
813 .store(true, Ordering::Release);
814 return;
815 }
816
817 for task in local_shutdown_tasks {
818 if task.complete_rx.await.is_err() {
819 tracing::debug!("shutdown task completion dropped during teardown");
820 }
821 }
822 }
823 }
824
825 #[cfg(not(feature = "wasm-runtime"))]
826 loop {
827 let mut abortable_shutdown_tasks = {
828 let mut guard = self.0.sleep.work.shutdown_tasks.lock();
829 let taken = std::mem::take(&mut *guard);
830 let mut unabortable_guard = self.0.sleep.work.unabortable_shutdown_tasks.lock();
831 let unabortable_taken = std::mem::take(&mut *unabortable_guard);
832 if taken.is_empty() && unabortable_taken.is_empty() {
833 self.0
834 .sleep
835 .work
836 .teardown_started
837 .store(true, Ordering::Release);
838 return;
839 }
840 (taken, unabortable_taken)
841 };
842
843 abortable_shutdown_tasks.0.shutdown().await;
844 while let Some(result) = abortable_shutdown_tasks.0.join_next().await {
845 if let Err(error) = result
846 && !error.is_cancelled()
847 {
848 tracing::error!(?error, "shutdown task join failed during teardown");
849 }
850 }
851 while let Some(result) = abortable_shutdown_tasks.1.join_next().await {
852 if let Err(error) = result
853 && !error.is_cancelled()
854 {
855 tracing::error!(?error, "shutdown task join failed during teardown");
856 }
857 }
858 }
859 }
860
861 pub(crate) fn sleep_state_config(&self) -> ActorConfig {
862 self.0.sleep.config.lock().clone()
863 }
864
865 pub(crate) fn active_http_request_count(&self) -> usize {
866 self.http_request_counter()
867 .map(|counter| counter.load())
868 .unwrap_or(0)
869 }
870
871 pub(crate) fn sleep_activity_notify(&self) -> Arc<Notify> {
872 self.0.sleep.work.activity_notify.clone()
873 }
874
875 fn http_request_counter(&self) -> Option<Arc<AsyncCounter>> {
876 if let Some(counter) = self.0.sleep.http_request_counter.lock().clone() {
877 return Some(counter);
878 }
879
880 let counter = self.lookup_http_request_counter(self.actor_id())?;
881 *self.0.sleep.http_request_counter.lock() = Some(counter.clone());
882 Some(counter)
883 }
884
885 fn lookup_http_request_counter(&self, actor_id: &str) -> Option<Arc<AsyncCounter>> {
886 let envoy_handle = self.0.sleep.envoy_handle.lock().clone();
887 let generation = *self.0.sleep.generation.lock();
888 let envoy_handle = envoy_handle?;
889 let counter = envoy_handle.http_request_counter(actor_id, generation)?;
890 counter.register_zero_notify(&self.0.sleep.work.idle_notify);
891 let ctx = self.clone();
896 counter.register_change_callback(Arc::new(move || {
897 ctx.0
898 .metrics
899 .set_http_requests_active(ctx.active_http_request_count());
900 ctx.reset_sleep_timer();
901 }));
902 self.0.metrics.set_http_requests_active(counter.load());
903 Some(counter)
904 }
905}
906
907#[cfg(test)]
909#[path = "../../tests/sleep.rs"]
910mod tests;