scoped_task/
zircon.rs

1// Copyright 2020 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 fuchsia_runtime as runtime;
6use lazy_static::lazy_static;
7use log::error;
8use std::borrow::Borrow;
9use std::ffi::CStr;
10use std::ops::Deref;
11use std::panic;
12
13lazy_static! {
14    static ref SCOPED_JOB: Scoped<zx::Job> = initialize();
15}
16
17fn initialize() -> Scoped<zx::Job> {
18    let job = runtime::job_default().create_child_job().expect("couldn't create job");
19
20    unsafe {
21        libc::atexit(exit_hook);
22    }
23    install_panic_hook();
24    // We cannot panic past this point.
25
26    Scoped::new(job)
27}
28
29#[cfg(panic = "abort")]
30fn install_panic_hook() {
31    let old_hook = panic::take_hook();
32    let new_hook = Box::new(
33        // TODO(https://fxbug.dev/357638987): Remove #[allow(deprecated)]
34        // and change PanicInfo to PanicHookInfo once we roll
35        // https://github.com/rust-lang/rust/commit/64e56db72aa2b1f112504504a7dceba63881cdef.
36        #[allow(deprecated)]
37        move |panic_info: &panic::PanicInfo<'_>| {
38            exit_hook();
39            old_hook(panic_info);
40        },
41    );
42    panic::set_hook(new_hook);
43}
44
45#[cfg(panic = "unwind")]
46fn install_panic_hook() {
47    // When panic=unwind we can rely on the destructor of the individual
48    // processes.
49}
50
51extern "C" fn exit_hook() {
52    kill(SCOPED_JOB.0.as_ref().unwrap(), "scoped job root");
53}
54
55/// Installs the hooks that ensure scoped tasks are cleaned up on panic and
56/// process exit.
57///
58/// Programs and tests that are multithreaded should call this before starting
59/// any threads. Single-threaded programs do not have to call this, and can rely
60/// on hooks being installed lazily.
61///
62/// This function can be called multiple times. It will not have any effect
63/// after the first call.
64pub fn install_hooks() {
65    let _ = &*SCOPED_JOB;
66}
67
68/// Returns the default job for spawning scoped processes.
69///
70/// This job is a child of the job returned by [`fuchsia_runtime::job_default`].
71pub fn job_default() -> &'static Scoped<zx::Job> {
72    &*SCOPED_JOB
73}
74
75/// Creates and returns a child job of the job returned by [`job_default`].
76pub fn create_child_job() -> Result<Scoped<zx::Job>, zx::Status> {
77    Ok(Scoped::new(SCOPED_JOB.create_child_job()?))
78}
79
80/// A convenience wrapper around [`fdio::spawn_etc`] that returns a
81/// [`Scoped`].
82///
83/// Note that you must assign the return value to a local. You can use a leading
84/// underscore in the name to avoid the unused variable lint, but don't use just
85/// `_` or the process will be killed immediately. For example,
86///
87/// ```
88/// let _process = scoped_task::spawn_etc(
89///     &scoped_task::job_default(),
90///     SpawnOptions::CLONE_ALL,
91///     c"/pkg/bin/echo",
92///     &[c"hello world"],
93///     None,
94///     &mut [],
95/// ).expect("could not spawn process");
96/// ```
97pub fn spawn_etc<'a>(
98    job: impl Borrow<&'a Scoped<zx::Job>>,
99    options: fdio::SpawnOptions,
100    path: &CStr,
101    argv: &[&CStr],
102    environ: Option<&[&CStr]>,
103    actions: &mut [fdio::SpawnAction<'_>],
104) -> Result<Scoped<zx::Process>, (zx::Status, String)> {
105    fdio::spawn_etc(job.borrow().0.as_ref().unwrap(), options, path, argv, environ, actions)
106        .map(Scoped::new)
107}
108
109/// A convenience wrapper around [`fdio::spawn`] that returns a
110/// [`Scoped`].
111///
112/// Note that you must assign the return value to a local. You can use a leading
113/// underscore in the name to avoid the unused variable lint, but don't use just
114/// `_` or the process will be killed immediately. For example,
115///
116/// ```
117/// let _process = scoped_task::spawn(
118///     &scoped_task::job_default(),
119///     SpawnOptions::CLONE_ALL,
120///     c"/pkg/bin/echo",
121///     &[c"hello world"],
122/// ).expect("could not spawn process");
123/// ```
124pub fn spawn<'a>(
125    job: impl Borrow<&'a Scoped<zx::Job>>,
126    options: fdio::SpawnOptions,
127    path: &CStr,
128    argv: &[&CStr],
129) -> Result<Scoped<zx::Process>, zx::Status> {
130    fdio::spawn(job.borrow().0.as_ref().unwrap(), options, path, argv).map(Scoped::new)
131}
132
133/// Scoped wrapper for a process or job backed by a Zircon handle.
134///
135/// The process or job is killed when the wrapper goes out of scope or the
136/// current process panics. See the module-level documentation for more details.
137#[must_use]
138pub struct Scoped<T: zx::Task = zx::Process>(Option<T>);
139
140impl<T: zx::Task> Scoped<T> {
141    fn new(process: T) -> Self {
142        Scoped(Some(process))
143    }
144
145    /// Kills the process, consuming the Scoped wrapper and returning
146    /// the inner task object.
147    pub fn kill(mut self) -> Result<T, zx::Status> {
148        let inner = self.0.take().unwrap();
149        let result = inner.kill();
150        result.map(|()| inner)
151    }
152
153    /// Consumes the Scoped wrapper and returns the inner task object. The task
154    /// will no longer be killed on Drop, but may still be killed on exit.
155    #[must_use]
156    pub fn into_inner(mut self) -> T {
157        self.0.take().unwrap()
158    }
159}
160
161impl<T: zx::Task> Drop for Scoped<T> {
162    fn drop(&mut self) {
163        if let Some(process) = self.0.take() {
164            kill(&process, "scoped task");
165        }
166    }
167}
168
169impl<T: zx::Task> Deref for Scoped<T> {
170    type Target = T;
171    fn deref(&self) -> &T {
172        self.0.as_ref().unwrap()
173    }
174}
175
176fn kill<T: zx::Task>(process: &T, what: &'static str) {
177    let result: Result<(), zx::Status> = (|| {
178        process.kill()?;
179        process.wait_handle(zx::Signals::TASK_TERMINATED, zx::MonotonicInstant::INFINITE)?;
180        Ok(())
181    })();
182    match result {
183        Ok(()) => {}
184        Err(status) => {
185            eprintln!("error: could not kill {what}: {status}");
186            error!(status:%; "error: could not kill {what}");
187        }
188    }
189}