Skip to main content

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

1//! Balance under the owning scheduler transaction.
2
3use super::*;
4
5impl TaskSystem {
6    /// Requests one owner-mediated pull from the busiest remote CPU.
7    ///
8    /// The target never locks or mutates the source runqueue. Its pinned request
9    /// node is published to the source owner-control inbox and the source owner
10    /// selects and hands off one affinity-compatible thread at a safe point.
11    pub fn request_idle_pull(&self, mut cpu: Pin<&mut CpuLocal>) -> Result<bool, TaskError> {
12        self.ensure_owner_cpu_context(&cpu)?;
13        if task_runtime::in_hard_irq() {
14            return Ok(false);
15        }
16        self.ensure_owner_cpu_online(&cpu)?;
17        if !self.root_domain.has_idle_pull_source() {
18            cpu.as_mut().reset_idle_pull_scan();
19            return Ok(false);
20        }
21        if !cpu.idle_pull_eligible() || cpu.has_remote_work() {
22            cpu.as_mut().reset_idle_pull_scan();
23            return Ok(false);
24        }
25        let target_remote = Arc::clone(cpu.remote());
26        let reservation = match target_remote.begin_idle_pull() {
27            IdlePullReservation::Started(reservation) => reservation,
28            IdlePullReservation::AlreadyPending => return Ok(true),
29            IdlePullReservation::Busy => return Ok(false),
30        };
31        if !cpu.idle_pull_eligible() || cpu.has_remote_work() {
32            target_remote.cancel_idle_pull(reservation);
33            cpu.as_mut().reset_idle_pull_scan();
34            return Ok(false);
35        }
36        let target = cpu.owner();
37        let source = self
38            .root_domain
39            .find_idle_pull_source(target, cpu.idle_pull_visited());
40        let Some((source, class)) = source else {
41            target_remote.cancel_idle_pull(reservation);
42            cpu.as_mut().reset_idle_pull_scan();
43            return Ok(false);
44        };
45        cpu.as_mut().mark_idle_pull_source(source);
46        let Some(source_local) = self.cpu_remote(source) else {
47            target_remote.cancel_idle_pull(reservation);
48            cpu.request_scheduler_work();
49            return Ok(true);
50        };
51        let message = InboxMessage::balance_request(source, target, reservation, class);
52        let result = source_local.publish_owner_control(cpu.balance_request_node(), message);
53        match result {
54            PublishResult::Published => Ok(true),
55            PublishResult::AlreadyPending => {
56                target_remote.cancel_idle_pull(reservation);
57                cpu.request_scheduler_work();
58                Ok(true)
59            }
60            PublishResult::WrongKind => {
61                target_remote.cancel_idle_pull(reservation);
62                cpu.request_scheduler_work();
63                Ok(true)
64            }
65        }
66    }
67
68    /// Lets the selected Linux-style ILB coordinate one Fair pull for every
69    /// CPU still published in the root-domain idle mask.
70    ///
71    /// The coordinator only reserves target-owned request nodes and publishes
72    /// them to source owners. Source and target runqueues remain private to
73    /// their owners; a failed or stale request ends this NOHZ pass instead of
74    /// kicking the target into an immediate retry loop.
75    pub(in crate::sched::system::task_system) fn request_fair_nohz_idle_pulls(&self) -> bool {
76        let mut requested = false;
77        for (index, target_remote) in self.cpu_remotes.iter().enumerate() {
78            let target = CpuId::new(index as u32);
79            if !self.root_domain.fair_nohz_idle_target(target)
80                || !target_remote.accepts_placement()
81                || !target_remote.is_scheduler_ready()
82            {
83                continue;
84            }
85            requested |= self.request_fair_nohz_idle_pull(target, target_remote);
86        }
87        requested
88    }
89
90    pub(super) fn request_fair_nohz_idle_pull(
91        &self,
92        target: CpuId,
93        target_remote: &CpuRemote,
94    ) -> bool {
95        let reservation = match target_remote.begin_idle_pull() {
96            IdlePullReservation::Started(reservation) => reservation,
97            IdlePullReservation::AlreadyPending => return true,
98            IdlePullReservation::Busy => return false,
99        };
100        if !self.root_domain.fair_nohz_idle_target(target)
101            || !target_remote.accepts_placement()
102            || !target_remote.is_scheduler_ready()
103        {
104            target_remote.cancel_idle_pull(reservation);
105            return false;
106        }
107        let Some(source) = self
108            .root_domain
109            .find_unvisited_fair_idle_pull_source(target)
110        else {
111            target_remote.cancel_idle_pull(reservation);
112            return false;
113        };
114        let Some(source_remote) = self.cpu_remote(source) else {
115            target_remote.cancel_idle_pull(reservation);
116            return false;
117        };
118        let message =
119            InboxMessage::balance_request(source, target, reservation, SchedulingClass::Fair);
120        match source_remote.publish_owner_control(target_remote.balance_request_node(), message) {
121            PublishResult::Published => true,
122            PublishResult::AlreadyPending | PublishResult::WrongKind => {
123                target_remote.cancel_idle_pull(reservation);
124                false
125            }
126        }
127    }
128
129    /// Pushes one queued thread from an overloaded owner to the least loaded CPU.
130    ///
131    /// Selection and dequeue happen only on `cpu`; the target receives an
132    /// intrusive handoff and enqueues it in its own safe-point drain.
133    pub fn push_rt_deadline(&self, cpu: Pin<&mut CpuLocal>) -> Result<Option<ThreadId>, TaskError> {
134        self.ensure_owner_cpu_context(&cpu)?;
135        if task_runtime::in_hard_irq() {
136            return Ok(None);
137        }
138        self.ensure_owner_cpu_online(&cpu)?;
139        self.push_rt_deadline_from_root_domain(cpu, None)
140    }
141
142    /// Pushes from the coherent owner snapshot published by the immediately
143    /// preceding runqueue transaction.
144    ///
145    /// Scheduler selection publishes after installing its next dispatch, so
146    /// its common tail can reuse that snapshot just as Linux keeps balancing
147    /// decisions under one owner-rq transaction. Callers must not mutate the
148    /// local runqueue or current dispatch between publication and this call.
149    pub(in crate::sched::system::task_system) fn push_rt_deadline_from_root_domain(
150        &self,
151        mut cpu: Pin<&mut CpuLocal>,
152        class: Option<SchedulingClass>,
153    ) -> Result<Option<ThreadId>, TaskError> {
154        if !class.map_or_else(
155            || self.root_domain.cpu_has_rt_deadline_overload(cpu.owner()),
156            |class| self.root_domain.cpu_has_overload(cpu.owner(), class),
157        ) {
158            return Ok(None);
159        }
160        let Some(selection) =
161            self.select_rt_deadline_balance_transfer(cpu.as_ref().get_ref(), class)
162        else {
163            return Ok(None);
164        };
165        let target = selection.target();
166        let outcome = self.commit_owner_balance_transfer(cpu.as_mut(), selection)?;
167        if outcome == BalanceTransferOutcome::Retry
168            && let Some(target_remote) = self.cpu_remote(target)
169        {
170            // Ask the idle destination to issue a fresh owner-mediated pull.
171            // This keeps retry asynchronous instead of spinning the source
172            // scheduler tail on a transient affinity/publication race.
173            target_remote.kick_scheduler_work();
174        }
175        Ok(outcome.migrated())
176    }
177}