Skip to main content

ax_task/sched/system/task_system/delivery/
control.rs

1//! Control under the owning scheduler transaction.
2
3use super::*;
4
5impl TaskSystem {
6    /// Applies a bounded batch of owner-CPU effective-policy updates.
7    pub fn drain_owner_control(
8        &self,
9        mut cpu: Pin<&mut CpuLocal>,
10    ) -> Result<OwnerControlDrain, TaskError> {
11        let drained = self.drain_owner_control_inner(cpu.as_mut())?;
12        if drained.pending {
13            // This standalone PI safe point has no scheduler transaction whose
14            // final recheck can rearm the detached bounded remainder.
15            cpu.defer_scheduler_work();
16        }
17        Ok(drained)
18    }
19
20    pub(super) fn drain_owner_control_inner(
21        &self,
22        mut cpu: Pin<&mut CpuLocal>,
23    ) -> Result<OwnerControlDrain, TaskError> {
24        self.ensure_owner_cpu_context(&cpu)?;
25        self.ensure_owner_cpu_online(&cpu)?;
26        // Owner-control work is ordered after the architecture switch tail.
27        // Until then `on_cpu` is a lifetime pin for the outgoing stack, not a
28        // runnable-placement owner. Consuming an affinity update in this
29        // window either has to republish itself indefinitely or can lose the
30        // completion when the tail detaches a blocked task. Linux closes the
31        // same interval in `finish_task_switch()` before the rq owner handles
32        // migration work. Keep the original intrusive publication pending and
33        // make the scheduler revisit it after tail instead.
34        if cpu.as_ref().get_ref().switch_handoff().is_some()
35            && cpu.remote().owner_control_inbox().has_pending()
36        {
37            return Ok(OwnerControlDrain {
38                drained: 0,
39                pending: true,
40            });
41        }
42        let (drained, pending) = {
43            let remote = Arc::clone(cpu.remote());
44            let scratch = cpu.as_mut().drain_state_mut();
45            let limit = scratch.batch_limit();
46            let batch = remote
47                .owner_control_inbox()
48                .drain(limit, &mut scratch.owner_control_buffer);
49            (batch.drained(), batch.pending())
50        };
51        let mut detached = [InboxMessage::EMPTY; crate::runtime::config::DEFAULT_BATCH_LIMIT];
52        detached[..drained].copy_from_slice(&cpu.drain_state().owner_control_buffer[..drained]);
53        // An incoming migration remains visible to placement readers until the
54        // owner has processed the complete detached batch. Releasing before
55        // enqueue creates a false-idle window in which another waker can stack
56        // work on this CPU.
57        let completed_incoming_migration_demand = detached[..drained]
58            .iter()
59            .filter(|message| message.operation() == InboxOperation::Migration)
60            .try_fold(0_u64, |demand, message| {
61                demand.checked_add(message.placement_demand())
62            })
63            .unwrap_or_else(|| {
64                task_runtime::fatal_invariant(0x4d49_4744, cpu.owner().as_u32() as usize)
65            });
66        let _incoming_migration = IncomingMigrationBatch::new(
67            Arc::clone(cpu.remote()),
68            completed_incoming_migration_demand,
69        );
70        let mut messages = DetachedOwnerMessageBatch::new(&detached[..drained]);
71        while let Some(message) = messages.next() {
72            let operation = message.operation();
73            if operation == InboxOperation::BalanceRequest {
74                let source = message
75                    .source_cpu()
76                    .ok_or(TaskError::InvalidConfiguration)?;
77                let target = message
78                    .target_cpu()
79                    .ok_or(TaskError::InvalidConfiguration)?;
80                if source != cpu.owner() {
81                    return Err(TaskError::CpuOwnerMismatch {
82                        expected: source.as_u32(),
83                        actual: cpu.owner().as_u32(),
84                    });
85                }
86                let reservation = message
87                    .balance_reservation()
88                    .ok_or(TaskError::InvalidConfiguration)?;
89                let balance_class = message
90                    .balance_class()
91                    .ok_or(TaskError::InvalidConfiguration)?;
92                let target_remote = self
93                    .cpu_remotes
94                    .get(target.as_usize())
95                    .ok_or(TaskError::InvalidCpu(target.as_u32()))?;
96                let Some(mut claim) = target_remote.claim_idle_pull(reservation) else {
97                    continue;
98                };
99                let source_has_candidate = match balance_class {
100                    SchedulingClass::Deadline | SchedulingClass::Realtime => self
101                        .root_domain
102                        .cpu_has_overload(cpu.owner(), balance_class),
103                    SchedulingClass::Fair => cpu.load_summary().has_pushable_fair(),
104                    SchedulingClass::Stop => false,
105                };
106                if !source_has_candidate {
107                    drop(claim);
108                    continue;
109                }
110                if !claim.commit() {
111                    continue;
112                }
113                let migrated = self.transfer_owner_balance_candidate(
114                    cpu.as_mut(),
115                    target,
116                    BalanceReason::IdlePull,
117                    Some(balance_class),
118                );
119                drop(claim);
120                match migrated {
121                    Ok(BalanceTransferOutcome::Migrated(_)) => {}
122                    // Linux `sched_balance_newidle()` ends this newly-idle
123                    // pass when the selected source cannot detach a task. A
124                    // later idle entry or periodic balance supplies the next
125                    // independent attempt; the target does not kick itself.
126                    Ok(BalanceTransferOutcome::NoCandidate) => {}
127                    Ok(BalanceTransferOutcome::Retry) => {}
128                    Err(error) => {
129                        return Err(error);
130                    }
131                }
132                continue;
133            }
134            if matches!(
135                operation,
136                InboxOperation::BalanceRequest | InboxOperation::Reclaim
137            ) {
138                return Err(TaskError::InvalidConfiguration);
139            }
140            if message.payload() == 0 {
141                continue;
142            }
143            // SAFETY: publication transfers one Arc count in the payload and
144            // this detached owner message consumes that count exactly once.
145            let core = unsafe {
146                Arc::from_raw(ptr::with_exposed_provenance::<ThreadCore>(
147                    message.payload(),
148                ))
149            };
150            let _delivery = core.accept_scheduler_inbox_delivery();
151            if core.id() != message.thread_id() {
152                continue;
153            }
154            let Some(_activity) = core.try_scheduler_activity() else {
155                // Exit owns the transition gate and will clear any pending
156                // migration target before publishing the reaper retry.
157                continue;
158            };
159            if core.state() == ThreadState::Exited {
160                continue;
161            }
162            let owner = cpu.owner();
163            let source = message
164                .source_cpu()
165                .ok_or(TaskError::InvalidConfiguration)?;
166            let target = message
167                .target_cpu()
168                .ok_or(TaskError::InvalidConfiguration)?;
169            if operation == InboxOperation::DeadlineRefresh {
170                if source != owner || target != owner {
171                    return Err(TaskError::CpuOwnerMismatch {
172                        expected: source.as_u32(),
173                        actual: owner.as_u32(),
174                    });
175                }
176                let mut sched = core.sched().lock();
177                if sched.placement.queued_cpu() == Some(owner) {
178                    self.activate_owner_deadline_bandwidth(&core, &mut sched, cpu.as_mut(), owner);
179                }
180                self.refresh_owner_deadline_timers_locked(&core, &mut sched, cpu.as_mut());
181                continue;
182            }
183            if operation == InboxOperation::AffinityUpdate {
184                if source != owner {
185                    return Err(TaskError::CpuOwnerMismatch {
186                        expected: source.as_u32(),
187                        actual: owner.as_u32(),
188                    });
189                }
190                self.reconcile_owner_affinity_update(cpu.as_mut(), &core)?;
191                continue;
192            }
193            if operation == InboxOperation::Migration {
194                if target != owner {
195                    return Err(TaskError::CpuOwnerMismatch {
196                        expected: target.as_u32(),
197                        actual: owner.as_u32(),
198                    });
199                }
200                let mut sched = core.sched().lock();
201                let committed_here = sched.placement.committed_migration_target() == Some(owner)
202                    && sched.placement.queued_cpu().is_none()
203                    && sched.placement.on_cpu().is_none();
204                let delayed_migration = sched.lifecycle.state() == ThreadState::Blocked
205                    && committed_here
206                    && core
207                        .sched()
208                        .active_option(&sched)
209                        .and_then(|active| active.entity().fair())
210                        .is_some_and(|fair| fair.is_delayed_migrating());
211                if delayed_migration {
212                    let needs_affinity_move = !sched.affinity.affinity.contains(owner)
213                        || sched.placement.requested_migration().is_some();
214                    cpu.remote().cancel_idle_pull_if_uncommitted();
215                    let remote = Arc::clone(cpu.remote());
216                    let mut transaction = OwnerRqTxn::begin(self, &remote);
217                    let current_fair = transaction.current_fair_contender();
218                    transaction.update_fair_virtual_time(current_fair);
219                    let policy = core.sched().active(&sched).policy();
220                    let metadata = sched.rq_task_metadata()?;
221                    let rt_quota_exempt = sched.is_pi_boosted_rt_owner_for(policy);
222                    let active = core.sched().take_active(&mut sched);
223                    let enqueue = transaction.enqueue_delayed_fair_transfer(
224                        QueuedThread::new(
225                            core.id(),
226                            active,
227                            Arc::clone(&core),
228                            rt_quota_exempt,
229                            sched.affinity.affinity.is_migration_capable(),
230                            metadata,
231                        ),
232                        current_fair,
233                    );
234                    transaction.update_fair_virtual_time(current_fair);
235                    sched.placement.activate(owner);
236                    core.publish_effective_schedule(policy, enqueue.entity());
237                    core.set_wake_cpu_hint(owner);
238                    let affinity_completed =
239                        Self::complete_affinity_if_satisfied_locked(&core, &sched);
240                    let scheduler_deadline_refresh_required =
241                        enqueue.scheduler_deadline_refresh_required();
242                    transaction.commit();
243                    drop(sched);
244                    if affinity_completed {
245                        core.notify_affinity_waiters();
246                    }
247                    if needs_affinity_move {
248                        self.reconcile_owner_affinity_update(cpu.as_mut(), &core)?;
249                    } else if scheduler_deadline_refresh_required {
250                        remote.kick_scheduler_work();
251                    }
252                    continue;
253                }
254
255                // A direct wake may win the task lock after the source commits
256                // its carrier but before this owner consumes it. In that case
257                // wake has already activated the exact committed destination;
258                // consume this now-stale transport and finish affinity work.
259                if sched.lifecycle.state() == ThreadState::Running
260                    && !committed_here
261                    && sched.placement.committed_migration_target().is_none()
262                    && (sched.placement.queued_cpu() == Some(owner)
263                        || sched.placement.on_cpu() == Some(owner))
264                {
265                    let needs_affinity_move = !sched.affinity.affinity.contains(owner)
266                        || sched.placement.requested_migration().is_some();
267                    let affinity_completed =
268                        Self::complete_affinity_if_satisfied_locked(&core, &sched);
269                    drop(sched);
270                    if affinity_completed {
271                        core.notify_affinity_waiters();
272                    }
273                    if needs_affinity_move {
274                        self.reconcile_owner_affinity_update(cpu.as_mut(), &core)?;
275                    }
276                    continue;
277                }
278
279                if sched.lifecycle.state() != ThreadState::Running || !committed_here {
280                    return Err(TaskError::InvalidConfiguration);
281                }
282                let needs_affinity_move = !sched.affinity.affinity.contains(owner)
283                    || sched.placement.requested_migration().is_some();
284                drop(sched);
285                self.enqueue_owner_thread(
286                    cpu.as_mut(),
287                    Arc::clone(&core),
288                    EnqueueReason::Migrated,
289                )?;
290                if needs_affinity_move {
291                    self.reconcile_owner_affinity_update(cpu.as_mut(), &core)?;
292                }
293                continue;
294            }
295            return Err(TaskError::InvalidConfiguration);
296        }
297        Ok(OwnerControlDrain { drained, pending })
298    }
299
300    /// Drains one bounded batch from every inbox owned by `cpu`.
301    ///
302    /// Owner-control inboxes, rather than `need_resched`, are the source of
303    /// truth for migration, policy, and deferred owner work. A bounded
304    /// owner-work remainder is rearmed by the scheduler transaction's final
305    /// recheck. Like Linux `irq_work_single()`, the drain itself only consumes
306    /// the claimed batch.
307    pub(in crate::sched::system::task_system) fn drain_owner_work(
308        &self,
309        mut cpu: Pin<&mut CpuLocal>,
310    ) -> Result<(), TaskError> {
311        let policy_pending = cpu.remote().owner_control_inbox().has_pending();
312        let _drain = policy_pending
313            .then(|| self.drain_owner_control_inner(cpu.as_mut()))
314            .transpose()?;
315
316        Ok(())
317    }
318}