1use std::sync::Arc;
2use std::sync::atomic::AtomicBool;
3
4#[cfg(feature = "wasm-runtime")]
5use futures::channel::oneshot as futures_oneshot;
6#[cfg(feature = "wasm-runtime")]
7use futures::future::AbortHandle;
8use parking_lot::Mutex;
9use rivet_envoy_client::async_counter::AsyncCounter;
10use tokio::sync::Notify;
11use tokio::task::JoinSet;
12
13use crate::actor::task_types::UserTaskKind;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum ActorWorkKind {
18 Action,
20 KeepAwake,
22 InternalKeepAwake,
24 WaitUntil,
26 RegisteredTask,
28 WebSocketCallback,
30 DisconnectCallback,
32}
33
34#[derive(Debug, Clone, Copy)]
36pub struct ActorWorkPolicy {
37 pub blocks_idle_sleep: bool,
39 pub drains_shutdown_grace: bool,
41 pub aborts_at_shutdown_deadline: bool,
43 pub user_task_kind: Option<UserTaskKind>,
45}
46
47impl ActorWorkKind {
48 pub fn policy(self) -> ActorWorkPolicy {
50 match self {
51 ActorWorkKind::Action => ActorWorkPolicy {
52 blocks_idle_sleep: true,
53 drains_shutdown_grace: true,
54 aborts_at_shutdown_deadline: true,
55 user_task_kind: Some(UserTaskKind::Action),
56 },
57 ActorWorkKind::KeepAwake => ActorWorkPolicy {
58 blocks_idle_sleep: true,
59 drains_shutdown_grace: true,
60 aborts_at_shutdown_deadline: true,
61 user_task_kind: None,
62 },
63 ActorWorkKind::InternalKeepAwake => ActorWorkPolicy {
64 blocks_idle_sleep: true,
65 drains_shutdown_grace: true,
66 aborts_at_shutdown_deadline: false,
67 user_task_kind: None,
68 },
69 ActorWorkKind::WaitUntil => ActorWorkPolicy {
70 blocks_idle_sleep: false,
71 drains_shutdown_grace: true,
72 aborts_at_shutdown_deadline: true,
73 user_task_kind: Some(UserTaskKind::WaitUntil),
74 },
75 ActorWorkKind::RegisteredTask => ActorWorkPolicy {
76 blocks_idle_sleep: false,
77 drains_shutdown_grace: true,
78 aborts_at_shutdown_deadline: true,
79 user_task_kind: Some(UserTaskKind::RegisteredTask),
80 },
81 ActorWorkKind::WebSocketCallback => ActorWorkPolicy {
82 blocks_idle_sleep: true,
83 drains_shutdown_grace: true,
84 aborts_at_shutdown_deadline: true,
85 user_task_kind: Some(UserTaskKind::WebSocketCallback),
86 },
87 ActorWorkKind::DisconnectCallback => ActorWorkPolicy {
88 blocks_idle_sleep: true,
89 drains_shutdown_grace: true,
90 aborts_at_shutdown_deadline: true,
91 user_task_kind: Some(UserTaskKind::DisconnectCallback),
92 },
93 }
94 }
95
96 pub(crate) fn label(self) -> &'static str {
98 match self {
99 ActorWorkKind::Action => "action",
100 ActorWorkKind::KeepAwake => "keep_awake",
101 ActorWorkKind::InternalKeepAwake => "internal_keep_awake",
102 ActorWorkKind::WaitUntil => "wait_until",
103 ActorWorkKind::RegisteredTask => "registered_task",
104 ActorWorkKind::WebSocketCallback => "websocket_callback",
105 ActorWorkKind::DisconnectCallback => "disconnect_callback",
106 }
107 }
108}
109
110pub(crate) struct WorkRegistry {
112 pub(crate) keep_awake: Arc<AsyncCounter>,
114 pub(crate) internal_keep_awake: Arc<AsyncCounter>,
116 pub(crate) websocket_callback: Arc<AsyncCounter>,
118 pub(crate) disconnect_callback: Arc<AsyncCounter>,
120 pub(crate) shutdown_counter: Arc<AsyncCounter>,
122 pub(crate) core_dispatched_hooks: Arc<AsyncCounter>,
124 pub(crate) shutdown_tasks: Mutex<JoinSet<()>>,
128 pub(crate) unabortable_shutdown_tasks: Mutex<JoinSet<()>>,
130 #[cfg(feature = "wasm-runtime")]
131 pub(crate) local_shutdown_tasks: Mutex<Vec<LocalShutdownTask>>,
133 pub(crate) idle_notify: Arc<Notify>,
135 pub(crate) activity_notify: Arc<Notify>,
140 pub(crate) teardown_started: AtomicBool,
142 pub(crate) shutdown_deadline_reached: AtomicBool,
144}
145
146#[cfg(feature = "wasm-runtime")]
147pub(crate) struct LocalShutdownTask {
148 pub(crate) abort_handle: AbortHandle,
149 pub(crate) complete_rx: futures_oneshot::Receiver<()>,
150 pub(crate) aborts_at_shutdown_deadline: bool,
151}
152
153impl WorkRegistry {
154 pub(crate) fn new() -> Self {
156 let idle_notify = Arc::new(Notify::new());
157 let keep_awake = Arc::new(AsyncCounter::new());
158 keep_awake.register_zero_notify(&idle_notify);
159 let internal_keep_awake = Arc::new(AsyncCounter::new());
160 internal_keep_awake.register_zero_notify(&idle_notify);
161 let websocket_callback = Arc::new(AsyncCounter::new());
162 websocket_callback.register_zero_notify(&idle_notify);
163 let disconnect_callback = Arc::new(AsyncCounter::new());
164 disconnect_callback.register_zero_notify(&idle_notify);
165
166 Self {
167 keep_awake,
168 internal_keep_awake,
169 websocket_callback,
170 disconnect_callback,
171 shutdown_counter: Arc::new(AsyncCounter::new()),
172 core_dispatched_hooks: Arc::new(AsyncCounter::new()),
173 shutdown_tasks: Mutex::new(JoinSet::new()),
174 unabortable_shutdown_tasks: Mutex::new(JoinSet::new()),
175 #[cfg(feature = "wasm-runtime")]
176 local_shutdown_tasks: Mutex::new(Vec::new()),
177 idle_notify,
178 activity_notify: Arc::new(Notify::new()),
179 teardown_started: AtomicBool::new(false),
180 shutdown_deadline_reached: AtomicBool::new(false),
181 }
182 }
183
184 pub(crate) fn keep_awake_guard(&self) -> RegionGuard {
186 RegionGuard::new(self.keep_awake.clone())
187 }
188
189 pub(crate) fn internal_keep_awake_guard(&self) -> RegionGuard {
191 RegionGuard::new(self.internal_keep_awake.clone())
192 }
193
194 pub(crate) fn websocket_callback_guard(&self) -> RegionGuard {
196 RegionGuard::new(self.websocket_callback.clone())
197 }
198
199 pub(crate) fn disconnect_callback_guard(&self) -> RegionGuard {
201 RegionGuard::new(self.disconnect_callback.clone())
202 }
203}
204
205impl Default for WorkRegistry {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211pub(crate) struct RegionGuard {
213 counter: Arc<AsyncCounter>,
214 log_kind: Option<&'static str>,
215 log_actor_id: Option<String>,
216}
217
218impl RegionGuard {
219 fn new(counter: Arc<AsyncCounter>) -> Self {
221 counter.increment();
222 Self {
223 counter,
224 log_kind: None,
225 log_actor_id: None,
226 }
227 }
228
229 pub(crate) fn from_incremented(counter: Arc<AsyncCounter>) -> Self {
231 Self {
232 counter,
233 log_kind: None,
234 log_actor_id: None,
235 }
236 }
237
238 pub(crate) fn with_log_fields(mut self, kind: &'static str, actor_id: Option<String>) -> Self {
240 let count = self.counter.load();
241 match actor_id.as_deref() {
242 Some(actor_id) => tracing::debug!(actor_id, kind, count, "sleep keep-awake engaged"),
243 None => tracing::debug!(kind, count, "sleep keep-awake engaged"),
244 }
245 self.log_kind = Some(kind);
246 self.log_actor_id = actor_id;
247 self
248 }
249}
250
251impl Drop for RegionGuard {
252 fn drop(&mut self) {
253 self.counter.decrement();
254 let Some(kind) = self.log_kind else {
255 return;
256 };
257 let count = self.counter.load();
258 match self.log_actor_id.as_deref() {
259 Some(actor_id) => tracing::debug!(actor_id, kind, count, "sleep keep-awake disengaged"),
260 None => tracing::debug!(kind, count, "sleep keep-awake disengaged"),
261 }
262 }
263}
264
265pub(crate) type CountGuard = RegionGuard;
267
268#[cfg(test)]
270#[path = "../../tests/work_registry.rs"]
271mod tests;