Skip to main content

ax_task/sched/system/cpu/local/
owner_deadline.rs

1//! Owner-only scheduler deadline and soft-timer facade.
2
3use super::*;
4use crate::sched::system::cpu::remote::SchedulerNonTimerDeadlines;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub(crate) struct SoftTimerExpireBatch {
8    expired: usize,
9    pending: bool,
10}
11
12pub(crate) enum KtimerServiceClaim {
13    Kernel(KernelTimerExecution),
14    Task(ExpiredTaskDeadline),
15    Reap(KernelTimerEntry),
16}
17
18pub(crate) enum HardTimerServiceClaim {
19    Kernel(KernelTimerExecution),
20    Park(Option<Arc<ThreadCore>>),
21    Scheduler(ExpiredTaskDeadline),
22}
23
24pub(crate) enum HardTimerServiceStep {
25    Claim(HardTimerServiceClaim),
26    Complete { soft: SoftTimerExpireBatch },
27}
28
29impl SoftTimerExpireBatch {
30    pub(crate) const fn expired(self) -> usize {
31        self.expired
32    }
33
34    pub(crate) const fn pending(self) -> bool {
35        self.pending
36    }
37}
38
39/// Deadline inputs derived coherently while one runqueue guard owns current,
40/// class membership, runtime accounting, and the periodic balance predicate.
41#[derive(Clone, Copy)]
42pub(crate) struct SchedulerDeadlineRqObservation {
43    runtime_deadline: SchedulerRuntimeDeadline,
44    has_periodic_fair_balance_work: bool,
45}
46
47enum SchedulerDeadlinePublicationOutcome {
48    Unchanged(SchedulerDeadlineUpdate),
49    Changed(SchedulerDeadlineUpdate),
50}
51
52impl SchedulerDeadlinePublicationOutcome {
53    const fn update(self) -> SchedulerDeadlineUpdate {
54        match self {
55            Self::Unchanged(update) | Self::Changed(update) => update,
56        }
57    }
58
59    const fn changed_update(self) -> Option<SchedulerDeadlineUpdate> {
60        match self {
61            Self::Unchanged(_) => None,
62            Self::Changed(update) => Some(update),
63        }
64    }
65}
66
67impl CpuLocal {
68    /// Returns whether every shared deadline source already has the
69    /// publication that this rq observation would derive.
70    ///
71    /// Linux does not restart hrtick or the RT period timer for a plain
72    /// FIFO-to-FIFO switch. Fair balance and the root RT period are absolute
73    /// shared deadlines, so the coherent deadline-base snapshot can prove that
74    /// they and the task/kernel timer heads are unchanged. The current class's
75    /// hrtick is rq-local and is published separately by the owner CPU.
76    pub(crate) fn can_reuse_scheduler_deadline_for_rq_observation(
77        &self,
78        rq_observation: SchedulerDeadlineRqObservation,
79    ) -> bool {
80        let fair_balance = rq_observation
81            .has_periodic_fair_balance_work
82            .then(|| self.dispatch.fair_balance_deadline())
83            .flatten();
84        let rt_period = self.rt_bandwidth.deadline_for(self.owner);
85        let non_timer = SchedulerNonTimerDeadlines {
86            deadline: [fair_balance, rt_period].into_iter().flatten().min(),
87        };
88        self.remote.deadline_publication_snapshot_matches(non_timer)
89    }
90
91    pub(crate) fn scheduler_work_due(
92        mut self: Pin<&mut Self>,
93        monotonic_now: MonotonicInstant,
94    ) -> SchedulerDeadlineRqObservation {
95        let rq_observation = self.scheduler_deadline_rq_observation();
96        self.as_mut()
97            .scheduler_work_due_from_rq_observation(monotonic_now, rq_observation)
98    }
99
100    pub(crate) fn scheduler_work_due_from_rq_observation(
101        self: Pin<&mut Self>,
102        monotonic_now: MonotonicInstant,
103        rq_observation: SchedulerDeadlineRqObservation,
104    ) -> SchedulerDeadlineRqObservation {
105        // SAFETY: the scheduler owns this pinned runqueue while refreshing RT
106        // bandwidth periods and querying its next local event.
107        let this = unsafe { self.get_unchecked_mut() };
108        if matches!(
109            rq_observation.runtime_deadline,
110            SchedulerRuntimeDeadline::Due
111        ) {
112            this.remote.request_reschedule(RescheduleKind::Immediate);
113        }
114        if rq_observation.has_periodic_fair_balance_work
115            && this.dispatch.publish_fair_balance_due(monotonic_now)
116        {
117            this.remote.request_scheduler_work();
118        }
119        rq_observation
120    }
121
122    pub(crate) fn scheduler_runtime_deadline_for_rq_observation(
123        &self,
124        rq_observation: SchedulerDeadlineRqObservation,
125    ) -> SchedulerRuntimeDeadline {
126        if self.remote.immediate_preemption_requested() {
127            SchedulerRuntimeDeadline::Disarmed
128        } else {
129            rq_observation.runtime_deadline
130        }
131    }
132
133    fn next_non_timer_deadline_from_rq_observation(
134        &self,
135        rq_observation: SchedulerDeadlineRqObservation,
136    ) -> SchedulerNonTimerDeadlines {
137        let fair_balance = rq_observation
138            .has_periodic_fair_balance_work
139            .then(|| self.dispatch.fair_balance_deadline())
140            .flatten();
141        let rt_period = self.rt_bandwidth.deadline_for(self.owner);
142        SchedulerNonTimerDeadlines {
143            deadline: [fair_balance, rt_period].into_iter().flatten().min(),
144        }
145    }
146
147    pub(crate) fn next_scheduler_deadline_update_if_changed(
148        mut self: Pin<&mut Self>,
149        source: SchedulerDeadlineDerivationSource,
150    ) -> Result<Option<SchedulerDeadlineUpdate>, TaskError> {
151        let rq_observation = self.scheduler_deadline_rq_observation();
152        self.as_mut()
153            .update_scheduler_deadline_publication_if_changed_from_rq_observation(
154                rq_observation,
155                source,
156            )
157    }
158
159    pub(crate) fn next_scheduler_deadline_update_if_changed_from_rq_observation(
160        mut self: Pin<&mut Self>,
161        rq_observation: SchedulerDeadlineRqObservation,
162        source: SchedulerDeadlineDerivationSource,
163    ) -> Result<Option<SchedulerDeadlineUpdate>, TaskError> {
164        self.as_mut()
165            .update_scheduler_deadline_publication_if_changed_from_rq_observation(
166                rq_observation,
167                source,
168            )
169    }
170
171    pub(crate) fn next_scheduler_deadline_update_from_rq_observation(
172        mut self: Pin<&mut Self>,
173        rq_observation: SchedulerDeadlineRqObservation,
174        source: SchedulerDeadlineDerivationSource,
175    ) -> Result<SchedulerDeadlineUpdate, TaskError> {
176        self.as_mut()
177            .update_scheduler_deadline_publication_from_rq_observation(rq_observation, source)
178            .map(SchedulerDeadlinePublicationOutcome::update)
179    }
180
181    fn update_scheduler_deadline_publication_from_rq_observation(
182        self: Pin<&mut Self>,
183        rq_observation: SchedulerDeadlineRqObservation,
184        source: SchedulerDeadlineDerivationSource,
185    ) -> Result<SchedulerDeadlinePublicationOutcome, TaskError> {
186        self.as_ref()
187            .get_ref()
188            .record_scheduler_deadline_derivation(source);
189        // Preserve the established rq/RT-period -> deadline-base lock order.
190        // The task/kernel timer head and publication metadata are then read
191        // and committed under one authoritative base lock.
192        let non_timer = self
193            .as_ref()
194            .get_ref()
195            .next_non_timer_deadline_from_rq_observation(rq_observation);
196        let mut task_deadlines = self.remote.lock_deadline_publication();
197        Self::update_scheduler_deadline_publication_in_base(&mut task_deadlines, non_timer)
198    }
199
200    fn update_scheduler_deadline_publication_if_changed_from_rq_observation(
201        self: Pin<&mut Self>,
202        rq_observation: SchedulerDeadlineRqObservation,
203        source: SchedulerDeadlineDerivationSource,
204    ) -> Result<Option<SchedulerDeadlineUpdate>, TaskError> {
205        self.as_ref()
206            .get_ref()
207            .record_scheduler_deadline_derivation(source);
208        let non_timer = self
209            .as_ref()
210            .get_ref()
211            .next_non_timer_deadline_from_rq_observation(rq_observation);
212        if self.remote.deadline_publication_snapshot_matches(non_timer) {
213            return Ok(None);
214        }
215        let mut task_deadlines = self.remote.lock_deadline_publication();
216        Self::update_scheduler_deadline_publication_in_base(&mut task_deadlines, non_timer)
217            .map(SchedulerDeadlinePublicationOutcome::changed_update)
218    }
219
220    fn record_scheduler_deadline_derivation(&self, source: SchedulerDeadlineDerivationSource) {
221        #[cfg(feature = "qperf-metrics")]
222        crate::diagnostics::counters::record_scheduler_deadline_derivation(source);
223        #[cfg(not(feature = "qperf-metrics"))]
224        let _ = source;
225    }
226
227    fn update_scheduler_deadline_publication_in_base(
228        task_deadlines: &mut crate::sched::system::cpu::remote::CpuDeadlineState,
229        non_timer: SchedulerNonTimerDeadlines,
230    ) -> Result<SchedulerDeadlinePublicationOutcome, TaskError> {
231        task_deadlines.non_timer = non_timer;
232        let timer = task_deadlines.timer_deadline();
233        let publication = SchedulerDeadlinePublicationState {
234            deadline: [timer, non_timer.deadline].into_iter().flatten().min(),
235        };
236        if task_deadlines.publication == Some(publication) {
237            let update =
238                SchedulerDeadlineUpdate::try_new(task_deadlines.generation, publication.deadline)
239                    .ok_or(TaskError::InvalidConfiguration)?;
240            return Ok(SchedulerDeadlinePublicationOutcome::Unchanged(update));
241        }
242        Self::commit_scheduler_deadline_publication(task_deadlines, publication)
243            .map(SchedulerDeadlinePublicationOutcome::Changed)
244    }
245
246    pub(crate) fn update_scheduler_deadline_registration_publication(
247        task_deadlines: &mut crate::sched::system::cpu::remote::CpuDeadlineState,
248        non_timer: SchedulerNonTimerDeadlines,
249    ) -> Result<SchedulerDeadlineUpdate, TaskError> {
250        // `task_deadlines` already owns the queue mutation. Reusing that guard
251        // matches Linux hrtimer enqueue/remove plus expires-next reprogramming.
252        Self::update_scheduler_deadline_publication_in_base(task_deadlines, non_timer)
253            .map(SchedulerDeadlinePublicationOutcome::update)
254    }
255
256    pub(crate) fn update_scheduler_deadline_registration_publication_if_changed(
257        task_deadlines: &mut crate::sched::system::cpu::remote::CpuDeadlineState,
258        non_timer: SchedulerNonTimerDeadlines,
259    ) -> Result<Option<SchedulerDeadlineUpdate>, TaskError> {
260        Self::update_scheduler_deadline_publication_in_base(task_deadlines, non_timer)
261            .map(SchedulerDeadlinePublicationOutcome::changed_update)
262    }
263
264    fn commit_scheduler_deadline_publication(
265        task_deadlines: &mut crate::sched::system::cpu::remote::CpuDeadlineState,
266        publication: SchedulerDeadlinePublicationState,
267    ) -> Result<SchedulerDeadlineUpdate, TaskError> {
268        task_deadlines.generation = task_deadlines
269            .generation
270            .checked_add(1)
271            .ok_or(TaskError::InvalidConfiguration)?;
272        let update =
273            SchedulerDeadlineUpdate::try_new(task_deadlines.generation, publication.deadline)
274                .ok_or(TaskError::InvalidConfiguration)?;
275        task_deadlines.publication = Some(publication);
276        Ok(update)
277    }
278
279    pub(crate) fn scheduler_deadline_rq_observation(&self) -> SchedulerDeadlineRqObservation {
280        let run_queue = self
281            .remote
282            .lock_run_queue(RunQueueGuardSource::TimerDeadlineDerivationObservation);
283        self.scheduler_deadline_rq_observation_in_run_queue(&run_queue)
284    }
285
286    pub(crate) fn scheduler_deadline_rq_observation_in_run_queue(
287        &self,
288        run_queue: &CpuRunQueueState,
289    ) -> SchedulerDeadlineRqObservation {
290        let current_thread = run_queue.current_thread();
291        let idle = run_queue.idle();
292        let runtime_deadline = run_queue.current_runtime_deadline();
293        let current_non_idle = current_thread.is_some() && current_thread != idle;
294        let has_periodic_fair_balance_work =
295            run_queue.has_fair() && run_queue.nr_running() > usize::from(current_non_idle);
296        SchedulerDeadlineRqObservation {
297            runtime_deadline,
298            has_periodic_fair_balance_work,
299        }
300    }
301
302    /// Returns one coherent remotely observable scheduling snapshot.
303    pub fn load_summary(&self) -> CpuLoadSummary {
304        self.remote.load_summary()
305    }
306
307    /// Returns the remotely observable queued runnable count.
308    pub fn queued_summary(&self) -> usize {
309        self.remote.queued_summary()
310    }
311
312    pub(crate) fn fair_balance_pending(&self) -> bool {
313        self.dispatch.fair_balance_pending()
314    }
315
316    pub(crate) fn reset_fair_balance(
317        self: Pin<&mut Self>,
318        now: MonotonicInstant,
319        minimum_interval_ns: u64,
320    ) {
321        // SAFETY: this owner-only runqueue update does not move CpuLocal.
322        let this = unsafe { self.get_unchecked_mut() };
323        let interval_ns = minimum_interval_ns.max(1);
324        this.dispatch.fair_balance_interval_ns = interval_ns;
325        this.dispatch.defer_fair_balance(now, interval_ns);
326    }
327
328    pub(crate) fn backoff_fair_balance(
329        self: Pin<&mut Self>,
330        now: MonotonicInstant,
331        minimum_interval_ns: u64,
332        maximum_interval_ns: u64,
333    ) {
334        // SAFETY: this owner-only runqueue update does not move CpuLocal.
335        let this = unsafe { self.get_unchecked_mut() };
336        let minimum_interval_ns = minimum_interval_ns.max(1);
337        let maximum_interval_ns = maximum_interval_ns.max(minimum_interval_ns);
338        let current_interval_ns = this
339            .dispatch
340            .fair_balance_interval_ns
341            .clamp(minimum_interval_ns, maximum_interval_ns);
342        let next_interval_ns = current_interval_ns
343            .saturating_mul(2)
344            .min(maximum_interval_ns);
345        this.dispatch.fair_balance_interval_ns = next_interval_ns;
346        this.dispatch.defer_fair_balance(now, next_interval_ns);
347    }
348
349    pub(crate) fn clear_fair_balance(self: Pin<&mut Self>) {
350        self.dispatch_state_mut().clear_fair_balance();
351    }
352
353    /// Selects one task-context timer under one Linux hrtimer-style base lock.
354    ///
355    /// The returned callback identity has already left the base. Its callback
356    /// must run without this guard, then a restartable kernel timer completes
357    /// through a separate base transaction, matching `__run_hrtimer()`.
358    pub(crate) fn claim_ktimer_service_step(
359        self: Pin<&mut Self>,
360        now: MonotonicInstant,
361        task_budget: usize,
362    ) -> (Option<KtimerServiceClaim>, bool) {
363        let batch_limit = self.drain.batch_limit();
364        let Some(mut deadlines) = self
365            .remote
366            .lock_active_deadline_activity(DeadlineBaseGuardSource::SoftExpiry)
367        else {
368            return (None, false);
369        };
370        if !deadlines.kernel_timers.has_expired() && deadlines.kernel_timers.has_due_soft(now) {
371            deadlines.kernel_timers.expire_due_soft(now, 1);
372        }
373        if deadlines.expired_count == 0
374            && deadlines.queue.has_immediately_actionable_soft_entry(now)
375        {
376            Self::promote_due_task_deadlines_in_base(&mut deadlines, batch_limit, now, task_budget);
377        }
378        let claim = if let Some(completed) = deadlines.kernel_timers.claim_completed() {
379            Some(KtimerServiceClaim::Reap(completed))
380        } else {
381            let has_kernel = deadlines.kernel_timers.has_expired();
382            let has_task = deadlines.expired_count != 0;
383            match deadlines.select_service_claim_class(has_kernel, has_task) {
384                Some(KtimerClaimClass::Kernel) => {
385                    let execution = deadlines
386                        .kernel_timers
387                        .claim_expired()
388                        .expect("an expired kernel timer must remain claimable");
389                    Some(KtimerServiceClaim::Kernel(execution))
390                }
391                Some(KtimerClaimClass::Task) => {
392                    let event = deadlines
393                        .claim_next_buffered_expiration()
394                        .expect("a buffered task expiration must remain claimable");
395                    Some(KtimerServiceClaim::Task(event))
396                }
397                None => None,
398            }
399        };
400        let pending = deadlines.expired_count != 0
401            || deadlines.queue.has_immediately_actionable_soft_entry(now)
402            || deadlines.kernel_timers.has_expired()
403            || deadlines.kernel_timers.has_completed()
404            || deadlines.kernel_timers.has_due_soft(now);
405        deadlines.softirq_activated = pending;
406        (claim, pending)
407    }
408
409    fn promote_due_task_deadlines_in_base(
410        task_deadlines: &mut crate::sched::system::cpu::remote::CpuDeadlineState,
411        batch_limit: usize,
412        now: MonotonicInstant,
413        budget: usize,
414    ) -> TaskDeadlineExpireBatch {
415        let expired_count = task_deadlines.expired_count;
416        let available = task_deadlines
417            .expired_buffer
418            .len()
419            .saturating_sub(expired_count);
420        let request = TaskDeadlineExpireRequest::new(now, budget.min(batch_limit).min(available));
421        let crate::sched::system::cpu::remote::CpuDeadlineState {
422            queue,
423            expired_buffer,
424            ..
425        } = &mut *task_deadlines;
426        let output = &mut expired_buffer[expired_count..];
427        let batch = queue.expire_soft(request, output);
428        task_deadlines.expired_count += batch.expired();
429        batch
430    }
431
432    /// Claims one earliest due hard timer or completes this clockevent pass.
433    ///
434    /// A claimed callback runs after this function releases the base. Once no
435    /// hard timer remains due, the same reacquired base transaction promotes
436    /// soft expirations and publishes expires-next, matching Linux's
437    /// `__hrtimer_run_queues()` plus `hrtimer_update_base()` boundary.
438    pub(crate) fn claim_due_hard_timer_step(
439        self: Pin<&mut Self>,
440        now: MonotonicInstant,
441        budget: usize,
442    ) -> Result<HardTimerServiceStep, TaskError> {
443        self.as_ref()
444            .get_ref()
445            .record_scheduler_deadline_derivation(SchedulerDeadlineDerivationSource::ClockEvent);
446        let batch_limit = self.drain.batch_limit();
447        let mut task_deadlines = self
448            .remote
449            .lock_deadline_activity(DeadlineBaseGuardSource::HardExpiry);
450        let scheduler_deadline = task_deadlines.queue.next_hard_deadline();
451        let kernel_deadline = task_deadlines.kernel_timers.next_hard_deadline();
452        let claim = match (scheduler_deadline, kernel_deadline) {
453            (Some(scheduler), Some(kernel)) if kernel < scheduler => task_deadlines
454                .kernel_timers
455                .claim_due_hard(now)
456                .map(HardTimerServiceClaim::Kernel),
457            (Some(scheduler), _) if now.reached(scheduler) => task_deadlines
458                .queue
459                .claim_due_hard(now)
460                .map(|claim| match claim {
461                    HardTaskDeadlineClaim::Park { event, thread } => {
462                        let park_generation = event
463                            .kind()
464                            .and_then(TaskDeadlineKind::park_generation)
465                            .expect("a hard park deadline retains its park generation");
466                        let completed = thread.complete_sleep_timer(event.token().generation());
467                        let ready = completed && thread.park_generation() == park_generation;
468                        HardTimerServiceClaim::Park(ready.then_some(thread))
469                    }
470                    HardTaskDeadlineClaim::Scheduler(event) => {
471                        HardTimerServiceClaim::Scheduler(event)
472                    }
473                }),
474            (_, Some(kernel)) if now.reached(kernel) => task_deadlines
475                .kernel_timers
476                .claim_due_hard(now)
477                .map(HardTimerServiceClaim::Kernel),
478            _ => None,
479        };
480        if let Some(claim) = claim {
481            return Ok(HardTimerServiceStep::Claim(claim));
482        }
483
484        let task_batch =
485            Self::promote_due_task_deadlines_in_base(&mut task_deadlines, batch_limit, now, budget);
486        let kernel_batch = task_deadlines
487            .kernel_timers
488            .expire_due_soft(now, budget.saturating_sub(task_batch.processed()));
489        if task_batch.expired() != 0
490            || task_batch.pending()
491            || kernel_batch.expired() != 0
492            || kernel_batch.pending()
493        {
494            task_deadlines.softirq_activated = true;
495        }
496        let soft = SoftTimerExpireBatch {
497            expired: task_batch.expired().saturating_add(kernel_batch.expired()),
498            pending: task_batch.pending() || kernel_batch.pending(),
499        };
500        let non_timer = task_deadlines.non_timer;
501        Self::update_scheduler_deadline_publication_in_base(&mut task_deadlines, non_timer)?;
502        drop(task_deadlines);
503        if soft.pending() || soft.expired() != 0 {
504            self.remote.publish_ktimer_work();
505        }
506        Ok(HardTimerServiceStep::Complete { soft })
507    }
508
509    pub(crate) fn complete_hard_kernel_timer_execution(
510        self: Pin<&mut Self>,
511        execution: KernelTimerExecution,
512        action: HardKernelTimerAction,
513    ) {
514        let mut deadlines = self
515            .remote
516            .lock_deadline_activity(DeadlineBaseGuardSource::HardExpiry);
517        if deadlines
518            .kernel_timers
519            .complete_hard_execution(execution, action)
520        {
521            // Callback ownership is reclaimed only by `ktimers/%u`; this bit
522            // describes deferred destruction, not the hard deadline that just
523            // left the active base.
524            deadlines.softirq_activated = true;
525            drop(deadlines);
526            self.remote.publish_ktimer_work();
527        }
528    }
529
530    /// Removes only the expiration owned by one move-only registration.
531    /// Park commit uses this to resolve its own timeout without running an
532    /// unrelated soft-timer batch inside the rq transition.
533    pub(crate) fn take_buffered_expiration(
534        self: Pin<&mut Self>,
535        registration: &TaskDeadlineRegistration,
536    ) -> Option<ExpiredTaskDeadline> {
537        self.remote
538            .lock_deadline_activity(DeadlineBaseGuardSource::SoftExpiry)
539            .take_buffered_expiration(registration)
540    }
541}