Skip to main content

ax_task/sched/system/task_system/scheduling/
yield_entry.rs

1//! Yield entry under the owning scheduler transaction.
2
3use super::*;
4
5impl TaskSystem {
6    /// Moves the current thread to its class tail and selects another thread.
7    ///
8    /// `current` must be the architecture-published task identity. The owner
9    /// runqueue transaction revalidates it against `rq->curr` before use.
10    pub fn yield_current(
11        &self,
12        cpu: Pin<&mut CpuLocal>,
13        current: &ThreadHandle,
14    ) -> Result<YieldOutcome, TaskError> {
15        self.yield_current_owner(
16            cpu,
17            Some(current.runtime_core_arc().as_ref()),
18            OwnerRqEntry::IrqSave,
19        )
20    }
21
22    /// Yields while the runtime owns the IRQ-off scheduler baton.
23    ///
24    /// # Safety
25    ///
26    /// The scheduler frame must remain active until this function returns.
27    pub(crate) unsafe fn yield_current_in_scheduler_frame(
28        &self,
29        cpu: Pin<&mut CpuLocal>,
30    ) -> Result<YieldOutcome, TaskError> {
31        self.yield_current_owner(cpu, None, OwnerRqEntry::SchedulerFrame)
32    }
33
34    pub(super) fn yield_current_owner(
35        &self,
36        mut cpu: Pin<&mut CpuLocal>,
37        expected_current: Option<&ThreadCore>,
38        rq_entry: OwnerRqEntry,
39    ) -> Result<YieldOutcome, TaskError> {
40        #[cfg(feature = "qperf-metrics")]
41        let owner_entry_started_ns = task_runtime::monotonic_now().as_nanos();
42        let validate_owner = rq_entry.requires_owner_context_validation();
43        if validate_owner {
44            self.ensure_owner_cpu_context(&cpu)?;
45        }
46        // SAFETY: the owner borrow pins the CpuLocal and its immutable remote
47        // endpoint while this scheduling transaction and switch tail are live.
48        let remote = unsafe { cpu.as_ref().get_ref().remote_for_owner() };
49        self.drain_owner_work(cpu.as_mut())?;
50        #[cfg(feature = "qperf-metrics")]
51        let owner_drain_finished_ns = task_runtime::monotonic_now().as_nanos();
52        if validate_owner {
53            self.ensure_owner_cpu_registration_online(&cpu)?;
54        }
55        // Probe rq ownership before taking the current task lock. Linux's
56        // ordinary sched_yield path holds only rq->lock; task state is needed
57        // only for migration, Deadline, or other task-control work.
58        // SAFETY: propagated from the selected entry contract.
59        #[cfg(feature = "qperf-metrics")]
60        let rq_begin_started_ns = task_runtime::monotonic_now().as_nanos();
61        let mut transaction = unsafe { rq_entry.begin(self, remote) };
62        #[cfg(feature = "qperf-metrics")]
63        let rq_begin_finished_ns = task_runtime::monotonic_now().as_nanos();
64        let (previous, current_policy, deadline_task_control) = {
65            let current = transaction.current().unwrap_or_else(|| {
66                task_runtime::fatal_invariant(0x5343_1207, cpu.owner().as_u32() as usize)
67            });
68            let previous_core = current.runtime_core();
69            if expected_current.is_some_and(|expected| !core::ptr::eq(expected, previous_core)) {
70                task_runtime::fatal_invariant(0x5343_1207, cpu.owner().as_u32() as usize);
71            }
72            (
73                previous_core.id(),
74                current.schedule_policy(),
75                current.metadata().deadline_bandwidth_scaled != 0,
76            )
77        };
78        // Linux does not inspect p->migration_pending on every sched_yield().
79        // A running task migration first publishes an ordinary reschedule
80        // request; only that exceptional decision needs to consult task-local
81        // placement before deciding whether rq ownership alone is sufficient.
82        // A request racing after this claim remains sticky for the frame's
83        // final scheduler recheck.
84        let request = transaction.merge_scheduler_request(SchedulerRequestScope::All);
85        let migration_task_control = request.preemption_requested()
86            && transaction
87                .current_core_ref()
88                .is_some_and(|core| core.sched().placement().requested_migration().is_some());
89        let requires_task_control = deadline_task_control
90            || matches!(current_policy, SchedulePolicy::Deadline(_))
91            || migration_task_control;
92        let kept_class = if requires_task_control {
93            None
94        } else {
95            owner_yield_kept_class(&mut transaction, current_policy)
96        };
97        if let Some(kept_class) = kept_class {
98            // For a lone Fair task, Linux's yield hook returns early but
99            // `pick_task_fair()` still calls `update_curr()`. Settle the same
100            // running interval before retaining the dispatch; otherwise a
101            // yield loop keeps stale vruntime and can starve later wakeups.
102            // A single-node RT queue is merely rotated onto itself and the
103            // `next == prev` switch tail performs no RT accounting.
104            if kept_class == SchedulerClass::Fair {
105                let _settled = transaction.settle_current(0);
106            }
107            #[cfg(feature = "qperf-metrics")]
108            {
109                let rq_preflight_finished_ns = task_runtime::monotonic_now().as_nanos();
110                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
111                    10,
112                    owner_entry_started_ns,
113                    owner_drain_finished_ns,
114                );
115                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
116                    11,
117                    rq_begin_started_ns,
118                    rq_begin_finished_ns,
119                );
120                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
121                    12,
122                    rq_begin_finished_ns,
123                    rq_preflight_finished_ns,
124                );
125            }
126            let _ = self.finish_owner_no_switch(
127                cpu.as_mut(),
128                transaction,
129                previous,
130                SchedulerRequestScope::All,
131                OwnerSchedulerDeadline::Unchanged,
132            )?;
133            return Ok(YieldOutcome::Unchanged);
134        }
135        let schedule_out = {
136            let previous_core = transaction.current_core_ref().unwrap_or_else(|| {
137                task_runtime::fatal_invariant(0x5343_1207, cpu.owner().as_u32() as usize)
138            });
139            self.prepare_owner_rq_schedule_out(&transaction, previous_core)
140        };
141        if let Some(schedule_out) = schedule_out {
142            #[cfg(feature = "qperf-metrics")]
143            {
144                let rq_preflight_finished_ns = task_runtime::monotonic_now().as_nanos();
145                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
146                    10,
147                    owner_entry_started_ns,
148                    owner_drain_finished_ns,
149                );
150                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
151                    11,
152                    rq_begin_started_ns,
153                    rq_begin_finished_ns,
154                );
155                crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
156                    12,
157                    rq_begin_finished_ns,
158                    rq_preflight_finished_ns,
159                );
160            }
161            return Ok(if schedule_out.is_linked_realtime() {
162                self.yield_current_rq_owned::<true>(cpu.as_mut(), transaction, schedule_out)
163            } else {
164                self.yield_current_rq_owned::<false>(cpu.as_mut(), transaction, schedule_out)
165            });
166        }
167        // Preserve requests merged by the rq-owned probe while restoring the
168        // full p->pi_lock -> rq order for exceptional task-control work.
169        let previous_core = transaction.current_core().unwrap_or_else(|| {
170            task_runtime::fatal_invariant(0x5343_1207, cpu.owner().as_u32() as usize)
171        });
172        let request = transaction.merge_scheduler_request(SchedulerRequestScope::All);
173        transaction.commit();
174
175        self.yield_current_task_control(cpu, previous_core.as_ref(), rq_entry, remote, request)
176    }
177}