1mod balance;
4mod cpu_lifecycle;
5mod deadline;
6mod deferred_work;
7mod delivery;
8mod dispatch;
9mod exited_work;
10mod lifecycle;
11mod membarrier;
12mod model;
13mod outcome;
14mod park_exit;
15mod pi;
16#[cfg(axtest)]
17pub use {
18 pi::PiScheduleTestProbeSnapshot, pi::begin_pi_schedule_test_probe,
19 pi::end_pi_schedule_test_probe, pi::pi_schedule_test_probe_snapshot,
20};
21mod placement;
22mod priority_index;
23mod registry;
24mod root_domain;
25mod scheduling;
26mod switch;
27mod thread_api;
28mod thread_callbacks;
29mod thread_creation;
30
31use alloc::{sync::Arc, vec::Vec};
32use core::{pin::Pin, ptr};
33
34use exited_work::ExitedThreadWork;
35pub(crate) use membarrier::{MembarrierCpuTargets, MembarrierTarget};
36use model::{
37 BalanceReason, BalanceTransferOutcome, DeferredTaskWorkClass, DetachedOwnerMessageBatch,
38 FAIR_BALANCE_BALANCED_BACKOFF_FACTOR, FAIR_BALANCE_CONSTRAINED_BACKOFF_FACTOR,
39 FairBalanceResult, FairPolicyPlacement,
40};
41pub use model::{DeferredTaskWorkBatch, OwnedThreadReapError, TaskSystem};
42pub(crate) use outcome::SwitchEndpoint;
43pub use outcome::{
44 ChargeOutcome, DeadlineActivitySnapshot, DeadlineRuntimeSnapshot, OwnerControlDrain,
45 ScheduleDecision, SchedulerOutcome, SwitchInCompletion, YieldOutcome,
46};
47pub(crate) use park_exit::CurrentExitPermit;
48use priority_index::RootDomainPriorityIndex;
49use registry::{
50 CpuRegistration, DeadlineCallbackClaim, DetachedThreadRecord, TaskSystemState, ThreadRecord,
51 ThreadSlot,
52};
53use root_domain::{DeadlineBandwidthRebuild, RootDomain, RootDomainPushClass, RootDomainState};
54use thread_callbacks::ThreadCallbackState;
55
56use super::thread_sched::{
57 PiScheduleUpdate, ThreadDeadlineInit, ThreadPlacementInit, ThreadPolicyInit, ThreadRuntimeInit,
58 ThreadSchedCell, ThreadSchedInit, ThreadSchedState,
59};
60use crate::{
61 executor::CoroutineHeader,
62 runtime::{
63 RuntimeStatus,
64 config::TaskSystemConfig,
65 cpu::{CpuLocal, CpuRemote, CpuRemoteHandle, CpuSnapshot, RuntimeCpuId},
66 delivery::{
67 inbox::{InboxKind, InboxMessage, InboxOperation, PublishResult, SchedulerInbox},
68 work::{TaskWorkConsumerGuard, TaskWorkDoorbell},
69 },
70 lock::{IrqScope, IrqTicketLock, PreemptTicketLock},
71 resource::{
72 AddressSpaceDestroyOutcome, AddressSpaceMembarrierId, AddressSpaceMembarrierState,
73 AddressSpaceReclaimArmOutcome, MembarrierRegistration, MembarrierRegistrationPhase,
74 ThreadResources,
75 },
76 switch::{ContextThreadBinding, CurrentThreadPublication, CurrentThreadRef},
77 sync::{PiMutexRaw, PiWaitToken},
78 task_runtime,
79 },
80 sched::{
81 CpuId, CpuSet, DeadlineBandwidthSnapshot, FairMode, SchedulePolicy, SchedulerTimestamp,
82 SchedulingClass, ThreadAffinityChange,
83 algorithm::{
84 ActiveSchedulingState, DeadlineAdmission, EnqueueReason, PickedThread, QueuedThread,
85 QueuedThreadSnapshot, RqTaskMetadata, SchedulingEntity,
86 },
87 system::{
88 CpuRemotePublication, SchedulerDeadlineDerivationSource, SchedulerPolicyRef,
89 cpu::{
90 CpuRunQueueState, CurrentDispatch, DeadlineBaseGuardSource, EqualRtWakeAction,
91 HardTimerServiceClaim, HardTimerServiceStep, IdlePullReservation,
92 KtimerServiceClaim, OwnerRqEntry, OwnerRqTxn, PreparedMigrationDelivery,
93 RescheduleKind, RunQueueClockSnapshot, RunQueueDomainPublication,
94 RunQueueGuardSource, SchedulerDeadlineRqObservation, SchedulerRequestScope,
95 SchedulerThreadRef, SoftTimerExpireBatch, WakePreemptionDecision,
96 },
97 },
98 },
99 thread::{
100 DEADLINE_CLASS_RANK, DeadlineEntity, DeadlineServer, OwnedThreadSchedulerExit, ParkCommit,
101 ParkPrepare, ParkTicket, PiDonation, PiWaitKey, PiWaitRegistration, REALTIME_CLASS_RANK,
102 SchedulingUrgency, SwitchReason, TaskError, ThreadCore, ThreadCoreInit, ThreadExtension,
103 ThreadExtensionBorrow, ThreadExtensionLease, ThreadExtensionView, ThreadHandle, ThreadId,
104 ThreadRuntimeSnapshot, ThreadSpec, ThreadState, ThreadWakeBatch, ThreadWakeHandle,
105 WaitWakeClaim, WaitWakeDelivery, WakeIntent, WakeResult,
106 },
107 time::{
108 MonotonicDeadline, MonotonicInstant,
109 queue::{
110 ExpiredTaskDeadline, KernelTimerExecution, TaskDeadlineArmPlan, TaskDeadlineError,
111 TaskDeadlineKind, TaskDeadlineNode, TaskDeadlineQueue, TaskDeadlineRegistration,
112 },
113 },
114};
115
116struct UnpublishedThreadGuard<'system> {
117 system: &'system TaskSystem,
118 spec: Option<ThreadSpec>,
119}
120
121fn apply_pi_schedule_update(
122 sched: &mut ThreadSchedState,
123 mut active: ActiveSchedulingState,
124 update: PiScheduleUpdate,
125 owner_now_ns: u64,
126 fair_placement: Option<FairPolicyPlacement>,
127) -> Result<ActiveSchedulingState, TaskError> {
128 if update.generation != sched.policy.dispatch_generation {
129 return Err(TaskError::InvalidPiState);
130 }
131
132 let PiScheduleUpdate {
133 policy,
134 donor,
135 deadline_donor,
136 deadline_donor_core,
137 deadline_donor_server,
138 generation: _,
139 } = update;
140 let old_donor = sched.pi.donor;
141 let old_deadline_donor = sched.pi.deadline_donor;
142 let base = sched.policy.base;
143 let old_uses_inherited = active.uses_inherited_entity();
144 let next_uses_inherited = donor.is_some() && !pi_reuses_base_entity(base, policy);
145 if old_uses_inherited && !next_uses_inherited {
146 active.use_base_entity(base);
147 }
148
149 let source_changed = old_donor != donor || old_deadline_donor != deadline_donor;
150 let donor_server = deadline_donor_server;
151 match (donor, policy) {
152 (None, base_policy) if base_policy == base => {
153 active.use_base_entity(base_policy);
154 }
155 (Some(_), SchedulePolicy::Deadline(_)) => {
156 if !next_uses_inherited {
157 return Err(TaskError::InvalidPiState);
158 }
159 if !old_uses_inherited || source_changed {
160 active.use_inherited_entity(
161 policy,
162 SchedulingEntity::Deadline(crate::thread::DeadlineEntity::from_donor_server(
163 sched.deadline.server.clone(),
164 donor_server.ok_or(TaskError::InvalidPiState)?,
165 )),
166 );
167 } else {
168 active.update_inherited_effective_policy(policy);
169 }
170 let SchedulingEntity::Deadline(deadline) = active.entity() else {
171 return Err(TaskError::InvalidPiState);
172 };
173 deadline.replenish_for_pi(owner_now_ns);
174 }
175 (Some(_), SchedulePolicy::Fifo { .. }) => {
176 if next_uses_inherited {
177 active.use_inherited_entity(policy, SchedulingEntity::Fifo);
178 } else if !matches!(active.base_entity(), SchedulingEntity::Fifo) {
179 return Err(TaskError::InvalidPiState);
180 } else {
181 active.use_base_entity_with_effective_policy(policy);
182 }
183 }
184 (Some(_), SchedulePolicy::RoundRobin { quantum_ns, .. }) => {
185 if next_uses_inherited {
186 return Err(TaskError::InvalidPiState);
187 }
188 if !matches!(active.base_entity(), SchedulingEntity::RoundRobin { .. }) {
189 return Err(TaskError::InvalidPiState);
190 }
191 if old_donor.is_none()
192 && let SchedulingEntity::RoundRobin {
193 remaining_quantum_ns,
194 } = active.base_entity_mut()
195 && *remaining_quantum_ns > quantum_ns
196 {
197 *remaining_quantum_ns = quantum_ns;
198 }
199 active.use_base_entity_with_effective_policy(policy);
200 }
201 (Some(_), SchedulePolicy::Fair { nice, mode }) => {
202 if next_uses_inherited {
203 return Err(TaskError::InvalidPiState);
204 }
205 let SchedulingEntity::Fair(fair) = *active.base_entity() else {
206 return Err(TaskError::InvalidPiState);
207 };
208 let placement = fair_placement.ok_or(TaskError::InvalidPiState)?;
209 active.replace_base_entity(SchedulingEntity::Fair(fair.reconfigure(
210 nice,
211 mode,
212 placement.source_virtual_time,
213 placement.destination_virtual_time,
214 )));
215 active.use_base_entity_with_effective_policy(policy);
216 }
217 _ => return Err(TaskError::InvalidPiState),
218 }
219
220 sched.pi.donor = donor;
221 sched.pi.deadline_donor = deadline_donor;
222 sched.pi.deadline_donor_core = deadline_donor_core;
223 Ok(active)
224}
225
226fn pi_reuses_base_entity(base: SchedulePolicy, effective: SchedulePolicy) -> bool {
227 matches!(
228 (base, effective),
229 (
230 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. },
231 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. }
232 ) | (SchedulePolicy::Fair { .. }, SchedulePolicy::Fair { .. })
233 )
234}
235
236struct OwnerNext {
237 core: SchedulerThreadRef,
238 policy: SchedulerPolicyRef,
239 urgency: SchedulingUrgency,
240}
241
242enum OwnerSchedulerDeadline {
249 Unchanged,
250 Reevaluate(SchedulerDeadlineRqObservation),
251}
252
253impl<'system> UnpublishedThreadGuard<'system> {
254 fn new(system: &'system TaskSystem, spec: ThreadSpec) -> Self {
255 Self {
256 system,
257 spec: Some(spec),
258 }
259 }
260
261 fn into_owned_parts(mut self) -> (Option<ThreadExtension>, ThreadResources) {
262 self.spec
263 .take()
264 .expect("unpublished thread transaction must still own its specification")
265 .into_owned_parts()
266 }
267
268 fn into_spec(mut self) -> ThreadSpec {
269 self.spec
270 .take()
271 .expect("unpublished thread transaction must still own its specification")
272 }
273
274 fn spec(&self) -> &ThreadSpec {
275 self.spec
276 .as_ref()
277 .expect("unpublished thread transaction must still own its specification")
278 }
279}
280
281impl Drop for UnpublishedThreadGuard<'_> {
282 fn drop(&mut self) {
283 if let Some(spec) = self.spec.take() {
284 let (extension, resources) = spec.into_owned_parts();
285 self.system
286 .release_unpublished_thread(DetachedThreadRecord::new(resources, extension));
287 }
288 }
289}
290
291impl TaskSystem {
292 pub fn new(config: TaskSystemConfig) -> Result<Self, TaskError> {
300 validate_config(config)?;
301 let task_work = Arc::new(TaskWorkDoorbell::new());
302 let cpu_remotes = (0..config.cpu_count())
303 .map(|index| CpuRemote::create(CpuId::new(index as u32), config))
304 .collect::<Vec<_>>();
305 let cpu_registrations = cpu_remotes
306 .iter()
307 .cloned()
308 .map(|remote| CpuRegistration { remote })
309 .collect();
310 let root_domain = RootDomain::new(config, cpu_remotes.clone());
311 Ok(Self {
312 config,
313 cpu_remotes,
314 state: PreemptTicketLock::new(TaskSystemState {
315 cpus: cpu_registrations,
316 slots: Vec::new(),
317 free_slots: Vec::new(),
318 pending_address_space_reclaims: Vec::new(),
319 task_work_class_cursor: DeferredTaskWorkClass::Deadline,
320 address_space_reclaim_first: true,
321 exited_work: ExitedThreadWork::new(),
322 }),
323 root_domain,
324 deferred_coroutine_reclaims: SchedulerInbox::new(InboxKind::Reclaim),
325 deferred_deadline_callbacks: SchedulerInbox::new(InboxKind::TaskWork),
326 deferred_scheduler_ticks: SchedulerInbox::new(InboxKind::TaskWork),
327 task_work,
328 })
329 }
330}
331
332fn validate_config(config: TaskSystemConfig) -> Result<(), TaskError> {
333 if config.cpu_count() == 0 || config.cpu_count() > u32::MAX as usize {
334 return Err(TaskError::InvalidCpuCount(config.cpu_count()));
335 }
336 if config.deadline_cap_percent() == 0
337 || config.deadline_cap_percent() > 100
338 || config.rt_period_ns() == 0
339 || config.rt_runtime_ns() > config.rt_period_ns()
340 || config.balance_interval_ns() == 0
341 || config.thread_capacity() == 0
342 || config.thread_capacity() > u32::MAX as usize
343 || config.batch_limit() == 0
344 || config.batch_limit() > crate::runtime::config::DEFAULT_BATCH_LIMIT
345 || config.pi_chain_limit() == 0
346 {
347 return Err(TaskError::InvalidConfiguration);
348 }
349 Ok(())
350}
351
352fn deadline_zero_lag(deadline: &DeadlineEntity) -> SchedulerTimestamp {
353 let policy = deadline.policy();
354 let lag_ns = deadline.remaining_runtime_ns() as u128 * policy.period_ns() as u128
355 / policy.runtime_ns() as u128;
356 let lag_ns = u64::try_from(lag_ns)
357 .expect("Deadline zero-lag interval cannot exceed one scheduler period");
358 SchedulerTimestamp::from_nanos(
359 deadline
360 .absolute_deadline_ns()
361 .expect("an active Deadline entity must own a zero-lag anchor"),
362 )
363 .retreat(lag_ns)
364}
365
366fn ensure_runtime_success(status: RuntimeStatus) -> Result<(), TaskError> {
367 if status == RuntimeStatus::Success {
368 Ok(())
369 } else {
370 Err(TaskError::RuntimeFailure(status as u32))
371 }
372}
373
374fn validate_affinity(affinity: &CpuSet, cpu_count: usize) -> Result<(), TaskError> {
375 if affinity.topology_len() == cpu_count {
376 Ok(())
377 } else {
378 Err(TaskError::InvalidConfiguration)
379 }
380}
381
382const MAX_THREAD_GENERATION: u32 = i32::MAX as u32;
386
387const fn next_generation(generation: u32) -> u32 {
388 if generation < MAX_THREAD_GENERATION {
389 generation + 1
390 } else {
391 generation
392 }
393}
394
395fn advance_thread_slot_generation(slot: &mut ThreadSlot) -> bool {
396 assert_eq!(
397 slot.pending_deadline_reservation, 0,
398 "a reusable thread slot must not retain Deadline admission"
399 );
400 let next = next_generation(slot.generation);
401 if next == slot.generation {
402 false
406 } else {
407 slot.generation = next;
408 true
409 }
410}