aion_server/worker/queue_service/
policy.rs1use std::time::Duration;
24
25use serde::Deserialize;
26
27#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
29#[serde(rename_all = "snake_case")]
30pub enum QueueServicePolicy {
31 #[default]
35 Strict,
36 DurablePending,
41}
42
43impl QueueServicePolicy {
44 #[must_use]
46 pub const fn as_str(self) -> &'static str {
47 match self {
48 Self::Strict => "strict",
49 Self::DurablePending => "durable_pending",
50 }
51 }
52}
53
54impl std::fmt::Display for QueueServicePolicy {
55 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 formatter.write_str(self.as_str())
57 }
58}
59
60#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
62#[serde(deny_unknown_fields)]
63pub struct QueueServiceOverride {
64 #[serde(default)]
66 pub namespace: Option<String>,
67 pub task_queue: String,
69 pub policy: QueueServicePolicy,
71}
72
73#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
75#[serde(default, deny_unknown_fields)]
76pub struct QueueServiceConfig {
77 pub default_policy: QueueServicePolicy,
79 pub overrides: Vec<QueueServiceOverride>,
82 #[serde(default, with = "optional_duration_millis")]
85 pub service_availability_deadline: Option<Duration>,
86 #[serde(default, with = "optional_duration_millis")]
90 pub schedule_to_start_timeout: Option<Duration>,
91}
92
93impl QueueServiceConfig {
94 #[must_use]
96 pub fn policy_for(&self, namespace: &str, task_queue: &str) -> QueueServicePolicy {
97 self.overrides
98 .iter()
99 .find(|entry| {
100 entry.task_queue == task_queue
101 && entry
102 .namespace
103 .as_ref()
104 .is_none_or(|scoped| scoped == namespace)
105 })
106 .map_or(self.default_policy, |entry| entry.policy)
107 }
108
109 #[must_use]
117 pub const fn availability_deadline_for(&self, policy: QueueServicePolicy) -> Option<Duration> {
118 match policy {
119 QueueServicePolicy::Strict => self.service_availability_deadline,
120 QueueServicePolicy::DurablePending => None,
121 }
122 }
123}
124
125mod optional_duration_millis {
128 use std::time::Duration;
129
130 use serde::{Deserialize, Deserializer};
131
132 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
133 where
134 D: Deserializer<'de>,
135 {
136 let millis = Option::<u64>::deserialize(deserializer)?;
137 Ok(millis.map(Duration::from_millis))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn strict_is_the_default_policy_and_no_clock_is_invented() {
147 let config = QueueServiceConfig::default();
148 assert_eq!(config.default_policy, QueueServicePolicy::Strict);
149 assert_eq!(
150 config.policy_for("default", "general"),
151 QueueServicePolicy::Strict
152 );
153 assert_eq!(config.service_availability_deadline, None);
154 assert_eq!(config.schedule_to_start_timeout, None);
155 }
156
157 #[test]
158 fn a_written_override_opts_one_queue_into_durable_pending() {
159 let config = QueueServiceConfig {
160 overrides: vec![QueueServiceOverride {
161 namespace: None,
162 task_queue: "general".to_owned(),
163 policy: QueueServicePolicy::DurablePending,
164 }],
165 ..QueueServiceConfig::default()
166 };
167 assert_eq!(
168 config.policy_for("default", "general"),
169 QueueServicePolicy::DurablePending
170 );
171 assert_eq!(
173 config.policy_for("default", "billing"),
174 QueueServicePolicy::Strict
175 );
176 }
177
178 #[test]
179 fn an_override_can_be_scoped_to_one_namespace() {
180 let config = QueueServiceConfig {
181 overrides: vec![QueueServiceOverride {
182 namespace: Some("lab".to_owned()),
183 task_queue: "general".to_owned(),
184 policy: QueueServicePolicy::DurablePending,
185 }],
186 ..QueueServiceConfig::default()
187 };
188 assert_eq!(
189 config.policy_for("lab", "general"),
190 QueueServicePolicy::DurablePending
191 );
192 assert_eq!(
193 config.policy_for("prod", "general"),
194 QueueServicePolicy::Strict
195 );
196 }
197
198 #[test]
199 fn the_availability_deadline_bounds_strict_only() {
200 let config = QueueServiceConfig {
201 service_availability_deadline: Some(Duration::from_secs(30)),
202 ..QueueServiceConfig::default()
203 };
204 assert_eq!(
205 config.availability_deadline_for(QueueServicePolicy::Strict),
206 Some(Duration::from_secs(30))
207 );
208 assert_eq!(
209 config.availability_deadline_for(QueueServicePolicy::DurablePending),
210 None
211 );
212 }
213
214 #[test]
215 fn queue_service_settings_deserialize_from_the_written_form() -> Result<(), toml::de::Error> {
216 let config: QueueServiceConfig = toml::from_str(
217 r#"
218 default_policy = "strict"
219 service_availability_deadline = 45000
220 schedule_to_start_timeout = 5000
221
222 [[overrides]]
223 task_queue = "general"
224 policy = "durable_pending"
225 "#,
226 )?;
227 assert_eq!(config.default_policy, QueueServicePolicy::Strict);
228 assert_eq!(
229 config.service_availability_deadline,
230 Some(Duration::from_secs(45))
231 );
232 assert_eq!(
233 config.schedule_to_start_timeout,
234 Some(Duration::from_secs(5))
235 );
236 assert_eq!(
237 config.policy_for("default", "general"),
238 QueueServicePolicy::DurablePending
239 );
240 Ok(())
241 }
242}