Skip to main content

starnix_core/task/
dynamic_thread_spawner.rs

1// Copyright 2022 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
5//! The API for spawning dynamic kernel threads.
6//!
7//! If you want to run a closure on a kernel thread, check out [SpawnRequestBuilder] on
8//! how to start and configure tasks that run closures.
9
10use crate::execution::create_kernel_thread;
11use crate::task::{
12    CurrentTask, DelayedReleaser, Task, ThreadLockupDetector, WrappedFuture, with_new_current_task,
13};
14use futures::TryFutureExt;
15use futures::channel::oneshot;
16use starnix_logging::{CATEGORY_STARNIX, log_debug, log_error};
17use starnix_sync::{DynamicThreadSpawnerLock, LockDepMutex};
18use starnix_task_command::TaskCommand;
19use starnix_types::ownership::release_after;
20use starnix_uapi::errno;
21use starnix_uapi::errors::Errno;
22use std::future::Future;
23use std::sync::mpsc::{SendError, SyncSender, TrySendError, sync_channel};
24use std::sync::{Arc, Weak};
25use std::thread::JoinHandle;
26
27type BoxedClosure = Box<dyn FnOnce(&CurrentTask) -> () + Send + 'static>;
28
29const DEFAULT_THREAD_ROLE: &str = "fuchsia.starnix.fair.16";
30
31/// A builder for configuring new tasks to spawn.
32///
33/// The builder allows us to set up several different flavors of possible
34/// tasks to spawn, with a menu of options as follows:
35///
36/// - A task may or may not have a name.
37/// - A task may or may not have an assigned scheduler role.
38/// - A task may be a sync, or an async closure.
39/// - A task may return a result, or may not return a result.
40/// - The task's result can be collected synchronously, asynchronously, or not collected at all.
41///
42/// Note that these parameters are not perfectly orthogonal. For example, a task spawned from an
43/// async closure can not return a value asynchronously. (This is not a limitation of the approach,
44/// rather it's the API that we explicitly use today.) Also, some parameter combinations do not
45/// make sense, for example a spawn request can not have both a sync and an async closure to run.
46///
47/// The builder API is designed in a way that only allows chaining the configuration options which
48/// are valid at that point in the configuration process. Invalid option combinations are
49/// compile-time(!) errors. It will only allow creating a [SpawnRequest] if enough parameters have
50/// been passed such that there is enough information to create a request. It will not allow
51/// passing conflicting parameters: for example, if you already passed one synchronous closure, it
52/// is impossible to pass another closure and have the code compile without errors.
53///
54/// ## Usage
55///
56/// Call [SpawnRequestBuilder::new()] to start building. Refer to the unit tests in this module for
57/// usage examples.
58pub struct SpawnRequestBuilder<C: ClosureKind> {
59    debug_name: &'static str,
60    role: Option<&'static str>,
61    closure_kind: C,
62}
63
64/// You can only create an empty request builder.
65impl SpawnRequestBuilder<ClosureNone> {
66    /// Creates a new spawn request builder.
67    pub fn new() -> Self {
68        Self { role: None, closure_kind: ClosureNone {}, debug_name: "kthreadd" }
69    }
70}
71
72/// You can call these at any point in the builder's lifecycle.
73impl<C: ClosureKind> SpawnRequestBuilder<C> {
74    /// Set a role to apply to the thread that will run your closure.
75    pub fn with_role(self, role: &'static str) -> Self {
76        Self { role: Some(role), ..self }
77    }
78
79    /// Set a task name to apply to the thread that will run your closure.
80    pub fn with_debug_name(self, debug_name: &'static str) -> Self {
81        Self { debug_name, ..self }
82    }
83}
84
85/// You can call these only if you have not provided a closure yet.
86impl SpawnRequestBuilder<ClosureNone> {
87    /// Provides the closure that the spawner will run.
88    pub fn with_sync_closure<F, T>(
89        self,
90        f: F,
91    ) -> SpawnRequestBuilder<impl FnOnce(&CurrentTask) -> T + Send + 'static>
92    where
93        T: Send + 'static,
94        F: FnOnce(&CurrentTask) -> T + Send + 'static,
95    {
96        let SpawnRequestBuilder { role, closure_kind: _, debug_name } = self;
97        SpawnRequestBuilder { role, closure_kind: f, debug_name }
98    }
99
100    /// Provides the closure that the spawner will run.
101    pub fn with_async_closure<F, T>(
102        self,
103        f: F,
104    ) -> SpawnRequestBuilder<impl FnOnce(&CurrentTask) -> T + Send + 'static>
105    where
106        T: Send + 'static,
107        F: AsyncFnOnce(&CurrentTask) -> T + Send + 'static,
108    {
109        let sync_fn = async_to_sync(f, self.debug_name);
110        self.with_sync_closure(sync_fn)
111    }
112}
113
114/// A fully configured spawn request.
115pub struct SpawnRequest {
116    /// The closure to run.
117    closure: BoxedClosure,
118    /// A name to give to the task.
119    debug_name: &'static str,
120}
121
122impl<T, F> SpawnRequestBuilder<F>
123where
124    T: Send + 'static,
125    F: FnOnce(&CurrentTask) -> T + Send + 'static,
126{
127    /// Build a spawn request.
128    pub fn build(self) -> SpawnRequest {
129        let Self { role, closure_kind, debug_name } = self;
130        let closure = closure_kind;
131        let closure = maybe_apply_role(role, closure);
132        let closure = Box::new(move |current_task: &CurrentTask| {
133            fuchsia_trace::duration!(CATEGORY_STARNIX, debug_name);
134            let _ = closure(current_task);
135        });
136        SpawnRequest { closure, debug_name }
137    }
138
139    /// Like [build], but allows receiving a result synchronously.
140    /// Do not forget to submit the spawn request to a spawner.
141    ///
142    /// Example:
143    ///
144    /// ```
145    /// let (result_fn, request) = /*...*/ .build_with_sync_result();
146    /// // spawn `request`
147    /// let result = result_fn();
148    /// ```
149    pub fn build_with_sync_result(self) -> (impl FnOnce() -> Result<T, Errno>, SpawnRequest) {
150        let Self { role, closure_kind, debug_name } = self;
151        let closure = closure_kind;
152        let (sender, receiver) = sync_channel::<T>(0);
153        let result_fn = move || {
154            receiver.recv().map_err(|err| errno!(EINTR, format!("while receiving: {err:?}")))
155        };
156        let closure = maybe_apply_role(role, closure);
157        let closure = Box::new(move |current_task: &CurrentTask| {
158            fuchsia_trace::duration!(CATEGORY_STARNIX, debug_name);
159            let _ = sender.send(closure(current_task));
160        });
161        (result_fn, SpawnRequest { closure, debug_name })
162    }
163
164    /// Like [build], but allows receiving a result as a future.
165    /// Do not forget to submit the spawn request to a spawner.
166    ///
167    /// Example:
168    ///
169    /// ```
170    /// let (result_fut, request) = /*...*/ .build_with_async_result();
171    /// // spawn `request`
172    /// let result = result_fut.await;
173    /// ```
174    pub fn build_with_async_result(self) -> (impl Future<Output = Result<T, Errno>>, SpawnRequest) {
175        let Self { role, closure_kind, debug_name } = self;
176        let closure = closure_kind;
177        let (sender_async, result_fut) = oneshot::channel::<T>();
178        let maybe_with_role = maybe_apply_role(role, closure);
179        let repackaged = Box::new(move |current_task: &CurrentTask| {
180            fuchsia_trace::duration!(CATEGORY_STARNIX, debug_name);
181            let result = maybe_with_role(current_task);
182            let _ = sender_async.send(result);
183        });
184        let result_fut =
185            result_fut.map_err(|err| errno!(EINTR, format!("while receiving async: {err:?}")));
186        (result_fut, SpawnRequest { closure: repackaged, debug_name })
187    }
188}
189
190/// A thread pool that immediately execute any new work sent to it and keep a maximum number of
191/// idle threads.
192#[derive(Debug)]
193pub struct DynamicThreadSpawner {
194    state: Arc<LockDepMutex<DynamicThreadSpawnerState, DynamicThreadSpawnerLock>>,
195    /// The weak system task to create the kernel thread associated with each thread.
196    system_task: Weak<Task>,
197    /// A persistent thread that is used to create new thread. This ensures that threads are
198    /// created from the initial starnix process and are not tied to a specific task.
199    persistent_thread: RunningThread,
200}
201
202/// Wrap a closure with a thread role assignment, if one is available.
203fn maybe_apply_role<R, F>(
204    role: Option<&'static str>,
205    f: F,
206) -> impl FnOnce(&CurrentTask) -> R + Send + 'static
207where
208    F: FnOnce(&CurrentTask) -> R + Send + 'static,
209{
210    move |current_task| {
211        if let Some(role) = role {
212            if let Err(e) = fuchsia_scheduler::set_role_for_this_thread(role) {
213                log_debug!(e:%; "failed to set kthread role");
214            }
215            let result = f(current_task);
216            if let Err(e) = fuchsia_scheduler::set_role_for_this_thread(DEFAULT_THREAD_ROLE) {
217                log_debug!(e:%; "failed to reset kthread role to default priority");
218            }
219            result
220        } else {
221            f(current_task)
222        }
223    }
224}
225
226/// Convert async closure to sync closure that can be submitted to the spawner.
227fn async_to_sync<T, F>(f: F, name: &'static str) -> impl FnOnce(&CurrentTask) -> T + Send + 'static
228where
229    T: Send + 'static,
230    F: AsyncFnOnce(&CurrentTask) -> T + Send + 'static,
231{
232    move |current_task| {
233        let mut exec = fuchsia_async::LocalExecutor::default();
234
235        let wrapped_future = WrappedSpawnedFuture::new(
236            current_task,
237            ThreadLockupDetector::track_future(f(current_task)),
238            name,
239        );
240        let _waiting_guard = ThreadLockupDetector::pause_tracking();
241        exec.run_singlethreaded(wrapped_future)
242    }
243}
244
245/// Denotes whether a closure has been provided. A request can not be
246/// built at all without a closure.
247///
248/// See [SpawnRequestBuilder] for usage details.
249pub trait ClosureKind {}
250
251/// A builder type state where no closure has been provided yet.
252///
253/// See [SpawnRequestBuilder] for usage details.
254pub struct ClosureNone {}
255impl ClosureKind for ClosureNone {}
256
257/// A builder type state where a closure has been provided.
258/// See [SpawnRequestBuilder] for usage details.
259impl<T: Send + 'static, FN: FnOnce(&CurrentTask) -> T + Send + 'static> ClosureKind for FN {}
260
261#[derive(Debug)]
262struct DynamicThreadSpawnerState {
263    threads: Vec<RunningThread>,
264    idle_threads: u8,
265    max_idle_threads: u8,
266}
267
268impl DynamicThreadSpawner {
269    pub fn new(
270        max_idle_threads: u8,
271        system_task: Weak<Task>,
272        debug_name: impl Into<String>,
273    ) -> Self {
274        let persistent_thread =
275            RunningThread::new_persistent(system_task.clone(), debug_name.into());
276        Self {
277            state: Arc::new(
278                DynamicThreadSpawnerState { max_idle_threads, idle_threads: 0, threads: vec![] }
279                    .into(),
280            ),
281            system_task,
282            persistent_thread,
283        }
284    }
285
286    /// Run a given closure on a thread based on the provided [SpawnRequest].
287    ///
288    /// Use [SpawnRequestBuilder::new()] to start configuring a [SpawnRequest].
289    ///
290    /// This method will use an idle thread in the pool if one is available, otherwise it will
291    /// start a new thread. When this method returns, it is guaranteed that a thread is
292    /// responsible to start running the closure.
293    pub fn spawn_from_request(&self, spawn_request: SpawnRequest) {
294        // Check whether a thread already exists to handle the request.
295        let mut function: BoxedClosure = spawn_request.closure;
296        let mut state = self.state.lock();
297        if state.idle_threads > 0 {
298            let mut i = 0;
299            while i < state.threads.len() {
300                // Increases `i` immediately, so that it can be decreased it the thread must be
301                // dropped.
302                let thread_index = i;
303                i += 1;
304                match state.threads[thread_index].try_dispatch(function) {
305                    Ok(_) => {
306                        // The dispatch succeeded.
307                        state.idle_threads -= 1;
308                        return;
309                    }
310                    Err(TrySendError::Full(f)) => {
311                        // The thread is busy.
312                        function = f;
313                    }
314                    Err(TrySendError::Disconnected(f)) => {
315                        // The receiver is disconnected, it means the thread has terminated, drop it.
316                        state.idle_threads -= 1;
317                        state.threads.remove(thread_index);
318                        i -= 1;
319                        function = f;
320                    }
321                }
322            }
323        }
324
325        // A new thread must be created. It needs to be done from the persistent thread.
326        let (sender, receiver) = sync_channel::<RunningThread>(0);
327        let dispatch_function: BoxedClosure = Box::new({
328            let state = self.state.clone();
329            let system_task = self.system_task.clone();
330            move |_| {
331                sender
332                    .send(RunningThread::new(
333                        state,
334                        system_task,
335                        spawn_request.debug_name.to_string(),
336                        function,
337                    ))
338                    .expect("receiver must not be dropped");
339            }
340        });
341        self.persistent_thread
342            .dispatch(dispatch_function)
343            .expect("persistent thread should not have ended.");
344        state.threads.push(receiver.recv().expect("persistent thread should not have ended."));
345    }
346}
347
348type WrappedSpawnedFuture<'a, F> = WrappedFuture<F, &'a CurrentTask>;
349
350impl<'a, F: 'a> WrappedSpawnedFuture<'a, F> {
351    fn new(current_task: &'a CurrentTask, fut: F, name: &'static str) -> Self {
352        Self::new_with_cleaner(current_task, trigger_delayed_releaser, fut, name)
353    }
354}
355
356fn trigger_delayed_releaser(current_task: &CurrentTask) {
357    current_task.trigger_delayed_releaser();
358}
359
360#[derive(Debug)]
361struct RunningThread {
362    thread: Option<JoinHandle<()>>,
363    sender: Option<SyncSender<BoxedClosure>>,
364}
365
366impl RunningThread {
367    fn new(
368        state: Arc<LockDepMutex<DynamicThreadSpawnerState, DynamicThreadSpawnerLock>>,
369        system_task: Weak<Task>,
370        debug_task_name: String,
371        f: BoxedClosure,
372    ) -> Self {
373        let (sender, receiver) = sync_channel::<BoxedClosure>(0);
374        let thread = Some(
375            std::thread::Builder::new()
376                .name("kthread-dynamic-worker".to_string())
377                .spawn(move || {
378                    let _rcu_registration = fuchsia_rcu::register_thread();
379
380                    let result =
381                        with_new_current_task(&system_task, debug_task_name, |current_task| {
382                            while let Ok(f) = receiver.recv() {
383                                let _guard = ThreadLockupDetector::track();
384                                f(&current_task);
385                                // Apply any delayed releasers.
386                                current_task.trigger_delayed_releaser();
387                                let mut state = state.lock();
388                                state.idle_threads += 1;
389                                if state.idle_threads > state.max_idle_threads {
390                                    // If the number of idle thread is greater than the max, the
391                                    // thread terminates.  This disconnects the receiver, which will
392                                    // ensure that the thread will be joined and remove from the list
393                                    // of available threads the next time the pool tries to use it.
394                                    return;
395                                }
396                            }
397                        });
398                    if let Err(e) = result {
399                        log_error!("Unable to create a kernel thread: {e:?}");
400                    }
401                })
402                .expect("able to create threads"),
403        );
404        let result = Self { thread, sender: Some(sender) };
405        // The dispatch cannot fail because the thread can only finish after having executed at
406        // least one task, and this is the first task ever dispatched to it.
407        result
408            .sender
409            .as_ref()
410            .expect("sender should never be None")
411            .send(f)
412            .expect("Dispatch cannot fail");
413        result
414    }
415
416    fn new_persistent(system_task: Weak<Task>, task_name: String) -> Self {
417        // The persistent thread doesn't need to do any rendez-vous when received task.
418        let (sender, receiver) = sync_channel::<BoxedClosure>(20);
419        let thread = Some(
420            std::thread::Builder::new()
421                .name("kthread-persistent-worker".to_string())
422                .spawn(move || {
423                    let _rcu_registration = fuchsia_rcu::register_thread();
424                    let current_task = {
425                        let Some(system_task) = system_task.upgrade() else {
426                            return;
427                        };
428                        match create_kernel_thread(
429                            &system_task,
430                            TaskCommand::new(task_name.as_bytes()),
431                        ) {
432                            Ok(task) => task,
433                            Err(e) => {
434                                log_error!("Unable to create a kernel thread: {e:?}");
435                                return;
436                            }
437                        }
438                    };
439                    release_after!(current_task, {
440                        while let Ok(f) = receiver.recv() {
441                            let _guard = ThreadLockupDetector::track();
442                            f(&current_task);
443
444                            // Apply any delayed releasers.
445                            current_task.trigger_delayed_releaser();
446                        }
447                    });
448
449                    // Ensure that no releasables are registered after this point as we unwind the stack.
450                    DelayedReleaser::finalize();
451                })
452                .expect("able to create threads"),
453        );
454        Self { thread, sender: Some(sender) }
455    }
456
457    fn try_dispatch(&self, f: BoxedClosure) -> Result<(), TrySendError<BoxedClosure>> {
458        self.sender.as_ref().expect("sender should never be None").try_send(f)
459    }
460
461    fn dispatch(&self, f: BoxedClosure) -> Result<(), SendError<BoxedClosure>> {
462        self.sender.as_ref().expect("sender should never be None").send(f)
463    }
464}
465
466impl Drop for RunningThread {
467    fn drop(&mut self) {
468        self.sender = None;
469        match self.thread.take() {
470            Some(thread) => thread.join().expect("Thread should join."),
471            _ => panic!("Thread should never be None"),
472        };
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use crate::testing::spawn_kernel_and_run;
480
481    #[fuchsia::test]
482    async fn run_simple_task() {
483        spawn_kernel_and_run(async |current_task| {
484            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
485            // Type decorations are needed sometimes to avoid "closure type is
486            // not general enough" error.
487            let closure = move |_: &CurrentTask| {};
488            let req = SpawnRequestBuilder::new().with_sync_closure(closure).build();
489            spawner.spawn_from_request(req);
490        })
491        .await;
492    }
493
494    #[fuchsia::test]
495    async fn run_10_tasks() {
496        spawn_kernel_and_run(async |current_task| {
497            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
498            for _ in 0..10 {
499                let closure = move |_: &CurrentTask| {};
500                let opts = SpawnRequestBuilder::new().with_sync_closure(closure).build();
501                spawner.spawn_from_request(opts);
502            }
503        })
504        .await;
505    }
506
507    #[fuchsia::test]
508    async fn blocking_task_do_not_prevent_further_processing() {
509        spawn_kernel_and_run(async |current_task| {
510            let spawner = DynamicThreadSpawner::new(1, current_task.weak_task(), "kthreadd");
511
512            let pair = Arc::new((fuchsia_sync::Mutex::new(false), fuchsia_sync::Condvar::new()));
513            for _ in 0..10 {
514                let pair2 = Arc::clone(&pair);
515                let closure = move |_: &CurrentTask| {
516                    let (lock, cvar) = &*pair2;
517                    let mut cont = lock.lock();
518                    while !*cont {
519                        cvar.wait(&mut cont);
520                    }
521                };
522                let req = SpawnRequestBuilder::new().with_sync_closure(closure).build();
523                spawner.spawn_from_request(req);
524            }
525
526            let closure = move |_: &CurrentTask| {
527                let (lock, cvar) = &*pair;
528                let mut cont = lock.lock();
529                *cont = true;
530                cvar.notify_all();
531            };
532
533            let (result, req) =
534                SpawnRequestBuilder::new().with_sync_closure(closure).build_with_sync_result();
535            spawner.spawn_from_request(req);
536
537            assert_eq!(result(), Ok(()));
538        })
539        .await;
540    }
541
542    #[fuchsia::test]
543    async fn run_spawn_and_get_result() {
544        spawn_kernel_and_run(async |current_task| {
545            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
546
547            let (result, req) =
548                SpawnRequestBuilder::new().with_sync_closure(|_| 3).build_with_sync_result();
549            spawner.spawn_from_request(req);
550            assert_eq!(result(), Ok(3));
551        })
552        .await;
553    }
554
555    #[fuchsia::test]
556    async fn test_spawn_async() {
557        spawn_kernel_and_run(async |current_task| {
558            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
559
560            // The closure free variables must be decorated with their respective types,
561            // the rust compiler gets confused otherwise and is unable to infer the correct
562            // lifetimes. Interestingly, adding your own lifetimes here does *not* help.
563            let closure = move |current_task: &CurrentTask| {
564                let mut exec = fuchsia_async::LocalExecutor::default();
565                let fut = async {};
566                let wrapped_future = WrappedSpawnedFuture::new(current_task, fut, "test-async");
567                exec.run_singlethreaded(wrapped_future);
568            };
569            let req = SpawnRequestBuilder::new().with_sync_closure(closure).build();
570            spawner.spawn_from_request(req);
571        })
572        .await;
573    }
574
575    #[fuchsia::test]
576    async fn test_spawn_async_closure() {
577        spawn_kernel_and_run(async |current_task| {
578            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
579            let fut = async |_: &CurrentTask| 42;
580            let (result, req) =
581                SpawnRequestBuilder::new().with_async_closure(fut).build_with_sync_result();
582            spawner.spawn_from_request(req);
583            assert_eq!(result(), Ok(42));
584        })
585        .await;
586    }
587
588    #[fuchsia::test]
589    async fn test_spawn_sync_to_async_result() {
590        spawn_kernel_and_run(async |current_task| {
591            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
592            let fut = async |_: &CurrentTask| 42;
593            let (result, req) =
594                SpawnRequestBuilder::new().with_async_closure(fut).build_with_sync_result();
595
596            let fut2 = async move |_: &CurrentTask| result().unwrap();
597            let (result2, req2) =
598                SpawnRequestBuilder::new().with_async_closure(fut2).build_with_sync_result();
599            spawner.spawn_from_request(req2);
600            spawner.spawn_from_request(req);
601            assert_eq!(result2(), Ok(42));
602        })
603        .await;
604    }
605
606    #[fuchsia::test]
607    async fn test_spawn_async_to_async_result() {
608        spawn_kernel_and_run(async |current_task| {
609            let spawner = DynamicThreadSpawner::new(2, current_task.weak_task(), "kthreadd");
610            let fut = async |_: &CurrentTask| 42;
611            let (result_fut, req) =
612                SpawnRequestBuilder::new().with_async_closure(fut).build_with_async_result();
613
614            let fut2 = async move |_: &CurrentTask| result_fut.await.unwrap();
615            let (result2, req2) =
616                SpawnRequestBuilder::new().with_async_closure(fut2).build_with_sync_result();
617            spawner.spawn_from_request(req2);
618            spawner.spawn_from_request(req);
619            assert_eq!(result2(), Ok(42));
620        })
621        .await;
622    }
623}