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