aion_server/worker/queue_service/
state.rs1use std::collections::{BTreeMap, HashMap};
14use std::sync::{Arc, Mutex};
15use std::time::{Duration, Instant};
16
17use aion_core::{ActivityId, WorkflowId};
18
19use super::census::PoolCensus;
20use super::policy::QueueServicePolicy;
21use super::taxonomy::{QueueServiceReason, ServiceAddress};
22use crate::error::ServerError;
23
24#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
29pub struct UnservedKey {
30 pub namespace: String,
32 pub task_queue: String,
34 pub activity_type: String,
36}
37
38impl UnservedKey {
39 #[must_use]
41 pub fn of(address: &ServiceAddress) -> Self {
42 Self {
43 namespace: address.namespace.clone(),
44 task_queue: address.task_queue.clone(),
45 activity_type: address.activity_type.clone(),
46 }
47 }
48}
49
50#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct UnservedDispatch {
53 pub workflow_id: WorkflowId,
55 pub activity_id: ActivityId,
57 pub node: Option<String>,
59 pub waiting_for: Duration,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct UnservedQueue {
66 pub key: UnservedKey,
68 pub reason: QueueServiceReason,
70 pub policy: QueueServicePolicy,
72 pub census: PoolCensus,
74 pub unserved_for: Duration,
76 pub waiting: Vec<UnservedDispatch>,
78}
79
80#[derive(Clone, Debug)]
81struct Entry {
82 reason: QueueServiceReason,
83 policy: QueueServicePolicy,
84 census: PoolCensus,
85 since: Instant,
86 waiting: HashMap<(WorkflowId, ActivityId), Waiting>,
90}
91
92#[derive(Clone, Debug)]
93struct Waiting {
94 node: Option<String>,
95 since: Instant,
96}
97
98#[derive(Clone, Debug, Default)]
100pub struct QueueServiceState {
101 inner: Arc<Mutex<BTreeMap<UnservedKey, Entry>>>,
102}
103
104impl QueueServiceState {
105 pub fn mark(&self, parked: Parked<'_>) -> Result<(), ServerError> {
111 let key = UnservedKey::of(parked.address);
112 let now = Instant::now();
113 let mut state = self.state()?;
114 let entry = state.entry(key).or_insert_with(|| Entry {
115 reason: parked.reason,
116 policy: parked.policy,
117 census: parked.census,
118 since: now,
119 waiting: HashMap::new(),
120 });
121 entry.reason = parked.reason;
122 entry.policy = parked.policy;
123 entry.census = parked.census;
124 entry
125 .waiting
126 .entry((parked.workflow_id.clone(), parked.activity_id.clone()))
127 .or_insert_with(|| Waiting {
128 node: parked.address.node.clone(),
129 since: now,
130 });
131 Ok(())
132 }
133
134 pub fn clear(
141 &self,
142 address: &ServiceAddress,
143 workflow_id: &WorkflowId,
144 activity_id: &ActivityId,
145 ) -> Result<(), ServerError> {
146 let key = UnservedKey::of(address);
147 let mut state = self.state()?;
148 let Some(entry) = state.get_mut(&key) else {
149 return Ok(());
150 };
151 entry
152 .waiting
153 .remove(&(workflow_id.clone(), activity_id.clone()));
154 if entry.waiting.is_empty() {
155 state.remove(&key);
156 }
157 Ok(())
158 }
159
160 pub fn unserved(&self) -> Result<Vec<UnservedQueue>, ServerError> {
166 let now = Instant::now();
167 let state = self.state()?;
168 Ok(state
169 .iter()
170 .map(|(key, entry)| {
171 let mut waiting: Vec<UnservedDispatch> = entry
172 .waiting
173 .iter()
174 .map(|((workflow_id, activity_id), held)| UnservedDispatch {
175 workflow_id: workflow_id.clone(),
176 activity_id: activity_id.clone(),
177 node: held.node.clone(),
178 waiting_for: now.saturating_duration_since(held.since),
179 })
180 .collect();
181 waiting.sort_by(|left, right| {
182 right.waiting_for.cmp(&left.waiting_for).then_with(|| {
183 left.workflow_id
184 .to_string()
185 .cmp(&right.workflow_id.to_string())
186 })
187 });
188 UnservedQueue {
189 key: key.clone(),
190 reason: entry.reason,
191 policy: entry.policy,
192 census: entry.census,
193 unserved_for: now.saturating_duration_since(entry.since),
194 waiting,
195 }
196 })
197 .collect())
198 }
199
200 pub fn parked_on_queue(&self, task_queue: &str) -> Result<usize, ServerError> {
214 Ok(self
215 .state()?
216 .iter()
217 .filter(|(key, _)| key.task_queue == task_queue)
218 .map(|(_, entry)| entry.waiting.len())
219 .sum())
220 }
221
222 pub fn is_unserved(
228 &self,
229 workflow_id: &WorkflowId,
230 activity_id: &ActivityId,
231 ) -> Result<bool, ServerError> {
232 let held = (workflow_id.clone(), activity_id.clone());
233 Ok(self
234 .state()?
235 .values()
236 .any(|entry| entry.waiting.contains_key(&held)))
237 }
238
239 fn state(
240 &self,
241 ) -> Result<std::sync::MutexGuard<'_, BTreeMap<UnservedKey, Entry>>, ServerError> {
242 self.inner
243 .lock()
244 .map_err(|_| ServerError::lock_poisoned("queue service state"))
245 }
246}
247
248#[derive(Clone, Copy, Debug)]
250pub struct Parked<'a> {
251 pub address: &'a ServiceAddress,
253 pub reason: QueueServiceReason,
255 pub policy: QueueServicePolicy,
257 pub census: PoolCensus,
259 pub workflow_id: &'a WorkflowId,
261 pub activity_id: &'a ActivityId,
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 fn address() -> ServiceAddress {
270 ServiceAddress {
271 namespace: "default".to_owned(),
272 task_queue: "general".to_owned(),
273 activity_type: "greet".to_owned(),
274 node: None,
275 }
276 }
277
278 fn parked<'a>(
279 address: &'a ServiceAddress,
280 workflow_id: &'a WorkflowId,
281 activity_id: &'a ActivityId,
282 ) -> Parked<'a> {
283 Parked {
284 address,
285 reason: QueueServiceReason::NoLivePollers,
286 policy: QueueServicePolicy::DurablePending,
287 census: PoolCensus::default(),
288 workflow_id,
289 activity_id,
290 }
291 }
292
293 #[test]
294 fn a_parked_dispatch_is_visible_immediately() -> Result<(), ServerError> {
295 let state = QueueServiceState::default();
296 let address = address();
297 let workflow_id = WorkflowId::new_v4();
298 let activity_id = ActivityId::from_sequence_position(3);
299 state.mark(parked(&address, &workflow_id, &activity_id))?;
300
301 let unserved = state.unserved()?;
302 assert_eq!(unserved.len(), 1);
303 assert_eq!(unserved[0].reason, QueueServiceReason::NoLivePollers);
304 assert_eq!(unserved[0].policy, QueueServicePolicy::DurablePending);
305 assert_eq!(unserved[0].key.task_queue, "general");
306 assert_eq!(unserved[0].waiting.len(), 1);
307 assert_eq!(unserved[0].waiting[0].workflow_id, workflow_id);
308 assert!(state.is_unserved(&workflow_id, &activity_id)?);
309 Ok(())
310 }
311
312 #[test]
313 fn clearing_the_last_waiter_removes_the_address() -> Result<(), ServerError> {
314 let state = QueueServiceState::default();
315 let address = address();
316 let first = WorkflowId::new_v4();
317 let second = WorkflowId::new_v4();
318 let activity_id = ActivityId::from_sequence_position(0);
319 state.mark(parked(&address, &first, &activity_id))?;
320 state.mark(parked(&address, &second, &activity_id))?;
321 assert_eq!(state.unserved()?.len(), 1);
322 assert_eq!(state.unserved()?[0].waiting.len(), 2);
323
324 state.clear(&address, &first, &activity_id)?;
325 assert!(!state.is_unserved(&first, &activity_id)?);
326 assert_eq!(state.unserved()?[0].waiting.len(), 1);
327
328 state.clear(&address, &second, &activity_id)?;
329 assert!(state.unserved()?.is_empty(), "the address must disappear");
330 Ok(())
331 }
332
333 #[test]
334 fn clearing_an_unknown_dispatch_is_a_no_op() -> Result<(), ServerError> {
335 let state = QueueServiceState::default();
336 state.clear(
337 &address(),
338 &WorkflowId::new_v4(),
339 &ActivityId::from_sequence_position(0),
340 )?;
341 assert!(state.unserved()?.is_empty());
342 Ok(())
343 }
344
345 #[test]
346 fn re_marking_refreshes_the_verdict_without_duplicating_the_waiter() -> Result<(), ServerError>
347 {
348 let state = QueueServiceState::default();
349 let address = address();
350 let workflow_id = WorkflowId::new_v4();
351 let activity_id = ActivityId::from_sequence_position(0);
352 state.mark(parked(&address, &workflow_id, &activity_id))?;
353 state.mark(Parked {
354 reason: QueueServiceReason::PollersIncompatible,
355 census: PoolCensus {
356 workers_in_pool: 2,
357 ..PoolCensus::default()
358 },
359 ..parked(&address, &workflow_id, &activity_id)
360 })?;
361
362 let unserved = state.unserved()?;
363 assert_eq!(unserved.len(), 1);
364 assert_eq!(unserved[0].waiting.len(), 1);
365 assert_eq!(unserved[0].reason, QueueServiceReason::PollersIncompatible);
366 assert_eq!(unserved[0].census.workers_in_pool, 2);
367 Ok(())
368 }
369}