Skip to main content

ax_task/thread/
execution.rs

1//! Publication tokens and task-owned execution/completion state.
2use alloc::{boxed::Box, string::String};
3use core::{
4    fmt,
5    sync::atomic::{AtomicBool, AtomicI32, Ordering},
6};
7
8use crate::{
9    runtime::{
10        context::runtime_task_system,
11        delivery::inbox::{InboxKind, InboxNode},
12        lock::PreemptTicketLock,
13        task_runtime,
14    },
15    sync::WaitQueue,
16    thread::{TaskError, ThreadHandle},
17};
18
19/// Owns a new task which has never entered a runqueue.
20///
21/// Drop queues cancellation for the task-context reaper, including in hard IRQ.
22/// Retain a thread handle and join it to observe cancellation completion.
23#[must_use = "prepare must be published or cancelled"]
24pub struct PreparedThread {
25    handle: Option<ThreadHandle>,
26}
27impl PreparedThread {
28    pub(crate) fn new(handle: ThreadHandle) -> Self {
29        Self {
30            handle: Some(handle),
31        }
32    }
33    /// Borrows task identity for OS resource initialization.
34    pub fn thread_handle(&self) -> ThreadHandle {
35        self.handle
36            .as_ref()
37            .expect("unconsumed preparation")
38            .clone()
39    }
40    /// Reserves initial placement without making the task runnable.
41    pub fn stage(mut self) -> Result<StagedThread, TaskError> {
42        let handle = self.handle.as_ref().expect("unconsumed preparation");
43        runtime_task_system()?.stage_new_thread(handle)?;
44        Ok(StagedThread {
45            handle: self.handle.take(),
46        })
47    }
48    /// Activates a task without an external identity publication transaction.
49    pub fn publish(self) -> Result<ThreadHandle, TaskError> {
50        Ok(self.stage()?.activate())
51    }
52}
53impl Drop for PreparedThread {
54    fn drop(&mut self) {
55        if let Some(handle) = self.handle.take() {
56            cancel_new_thread(handle);
57        }
58    }
59}
60
61/// Owns a reserved first activation, analogous to Linux's pre-wake TASK_NEW.
62///
63/// Drop queues cancellation; the reaper releases the reservation and entry.
64/// The task never becomes runnable when cancelled.
65#[must_use = "staged publication must be activated or cancelled"]
66pub struct StagedThread {
67    handle: Option<ThreadHandle>,
68}
69impl StagedThread {
70    /// Borrows task identity while the OS publishes its resources.
71    pub fn thread_handle(&self) -> ThreadHandle {
72        self.handle.as_ref().expect("unconsumed stage").clone()
73    }
74    /// Commits first activation after all external identity publication is complete.
75    pub fn activate(mut self) -> ThreadHandle {
76        let mut irq = crate::runtime::context::RuntimeIrqGuard::enter();
77        let mut cpu = crate::runtime::context::runtime_current_cpu_mut(&mut irq)
78            .expect("activation requires an installed owner CPU");
79        let handle = self.handle.as_ref().expect("unconsumed stage");
80        runtime_task_system()
81            .expect("staged system remains installed")
82            .activate_staged_thread(cpu.as_mut(), handle);
83        // Until admission commits, Drop must retain cancellation ownership.
84        // Consume it before dropping IRQ/preemption guards, which may schedule.
85        self.handle.take().expect("committed stage")
86    }
87}
88impl Drop for StagedThread {
89    fn drop(&mut self) {
90        if let Some(handle) = self.handle.take() {
91            cancel_new_thread(handle);
92        }
93    }
94}
95
96pub(crate) struct ThreadExecution {
97    pub(crate) cancellation_node: InboxNode,
98    entry: PreemptTicketLock<Option<Box<dyn FnOnce() + Send + 'static>>>,
99    completion: WaitQueue,
100    completed: AtomicBool,
101    exit_code: AtomicI32,
102    name: String,
103}
104impl fmt::Debug for ThreadExecution {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.debug_struct("ThreadExecution")
107            .field("name", &self.name)
108            .finish_non_exhaustive()
109    }
110}
111impl ThreadExecution {
112    pub(crate) fn new(entry: Box<dyn FnOnce() + Send + 'static>, name: String) -> Self {
113        Self {
114            cancellation_node: InboxNode::new(InboxKind::Reclaim),
115            entry: PreemptTicketLock::new(Some(entry)),
116            completion: WaitQueue::new(),
117            completed: AtomicBool::new(false),
118            exit_code: AtomicI32::new(0),
119            name,
120        }
121    }
122    pub(crate) fn finish(&self) {
123        // A cancelled TASK_NEW never consumes its entry. Dispose its captures
124        // in task context before publishing completion, outside the entry lock.
125        let unused_entry = self.entry.lock().take();
126        drop(unused_entry);
127        if !self.completed.swap(true, Ordering::AcqRel) {
128            self.completion.notify_all();
129        }
130    }
131}
132
133impl ThreadHandle {
134    /// Borrows the directly attached OS extension for the lifetime of this handle.
135    pub fn extension(&self) -> Option<crate::thread::ThreadExtensionBorrow<'_>> {
136        self.extension_view()
137            .map(|view| crate::thread::ThreadExtensionBorrow::new(view, self))
138    }
139
140    /// Waits for logical exit while retaining the task identity and OS extension.
141    pub fn wait(&self) -> Result<i32, TaskError> {
142        if crate::thread::current::current_thread_id()? == self.id() {
143            return Err(TaskError::InvalidConfiguration);
144        }
145        let execution = self
146            .core
147            .execution
148            .as_ref()
149            .ok_or(TaskError::InvalidConfiguration)?;
150        execution
151            .completion
152            .try_wait_until(|| execution.completed.load(Ordering::Acquire))?;
153        Ok(execution.exit_code.load(Ordering::Acquire))
154    }
155    /// Waits for exit and transfers final reclamation to the task-context reaper.
156    pub fn join(self) -> Result<i32, TaskError> {
157        let code = self.wait()?;
158        match runtime_task_system()?.reap_thread_handle(self) {
159            Ok(()) => (),
160            Err(error)
161                if matches!(
162                    error.task_error(),
163                    TaskError::ThreadBusy | TaskError::NotExited
164                ) =>
165            {
166                drop(error.into_retry_handle())
167            }
168            Err(error) => return Err(error.task_error()),
169        }
170        Ok(code)
171    }
172    /// Relinquishes the caller's management lease; the scheduler owns execution.
173    pub fn detach(self) {
174        drop(self);
175    }
176}
177
178/// Publishes a return code and exits through the one scheduler exit transaction.
179pub fn exit_current(exit_code: i32) -> ! {
180    let permit = crate::thread::current::prepare_current_exit()
181        .unwrap_or_else(|error| task_runtime::fatal_invariant(15, error_code(error)));
182    let core = crate::thread::current::current_thread_core_arc()
183        .unwrap_or_else(|error| task_runtime::fatal_invariant(10, error_code(error)));
184    if let Some(execution) = core.execution.as_ref() {
185        execution.exit_code.store(exit_code, Ordering::Relaxed);
186        execution.finish();
187    }
188    drop(core);
189    crate::thread::current::commit_current_exit(permit)
190}
191
192pub(crate) unsafe extern "C" fn thread_entry() -> ! {
193    // SAFETY: a fresh architecture context transfers exactly one switch baton.
194    unsafe { crate::runtime::switch::finish_initial_context_switch() }
195        .unwrap_or_else(|error| task_runtime::fatal_invariant(9, error_code(error)));
196    let core = crate::thread::current::current_thread_core_arc()
197        .unwrap_or_else(|error| task_runtime::fatal_invariant(10, error_code(error)));
198    let entry = core
199        .execution
200        .as_ref()
201        .expect("thread entry requires execution state")
202        .entry
203        .lock()
204        .take()
205        .expect("thread entry runs once");
206    // The entry may exit without unwinding. Do not retain an owning reference on its stack.
207    drop(core);
208    entry();
209    exit_current(0)
210}
211
212fn cancel_new_thread(handle: ThreadHandle) {
213    let system = runtime_task_system().expect("prepared task system remains installed");
214    system.publish_thread_cancellation(&handle.core);
215    drop(handle);
216}
217
218const fn error_code(error: TaskError) -> usize {
219    match error {
220        TaskError::NotInitialized => 1,
221        TaskError::InvalidRuntimeHandle => 2,
222        TaskError::NoRunnableThread => 3,
223        TaskError::UnsafeContext => 4,
224        _ => 255,
225    }
226}