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