/build/source/nativelink-scheduler/src/awaited_action_db/awaited_action.rs
Line | Count | Source |
1 | | // Copyright 2024 Trace Machina, Inc. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use std::sync::Arc; |
16 | | use std::time::{SystemTime, UNIX_EPOCH}; |
17 | | |
18 | | use nativelink_error::{Error, ResultExt, make_input_err}; |
19 | | use nativelink_metric::{ |
20 | | MetricFieldData, MetricKind, MetricPublishKnownKindData, MetricsComponent, |
21 | | }; |
22 | | use nativelink_util::action_messages::{ |
23 | | ActionInfo, ActionStage, ActionState, OperationId, WorkerId, |
24 | | }; |
25 | | use nativelink_util::origin_event::{ |
26 | | BAZEL_METADATA_KEY, OriginMetadata, request_metadata_from_baggage, |
27 | | }; |
28 | | use opentelemetry::baggage::BaggageExt; |
29 | | use opentelemetry::context::Context; |
30 | | use opentelemetry_semantic_conventions::attribute::ENDUSER_ID; |
31 | | use serde::{Deserialize, Serialize}; |
32 | | use static_assertions::{assert_eq_size, const_assert, const_assert_eq}; |
33 | | |
34 | | /// The version of the awaited action. |
35 | | /// This number will always increment by one each time |
36 | | /// the action is updated. |
37 | | #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] |
38 | | struct AwaitedActionVersion(i64); |
39 | | |
40 | | impl MetricsComponent for AwaitedActionVersion { |
41 | 0 | fn publish( |
42 | 0 | &self, |
43 | 0 | _kind: MetricKind, |
44 | 0 | _field_metadata: MetricFieldData, |
45 | 0 | ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> { |
46 | 0 | Ok(MetricPublishKnownKindData::Counter(u64::from_ne_bytes( |
47 | 0 | self.0.to_ne_bytes(), |
48 | 0 | ))) |
49 | 0 | } |
50 | | } |
51 | | |
52 | | /// An action that is being awaited on and last known state. |
53 | | #[derive(Debug, Clone, MetricsComponent, Serialize, Deserialize)] |
54 | | pub struct AwaitedAction { |
55 | | /// The current version of the action. |
56 | | #[metric(help = "The version of the AwaitedAction")] |
57 | | version: AwaitedActionVersion, |
58 | | |
59 | | /// The action that is being awaited on. |
60 | | #[metric(help = "The action info of the AwaitedAction")] |
61 | | action_info: Arc<ActionInfo>, |
62 | | |
63 | | /// The operation id of the action. |
64 | | // If you need the client operation id, it may be set in |
65 | | // ActionState::operation_id. |
66 | | #[metric(help = "The operation id of the AwaitedAction")] |
67 | | operation_id: OperationId, |
68 | | |
69 | | /// The sort key as older schedulers read it. Never used for ordering |
70 | | /// here; see `PersistedSortKey`. |
71 | | #[metric(help = "The sort key of the AwaitedAction")] |
72 | | sort_key: PersistedSortKey, |
73 | | |
74 | | /// The time the action was last updated. |
75 | | #[metric(help = "The last time the worker updated the AwaitedAction")] |
76 | | last_worker_updated_timestamp: SystemTime, |
77 | | |
78 | | /// The last time the client sent a keepalive message. |
79 | | #[metric(help = "The last time the client sent a keepalive message")] |
80 | | last_client_keepalive_timestamp: SystemTime, |
81 | | |
82 | | /// Worker that is currently running this action, None if unassigned. |
83 | | #[metric(help = "The worker id of the AwaitedAction")] |
84 | | worker_id: Option<WorkerId>, |
85 | | |
86 | | /// The current state of the action. |
87 | | #[metric(help = "The state of the AwaitedAction")] |
88 | | state: Arc<ActionState>, |
89 | | |
90 | | /// The origin metadata of the action. |
91 | | maybe_origin_metadata: Option<OriginMetadata>, |
92 | | |
93 | | /// Number of attempts the job has been tried. |
94 | | #[metric(help = "The number of attempts the AwaitedAction has been tried")] |
95 | | pub attempts: usize, |
96 | | |
97 | | /// Requeues after the worker running the action was lost. Not an |
98 | | /// attempt: the action did nothing wrong. Absent in records written |
99 | | /// before the field existed. |
100 | | #[serde(default)] |
101 | | #[metric(help = "How many times the AwaitedAction lost its worker")] |
102 | | pub worker_losses: usize, |
103 | | |
104 | | /// Requeues with a larger memory reservation after a memory kill. Not |
105 | | /// an attempt either. Absent in older records. |
106 | | #[serde(default)] |
107 | | #[metric(help = "How many times the AwaitedAction was escalated")] |
108 | | pub escalations: usize, |
109 | | |
110 | | /// Requeues after the worker reported it could not reach its store |
111 | | /// (`Unavailable`): a CAS pod being replaced, not the action's doing, |
112 | | /// so not an attempt. Bounded like a lost worker. Absent in older |
113 | | /// records. |
114 | | #[serde(default)] |
115 | | #[metric(help = "How many times the AwaitedAction's worker could not reach its store")] |
116 | | pub store_losses: usize, |
117 | | |
118 | | /// Requeues after the command ran past its timeout on a worker. Not an |
119 | | /// attempt: a worker gone slow times out everything it is given, and |
120 | | /// the retry is the second opinion from another worker. Absent in |
121 | | /// older records, and left out while zero so a record without one |
122 | | /// reads as before. |
123 | | #[serde(default, skip_serializing_if = "is_zero")] |
124 | | #[metric(help = "How many times the AwaitedAction timed out on a worker and was queued again")] |
125 | | pub timeouts: usize, |
126 | | |
127 | | /// Workers this action failed or timed out on, most recent last; a |
128 | | /// retry prefers any other worker while one can take it. Absent in |
129 | | /// older records, and left out while empty. |
130 | | #[serde(default, skip_serializing_if = "Vec::is_empty")] |
131 | | avoid_worker_ids: Vec<WorkerId>, |
132 | | |
133 | | /// The nodes of those workers, as they advertised them, so every |
134 | | /// scheduler replica can keep a retry off the node and not only the |
135 | | /// replica that knows the worker. Absent in older records, and left out |
136 | | /// while empty. |
137 | | #[serde(default, skip_serializing_if = "Vec::is_empty")] |
138 | | avoid_node_names: Vec<String>, |
139 | | } |
140 | | |
141 | | /// Most workers an action remembers failing on. |
142 | | const MAX_AVOIDED_WORKERS: usize = 8; |
143 | | |
144 | | // serde's `skip_serializing_if` takes the field by reference. |
145 | | #[allow(clippy::trivially_copy_pass_by_ref)] |
146 | 15.8k | const fn is_zero(n: &usize) -> bool { |
147 | 15.8k | *n == 0 |
148 | 15.8k | } |
149 | | |
150 | | impl AwaitedAction { |
151 | 16.6k | pub fn new(operation_id: OperationId, action_info: Arc<ActionInfo>, now: SystemTime) -> Self { |
152 | 16.6k | let sort_key = PersistedSortKey::from_action_info(&action_info); |
153 | 16.6k | let action_state = Arc::new(ActionState { |
154 | 16.6k | stage: ActionStage::Queued, |
155 | 16.6k | // Note: We don't use the real client_operation_id here because |
156 | 16.6k | // the only place AwaitedAction::new should ever be called is |
157 | 16.6k | // when the action is first created and this struct will be stored |
158 | 16.6k | // in the database, so we don't want to accidentally leak the |
159 | 16.6k | // client_operation_id to all clients. |
160 | 16.6k | client_operation_id: operation_id.clone(), |
161 | 16.6k | action_digest: action_info.unique_qualifier.digest(), |
162 | 16.6k | last_transition_timestamp: now, |
163 | 16.6k | }); |
164 | | |
165 | 16.6k | let ctx = Context::current(); |
166 | 16.6k | let baggage = ctx.baggage(); |
167 | | |
168 | 16.6k | let maybe_origin_metadata = if baggage.is_empty() { |
169 | 16.6k | None |
170 | | } else { |
171 | 5 | let bazel_metadata = baggage |
172 | 5 | .get(BAZEL_METADATA_KEY) |
173 | 5 | .and_then(|value| request_metadata_from_baggage1 (value.as_str()1 ).ok1 ()); |
174 | | Some(OriginMetadata { |
175 | 5 | identity: baggage |
176 | 5 | .get(ENDUSER_ID) |
177 | 5 | .map(|v| v.as_str().to_string()) |
178 | 5 | .unwrap_or_default(), |
179 | 5 | bazel_metadata, |
180 | | }) |
181 | | }; |
182 | | |
183 | 16.6k | Self { |
184 | 16.6k | version: AwaitedActionVersion(0), |
185 | 16.6k | action_info, |
186 | 16.6k | operation_id, |
187 | 16.6k | sort_key, |
188 | 16.6k | attempts: 0, |
189 | 16.6k | worker_losses: 0, |
190 | 16.6k | escalations: 0, |
191 | 16.6k | store_losses: 0, |
192 | 16.6k | timeouts: 0, |
193 | 16.6k | avoid_worker_ids: Vec::new(), |
194 | 16.6k | avoid_node_names: Vec::new(), |
195 | 16.6k | last_worker_updated_timestamp: now, |
196 | 16.6k | last_client_keepalive_timestamp: now, |
197 | 16.6k | maybe_origin_metadata, |
198 | 16.6k | worker_id: None, |
199 | 16.6k | state: action_state, |
200 | 16.6k | } |
201 | 16.6k | } |
202 | | |
203 | 56.0k | pub(crate) const fn version(&self) -> i64 { |
204 | 56.0k | self.version.0 |
205 | 56.0k | } |
206 | | |
207 | 549k | pub(crate) const fn set_version(&mut self, version: i64) { |
208 | 549k | self.version = AwaitedActionVersion(version); |
209 | 549k | } |
210 | | |
211 | 21.5k | pub(crate) const fn increment_version(&mut self) { |
212 | 21.5k | self.version = AwaitedActionVersion(self.version.0 + 1); |
213 | 21.5k | } |
214 | | |
215 | 6.84M | pub const fn action_info(&self) -> &Arc<ActionInfo> { |
216 | 6.84M | &self.action_info |
217 | 6.84M | } |
218 | | |
219 | 558k | pub const fn operation_id(&self) -> &OperationId { |
220 | 558k | &self.operation_id |
221 | 558k | } |
222 | | |
223 | | /// The key this action is ordered by. Computed from the action info |
224 | | /// rather than read from the record, so a record written by any version |
225 | | /// of the scheduler orders the same way. |
226 | 41.0k | pub(crate) fn sort_key(&self) -> AwaitedActionSortKey { |
227 | 41.0k | AwaitedActionSortKey::new_with_unique_key( |
228 | 41.0k | self.action_info.priority, |
229 | 41.0k | &self.action_info.insert_timestamp, |
230 | | ) |
231 | 41.0k | } |
232 | | |
233 | 13.7M | pub const fn state(&self) -> &Arc<ActionState> { |
234 | 13.7M | &self.state |
235 | 13.7M | } |
236 | | |
237 | 7.90k | pub fn is_complete(&self) -> bool { |
238 | 7.90k | match &self.state.stage { |
239 | | ActionStage::Unknown |
240 | | | ActionStage::CacheCheck |
241 | | | ActionStage::Queued |
242 | 6.35k | | ActionStage::Executing => false, |
243 | 1.55k | ActionStage::Completed(_) | ActionStage::CompletedFromCache(_) => true, |
244 | | } |
245 | 7.90k | } |
246 | | |
247 | 6.73M | pub(crate) const fn maybe_origin_metadata(&self) -> Option<&OriginMetadata> { |
248 | 6.73M | self.maybe_origin_metadata.as_ref() |
249 | 6.73M | } |
250 | | |
251 | 66.6k | pub(crate) const fn worker_id(&self) -> Option<&WorkerId> { |
252 | 66.6k | self.worker_id.as_ref() |
253 | 66.6k | } |
254 | | |
255 | 6.67M | pub(crate) const fn last_worker_updated_timestamp(&self) -> SystemTime { |
256 | 6.67M | self.last_worker_updated_timestamp |
257 | 6.67M | } |
258 | | |
259 | | /// Workers this action failed or timed out on, most recent last. |
260 | 6.66M | pub fn avoid_worker_ids(&self) -> &[WorkerId] { |
261 | 6.66M | &self.avoid_worker_ids |
262 | 6.66M | } |
263 | | |
264 | | /// Remembers that this action failed or timed out on `worker_id`, so a |
265 | | /// retry prefers another worker. Bounded; the oldest is forgotten. |
266 | 270 | pub(crate) fn avoid_worker(&mut self, worker_id: WorkerId) { |
267 | 270 | self.avoid_worker_ids.retain(|id| id2 != &worker_id2 ); |
268 | 270 | self.avoid_worker_ids.push(worker_id); |
269 | 270 | if self.avoid_worker_ids.len() > MAX_AVOIDED_WORKERS { |
270 | 0 | self.avoid_worker_ids.remove(0); |
271 | 270 | } |
272 | 270 | } |
273 | | |
274 | | /// The nodes this action failed or timed out on, most recent last. |
275 | 6.66M | pub fn avoid_node_names(&self) -> &[String] { |
276 | 6.66M | &self.avoid_node_names |
277 | 6.66M | } |
278 | | |
279 | | /// Remembers the node of a worker this action failed on. Bounded like |
280 | | /// the workers. |
281 | 2 | pub(crate) fn avoid_node(&mut self, node: &str) { |
282 | 2 | self.avoid_node_names.retain(|n| n0 != node0 ); |
283 | 2 | self.avoid_node_names.push(node.to_owned()); |
284 | 2 | if self.avoid_node_names.len() > MAX_AVOIDED_WORKERS { |
285 | 0 | self.avoid_node_names.remove(0); |
286 | 2 | } |
287 | 2 | } |
288 | | |
289 | 36.2k | pub(crate) const fn worker_keep_alive(&mut self, now: SystemTime) { |
290 | 36.2k | self.last_worker_updated_timestamp = now; |
291 | 36.2k | } |
292 | | |
293 | 7.21M | pub(crate) const fn last_client_keepalive_timestamp(&self) -> SystemTime { |
294 | 7.21M | self.last_client_keepalive_timestamp |
295 | 7.21M | } |
296 | | |
297 | 53.7k | pub(crate) const fn update_client_keep_alive(&mut self, now: SystemTime) { |
298 | 53.7k | self.last_client_keepalive_timestamp = now; |
299 | 53.7k | } |
300 | | |
301 | | /// Replaces the action's info, for an escalated reservation. The sort |
302 | | /// key and identity stay: the action is the same one, asking for more. |
303 | 23 | pub(crate) fn set_action_info(&mut self, action_info: Arc<ActionInfo>) { |
304 | 23 | self.action_info = action_info; |
305 | 23 | } |
306 | | |
307 | 106k | pub(crate) fn set_client_operation_id(&mut self, client_operation_id: OperationId) { |
308 | 106k | Arc::make_mut(&mut self.state).client_operation_id = client_operation_id; |
309 | 106k | } |
310 | | |
311 | | /// Sets the worker id that is currently processing this action. |
312 | 21.9k | pub fn set_worker_id(&mut self, new_maybe_worker_id: Option<WorkerId>, now: SystemTime) { |
313 | 21.9k | if self.worker_id != new_maybe_worker_id { |
314 | 11.8k | self.worker_id = new_maybe_worker_id; |
315 | 11.8k | self.worker_keep_alive(now); |
316 | 11.8k | }10.1k |
317 | 21.9k | } |
318 | | |
319 | | /// Sets the current state of the action and updates the last worker updated timestamp. |
320 | 24.4k | pub fn worker_set_state(&mut self, mut state: Arc<ActionState>, now: SystemTime) { |
321 | 24.4k | core::mem::swap(&mut self.state, &mut state); |
322 | 24.4k | self.worker_keep_alive(now); |
323 | 24.4k | } |
324 | | } |
325 | | |
326 | | impl TryFrom<&[u8]> for AwaitedAction { |
327 | | type Error = Error; |
328 | 2 | fn try_from(value: &[u8]) -> Result<Self, Self::Error> { |
329 | 2 | serde_json::from_slice(value) |
330 | 2 | .map_err(|e| make_input_err!0 ("{}", e0 .to_string0 ())) |
331 | 2 | .err_tip(|| "In AwaitedAction::TryFrom::&[u8]") |
332 | 2 | } |
333 | | } |
334 | | |
335 | | /// The key used to sort the awaited actions. |
336 | | /// |
337 | | /// The rules for sorting are as follows: |
338 | | /// 1. priority of the action (higher runs sooner) |
339 | | /// 2. insert order of the action (older runs sooner) |
340 | | /// |
341 | | /// The key packs both into one `u128`: priority in bits 64 to 95 and the |
342 | | /// insert time in nanoseconds since the epoch, inverted, in the low 64 bits, |
343 | | /// so that reading the set in descending order gives the rules above. The |
344 | | /// high 32 bits are zero. Before this the low half held whole seconds, so a |
345 | | /// burst inserted within one second had no order at all and each backend |
346 | | /// picked its own; nanoseconds order every arrival a clock can tell apart, |
347 | | /// and the operation id breaks the rest. |
348 | | /// |
349 | | /// This key is not what the record carries; that is `PersistedSortKey`, |
350 | | /// which older schedulers can read. Every record, whichever version wrote |
351 | | /// it, orders by this key computed from its action info. What does differ |
352 | | /// during a rolling upgrade is the Redis index: an older scheduler writes |
353 | | /// the persisted key as 16 hex digits and this one writes this key as 32, |
354 | | /// with leading zeros, and the index sorts the strings lexicographically. |
355 | | /// Under the descending read the 16-digit entries come first, so the |
356 | | /// actions queued before the upgrade run first, which is their order anyway. |
357 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] |
358 | | #[repr(transparent)] |
359 | | pub struct AwaitedActionSortKey(u128); |
360 | | |
361 | | /// The sort key in the layout every released scheduler reads: the priority |
362 | | /// above 32 bits of inverted whole seconds, as a `u64`. It is what the |
363 | | /// record carries under `sort_key`, so a record written here still loads on |
364 | | /// an older scheduler during a rolling upgrade or after a rollback; a |
365 | | /// `u128` there fails to parse as `u64` and takes queue reads and operation |
366 | | /// lookups with it. Ordering never reads it: see `AwaitedAction::sort_key`. |
367 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
368 | | #[repr(transparent)] |
369 | | pub struct PersistedSortKey(u64); |
370 | | |
371 | | impl MetricsComponent for PersistedSortKey { |
372 | 0 | fn publish( |
373 | 0 | &self, |
374 | 0 | _kind: MetricKind, |
375 | 0 | _field_metadata: MetricFieldData, |
376 | 0 | ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> { |
377 | 0 | Ok(MetricPublishKnownKindData::Counter(self.0)) |
378 | 0 | } |
379 | | } |
380 | | |
381 | | impl PersistedSortKey { |
382 | 16.6k | const fn new(priority: i32, insert_seconds: u32) -> Self { |
383 | 16.6k | let priority_u32 = i32::MIN.unsigned_abs().wrapping_add_signed(priority); |
384 | 16.6k | let timestamp = insert_seconds ^ u32::MAX; |
385 | 16.6k | Self(((priority_u32 as u64) << 32) | timestamp as u64) |
386 | 16.6k | } |
387 | | |
388 | 16.6k | pub fn from_action_info(action_info: &ActionInfo) -> Self { |
389 | 16.6k | let seconds = u32::try_from( |
390 | 16.6k | action_info |
391 | 16.6k | .insert_timestamp |
392 | 16.6k | .duration_since(UNIX_EPOCH) |
393 | 16.6k | .unwrap() |
394 | 16.6k | .as_secs(), |
395 | | ) |
396 | 16.6k | .unwrap_or(u32::MAX); |
397 | 16.6k | Self::new(action_info.priority, seconds) |
398 | 16.6k | } |
399 | | |
400 | | /// The key as an older scheduler writes it into the Redis index. |
401 | | #[must_use] |
402 | 5 | pub fn index_field(self) -> String { |
403 | 5 | format!("{:016x}", self.0) |
404 | 5 | } |
405 | | |
406 | | #[must_use] |
407 | 0 | pub const fn as_u64(self) -> u64 { |
408 | 0 | self.0 |
409 | 0 | } |
410 | | } |
411 | | |
412 | | // The released layout, bit for bit. |
413 | | const_assert_eq!( |
414 | | PersistedSortKey::new(0x1234_5678, 0x9abc_def0).0, |
415 | | 0x9234_5678_6543_210f |
416 | | ); |
417 | | |
418 | | impl MetricsComponent for AwaitedActionSortKey { |
419 | 0 | fn publish( |
420 | 0 | &self, |
421 | 0 | _kind: MetricKind, |
422 | 0 | _field_metadata: MetricFieldData, |
423 | 0 | ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> { |
424 | | // The priority and the top of the timestamp; the low bits are noise |
425 | | // for a metric. |
426 | | #[expect( |
427 | | clippy::cast_possible_truncation, |
428 | | reason = "the high half is what is wanted" |
429 | | )] |
430 | 0 | Ok(MetricPublishKnownKindData::Counter((self.0 >> 32) as u64)) |
431 | 0 | } |
432 | | } |
433 | | |
434 | | impl AwaitedActionSortKey { |
435 | 41.0k | const fn new(priority: i32, insert_nanos: u64) -> Self { |
436 | | // Shift the signed i32 range [i32::MIN, i32::MAX] to the unsigned u32 range |
437 | | // [0, u32::MAX] to preserve ordering when we convert to bytes for sorting. |
438 | 41.0k | let priority_u32 = i32::MIN.unsigned_abs().wrapping_add_signed(priority); |
439 | | |
440 | | // Invert the timestamp so the larger the timestamp the lower the |
441 | | // number: descending order reads the oldest first. |
442 | 41.0k | let timestamp = insert_nanos ^ u64::MAX; |
443 | | |
444 | 41.0k | Self(((priority_u32 as u128) << 64) | timestamp as u128) |
445 | 41.0k | } |
446 | | |
447 | 41.0k | fn new_with_unique_key(priority: i32, insert_timestamp: &SystemTime) -> Self { |
448 | 41.0k | let nanos = u64::try_from( |
449 | 41.0k | insert_timestamp |
450 | 41.0k | .duration_since(UNIX_EPOCH) |
451 | 41.0k | .unwrap() |
452 | 41.0k | .as_nanos(), |
453 | | ) |
454 | 41.0k | .unwrap_or(u64::MAX); |
455 | 41.0k | Self::new(priority, nanos) |
456 | 41.0k | } |
457 | | |
458 | 0 | pub const fn as_u128(self) -> u128 { |
459 | 0 | self.0 |
460 | 0 | } |
461 | | |
462 | | /// The key as this scheduler writes it into the Redis index. |
463 | | #[must_use] |
464 | 7.91k | pub fn index_field(self) -> String { |
465 | 7.91k | format!("{:032x}", self.0) |
466 | 7.91k | } |
467 | | } |
468 | | |
469 | | // Ensure the size of the sort key is the same as a `u128`. |
470 | | assert_eq_size!(AwaitedActionSortKey, u128); |
471 | | |
472 | | const_assert_eq!( |
473 | | AwaitedActionSortKey::new(0x1234_5678, 0x9abc_def0_1234_5678).0, |
474 | | // Note: Result has 0x12345678 + 0x80000000 = 0x92345678 because we need |
475 | | // to shift the `i32::MIN` value to be represented by zero. |
476 | | // Note: `6543210f_edcb_a987` are the inverted bits of the timestamp. |
477 | | AwaitedActionSortKey(0x9234_5678_6543_210f_edcb_a987).0 |
478 | | ); |
479 | | // Ensure the priority is used as the sort key first. |
480 | | const_assert!( |
481 | | AwaitedActionSortKey::new(i32::MAX, 0).0 > AwaitedActionSortKey::new(i32::MAX - 1, 0).0 |
482 | | ); |
483 | | const_assert!(AwaitedActionSortKey::new(i32::MAX - 1, 0).0 > AwaitedActionSortKey::new(1, 0).0); |
484 | | const_assert!(AwaitedActionSortKey::new(1, 0).0 > AwaitedActionSortKey::new(0, 0).0); |
485 | | const_assert!(AwaitedActionSortKey::new(0, 0).0 > AwaitedActionSortKey::new(-1, 0).0); |
486 | | const_assert!(AwaitedActionSortKey::new(-1, 0).0 > AwaitedActionSortKey::new(i32::MIN + 1, 0).0); |
487 | | const_assert!( |
488 | | AwaitedActionSortKey::new(i32::MIN + 1, 0).0 > AwaitedActionSortKey::new(i32::MIN, 0).0 |
489 | | ); |
490 | | |
491 | | // Ensure the insert timestamp is used as the sort key second, and that one |
492 | | // nanosecond is enough to tell two arrivals apart. |
493 | | const_assert!(AwaitedActionSortKey::new(0, u64::MIN).0 > AwaitedActionSortKey::new(0, u64::MAX).0); |
494 | | const_assert!(AwaitedActionSortKey::new(0, 1_000).0 > AwaitedActionSortKey::new(0, 1_001).0); |