Skip to main content

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

1//! Preemption under the owning scheduler transaction.
2
3use super::*;
4
5impl TaskSystem {
6    pub(super) fn commit_requested_preemption_in_rq(
7        &self,
8        mut cpu: Pin<&mut CpuLocal>,
9        mut transaction: OwnerRqTxn<'_>,
10        state: RequestedPreemptionState,
11    ) -> RequestedPreemptionCommit {
12        let next = self.pick_owner_next_after_preemption_in_rq(
13            cpu.as_mut(),
14            &mut transaction,
15            state.previous,
16        );
17        let OwnerNext {
18            core: next_core,
19            policy: next_policy_ref,
20            urgency: next_urgency,
21        } = next;
22        let next_endpoint = transaction.current_switch_endpoint().unwrap_or_else(|| {
23            task_runtime::fatal_invariant(0x5343_1206, next_core.as_ref().id().as_u64() as usize)
24        });
25        let migrated = state.migration.is_some();
26        let handoff = Self::prepare_switch_handoff(
27            state.previous,
28            state.previous_core,
29            next_core,
30            next_policy_ref,
31            PreviousSwitchDisposition::Live,
32            state.migration,
33        );
34        let reason = if migrated {
35            SwitchReason::Migrated
36        } else {
37            SwitchReason::Preempted
38        };
39        let deadline_rq_observation =
40            transaction.scheduler_deadline_rq_observation(cpu.as_ref().get_ref());
41        self.commit_owner_switch_selection(
42            cpu.as_mut(),
43            transaction,
44            handoff,
45            !migrated && !state.dispatch.has_deferred_task_lock_work(),
46        );
47        let decision =
48            Self::owner_switch_plan(state.previous_endpoint, next_endpoint, reason, state.now_ns);
49        RequestedPreemptionCommit {
50            decision,
51            previous_urgency: state.previous_urgency,
52            next_urgency,
53            dispatch: state.dispatch,
54            deadline_rq_observation,
55        }
56    }
57
58    pub(super) fn finish_requested_preemption(
59        &self,
60        mut cpu: Pin<&mut CpuLocal>,
61        commit: RequestedPreemptionCommit,
62    ) -> SchedulerOutcome {
63        self.finish_owner_dispatch_commit(commit.dispatch);
64        self.finish_owner_selection(
65            cpu.as_mut(),
66            commit.decision.previous(),
67            commit.decision.next(),
68            commit.previous_urgency,
69            commit.next_urgency,
70            OwnerSchedulerDeadline::Reevaluate(commit.deadline_rq_observation),
71        );
72        SchedulerOutcome::Decision(commit.decision)
73    }
74
75    pub(super) fn lone_realtime_preemption_keeps_dispatch(
76        &self,
77        transaction: &mut OwnerRqTxn<'_>,
78        current: &ThreadCore,
79    ) -> bool {
80        let Some(current_policy) = transaction.current().map(CurrentDispatch::schedule_policy)
81        else {
82            return false;
83        };
84        realtime_current_remains_selected(transaction, current_policy)
85            && self
86                .prepare_owner_rq_schedule_out(transaction, current)
87                .is_some()
88    }
89
90    pub(super) fn finish_owner_no_switch(
91        &self,
92        mut cpu: Pin<&mut CpuLocal>,
93        mut transaction: OwnerRqTxn<'_>,
94        current: ThreadId,
95        request_scope: SchedulerRequestScope,
96        scheduler_deadline: OwnerSchedulerDeadline,
97    ) -> Result<SchedulerOutcome, TaskError> {
98        let runtime_overrun_work = self.sync_owner_current_dispatch_in_rq(&mut transaction);
99        let request = transaction.commit_and_finish_scheduler_request();
100
101        if let Some(core) = runtime_overrun_work {
102            self.publish_deadline_overrun_work(core);
103        }
104        let run_queue_changed = if request.owner_work_requested()
105            && self.owner_balance_work_pending(cpu.as_ref().get_ref(), current)
106        {
107            self.service_owner_balance(cpu.as_mut(), current)?
108                .run_queue_changed()
109        } else {
110            false
111        };
112        match (run_queue_changed, scheduler_deadline) {
113            (true, _) => self.program_local_timer(
114                cpu.as_mut(),
115                SchedulerDeadlineDerivationSource::ScheduleNoSwitch,
116            )?,
117            (false, OwnerSchedulerDeadline::Unchanged) => {}
118            (false, OwnerSchedulerDeadline::Reevaluate(deadline_rq_observation)) => self
119                .program_local_timer_from_rq_observation(
120                    cpu.as_mut(),
121                    deadline_rq_observation,
122                    SchedulerDeadlineDerivationSource::ScheduleNoSwitch,
123                )?,
124        }
125        Ok(
126            if cpu.scheduler_request_pending(request_scope) || cpu.has_remote_work() {
127                SchedulerOutcome::OwnerWorkPending
128            } else {
129                SchedulerOutcome::Quiescent
130            },
131        )
132    }
133
134    /// Services sticky scheduler work and switches only for a real preemption.
135    ///
136    /// `current` must be the architecture-published task identity. The owner
137    /// runqueue transaction revalidates it against `rq->curr` before use.
138    pub fn schedule_if_requested(
139        &self,
140        cpu: Pin<&mut CpuLocal>,
141        current: &ThreadHandle,
142    ) -> Result<SchedulerOutcome, TaskError> {
143        self.schedule_if_requested_owner(
144            cpu,
145            current.runtime_core_arc(),
146            OwnerRqEntry::IrqSave,
147            SchedulerRequestScope::All,
148        )
149    }
150
151    /// Services scheduler work while the runtime owns the IRQ-off baton.
152    ///
153    /// # Safety
154    ///
155    /// The scheduler frame must remain active until this function returns.
156    pub(crate) unsafe fn schedule_if_requested_in_scheduler_frame(
157        &self,
158        cpu: Pin<&mut CpuLocal>,
159        current: &CurrentThreadRef,
160        request_scope: SchedulerRequestScope,
161    ) -> Result<SchedulerOutcome, TaskError> {
162        self.schedule_if_requested_owner(
163            cpu,
164            current.runtime_core(),
165            OwnerRqEntry::SchedulerFrame,
166            request_scope,
167        )
168    }
169
170    pub(super) fn schedule_if_requested_owner(
171        &self,
172        mut cpu: Pin<&mut CpuLocal>,
173        current: &ThreadCore,
174        rq_entry: OwnerRqEntry,
175        request_scope: SchedulerRequestScope,
176    ) -> Result<SchedulerOutcome, TaskError> {
177        let validate_owner = rq_entry.requires_owner_context_validation();
178        if validate_owner {
179            self.ensure_owner_cpu_context(&cpu)?;
180        }
181        // SAFETY: the owner borrow pins the CpuLocal and its immutable remote
182        // endpoint while this scheduling transaction and switch tail are live.
183        let remote = unsafe { cpu.as_ref().get_ref().remote_for_owner() };
184        let initial_request = remote.claim_scheduler_request(request_scope);
185        self.drain_owner_work(cpu.as_mut())?;
186        if validate_owner {
187            self.ensure_owner_cpu_registration_online(&cpu)?;
188        }
189        let previous_core_hint = current;
190        // Probe the rq-owned decision first. Linux's ordinary no-switch pass
191        // never acquires p->pi_lock; task scheduler state is needed only after
192        // this transaction proves that put_prev_task() will run.
193        // SAFETY: propagated from the selected entry contract.
194        let mut transaction = unsafe { rq_entry.begin(self, remote) };
195        transaction.adopt_scheduler_request(initial_request);
196        if transaction.current().is_some() {
197            let _settled = transaction.settle_current(0);
198        }
199        // This claim is the decision boundary: requests published by current
200        // accounting participate in this pass; later sticky publications stay
201        // set for the scheduler loop's final recheck.
202        let mut request = transaction.merge_scheduler_request(request_scope);
203        if request_scope == SchedulerRequestScope::Immediate
204            && request.immediate_preempt_requested()
205        {
206            // Once an ordinary request enters `__schedule()`, Linux clears
207            // both task flags. Claim a concurrent/lower-priority lazy request
208            // as part of that same scheduling decision.
209            request = transaction.merge_scheduler_request(SchedulerRequestScope::All);
210        }
211        if transaction.current_core_ref().map(ThreadCore::state) == Some(ThreadState::Parking) {
212            // The interrupted owner still holds a generation-checked park
213            // token and remains `current` / `on_cpu`. Consume this safe-point
214            // doorbell so an IRQ-return `while need_resched` loop can return to
215            // `commit_park`. A real preemption request is kept separately and
216            // restored only if the park is cancelled.
217            cpu.defer_park_preemption(request);
218            transaction.commit_and_finish_scheduler_request();
219            return Ok(SchedulerOutcome::ParkingDeferred);
220        }
221        let switch_requested = request.preemption_requested();
222        let previous = transaction.current_thread();
223        if transaction
224            .current_core_ref()
225            .is_none_or(|current| !core::ptr::eq(current, previous_core_hint))
226        {
227            task_runtime::fatal_invariant(0x5343_1204, cpu.owner().as_u32() as usize);
228        }
229        if !switch_requested
230            || self.lone_realtime_preemption_keeps_dispatch(&mut transaction, previous_core_hint)
231        {
232            let deadline_rq_observation =
233                transaction.scheduler_deadline_rq_observation(cpu.as_ref().get_ref());
234            return self.finish_owner_no_switch(
235                cpu.as_mut(),
236                transaction,
237                previous_core_hint.id(),
238                request_scope,
239                OwnerSchedulerDeadline::Reevaluate(deadline_rq_observation),
240            );
241        }
242        if let Some(schedule_out) =
243            self.prepare_owner_rq_schedule_out(&transaction, previous_core_hint)
244        {
245            let now_ns = transaction.clock().wall().as_nanos();
246            let dispatch_commit = self.sync_owner_settled_current_dispatch_in_rq(&mut transaction);
247            let OwnerRqScheduledOut {
248                core: previous_core,
249                endpoint: previous_endpoint,
250                fifo: _,
251                urgency: previous_urgency,
252                realtime_yield_head: _,
253            } = self.schedule_out_owner_rq_owned(
254                &mut transaction,
255                schedule_out,
256                EnqueueReason::Preempted,
257            );
258            let commit = self.commit_requested_preemption_in_rq(
259                cpu.as_mut(),
260                transaction,
261                RequestedPreemptionState {
262                    previous,
263                    previous_core: Some(previous_core),
264                    previous_endpoint: Some(previous_endpoint),
265                    previous_urgency: Some(previous_urgency),
266                    dispatch: dispatch_commit,
267                    migration: None,
268                    now_ns,
269                },
270            );
271            return Ok(self.finish_requested_preemption(cpu.as_mut(), commit));
272        }
273        // Preserve the merged preemption decision while releasing rq.
274        // Publications in this gap leave their sticky bits set and are merged
275        // by the second transaction instead of being lost.
276        transaction.commit();
277
278        // A real switch follows the established p->pi_lock -> rq order. The
279        // second rq pass resamples its clock and revalidates current rather
280        // than carrying a stale snapshot across the unlocked interval.
281        // SAFETY: propagated from the selected entry contract.
282        let mut previous_sched = unsafe { rq_entry.lock_thread_sched(previous_core_hint.sched()) };
283        // SAFETY: propagated from the selected entry contract.
284        let mut transaction = unsafe { rq_entry.begin(self, remote) };
285        transaction.adopt_scheduler_request(request);
286        if transaction.current().is_some() {
287            let _settled = transaction.settle_current(0);
288        }
289        let request = transaction.merge_scheduler_request(SchedulerRequestScope::All);
290        if !request.preemption_requested() {
291            task_runtime::fatal_invariant(0x5343_120a, cpu.owner().as_u32() as usize);
292        }
293        let now_ns = transaction.clock().wall().as_nanos();
294        let previous = transaction.current_thread();
295        let previous_core = transaction.current_core();
296        let previous_endpoint = transaction.current_switch_endpoint();
297        if previous_core
298            .as_ref()
299            .is_none_or(|core| !core::ptr::eq(core.as_ref(), previous_core_hint))
300        {
301            task_runtime::fatal_invariant(0x5343_1204, cpu.owner().as_u32() as usize);
302        }
303        let dispatch_commit = self.sync_owner_settled_current_dispatch_in_rq(&mut transaction);
304        let previous_urgency = transaction.current_scheduling_urgency();
305        let mut migration = None;
306        if let Some(core) = previous_core.as_ref() {
307            let schedule_out = self.schedule_out_owner_running_in_rq(
308                cpu.as_mut(),
309                &mut transaction,
310                Arc::clone(core),
311                &mut previous_sched,
312                now_ns,
313                EnqueueReason::Preempted,
314            );
315            migration = schedule_out.migration;
316        }
317        let commit = self.commit_requested_preemption_in_rq(
318            cpu.as_mut(),
319            transaction,
320            RequestedPreemptionState {
321                previous,
322                previous_core: previous_core.map(PreviousSwitchOwnership::retained),
323                previous_endpoint,
324                previous_urgency,
325                dispatch: dispatch_commit,
326                migration,
327                now_ns,
328            },
329        );
330        drop(previous_sched);
331        Ok(self.finish_requested_preemption(cpu.as_mut(), commit))
332    }
333}