Skip to main content

ax_task/sched/system/task_system/park_exit/
park_commit.rs

1//! Park commit under the owning scheduler transaction.
2
3use super::*;
4
5impl TaskSystem {
6    /// Rechecks a prepared park and either cancels it or commits schedule-out.
7    pub fn commit_park(
8        &self,
9        cpu: Pin<&mut CpuLocal>,
10        current: &ThreadHandle,
11        token: &mut ParkTicket,
12    ) -> Result<ParkCommit, TaskError> {
13        self.commit_park_owner(
14            cpu,
15            current.runtime_core_arc(),
16            token,
17            OwnerRqEntry::IrqSave,
18        )
19    }
20
21    /// Commits park while the runtime owns the IRQ-off scheduler baton.
22    ///
23    /// # Safety
24    ///
25    /// The scheduler frame must remain active until this function returns.
26    pub(crate) unsafe fn commit_park_in_scheduler_frame(
27        &self,
28        cpu: Pin<&mut CpuLocal>,
29        current: &Arc<ThreadCore>,
30        token: &mut ParkTicket,
31    ) -> Result<ParkCommit, TaskError> {
32        self.commit_park_owner(cpu, current, token, OwnerRqEntry::SchedulerFrame)
33    }
34
35    pub(super) fn commit_park_owner(
36        &self,
37        mut cpu: Pin<&mut CpuLocal>,
38        current: &Arc<ThreadCore>,
39        token: &mut ParkTicket,
40        rq_entry: OwnerRqEntry,
41    ) -> Result<ParkCommit, TaskError> {
42        if token.is_resolved() || current.id() != token.thread() {
43            return Err(TaskError::StaleThreadId);
44        }
45        if rq_entry.requires_owner_context_validation() {
46            self.ensure_owner_cpu_context(&cpu)?;
47        }
48        // SAFETY: the owner borrow pins the CpuLocal and its immutable remote
49        // endpoint for the complete park transaction.
50        let remote = unsafe { cpu.as_ref().get_ref().remote_for_owner() };
51        if let Some(registration) = token.deadline()
52            && registration.may_enter_soft_expiry_buffer()
53            && let Some(event) = cpu.as_mut().take_buffered_expiration(registration)
54        {
55            self.service_expired_park_deadline(event)?;
56        }
57        let initial_request = remote.claim_scheduler_request(SchedulerRequestScope::All);
58        self.drain_owner_work(cpu.as_mut())?;
59        self.ensure_owner_cpu_online(&cpu)?;
60
61        if matches!(
62            current.effective_policy_snapshot(),
63            SchedulePolicy::Fair { .. }
64                | SchedulePolicy::Fifo { .. }
65                | SchedulePolicy::RoundRobin { .. }
66        ) && !current.sched().placement().has_pending_migration()
67            && let Some(commit) = self.try_commit_park_in_rq(
68                cpu.as_mut(),
69                token,
70                remote,
71                current,
72                initial_request,
73                rq_entry,
74            )?
75        {
76            return Ok(commit);
77        }
78
79        // SAFETY: propagated from the selected entry contract.
80        let mut previous_sched = unsafe { rq_entry.lock_thread_sched(current.sched()) };
81        // SAFETY: propagated from the selected entry contract.
82        let mut transaction = unsafe { rq_entry.begin(self, remote) };
83
84        transaction.adopt_scheduler_request(initial_request);
85        let scheduler_request = transaction.merge_scheduler_request(SchedulerRequestScope::All);
86        let now_ns = transaction.clock().wall().as_nanos();
87
88        if transaction.current_thread() != Some(token.thread()) {
89            transaction.commit_and_finish_scheduler_request();
90            return Err(TaskError::StaleThreadId);
91        }
92        let Some(previous_core) = transaction.current_core() else {
93            transaction.commit_and_finish_scheduler_request();
94            return Err(TaskError::NoRunnableThread);
95        };
96        if !Arc::ptr_eq(&previous_core, current) {
97            transaction.commit_and_finish_scheduler_request();
98            return Err(TaskError::InvalidConfiguration);
99        }
100        let generation = previous_core.park_generation();
101        if generation != token.generation() {
102            transaction.commit_and_finish_scheduler_request();
103            return Err(TaskError::StaleThreadId);
104        }
105        let notified = previous_core.take_park_notification();
106        if notified {
107            previous_sched
108                .transition(&previous_core, ThreadState::Running)
109                .unwrap_or_else(|_| {
110                    task_runtime::fatal_invariant(0x504b_1101, previous_core.id().as_u64() as usize)
111                });
112            cpu.restore_claimed_park_preemption(scheduler_request);
113            transaction.commit_and_finish_scheduler_request();
114            token.mark_resolved();
115            return Ok(ParkCommit::Notified);
116        }
117        cpu.defer_park_preemption(scheduler_request);
118        let dispatch_commit = self.settle_owner_current_dispatch_in_rq(&mut transaction);
119
120        let previous_endpoint = transaction.current_switch_endpoint().unwrap_or_else(|| {
121            task_runtime::fatal_invariant(0x504b_1102, previous_core.id().as_u64() as usize)
122        });
123        let previous_urgency = transaction.current_scheduling_urgency().unwrap_or_else(|| {
124            task_runtime::fatal_invariant(0x504b_1102, previous_core.id().as_u64() as usize)
125        });
126        let resumed = {
127            let placement = previous_core.sched().placement();
128            let sched = &mut *previous_sched;
129            // Lifecycle and wake publication share one atomic word. A wake
130            // that observes Parking sets PARK_NOTIFIED in that word; this CAS
131            // either consumes it and restores Running or uniquely publishes
132            // Blocked before a later waker enters the task-lock activation
133            // path.
134            if previous_core
135                .publish_blocked_from_parking()
136                .unwrap_or_else(|_| {
137                    task_runtime::fatal_invariant(0x504b_1103, previous_core.id().as_u64() as usize)
138                })
139                == ParkPublication::Notified
140            {
141                true
142            } else {
143                if sched.lifecycle.state() != ThreadState::Blocked
144                    || placement.queued_cpu() != Some(cpu.owner())
145                    || placement.on_cpu() != Some(cpu.owner())
146                {
147                    task_runtime::fatal_invariant(
148                        0x504b_1104,
149                        previous_core.id().as_u64() as usize,
150                    );
151                }
152                // Timer replacement is the final recoverable preparation.
153                // A wake cannot cross this point while the thread lock is
154                // held; all following rq and placement changes are one owner
155                // commit and cannot return a partial block.
156
157                let force_delayed = false;
158                let timing_granularity_ns = self.config.timing_granularity_ns();
159                let delayed = !transaction.is_linked_current(previous_core.id())
160                    && transaction
161                        .delay_dequeue_unlinked_current(
162                            previous_core.id(),
163                            timing_granularity_ns,
164                            force_delayed,
165                        )
166                        .is_some();
167                if delayed {
168                    placement.delay_dequeue_current(cpu.owner());
169                } else {
170                    let active = if transaction.is_linked_current(previous_core.id()) {
171                        transaction
172                            .deactivate_task(previous_core.id())
173                            .into_active()
174                    } else {
175                        transaction.deactivate_unlinked_current(previous_core.id());
176                        transaction
177                            .take_current()
178                            .and_then(CurrentDispatch::into_active)
179                            .unwrap_or_else(|| {
180                                task_runtime::fatal_invariant(
181                                    0x504b_1105,
182                                    previous_core.id().as_u64() as usize,
183                                )
184                            })
185                    };
186                    previous_core.sched().install_active(sched, active);
187                }
188                self.mark_owner_deadline_non_contending_in_rq(
189                    &previous_core,
190                    sched,
191                    cpu.as_mut(),
192                    now_ns,
193                    &mut transaction,
194                );
195                if !delayed {
196                    let mut active = previous_core.sched().active(sched);
197                    if let Some(fair) = active.base_entity().fair() {
198                        let virtual_time = transaction.virtual_time();
199                        let rq_max_slice_ns = transaction
200                            .max_fair_service_request_ns()
201                            .unwrap_or(fair.service_request_ns())
202                            .max(fair.service_request_ns());
203                        active.base_entity_mut().capture_fair_sleep_lag(
204                            virtual_time,
205                            rq_max_slice_ns,
206                            timing_granularity_ns,
207                        );
208                    }
209                }
210                if !delayed {
211                    placement.block_current(cpu.owner());
212                }
213                false
214            }
215        };
216        if resumed {
217            transaction.commit_and_finish_scheduler_request();
218            drop(previous_sched);
219            self.finish_owner_dispatch_commit(dispatch_commit);
220            cpu.finish_park_preemption(true);
221            token.mark_resolved();
222            return Ok(ParkCommit::Notified);
223        }
224
225        cpu.finish_park_preemption(false);
226        transaction.take_current();
227        // This branch commits a real switch, so the request generated while
228        // settling the outgoing dispatch belongs to this decision. The
229        // resumed branch above deliberately leaves it for the next pass.
230        transaction.merge_scheduler_request(SchedulerRequestScope::All);
231
232        let next = self.pick_owner_next_in_rq(
233            cpu.as_mut(),
234            &mut transaction,
235            Some((&previous_core, &mut previous_sched)),
236        );
237        let OwnerNext {
238            core: next_core,
239            policy: next_policy_ref,
240            urgency: next_urgency,
241        } = next;
242        let next_endpoint = transaction.current_switch_endpoint().unwrap_or_else(|| {
243            task_runtime::fatal_invariant(0x504b_1107, next_core.as_ref().id().as_u64() as usize)
244        });
245        let handoff = Self::prepare_switch_handoff(
246            Some(token.thread()),
247            Some(PreviousSwitchOwnership::retained(previous_core)),
248            next_core,
249            next_policy_ref,
250            PreviousSwitchDisposition::Live,
251            None,
252        );
253
254        let deadline_rq_observation =
255            transaction.scheduler_deadline_rq_observation(cpu.as_ref().get_ref());
256
257        self.commit_owner_switch_selection(
258            cpu.as_mut(),
259            transaction,
260            handoff,
261            !dispatch_commit.has_deferred_task_lock_work(),
262        );
263
264        drop(previous_sched);
265        self.finish_owner_dispatch_commit(dispatch_commit);
266        self.finish_owner_selection(
267            cpu.as_mut(),
268            Some(previous_endpoint.thread()),
269            next_endpoint.thread(),
270            Some(previous_urgency),
271            next_urgency,
272            OwnerSchedulerDeadline::Reevaluate(deadline_rq_observation),
273        );
274        let decision = Self::owner_switch_plan(
275            Some(previous_endpoint),
276            next_endpoint,
277            SwitchReason::Blocked,
278            now_ns,
279        );
280
281        token.mark_resolved();
282        Ok(ParkCommit::Blocked(decision))
283    }
284}