Skip to main content

ax_task/sched/system/task_system/
deferred_work.rs

1//! Deferred task-context work and resource reclamation.
2
3use super::*;
4use crate::runtime::service::{SchedulerTickMode, SchedulerTickWorkDisposition};
5
6#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
7struct SchedulerTickDispatch {
8    events: usize,
9    callbacks: usize,
10    retry_deferred: bool,
11}
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14enum ResourceReclaim {
15    None,
16    Coroutine,
17    AddressSpace,
18}
19
20impl TaskSystem {
21    pub(crate) fn publish_current_scheduler_tick_work(
22        &self,
23        cpu: &CpuLocal,
24        expected: ThreadId,
25        observed_ns: u64,
26        mode: SchedulerTickMode,
27        tick_ns: u64,
28    ) -> Result<(), TaskError> {
29        let Some(core) = cpu.current_core() else {
30            return Err(TaskError::NoRunnableThread);
31        };
32        if core.id() != expected {
33            return Err(TaskError::StaleThreadId);
34        }
35        core.sample_scheduler_tick_cpu_time(mode, tick_ns);
36        self.publish_scheduler_tick_work(&core, observed_ns);
37        Ok(())
38    }
39
40    fn publish_scheduler_tick_work(&self, core: &Arc<ThreadCore>, observed_ns: u64) {
41        if !core.begin_scheduler_tick_work(observed_ns) {
42            return;
43        }
44        self.publish_claimed_scheduler_tick_work(core);
45    }
46
47    fn publish_claimed_scheduler_tick_work(&self, core: &Arc<ThreadCore>) {
48        if !core.reserve_scheduler_inbox_delivery() {
49            core.cancel_scheduler_tick_work();
50            return;
51        }
52
53        let core_ptr = Arc::as_ptr(core);
54        // SAFETY: the retained strong count is transferred to the task-work
55        // inbox and released by its sole consumer after callback completion.
56        unsafe { Arc::increment_strong_count(core_ptr) };
57        // SAFETY: Arc allocations are pinned for their lifetime, and the
58        // delivery reservation keeps this core and its extension alive.
59        let node = unsafe { Pin::new_unchecked((&*core_ptr).scheduler_tick_work_node()) };
60        let result = self.deferred_scheduler_ticks.publish(
61            node,
62            InboxMessage::scheduler_tick(core.id(), core_ptr.expose_provenance()),
63        );
64        if result == PublishResult::Published {
65            self.task_work.publish();
66            return;
67        }
68
69        // SAFETY: a rejected publication did not consume the transferred Arc.
70        unsafe { Arc::decrement_strong_count(core_ptr) };
71        core.cancel_scheduler_inbox_delivery();
72        core.cancel_scheduler_tick_work();
73        task_runtime::fatal_invariant(0x5457_0001, result as usize);
74    }
75
76    /// Publishes one zero-reference coroutine header from hard IRQ context.
77    pub(crate) fn publish_deferred_coroutine_reclaim(
78        &self,
79        header: Pin<&'static CoroutineHeader>,
80    ) -> PublishResult {
81        let data = header.address();
82        let _publication = IrqScope::enter();
83        let result = self.deferred_coroutine_reclaims.publish(
84            header.reclaim_node(),
85            InboxMessage::reclaim(ThreadId::from_parts(0, 0), 0, data),
86        );
87        if result == PublishResult::Published {
88            self.task_work.publish();
89        }
90        result
91    }
92
93    pub(crate) fn task_work_doorbell(&self) -> Arc<TaskWorkDoorbell> {
94        Arc::clone(&self.task_work)
95    }
96
97    pub(crate) fn begin_task_work_worker_install(&self) -> Result<(), TaskError> {
98        self.task_work.begin_worker_install()
99    }
100
101    pub(crate) fn finish_task_work_worker_install(&self) {
102        self.task_work.finish_worker_install();
103    }
104
105    pub(crate) fn cancel_task_work_worker_install(&self) {
106        self.task_work.cancel_worker_install();
107    }
108
109    /// Reports whether a sticky task-work publication awaits the service thread.
110    pub fn deferred_task_work_pending(&self) -> bool {
111        self.task_work.is_pending()
112    }
113
114    pub(crate) fn publish_resource_release_ready(&self) {
115        self.task_work.publish();
116    }
117
118    /// Runs one bounded task-context pass as the single task-work consumer.
119    ///
120    /// Unrelated work classes are interleaved through a persistent round-robin
121    /// cursor. Per-thread claim predicates still enforce Deadline callback,
122    /// exit callback, and record-reaping order. A concurrent or reentrant
123    /// consumer receives [`TaskError::ThreadBusy`] without consuming work.
124    pub fn service_deferred_task_work(
125        &self,
126        limit: usize,
127    ) -> Result<DeferredTaskWorkBatch, TaskError> {
128        crate::runtime::delivery::work::validate_task_work_context()?;
129        let limit = limit.min(crate::runtime::config::DEFAULT_BATCH_LIMIT);
130        if limit == 0 {
131            return Ok(DeferredTaskWorkBatch::default());
132        }
133        let _consumer: TaskWorkConsumerGuard<'_> = self.task_work.try_claim_consumer()?;
134        let mut next_class = self.state.lock().task_work_class_cursor;
135        let outcome = (|| {
136            let mut batch = DeferredTaskWorkBatch::default();
137            let mut classes_without_progress = 0;
138            let mut scheduler_tick_retry_deferred = false;
139            while batch.processed() < limit
140                && classes_without_progress < DeferredTaskWorkClass::COUNT
141            {
142                let class = next_class;
143                next_class = class.next();
144                let processed = match class {
145                    DeferredTaskWorkClass::Cancellation => {
146                        let events = self.process_thread_cancellation()?;
147                        batch.cancellation_events += events;
148                        events
149                    }
150                    DeferredTaskWorkClass::Deadline => {
151                        let (events, callbacks) = self.dispatch_deadline_overruns_inner(1)?;
152                        batch.deadline_events += events;
153                        batch.deadline_callbacks += callbacks;
154                        events
155                    }
156                    DeferredTaskWorkClass::SchedulerTick if scheduler_tick_retry_deferred => 0,
157                    DeferredTaskWorkClass::SchedulerTick => {
158                        let dispatch = self.dispatch_scheduler_tick_work_inner(1)?;
159                        batch.scheduler_tick_events += dispatch.events;
160                        batch.scheduler_tick_callbacks += dispatch.callbacks;
161                        scheduler_tick_retry_deferred |= dispatch.retry_deferred;
162                        dispatch.events
163                    }
164                    DeferredTaskWorkClass::Exit => {
165                        let callbacks = self.dispatch_exit_callbacks_inner(1)?;
166                        batch.exit_callbacks += callbacks;
167                        callbacks
168                    }
169                    DeferredTaskWorkClass::Reap => {
170                        if self.reclaim_exited_execution()? {
171                            batch.execution_reclaims += 1;
172                            1
173                        } else {
174                            let reaped = self.reap_unreferenced_exited_inner(1)?;
175                            batch.reaped_threads += reaped;
176                            reaped
177                        }
178                    }
179                    DeferredTaskWorkClass::Reclaim => match self.reclaim_one_resource()? {
180                        ResourceReclaim::None => 0,
181                        ResourceReclaim::Coroutine => {
182                            batch.coroutine_reclaims += 1;
183                            1
184                        }
185                        ResourceReclaim::AddressSpace => {
186                            batch.address_space_reclaims += 1;
187                            1
188                        }
189                    },
190                };
191                if processed == 0 {
192                    classes_without_progress += 1;
193                } else {
194                    classes_without_progress = 0;
195                }
196            }
197            debug_assert!(batch.processed() <= limit);
198            #[cfg(feature = "qperf-metrics")]
199            crate::diagnostics::counters::record_task_work_classes(
200                batch.deadline_events,
201                batch.scheduler_tick_events,
202                batch.exit_callbacks,
203                batch.reaped_threads,
204                batch.coroutine_reclaims,
205                batch.address_space_reclaims,
206            );
207            Ok(batch)
208        })();
209        self.state.lock().task_work_class_cursor = next_class;
210        outcome
211    }
212
213    fn dispatch_scheduler_tick_work_inner(
214        &self,
215        limit: usize,
216    ) -> Result<SchedulerTickDispatch, TaskError> {
217        let mut messages = [InboxMessage::EMPTY; crate::runtime::config::DEFAULT_BATCH_LIMIT];
218        let batch = self.deferred_scheduler_ticks.drain(
219            limit.min(crate::runtime::config::DEFAULT_BATCH_LIMIT),
220            &mut messages,
221        );
222        let mut callbacks = 0;
223        let mut retry_deferred = false;
224        for message in messages.iter().take(batch.drained()) {
225            if message.operation() != InboxOperation::SchedulerTick || message.payload() == 0 {
226                task_runtime::fatal_invariant(0x5457_0002, message.payload());
227            }
228            let core = unsafe {
229                // SAFETY: publication transferred exactly one Arc strong count
230                // whose pointer is carried by this detached message.
231                Arc::from_raw(ptr::with_exposed_provenance::<ThreadCore>(
232                    message.payload(),
233                ))
234            };
235            let _delivery = core.accept_scheduler_inbox_delivery();
236            if core.id() != message.thread_id() {
237                core.cancel_scheduler_tick_work();
238                continue;
239            }
240            let claim = core.take_scheduler_tick_work();
241            if let Some(claim) = claim
242                && let Some(extension) = core.extension_view()
243            {
244                // SAFETY: the inbox delivery reservation prevents extension
245                // reclamation even if the carrier thread exits concurrently.
246                // The gate generation decides whether the process/subsystem
247                // work remains relevant; carrier-thread state does not.
248                let disposition = unsafe { claim.invoke(extension.data(), core.id()) };
249                callbacks += 1;
250                if disposition == SchedulerTickWorkDisposition::Retry {
251                    retry_deferred = true;
252                    if core.retry_scheduler_tick_work(&claim) {
253                        self.publish_claimed_scheduler_tick_work(&core);
254                    }
255                }
256            }
257        }
258        if batch.pending() {
259            self.task_work.publish();
260        }
261        Ok(SchedulerTickDispatch {
262            events: batch.drained(),
263            callbacks,
264            retry_deferred,
265        })
266    }
267
268    fn reclaim_one_resource(&self) -> Result<ResourceReclaim, TaskError> {
269        let address_space_reclaim_first = {
270            let mut state = self.state.lock();
271            let current = state.address_space_reclaim_first;
272            state.address_space_reclaim_first = !current;
273            current
274        };
275        if address_space_reclaim_first {
276            if self.reclaim_pending_address_space() {
277                return Ok(ResourceReclaim::AddressSpace);
278            }
279            self.drain_deferred_coroutine_reclaims_inner(1)
280                .map(|count| match count {
281                    0 => ResourceReclaim::None,
282                    1 => ResourceReclaim::Coroutine,
283                    _ => unreachable!("single-resource drain exceeded its bound"),
284                })
285        } else {
286            let reclaimed = self.drain_deferred_coroutine_reclaims_inner(1)?;
287            if reclaimed != 0 {
288                Ok(ResourceReclaim::Coroutine)
289            } else if self.reclaim_pending_address_space() {
290                Ok(ResourceReclaim::AddressSpace)
291            } else {
292                Ok(ResourceReclaim::None)
293            }
294        }
295    }
296
297    fn reclaim_pending_address_space(&self) -> bool {
298        let Some(address_space) = self.state.lock().pending_address_space_reclaims.pop() else {
299            return false;
300        };
301        let handle = address_space.handle();
302        match task_runtime::destroy_address_space(handle) {
303            AddressSpaceDestroyOutcome::Released => {}
304            AddressSpaceDestroyOutcome::Active => {
305                self.state
306                    .lock()
307                    .pending_address_space_reclaims
308                    .push(address_space);
309                match task_runtime::arm_address_space_reclaim(handle) {
310                    AddressSpaceReclaimArmOutcome::Ready => self.task_work.publish(),
311                    AddressSpaceReclaimArmOutcome::Armed => {}
312                }
313                return false;
314            }
315        }
316        true
317    }
318
319    fn drain_deferred_coroutine_reclaims_inner(&self, limit: usize) -> Result<usize, TaskError> {
320        const MAX_DRAIN_BATCH: usize = 64;
321
322        let mut messages = [InboxMessage::EMPTY; MAX_DRAIN_BATCH];
323        let batch = self
324            .deferred_coroutine_reclaims
325            .drain(limit.min(MAX_DRAIN_BATCH), &mut messages);
326        for message in messages.iter().take(batch.drained()) {
327            if message.operation() != InboxOperation::Reclaim || message.payload() == 0 {
328                task_runtime::fatal_invariant(0x4558_0009, message.payload());
329            }
330            let header = ptr::with_exposed_provenance_mut::<CoroutineHeader>(message.payload());
331            unsafe {
332                // Detachment cleared the embedded reclaim membership. Zero
333                // references and FUTURE_EMPTY make the type-erased allocation
334                // exclusively owned by this task-context consumer.
335                CoroutineHeader::deallocate_raw(header);
336            }
337        }
338        Ok(batch.drained())
339    }
340}