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