Skip to main content

ax_task/thread/
spawn.rs

1//! Common thread construction, publication, completion, and join ownership.
2
3use alloc::string::String;
4
5use crate::{
6    runtime::{
7        RuntimeStatus,
8        context::runtime_task_system,
9        resource::{
10            ExecutionContextHandle, KernelContextRequest, StackHandle, StackRequest,
11            ThreadResources, TlsHandle,
12        },
13        task_runtime,
14    },
15    sched::{CpuSet, SchedulePolicy},
16    thread::{
17        TaskError, ThreadExtension, ThreadHandle, ThreadSpec,
18        execution::{PreparedThread, ThreadExecution, thread_entry},
19    },
20};
21
22/// Default usable stack size for portable kernel service threads.
23pub const DEFAULT_KERNEL_THREAD_STACK_SIZE: usize = 256 * 1024;
24
25/// Configuration shared by kernel and user execution contexts.
26#[derive(Debug)]
27pub struct ThreadBuilder {
28    name: String,
29    stack_size: usize,
30    stack_alignment: usize,
31    guard_size: usize,
32    policy: SchedulePolicy,
33    affinity: Option<CpuSet>,
34    os_extension: Option<ThreadExtension>,
35}
36
37impl ThreadBuilder {
38    /// Starts a thread configuration with portable stack requirements.
39    pub fn new(name: String) -> Self {
40        Self {
41            name,
42            stack_size: DEFAULT_KERNEL_THREAD_STACK_SIZE,
43            stack_alignment: 16,
44            guard_size: 0,
45            policy: SchedulePolicy::default(),
46            affinity: None,
47            os_extension: None,
48        }
49    }
50    /// Sets the usable stack size in bytes.
51    pub fn stack_size(mut self, size: usize) -> Self {
52        self.stack_size = size;
53        self
54    }
55    /// Sets stack alignment in bytes.
56    pub fn stack_alignment(mut self, alignment: usize) -> Self {
57        self.stack_alignment = alignment;
58        self
59    }
60    /// Sets the inaccessible guard size in bytes.
61    pub fn guard_size(mut self, size: usize) -> Self {
62        self.guard_size = size;
63        self
64    }
65    /// Sets the scheduling policy.
66    pub fn policy(mut self, policy: SchedulePolicy) -> Self {
67        self.policy = policy;
68        self
69    }
70    /// Restricts initial and subsequent placement.
71    pub fn affinity(mut self, affinity: CpuSet) -> Self {
72        self.affinity = Some(affinity);
73        self
74    }
75    /// Transfers an OS extension directly to the scheduler record.
76    pub fn extension(mut self, extension: ThreadExtension) -> Self {
77        self.os_extension = Some(extension);
78        self
79    }
80
81    /// Creates a new, non-runnable kernel thread.
82    pub fn prepare(
83        self,
84        entry: impl FnOnce() + Send + 'static,
85    ) -> Result<PreparedThread, TaskError> {
86        // SAFETY: the built-in allocator installs precisely the supplied trampoline
87        // and transfers one complete runtime-owned resource bundle.
88        unsafe {
89            self.prepare_with(entry, |request, _trampoline| {
90                allocate_thread_resources(runtime_task_system()?, request)
91            })
92        }
93    }
94
95    /// Creates and activates a kernel thread without an external publication transaction.
96    pub fn spawn(self, entry: impl FnOnce() + Send + 'static) -> Result<ThreadHandle, TaskError> {
97        self.prepare(entry)?.publish()
98    }
99
100    /// Creates a thread using an architecture-specific resource constructor.
101    ///
102    /// # Safety
103    /// The constructor must install the supplied trampoline as its initial entry,
104    /// return uniquely owned resources satisfying `ThreadResources::new`, and
105    /// release every partial allocation on failure. It must not publish the context.
106    pub unsafe fn prepare_with(
107        mut self,
108        entry: impl FnOnce() + Send + 'static,
109        resources: impl FnOnce(
110            StackRequest,
111            unsafe extern "C" fn() -> !,
112        ) -> Result<ThreadResources, TaskError>,
113    ) -> Result<PreparedThread, TaskError> {
114        validate_spec(&self)?;
115        let system = runtime_task_system()?;
116        let execution = crate::thread::allocation::try_arc(ThreadExecution::new(
117            crate::thread::allocation::try_box(entry)?,
118            core::mem::take(&mut self.name),
119        ))?;
120        let resources = resources(self.stack_request(), thread_entry)?;
121        // SAFETY: the integration constructor transfers the owning resource bundle.
122        let mut spec = unsafe { ThreadSpec::new(self.policy).with_resources(resources) };
123        spec.execution = Some(execution);
124        if let Some(extension) = self.os_extension.take() {
125            spec = spec.with_extension(extension);
126        }
127        if let Some(affinity) = self.affinity.take() {
128            spec = spec.with_affinity(affinity);
129        }
130        Ok(PreparedThread::new(system.create_thread(spec)?))
131    }
132
133    fn stack_request(&self) -> StackRequest {
134        StackRequest {
135            usable_size: self.stack_size,
136            alignment: self.stack_alignment,
137            guard_size: self.guard_size,
138        }
139    }
140}
141
142fn validate_spec(spec: &ThreadBuilder) -> Result<(), TaskError> {
143    if spec.stack_size == 0 || spec.stack_alignment == 0 || !spec.stack_alignment.is_power_of_two()
144    {
145        Err(TaskError::InvalidConfiguration)
146    } else {
147        Ok(())
148    }
149}
150
151fn allocate_thread_resources(
152    system: &crate::runtime::TaskSystem,
153    request: StackRequest,
154) -> Result<ThreadResources, TaskError> {
155    let stack_result = task_runtime::allocate_stack(request);
156    if stack_result.status != RuntimeStatus::Success {
157        return Err(runtime_error(stack_result.status));
158    }
159    if stack_result.handle == 0 {
160        return Err(TaskError::InvalidRuntimeHandle);
161    }
162    // SAFETY: successful TaskRuntime stack allocation returns one non-zero,
163    // uniquely owned handle that remains live until deallocation.
164    let stack = unsafe { StackHandle::from_raw(stack_result.handle) };
165    let tls_result = task_runtime::allocate_kernel_tls();
166    let tls = match (tls_result.status, tls_result.handle) {
167        (RuntimeStatus::Success, 0) => {
168            return Err(release_partial_thread_resources(
169                system,
170                stack,
171                TlsHandle::NONE,
172                TaskError::InvalidRuntimeHandle,
173            ));
174        }
175        (RuntimeStatus::Success, handle) => {
176            // SAFETY: successful TaskRuntime TLS allocation returns one
177            // non-zero, uniquely owned handle live until deallocation.
178            unsafe { TlsHandle::from_raw(handle) }
179        }
180        (RuntimeStatus::Unsupported, _) => TlsHandle::NONE,
181        (status, _) => {
182            return Err(release_partial_thread_resources(
183                system,
184                stack,
185                TlsHandle::NONE,
186                runtime_error(status),
187            ));
188        }
189    };
190    let context_result = task_runtime::create_kernel_context(KernelContextRequest {
191        stack,
192        entry: thread_entry,
193        tls,
194    });
195    if context_result.status != RuntimeStatus::Success {
196        return Err(release_partial_thread_resources(
197            system,
198            stack,
199            tls,
200            runtime_error(context_result.status),
201        ));
202    }
203    if context_result.handle == 0 {
204        return Err(release_partial_thread_resources(
205            system,
206            stack,
207            tls,
208            TaskError::InvalidRuntimeHandle,
209        ));
210    }
211    Ok(unsafe {
212        // SAFETY: all handles were just created by the active runtime and their
213        // unique destruction rights move into the returned bundle.
214        ThreadResources::new(
215            ExecutionContextHandle::from_raw(context_result.handle),
216            stack,
217            tls,
218            crate::runtime::resource::AddressSpaceToken::NONE,
219        )
220    })
221}
222
223fn release_partial_thread_resources(
224    system: &crate::runtime::TaskSystem,
225    stack: StackHandle,
226    tls: TlsHandle,
227    creation_error: TaskError,
228) -> TaskError {
229    let resources = unsafe {
230        // SAFETY: this construction transaction uniquely owns both successful
231        // allocations and has not created an execution context.
232        ThreadResources::new(
233            ExecutionContextHandle::NONE,
234            stack,
235            tls,
236            crate::runtime::resource::AddressSpaceToken::NONE,
237        )
238    };
239    system.release_unpublished_resources(resources);
240    creation_error
241}
242
243const fn runtime_error(status: RuntimeStatus) -> TaskError {
244    TaskError::RuntimeFailure(status as u32)
245}
246#[cfg(test)]
247mod tests {
248    use core::{
249        ptr,
250        sync::atomic::{AtomicUsize, Ordering},
251    };
252
253    use super::*;
254    use crate::thread::{SwitchReason, ThreadExtensionOps, ThreadId};
255
256    static TEST_EXTENSION_OPS: ThreadExtensionOps = ThreadExtensionOps {
257        on_switch_in: test_extension_switch_in,
258        on_switch_out: test_extension_switch_out,
259        on_exit: test_extension_hook,
260        on_deadline_overrun: test_extension_hook,
261        drop: test_extension_drop,
262    };
263
264    #[test]
265    fn dropping_unspawned_builder_releases_owned_extension() {
266        let drops = AtomicUsize::new(0);
267        let extension = unsafe {
268            // SAFETY: the builder is dropped synchronously while `drops` lives.
269            ThreadExtension::new(
270                (&drops as *const AtomicUsize).expose_provenance(),
271                &TEST_EXTENSION_OPS,
272            )
273        };
274        let builder = ThreadBuilder::new(String::from("drop-test")).extension(extension);
275
276        drop(builder);
277
278        assert_eq!(drops.load(Ordering::Acquire), 1);
279    }
280
281    #[test]
282    fn invalid_spec_releases_extension_before_runtime_lookup() {
283        let drops = AtomicUsize::new(0);
284        let extension = unsafe {
285            // SAFETY: invalid-spec validation drops the extension synchronously.
286            ThreadExtension::new(
287                (&drops as *const AtomicUsize).expose_provenance(),
288                &TEST_EXTENSION_OPS,
289            )
290        };
291        let spec = {
292            // SAFETY: this test transfers the sole callback ownership.
293            ThreadBuilder::new(String::from("invalid-test"))
294                .stack_size(0)
295                .extension(extension)
296        };
297
298        let result = validate_spec(&spec);
299        drop(spec);
300
301        assert_eq!(result.unwrap_err(), TaskError::InvalidConfiguration);
302        assert_eq!(drops.load(Ordering::Acquire), 1);
303    }
304
305    unsafe extern "Rust" fn test_extension_hook(_data: usize, _thread: ThreadId) {}
306
307    unsafe extern "Rust" fn test_extension_switch_in(
308        _data: usize,
309        _thread: ThreadId,
310        _policy: SchedulePolicy,
311        _charged_runtime_ns: u64,
312    ) {
313    }
314
315    unsafe extern "Rust" fn test_extension_switch_out(
316        _data: usize,
317        _thread: ThreadId,
318        _reason: SwitchReason,
319    ) {
320    }
321
322    unsafe extern "Rust" fn test_extension_drop(data: usize) {
323        // SAFETY: each test supplies a live AtomicUsize for the synchronous drop.
324        let drops = unsafe { &*ptr::with_exposed_provenance::<AtomicUsize>(data) };
325        drops.fetch_add(1, Ordering::AcqRel);
326    }
327}