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