Skip to main content

ax_task/
api.rs

1//! Task APIs for multi-task configuration.
2
3use alloc::{
4    collections::BTreeMap,
5    string::String,
6    sync::{Arc, Weak},
7};
8use core::fmt;
9
10use ax_memory_addr::VirtAddr;
11
12#[cfg(feature = "lockdep")]
13pub use crate::lockdep::{HeldLock, HeldLockStack};
14pub(crate) use crate::run_queue::{current_run_queue, select_run_queue, select_wake_run_queue};
15use crate::sync::PreemptIrqSaveState;
16#[cfg_attr(doc, doc(cfg(feature = "task-ext")))]
17#[cfg(feature = "task-ext")]
18pub use crate::task::{AxTaskExt, TaskExt};
19pub use crate::{
20    interrupt::InterruptSnapshot,
21    task::{CurrentTask, TaskId, TaskInner, TaskState},
22    timers::{
23        ClockEventControl, HardKernelTimerAction, HardKernelTimerCallback, KernelTimerAction,
24        KernelTimerCallback, KernelTimerCancelOutcome, KernelTimerError, KernelTimerHandle,
25        MonotonicDeadline, MonotonicInstant, RestartableKernelTimerCallback, TimerCpuId,
26        arm_hard_kernel_timer, cancel_kernel_timer, disarm_hard_kernel_timer, init_timer_service,
27        register_hard_restartable_kernel_timer, register_kernel_timer,
28        register_restartable_kernel_timer, register_timer_callback,
29    },
30    wait_queue::WaitQueue,
31};
32
33/// The reference type of a task.
34pub type AxTaskRef = Arc<AxTask>;
35
36/// The weak reference type of a task.
37pub type WeakAxTaskRef = Weak<AxTask>;
38
39static TASK_REGISTRY: ax_lazyinit::LazyLock<crate::sync::SpinRwLock<BTreeMap<u64, WeakAxTaskRef>>> =
40    ax_lazyinit::LazyLock::new(|| crate::sync::SpinRwLock::new(BTreeMap::new()));
41
42/// The wrapper type for [`ax_cpumask::CpuMask`] with SMP configuration.
43pub type AxCpuMask = ax_cpumask::CpuMask<{ crate::build_info::CPU_CAPACITY }>;
44
45/// Returns the default stack size used by task creation helpers.
46pub fn default_task_stack_size() -> usize {
47    crate::build_info::DEFAULT_TASK_STACK_SIZE
48}
49
50cfg_if::cfg_if! {
51    if #[cfg(feature = "sched-rr")] {
52        const MAX_TIME_SLICE: usize = 5;
53        pub(crate) type AxTask = ax_sched::RRTask<TaskInner, MAX_TIME_SLICE>;
54        pub(crate) type Scheduler = ax_sched::RRScheduler<TaskInner, MAX_TIME_SLICE>;
55    } else if #[cfg(feature = "sched-cfs")] {
56        pub(crate) type AxTask = ax_sched::CFSTask<TaskInner>;
57        pub(crate) type Scheduler = ax_sched::CFScheduler<TaskInner>;
58    } else {
59        // If no scheduler features are set, use FIFO as the default.
60        pub(crate) type AxTask = ax_sched::FifoTask<TaskInner>;
61        pub(crate) type Scheduler = ax_sched::FifoScheduler<TaskInner>;
62    }
63}
64
65/// Gets the current task, or returns [`None`] if the current task is not
66/// initialized.
67pub fn current_may_uninit() -> Option<CurrentTask> {
68    CurrentTask::try_get()
69}
70
71/// Reports whether the given fault address hits the current task's stack guard page.
72#[cfg(feature = "stack-guard-page")]
73pub fn diagnose_current_stack_guard_page_fault(fault_addr: VirtAddr) -> bool {
74    current_may_uninit().is_some_and(|curr| curr.diagnose_stack_guard_page_fault(fault_addr))
75}
76
77/// Gets the current task.
78///
79/// # Panics
80///
81/// Panics if the current task is not initialized.
82pub fn current() -> CurrentTask {
83    CurrentTask::get()
84}
85
86/// Disables preemption for the current task when preemption is configured.
87#[doc(hidden)]
88pub fn disable_preempt() -> usize {
89    #[cfg(feature = "preempt")]
90    {
91        crate::runtime_preempt::enter()
92    }
93    #[cfg(not(feature = "preempt"))]
94    {
95        0
96    }
97}
98
99/// Enables preemption for the current task when preemption is configured.
100#[doc(hidden)]
101pub fn enable_preempt(token: usize) {
102    #[cfg(feature = "preempt")]
103    {
104        crate::runtime_preempt::exit(token);
105    }
106    #[cfg(not(feature = "preempt"))]
107    let _ = token;
108}
109
110/// Enables preemption at the final IRQ-return boundary.
111#[doc(hidden)]
112pub fn enable_preempt_from_irq_return(token: usize) {
113    #[cfg(feature = "preempt")]
114    {
115        crate::runtime_preempt::exit_from_irq_return(token);
116    }
117    #[cfg(not(feature = "preempt"))]
118    let _ = token;
119}
120
121/// Reports scheduler work to the runtime preemption safe-point adapter.
122#[doc(hidden)]
123pub fn runtime_preemption_pending() -> bool {
124    #[cfg(feature = "preempt")]
125    {
126        current_may_uninit().is_some_and(|curr| curr.preemption_pending())
127    }
128    #[cfg(not(feature = "preempt"))]
129    {
130        false
131    }
132}
133
134/// Runs the legacy scheduler action after the runtime claims its baton.
135#[doc(hidden)]
136pub fn runtime_preempt_current() {
137    #[cfg(feature = "preempt")]
138    crate::task::TaskInner::current_check_preempt_pending();
139}
140
141#[cfg(feature = "lockdep")]
142#[doc(hidden)]
143pub fn collect_current_task_held_locks(snapshot: &mut crate::sync::HeldLockSnapshot) {
144    let _irq_guard = crate::sync::IrqSaveGuard::new();
145    if let Some(curr) = current_may_uninit() {
146        curr.with_held_locks(|stack| snapshot.extend(stack));
147    }
148}
149
150#[cfg(feature = "lockdep")]
151#[doc(hidden)]
152pub fn push_current_task_held_lock(held: crate::sync::HeldLock) {
153    let _irq_guard = crate::sync::IrqSaveGuard::new();
154    if let Some(curr) = current_may_uninit() {
155        curr.with_held_locks(|stack| stack.push(held));
156    }
157}
158
159#[cfg(feature = "lockdep")]
160#[doc(hidden)]
161pub fn pop_current_task_held_lock(lock_addr: usize) {
162    let _irq_guard = crate::sync::IrqSaveGuard::new();
163    if let Some(curr) = current_may_uninit() {
164        curr.with_held_locks(|stack| stack.pop_checked(lock_addr));
165    }
166}
167
168#[cfg(feature = "lockdep")]
169pub fn with_current_lockdep_stack<R>(f: impl FnOnce(&mut HeldLockStack) -> R) -> R {
170    current().with_held_locks(f)
171}
172
173/// Initializes the task scheduler (for the primary CPU).
174pub fn init_scheduler() {
175    info!("Initialize scheduling...");
176
177    #[cfg(feature = "host-test")]
178    ax_hal::percpu::initialize_host_test_cpu();
179
180    // Initialize the run queue.
181    crate::run_queue::init();
182
183    info!("  use {} scheduler.", Scheduler::scheduler_name());
184}
185
186pub(crate) fn cpu_mask_full() -> AxCpuMask {
187    use ax_lazyinit::LazyLock;
188
189    static CPU_MASK_FULL: LazyLock<AxCpuMask> = LazyLock::new(|| {
190        let cpu_num = ax_hal::cpu_num();
191        let mut cpumask = AxCpuMask::new();
192        for cpu_id in 0..cpu_num {
193            cpumask.set(cpu_id, true);
194        }
195        cpumask
196    });
197
198    *CPU_MASK_FULL
199}
200
201/// Initializes the task scheduler for secondary CPUs.
202pub fn init_scheduler_secondary(stack_ptr: VirtAddr, stack_size: usize) {
203    crate::run_queue::init_secondary(stack_ptr, stack_size);
204}
205
206/// Handles periodic timer ticks for the task manager.
207///
208/// For example, advance scheduler states, checks timed events, etc.
209pub fn on_timer_tick() {
210    on_timer_irq(true);
211}
212
213/// Handles a hardware timer interrupt.
214pub fn on_timer_irq(scheduler_tick: bool) {
215    crate::timers::check_events(scheduler_tick);
216    if scheduler_tick {
217        // Since irq and preemption are both disabled here,
218        // we can get the current run queue without another context transition.
219        current_run_queue::<crate::sync::RawState>().scheduler_timer_tick();
220    }
221}
222
223#[doc(hidden)]
224pub fn next_timer_deadline_nanos() -> Option<u64> {
225    crate::timers::next_deadline_nanos()
226}
227
228/// Scheduler ticks CPU `cpu` has spent running a non-idle task since boot.
229///
230/// This is the load metric for an ondemand cpufreq governor: a monotonic per-CPU
231/// counter bumped once per timer tick when the CPU is not idle. Sample the delta
232/// over a window and divide by the elapsed ticks to get the busy fraction. Returns
233/// 0 for an out-of-range `cpu`. The counter only advances inside the timer tick.
234pub fn cpu_busy_ticks(cpu: usize) -> u64 {
235    crate::run_queue::BUSY_TICKS
236        .get(cpu)
237        .map_or(0, |t| t.load(core::sync::atomic::Ordering::Relaxed))
238}
239
240/// Adds the given task to the run queue, returns the task reference.
241pub fn spawn_task(task: TaskInner) -> AxTaskRef {
242    spawn_task_with(task, |_| {})
243}
244
245/// Initializes the given task before adding it to the run queue.
246///
247/// The `initialize` callback receives the stable task reference before the task
248/// becomes runnable. Use it to publish runtime-specific task metadata that the
249/// task must be able to observe on its first instruction. The callback must not
250/// wait for the new task to run because it has not been registered or queued.
251///
252/// # Panics
253///
254/// Panics if `initialize` panics.
255pub fn spawn_task_with<F>(task: TaskInner, initialize: F) -> AxTaskRef
256where
257    F: FnOnce(&AxTaskRef),
258{
259    let task_ref = task.into_arc();
260    initialize_task_before_schedule(&task_ref, initialize, |task_ref| {
261        register_task(task_ref);
262        select_run_queue::<PreemptIrqSaveState>(task_ref).add_task(task_ref.clone());
263    });
264    task_ref
265}
266
267fn initialize_task_before_schedule<T>(
268    task: &T,
269    initialize: impl FnOnce(&T),
270    schedule: impl FnOnce(&T),
271) {
272    initialize(task);
273    schedule(task);
274}
275
276/// Spawns a new task with the given parameters.
277///
278/// Returns the task reference.
279pub fn spawn_raw<F>(f: F, name: String, stack_size: usize) -> AxTaskRef
280where
281    F: FnOnce() + Send + 'static,
282{
283    spawn_task(TaskInner::new(f, name, stack_size))
284}
285
286/// Spawns a new task with the given name and the default stack size.
287///
288/// Returns the task reference.
289pub fn spawn_with_name<F>(f: F, name: String) -> AxTaskRef
290where
291    F: FnOnce() + Send + 'static,
292{
293    spawn_raw(f, name, default_task_stack_size())
294}
295
296/// Spawns a new task with the default parameters.
297///
298/// The default task name is an empty string. The default task stack size is
299/// [`default_task_stack_size`].
300///
301/// Returns the task reference.
302pub fn spawn<F>(f: F) -> AxTaskRef
303where
304    F: FnOnce() + Send + 'static,
305{
306    spawn_with_name(f, String::new())
307}
308
309/// Set the priority for current task.
310///
311/// The range of the priority is dependent on the underlying scheduler. For
312/// example, in the [CFS] scheduler, the priority is the nice value, ranging from
313/// -20 to 19.
314///
315/// Returns `true` if the priority is set successfully.
316///
317/// [CFS]: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler
318pub fn set_priority(prio: isize) -> bool {
319    current_run_queue::<PreemptIrqSaveState>().set_current_priority(prio)
320}
321
322/// Set the affinity for the current task.
323/// [`AxCpuMask`] is used to specify the CPU affinity.
324/// Returns `true` if the affinity is set successfully.
325///
326/// TODO: support set the affinity for other tasks.
327#[track_caller]
328pub fn set_current_affinity(cpumask: AxCpuMask) -> bool {
329    might_sleep();
330
331    if cpumask.is_empty() {
332        false
333    } else {
334        let curr = current().clone();
335
336        curr.set_cpumask(cpumask);
337        // After setting the affinity, we need to check if current cpu matches
338        // the affinity. If not, we need to migrate the task to the correct CPU.
339        #[cfg(feature = "smp")]
340        if !cpumask.get(ax_hal::percpu::this_cpu_id()) {
341            // Spawn a new migration task for migrating.
342            let migration_task = TaskInner::new(
343                move || crate::run_queue::migrate_entry(curr),
344                "migration-task".into(),
345                default_task_stack_size(),
346            )
347            .into_arc();
348
349            // Migrate the current task to the correct CPU using the migration task.
350            current_run_queue::<PreemptIrqSaveState>().migrate_current(migration_task);
351        }
352        true
353    }
354}
355
356/// Current task gives up the CPU time voluntarily, and switches to another
357/// ready task.
358#[track_caller]
359pub fn yield_now() {
360    might_sleep();
361
362    yield_now_unchecked();
363}
364
365/// Gives up the CPU from a kernel-internal path.
366///
367/// This bypasses the public `might_sleep()` guard and is intended only for
368/// carefully reviewed scheduler or syscall paths that must yield while running
369/// under internal kernel guards.
370#[doc(hidden)]
371pub(crate) fn yield_now_unchecked() {
372    current_run_queue::<PreemptIrqSaveState>().yield_current()
373}
374
375/// Current task is going to sleep for the given duration.
376#[track_caller]
377pub fn sleep(dur: core::time::Duration) {
378    sleep_until(ax_hal::time::monotonic_time() + dur);
379}
380
381/// Current task is going to sleep, it will be woken up at the given deadline.
382/// The deadline is measured against the monotonic clock.
383#[track_caller]
384pub fn sleep_until(deadline: ax_hal::time::TimeValue) {
385    might_sleep();
386    current_run_queue::<PreemptIrqSaveState>().sleep_until(deadline);
387}
388
389/// Exits the current task.
390#[track_caller]
391pub fn exit(exit_code: i32) -> ! {
392    might_sleep();
393
394    current_run_queue::<PreemptIrqSaveState>().exit_current(exit_code)
395}
396
397fn current_irq_context() -> bool {
398    #[cfg(not(feature = "host-test"))]
399    {
400        ax_hal::irq::in_irq_context()
401    }
402    #[cfg(feature = "host-test")]
403    {
404        false
405    }
406}
407
408fn current_cpu_id() -> usize {
409    #[cfg(not(feature = "host-test"))]
410    {
411        ax_hal::percpu::this_cpu_id()
412    }
413    #[cfg(feature = "host-test")]
414    {
415        0
416    }
417}
418
419#[derive(Clone, Copy)]
420struct AtomicContextReasons {
421    irq_disabled: bool,
422    irq_context: bool,
423    preempt_disabled: bool,
424}
425
426impl AtomicContextReasons {
427    const fn is_atomic(self) -> bool {
428        self.irq_disabled || self.irq_context || self.preempt_disabled
429    }
430}
431
432impl fmt::Display for AtomicContextReasons {
433    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434        let mut wrote_any = false;
435        f.write_str("[")?;
436        if self.irq_disabled {
437            f.write_str("irq_disabled")?;
438            wrote_any = true;
439        }
440        if self.irq_context {
441            if wrote_any {
442                f.write_str(",")?;
443            }
444            f.write_str("irq_context")?;
445            wrote_any = true;
446        }
447        if self.preempt_disabled {
448            if wrote_any {
449                f.write_str(",")?;
450            }
451            f.write_str("preempt_disabled")?;
452            wrote_any = true;
453        }
454        if !wrote_any {
455            f.write_str("none")?;
456        }
457        f.write_str("]")
458    }
459}
460
461#[derive(Clone, Copy)]
462struct AtomicContextSnapshot {
463    irq_enabled: bool,
464    irq_context: bool,
465    preempt_count: usize,
466    cpu_id: usize,
467    task_id: Option<u64>,
468    task_state: Option<TaskState>,
469}
470
471impl AtomicContextSnapshot {
472    fn capture() -> Self {
473        let current = current_may_uninit();
474        let preempt_count = {
475            #[cfg(feature = "preempt")]
476            {
477                let task_depth = current.as_ref().map_or(0, |curr| curr.preempt_count());
478                #[cfg(not(feature = "host-test"))]
479                {
480                    task_depth
481                }
482                #[cfg(feature = "host-test")]
483                {
484                    task_depth + crate::sync::host_preempt_depth()
485                }
486            }
487            #[cfg(not(feature = "preempt"))]
488            {
489                0
490            }
491        };
492
493        Self {
494            irq_enabled: ax_hal::asm::irqs_enabled(),
495            irq_context: current_irq_context(),
496            preempt_count,
497            cpu_id: current_cpu_id(),
498            task_id: current.as_ref().map(|curr| curr.id().as_u64()),
499            task_state: current.as_ref().map(|curr| curr.state()),
500        }
501    }
502
503    fn reasons(self) -> AtomicContextReasons {
504        let irq_disabled = !self.irq_enabled;
505
506        AtomicContextReasons {
507            irq_disabled,
508            irq_context: self.irq_context,
509            preempt_disabled: self.preempt_count != 0,
510        }
511    }
512
513    fn is_atomic(self) -> bool {
514        self.reasons().is_atomic()
515    }
516}
517
518/// Returns whether the current context is atomic, meaning sleeping or
519/// rescheduling is not allowed.
520///
521/// This matches the intent of Linux's `might_sleep()`: catch misuse from
522/// IRQ-disabled or preempt-disabled regions before a sleep-like action happens.
523pub fn in_atomic_context() -> bool {
524    AtomicContextSnapshot::capture().is_atomic()
525}
526
527/// Marks an operation as one that may sleep or reschedule.
528///
529/// Panics if it is executed in an atomic context.
530#[track_caller]
531pub fn might_sleep() {
532    might_sleep_at(core::panic::Location::caller());
533}
534
535/// Checks a sleep-like operation and attributes failures to `caller`.
536///
537/// Runtime capability adapters use this entry point because their generated
538/// cross-crate shim cannot preserve Rust's implicit `#[track_caller]` argument.
539#[doc(hidden)]
540pub fn might_sleep_at(caller: &'static core::panic::Location<'static>) {
541    let snapshot = AtomicContextSnapshot::capture();
542    if snapshot.is_atomic() {
543        panic_atomic_sleep(snapshot, caller);
544    }
545}
546
547#[cfg(not(feature = "lockdep"))]
548fn panic_atomic_sleep(
549    snapshot: AtomicContextSnapshot,
550    caller: &'static core::panic::Location<'static>,
551) -> ! {
552    panic!(
553        "sleeping or rescheduling is not allowed in atomic context: caller={}, reasons={}, \
554         irq_enabled={}, irq_context={}, preempt_count={}, cpu_id={}, task_id={:?}, \
555         task_state={:?}",
556        caller,
557        snapshot.reasons(),
558        snapshot.irq_enabled,
559        snapshot.irq_context,
560        snapshot.preempt_count,
561        snapshot.cpu_id,
562        snapshot.task_id,
563        snapshot.task_state
564    );
565}
566
567#[cfg(feature = "lockdep")]
568fn panic_atomic_sleep(
569    snapshot: AtomicContextSnapshot,
570    caller: &'static core::panic::Location<'static>,
571) -> ! {
572    let held_locks = crate::sync::current_task_held_lock_snapshot();
573    panic!(
574        "sleeping or rescheduling is not allowed in atomic context: caller={}, reasons={}, \
575         irq_enabled={}, irq_context={}, preempt_count={}, cpu_id={}, task_id={:?}, \
576         task_state={:?}, held_locks={}",
577        caller,
578        snapshot.reasons(),
579        snapshot.irq_enabled,
580        snapshot.irq_context,
581        snapshot.preempt_count,
582        snapshot.cpu_id,
583        snapshot.task_id,
584        snapshot.task_state,
585        held_locks
586    );
587}
588
589/// Wakes a task that may be sleeping, ensuring it can observe a newly-
590/// delivered signal.
591///
592/// `TaskInner::interrupt()` sets the task's interrupt flag and fires the
593/// interrupt waker, which unblocks the task via `AxWaker::wake_by_ref`. This
594/// covers the common case where the task is blocked in `block_on` with
595/// `interruptible` wrapping. For tasks blocked on raw `WaitQueue` objects
596/// (which do not register an interrupt waker), this function provides an
597/// escape hatch by additionally force-unblocking when the task appears to
598/// be parked on a wait queue.
599pub fn wake_task(task: &AxTaskRef) {
600    // Fire the interrupt: sets the flag and wakes the interrupt_waker.
601    // For tasks in block_on (the common case), AxWaker::wake_by_ref already
602    // unblocks the task via the registered waker callback.
603    task.interrupt();
604
605    // For tasks blocked on a raw WaitQueue, interrupt_waker.wake() is a
606    // no-op (no waker registered). Force-unblock by transitioning the task
607    // from Blocked to Ready and placing it on the run queue of its
608    // affinity CPU.
609    //
610    // SAFETY: unblock_task uses a CAS on the task state (Blocked → Ready),
611    // so if the task is concurrently being woken by its WaitQueue, the CAS
612    // fails and this is a harmless no-op. The stale entry in the WaitQueue
613    // is benign: when WaitQueue::notify_one eventually pops it, the
614    // subsequent unblock_task call will again CAS-fail (task already Ready
615    // or Running).
616    if task.state() == TaskState::Blocked {
617        let mut rq = select_run_queue::<PreemptIrqSaveState>(task);
618        rq.unblock_task(task.clone(), false);
619    }
620}
621
622/// Registers a task for lookup by its scheduler task id.
623///
624/// This keeps a weak reference only; expired entries are ignored by lookup.
625pub fn register_task(task: &AxTaskRef) {
626    TASK_REGISTRY
627        .write()
628        .insert(task.id().as_u64(), Arc::downgrade(task));
629}
630
631/// Finds a task by its scheduler task id.
632pub fn task_by_id(task_id: TaskId) -> Option<AxTaskRef> {
633    TASK_REGISTRY
634        .read()
635        .get(&task_id.as_u64())
636        .and_then(|task| task.upgrade())
637}
638
639/// Wakes a task by its scheduler task id.
640pub fn wake_task_by_id(task_id: TaskId) -> bool {
641    let Some(task) = task_by_id(task_id) else {
642        return false;
643    };
644    wake_task(&task);
645    true
646}
647
648/// The idle task routine.
649///
650/// It runs an infinite loop that keeps trying to hand over the CPU before
651/// waiting for the next interrupt.
652pub fn run_idle() -> ! {
653    loop {
654        yield_now_unchecked();
655        trace!("idle task: waiting for IRQs...");
656        #[cfg(not(feature = "host-test"))]
657        ax_hal::asm::wait_for_irqs();
658    }
659}
660
661#[cfg(test)]
662mod tests {
663    use core::cell::Cell;
664
665    #[test]
666    #[cfg(feature = "host-test")]
667    fn host_atomic_context_query_does_not_require_cpu_local_state() {
668        assert!(!super::in_atomic_context());
669    }
670
671    #[test]
672    fn task_initialization_precedes_scheduling() {
673        let initialized = Cell::new(false);
674
675        super::initialize_task_before_schedule(
676            &(),
677            |_| initialized.set(true),
678            |_| {
679                assert!(
680                    initialized.get(),
681                    "task was scheduled before initialization"
682                )
683            },
684        );
685
686        assert!(initialized.get());
687    }
688}
689
690#[cfg(test)]
691mod std_tests {
692    use super::*;
693
694    fn axtask_api_constants_hold_for_test() -> bool {
695        // default_task_stack_size should return a non-zero value
696        let stack_size = default_task_stack_size();
697        assert!(stack_size > 0);
698        assert!(stack_size.is_multiple_of(4096)); // Should be page-aligned
699
700        true
701    }
702
703    fn axtask_api_type_aliases_hold_for_test() -> bool {
704        // Test that type aliases exist and are usable
705        // AxTaskRef = Arc<AxTask>
706        // WeakAxTaskRef = Weak<AxTask>
707        let _type_check: Option<super::AxTaskRef> = None;
708        let _weak_check: Option<super::WeakAxTaskRef> = None;
709
710        true
711    }
712
713    fn axtask_api_scheduler_name_hold_for_test() -> bool {
714        // Test that Scheduler::scheduler_name() returns a non-empty string
715        let name = super::Scheduler::scheduler_name();
716        assert!(!name.is_empty());
717
718        true
719    }
720
721    fn axtask_api_task_registry_functions_exist_hold_for_test() -> bool {
722        let _lookup: fn(super::TaskId) -> Option<super::AxTaskRef> = super::task_by_id;
723        let _wake: fn(super::TaskId) -> bool = super::wake_task_by_id;
724
725        true
726    }
727
728    #[test]
729    fn axtask_api_constants_hold() {
730        assert!(axtask_api_constants_hold_for_test());
731    }
732
733    #[test]
734    fn axtask_api_type_aliases_hold() {
735        assert!(axtask_api_type_aliases_hold_for_test());
736    }
737
738    #[test]
739    fn axtask_api_scheduler_name_hold() {
740        assert!(axtask_api_scheduler_name_hold_for_test());
741    }
742
743    #[test]
744    fn axtask_api_task_registry_functions_exist_hold() {
745        assert!(axtask_api_task_registry_functions_exist_hold_for_test());
746    }
747}