Skip to main content

starnix_core/task/
delayed_release.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::task::CurrentTask;
6use starnix_logging::log_warn;
7use starnix_types::ownership::Releasable;
8use std::cell::{Cell, RefCell};
9use std::ops::DerefMut;
10
11/// Register the container to be deferred released.
12pub fn register_delayed_release<T: for<'a> Releasable<Context<'a> = &'a CurrentTask> + 'static>(
13    to_release: T,
14) {
15    HAS_PENDING_RELEASERS.with(|flag| flag.set(true));
16    RELEASERS.with(|cell| {
17        let mut cell = cell.borrow_mut();
18        let list = &mut cell.as_mut().expect("DelayedReleaser hasn't been finalized").releasables;
19        list.push(Box::new(Some(to_release)));
20    });
21}
22
23impl<T> CurrentTaskReleasable for Option<T>
24where
25    for<'a> T: Releasable<Context<'a> = &'a CurrentTask>,
26{
27    fn release_with_context(&mut self, context: &CurrentTask) {
28        if let Some(this) = self.take() {
29            <T as Releasable>::release(this, context);
30        }
31    }
32}
33
34/// An object-safe/dyn-compatible trait to wrap `Releasable` types.
35pub trait CurrentTaskReleasable {
36    fn release_with_context(&mut self, context: &CurrentTask);
37}
38
39struct DelayedCallback<F>(F);
40
41impl<F> Releasable for DelayedCallback<F>
42where
43    F: FnOnce(&CurrentTask) + 'static,
44{
45    type Context<'a> = &'a CurrentTask;
46
47    fn release<'a>(self, context: Self::Context<'a>) {
48        (self.0)(context);
49    }
50}
51
52/// Register a callback to be run during delayed release.
53pub fn register_delayed_call<F>(callback: F)
54where
55    F: FnOnce(&CurrentTask) + 'static,
56{
57    register_delayed_release(DelayedCallback(callback));
58}
59
60thread_local! {
61    /// Fast-path flag indicating whether `RELEASERS` is non-empty on the current thread.
62    static HAS_PENDING_RELEASERS: Cell<bool> = const { Cell::new(false) };
63
64    /// Container of all `FileObject` that are not used anymore, but have not been closed yet.
65    static RELEASERS: RefCell<Option<LocalReleasers>> =
66        RefCell::new(Some(LocalReleasers::default()));
67}
68
69#[derive(Default)]
70struct LocalReleasers {
71    /// The list of entities to be deferred released.
72    releasables: Vec<Box<dyn CurrentTaskReleasable>>,
73}
74
75impl LocalReleasers {
76    fn is_empty(&self) -> bool {
77        self.releasables.is_empty()
78    }
79}
80
81impl Releasable for LocalReleasers {
82    type Context<'a> = &'a CurrentTask;
83
84    fn release<'a>(self, context: &'a CurrentTask) {
85        let current_task = context;
86        for mut releasable in self.releasables {
87            releasable.release_with_context(current_task);
88        }
89    }
90}
91
92/// Service to handle delayed releases.
93///
94/// Delayed releases are cleanup code that is run at specific point where the lock level is
95/// known. The starnix kernel must ensure that delayed releases are run regularly.
96#[derive(Debug, Default)]
97pub struct DelayedReleaser {}
98
99impl DelayedReleaser {
100    /// Run all current delayed releases for the current thread.
101    #[inline]
102    pub fn apply(&self, current_task: &CurrentTask) {
103        debug_assert!(
104            RELEASERS.with(|cell| cell.borrow().is_some()),
105            "DelayedReleaser has been finalized"
106        );
107        if !HAS_PENDING_RELEASERS.with(|flag| flag.replace(false)) {
108            return;
109        }
110        self.apply_slow(current_task);
111    }
112
113    #[cold]
114    #[inline(never)]
115    fn apply_slow(&self, current_task: &CurrentTask) {
116        let mut counter = 0u32;
117        loop {
118            HAS_PENDING_RELEASERS.with(|flag| flag.set(false));
119            let releasers = RELEASERS.with(|cell| {
120                std::mem::take(
121                    cell.borrow_mut()
122                        .as_mut()
123                        .expect("DelayedReleaser hasn't been finalized yet")
124                        .deref_mut(),
125                )
126            });
127            if releasers.is_empty() {
128                return;
129            }
130            releasers.release(current_task);
131            counter += 1;
132            if counter == 100 {
133                log_warn!("DelayedReleaser: applied >=100 delayed releases");
134            }
135            if counter > 10000 {
136                panic!("DelayedReleaser: applied >10000 delayed releases");
137            }
138        }
139    }
140
141    /// Prevent any further releasables from being registered on this thread.
142    ///
143    /// This function should be called during thread teardown to ensure that we do not
144    /// register any new releasables on this thread after we have finalized the delayed
145    /// releasables for the last time.
146    pub fn finalize() {
147        HAS_PENDING_RELEASERS.with(|flag| flag.set(false));
148        RELEASERS.with(|cell| {
149            let list = cell.borrow_mut().take().expect("DelayedReleaser hasn't been finalized");
150            assert!(list.is_empty());
151        });
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::testing::spawn_kernel_and_run;
159    use std::sync::Arc;
160    use std::sync::atomic::{AtomicBool, Ordering};
161
162    #[::fuchsia::test]
163    async fn test_register_delayed_call() {
164        spawn_kernel_and_run(async move |current_task| {
165            let flag = Arc::new(AtomicBool::new(false));
166            let flag_clone = flag.clone();
167            register_delayed_call(move |_current_task| {
168                flag_clone.store(true, Ordering::SeqCst);
169            });
170
171            assert!(!flag.load(Ordering::SeqCst));
172            DelayedReleaser::default().apply(current_task);
173            assert!(flag.load(Ordering::SeqCst));
174        })
175        .await;
176    }
177
178    #[::fuchsia::test]
179    async fn test_delayed_call_registered_during_apply() {
180        spawn_kernel_and_run(async move |current_task| {
181            let flag = Arc::new(AtomicBool::new(false));
182            let flag_clone = flag.clone();
183            register_delayed_call(move |_current_task| {
184                // Releasables registered while applying are drained by the same `apply()` call.
185                register_delayed_call(move |_current_task| {
186                    flag_clone.store(true, Ordering::SeqCst);
187                });
188            });
189
190            DelayedReleaser::default().apply(current_task);
191            assert!(flag.load(Ordering::SeqCst));
192        })
193        .await;
194    }
195}