Skip to main content

qubit_executor/task/
task_status.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10// qubit-style: allow inline-tests
11use core::mem::transmute;
12
13use super::{
14    TaskExecutionError,
15    TaskResult,
16};
17
18/// Number of [`TaskStatus`] variants; compact codes are `0..TASK_STATUS_COUNT`.
19pub(crate) const TASK_STATUS_COUNT: usize = 7;
20
21/// Observable lifecycle status for a submitted task.
22///
23/// `#[repr(usize)]` assigns stable discriminants `0..TASK_STATUS_COUNT` for internal
24/// compact state-machine encoding.
25#[repr(usize)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum TaskStatus {
28    /// The task has been accepted but has not started running.
29    Pending = 0,
30    /// The task has started running.
31    Running = 1,
32    /// The task completed successfully.
33    Succeeded = 2,
34    /// The task returned its own error value.
35    Failed = 3,
36    /// The task panicked while running.
37    Panicked = 4,
38    /// The task was explicitly cancelled before producing a value.
39    Cancelled = 5,
40    /// The accepted runner-side completion endpoint was abandoned before
41    /// producing an explicit terminal value.
42    Dropped = 6,
43}
44
45impl TaskStatus {
46    /// Returns whether this status is terminal.
47    ///
48    /// # Returns
49    ///
50    /// `true` after success, failure, panic, cancellation, or dropped
51    /// completion.
52    #[inline]
53    pub const fn is_done(self) -> bool {
54        matches!(
55            self,
56            Self::Succeeded | Self::Failed | Self::Panicked | Self::Cancelled | Self::Dropped
57        )
58    }
59
60    /// Converts this status to its compact state-machine representation.
61    ///
62    /// # Returns
63    ///
64    /// A stable integer code used by task completion state.
65    #[inline]
66    pub(crate) const fn as_usize(self) -> usize {
67        self as usize
68    }
69
70    /// Converts a compact state-machine representation into a task status.
71    ///
72    /// # Parameters
73    ///
74    /// * `value` - Integer value previously produced by [`Self::as_usize`].
75    ///
76    /// # Returns
77    ///
78    /// The represented task status.
79    ///
80    /// # Panics
81    ///
82    /// Panics if `value` is not a valid task status code.
83    #[inline]
84    pub(crate) const fn from_usize(value: usize) -> Self {
85        if value >= TASK_STATUS_COUNT {
86            panic!("invalid task status code");
87        }
88        unsafe { transmute::<usize, Self>(value) }
89    }
90
91    /// Returns the terminal status represented by a task result.
92    ///
93    /// # Parameters
94    ///
95    /// * `result` - Final task result being published.
96    ///
97    /// # Returns
98    ///
99    /// The terminal status matching `result`.
100    #[inline]
101    pub(crate) const fn from_result<R, E>(result: &TaskResult<R, E>) -> Self {
102        match result {
103            Ok(_) => Self::Succeeded,
104            Err(TaskExecutionError::Failed(_)) => Self::Failed,
105            Err(TaskExecutionError::Panicked) => Self::Panicked,
106            Err(TaskExecutionError::Cancelled) => Self::Cancelled,
107            Err(TaskExecutionError::Dropped) => Self::Dropped,
108        }
109    }
110}
111
112#[cfg(test)]
113mod compact_encoding_tests {
114    use super::{
115        TASK_STATUS_COUNT,
116        TaskStatus,
117    };
118
119    #[test]
120    fn task_status_as_usize_matches_stable_discriminants() {
121        assert_eq!(TaskStatus::Pending.as_usize(), 0);
122        assert_eq!(TaskStatus::Running.as_usize(), 1);
123        assert_eq!(TaskStatus::Succeeded.as_usize(), 2);
124        assert_eq!(TaskStatus::Failed.as_usize(), 3);
125        assert_eq!(TaskStatus::Panicked.as_usize(), 4);
126        assert_eq!(TaskStatus::Cancelled.as_usize(), 5);
127        assert_eq!(TaskStatus::Dropped.as_usize(), 6);
128    }
129
130    #[test]
131    fn task_status_from_usize_restores_each_variant() {
132        let variants = [
133            TaskStatus::Pending,
134            TaskStatus::Running,
135            TaskStatus::Succeeded,
136            TaskStatus::Failed,
137            TaskStatus::Panicked,
138            TaskStatus::Cancelled,
139            TaskStatus::Dropped,
140        ];
141        for status in variants {
142            assert_eq!(TaskStatus::from_usize(status.as_usize()), status);
143        }
144    }
145
146    #[test]
147    fn task_status_round_trip_all_codes_in_range() {
148        for code in 0..TASK_STATUS_COUNT {
149            let status = TaskStatus::from_usize(code);
150            assert_eq!(status.as_usize(), code);
151            assert_eq!(TaskStatus::from_usize(code), status);
152        }
153    }
154
155    #[test]
156    #[should_panic(expected = "invalid task status code")]
157    fn task_status_from_usize_panics_at_upper_boundary() {
158        TaskStatus::from_usize(TASK_STATUS_COUNT);
159    }
160
161    #[test]
162    #[should_panic(expected = "invalid task status code")]
163    fn task_status_from_usize_panics_on_invalid_large_code() {
164        TaskStatus::from_usize(usize::MAX);
165    }
166
167    /// [`TASK_STATUS_COUNT`] must stay aligned with `#[repr(usize)]` discriminants.
168    #[test]
169    fn task_status_variant_count_matches_constant() {
170        assert_eq!(
171            TaskStatus::Dropped as usize + 1,
172            TASK_STATUS_COUNT,
173            "last discriminant + 1 must equal TASK_STATUS_COUNT"
174        );
175    }
176}