| // Copyright 2020 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| use core::ffi::{CStr, c_void}; |
| use core::marker::PhantomPinned; |
| use core::mem::MaybeUninit; |
| use core::pin::Pin; |
| use core::sync::atomic::{AtomicBool, Ordering}; |
| use fbl::{RefPtr, UniquePtr}; |
| use kalloc::Box; |
| use ksync::declare_singleton_mutex; |
| use object_constants_rs as object_constants; |
| use zr::ToMutPtr; |
| use zx_status::Status; |
| use zx_types::{ZX_KOID_INVALID, ZX_MAX_NAME_LEN, zx_koid_t, zx_signals_t}; |
| |
| use super::handle::HandleRef; |
| use super::job_dispatcher::{JobDispatcher, ZX_JOB_NO_CHILDREN}; |
| use super::process_dispatcher::ProcessDispatcher; |
| use super::root_job_observer_ffi::{ |
| cpp_root_job_signal_observer_destroy, cpp_root_job_signal_observer_init, |
| }; |
| use crate::platform_rs::halt_token::HaltToken; |
| use crate::platform_rs::power::{PlatformHaltAction, ZirconCrashReason, platform_halt}; |
| use crate::platform_rs::timer::{DurationMono, current_mono_time}; |
| |
| declare_singleton_mutex!(CriticalProcessNameLock); |
| |
| static CRITICAL_PROCESS_NAME: ksync::KCell<[u8; ZX_MAX_NAME_LEN], CriticalProcessNameLock> = |
| ksync::KCell::new([0; ZX_MAX_NAME_LEN]); |
| static CRITICAL_PROCESS_KOID: ksync::KCell<zx_koid_t, CriticalProcessNameLock> = |
| ksync::KCell::new(ZX_KOID_INVALID); |
| static CRITICAL_PROCESS_DYING: AtomicBool = AtomicBool::new(false); |
| |
| /// Inline storage for C++ `RootJobSignalObserver`. |
| #[derive(Default)] |
| #[repr(C, align(8))] |
| struct RootJobSignalObserverStorage( |
| zr::OpaqueBytes<{ object_constants::kRootJobSignalObserverSize }>, |
| ); |
| |
| zr::static_assert_size_and_align!( |
| RootJobSignalObserverStorage, |
| object_constants::kRootJobSignalObserverSize, |
| object_constants::kRootJobSignalObserverAlign, |
| ); |
| |
| impl RootJobSignalObserverStorage { |
| #[inline] |
| fn as_mut_ptr(&mut self) -> *mut c_void { |
| self.0.to_mut_ptr().cast() |
| } |
| } |
| |
| type Callback = Box<dyn FnMut() + Send>; |
| |
| /// Observes termination of the root job and coordinates system shutdown or notifications. |
| #[derive(fbl::Recyclable)] |
| #[repr(C, align(8))] |
| pub struct RootJobObserver { |
| root_job: RefPtr<JobDispatcher>, |
| signal_observer: RootJobSignalObserverStorage, |
| #[cfg(ktest)] |
| callback: Option<Callback>, |
| #[cfg(not(ktest))] |
| _reserved: [u8; core::mem::size_of::<Option<Callback>>()], |
| _pin: PhantomPinned, |
| } |
| |
| // SAFETY: RootJobObserver can be moved across threads safely. |
| unsafe impl Send for RootJobObserver {} |
| |
| zr::static_assert_size_and_align!( |
| RootJobObserver, |
| object_constants::kRootJobObserverStorageSize, |
| object_constants::kRootJobObserverStorageAlign, |
| ); |
| |
| impl RootJobObserver { |
| /// Initializes a `RootJobObserver` in the provided `storage` that monitors `root_job`. |
| /// |
| /// When the root job asserts `ZX_JOB_NO_CHILDREN`, the observer takes action to halt |
| /// or reboot the system. |
| /// |
| /// # Safety |
| /// |
| /// - `storage` must be non-null, properly aligned to `kRootJobObserverStorageAlign`, |
| /// and point to valid, uninitialized memory of at least `kRootJobObserverStorageSize` bytes. |
| /// - The memory pointed to by `storage` must not be moved while initialized. |
| /// - The instance in `storage` must be destroyed via `rust_root_job_observer_destroy` or |
| /// `core::ptr::drop_in_place` before freeing or reusing the memory. |
| pub unsafe fn init_in_storage( |
| storage: *mut MaybeUninit<Self>, |
| root_job: RefPtr<JobDispatcher>, |
| root_job_handle: Option<HandleRef<'_>>, |
| ) { |
| // SAFETY: Caller guarantees `storage` is non-null, aligned, and valid for writes. |
| let observer = unsafe { |
| (*storage).write(Self { |
| root_job, |
| signal_observer: RootJobSignalObserverStorage::default(), |
| #[cfg(ktest)] |
| callback: None, |
| #[cfg(not(ktest))] |
| _reserved: Default::default(), |
| _pin: PhantomPinned, |
| }) |
| }; |
| let observer_ptr: *mut Self = observer; |
| let signal_observer_ptr = observer.signal_observer.as_mut_ptr(); |
| // SAFETY: Passing null context creates a SignalObserver that delivers null to rust_root_job_observer_on_match. |
| unsafe { |
| cpp_root_job_signal_observer_init(signal_observer_ptr, core::ptr::null_mut()); |
| } |
| let handle_ptr = root_job_handle.map_or(core::ptr::null(), |h| h.as_ptr()); |
| // Observer registration on the root job object will always succeed. |
| // SAFETY: signal_observer is a valid SignalObserver and handle_ptr is a valid handle pointer or null. |
| let _ = unsafe { |
| (*observer_ptr).root_job.add_observer( |
| signal_observer_ptr, |
| handle_ptr, |
| ZX_JOB_NO_CHILDREN, |
| ) |
| }; |
| } |
| |
| /// Create a `RootJobObserver` that halts the system when the root job terminates |
| /// (i.e. asserts `ZX_JOB_NO_CHILDREN`). |
| pub fn new( |
| root_job: RefPtr<JobDispatcher>, |
| root_job_handle: Option<HandleRef<'_>>, |
| ) -> Result<Pin<UniquePtr<Self>>, Status> { |
| let mut observer = Box::<Self>::try_new_uninit().map_err(|_| Status::NO_MEMORY)?; |
| // SAFETY: `observer` is a valid, properly aligned, uninitialized heap allocation that |
| // is pinned below and will not move before being dropped in-place by `UniquePtr::drop`. |
| unsafe { |
| Self::init_in_storage(&mut *observer, root_job, root_job_handle); |
| let raw = Box::into_raw(observer.assume_init()); |
| Ok(Pin::new_unchecked(UniquePtr::from_raw(raw))) |
| } |
| } |
| |
| /// Creates a new `RootJobObserver` that calls `callback` when the root job asserts `ZX_JOB_NO_CHILDREN`. |
| /// |
| /// The callback is called while holding the watched `JobDispatcher`'s lock, so the callback |
| /// must avoid calling anything that may attempt to acquire that lock again, introduce a lock |
| /// cycle, etc. |
| /// |
| /// Exposed for testing. |
| #[cfg(ktest)] |
| pub fn new_with_callback<F: FnMut() + Send + 'static>( |
| root_job: RefPtr<JobDispatcher>, |
| root_job_handle: Option<HandleRef<'_>>, |
| callback: F, |
| ) -> Result<Pin<UniquePtr<Self>>, Status> { |
| let boxed = Box::try_new(callback).map_err(|_| Status::NO_MEMORY)?; |
| let raw_callback: *mut (dyn FnMut() + Send) = Box::<F>::into_raw(boxed); |
| // SAFETY: raw_callback was allocated via Box::try_new. |
| let callback_box: Callback = unsafe { Box::from_raw(raw_callback) }; |
| |
| let mut observer = UniquePtr::try_new(Self { |
| root_job, |
| signal_observer: RootJobSignalObserverStorage::default(), |
| callback: Some(callback_box), |
| _pin: PhantomPinned, |
| }) |
| .map_err(|_| Status::NO_MEMORY)?; |
| let observer_ptr: *mut Self = UniquePtr::as_mut_ptr(&mut observer); |
| let ctx = observer_ptr as *mut c_void; |
| // SAFETY: observer_ptr is valid and points to the heap-allocated RootJobObserver. |
| let signal_observer_ptr = unsafe { (*observer_ptr).signal_observer.as_mut_ptr() }; |
| // SAFETY: observer is boxed and has a stable heap address; ctx points to live RootJobObserver. |
| unsafe { |
| cpp_root_job_signal_observer_init(signal_observer_ptr, ctx); |
| } |
| let handle_ptr = root_job_handle.map_or(core::ptr::null(), |h| h.as_ptr()); |
| // SAFETY: signal_observer is a valid SignalObserver and handle_ptr is a valid handle pointer or null. |
| let _ = unsafe { |
| observer.root_job.add_observer(signal_observer_ptr, handle_ptr, ZX_JOB_NO_CHILDREN) |
| }; |
| // SAFETY: `observer` is heap-allocated and will not be moved out of the pinned UniquePtr. |
| Ok(unsafe { Pin::new_unchecked(observer) }) |
| } |
| |
| /// Halts or reboots the platform in response to root job termination. |
| /// |
| /// May or may not return depending on whether halt/reboot is already in progress. |
| /// Once committed with the halt token, this function does not return. |
| pub fn halt() { |
| let boot_options = boot_options::BootOptions::get(); |
| if !HaltToken::get().take() { |
| kprint::kprintln!("root-job: halt/reboot already in progress; returning"); |
| return; |
| } |
| // We now have the halt token so we're committed. There is no return from this point. |
| |
| if let Ok(notice) = CStr::from_bytes_until_nul(&boot_options.root_job_notice) { |
| let notice_len = CStr::count_bytes(notice); |
| if notice_len != 0 { |
| kprint::kprintln!("root-job: notice: {:cs}", notice); |
| } |
| } |
| |
| let (action, action_name) = match boot_options.root_job_behavior { |
| boot_options::RootJobBehavior::Halt => (PlatformHaltAction::Halt, "halt"), |
| boot_options::RootJobBehavior::Bootloader => { |
| (PlatformHaltAction::RebootBootloader, "bootloader") |
| } |
| boot_options::RootJobBehavior::Recovery => { |
| (PlatformHaltAction::RebootRecovery, "recovery") |
| } |
| boot_options::RootJobBehavior::Shutdown => (PlatformHaltAction::Shutdown, "shutdown"), |
| boot_options::RootJobBehavior::Reboot => (PlatformHaltAction::Reboot, "reboot"), |
| }; |
| |
| kprint::kprintln!("root-job: taking {:s} action", action_name); |
| let dlog_deadline = current_mono_time() + DurationMono::from_seconds(5); |
| let _ = crate::debuglog_rs::dlog_shutdown(dlog_deadline); |
| // Does not return. |
| platform_halt(action, ZirconCrashReason::UserspaceRootJobTermination); |
| } |
| |
| /// Record that any critical process is in some stage of being torn down. |
| pub fn set_critical_process_dying() { |
| CRITICAL_PROCESS_DYING.store(true, Ordering::Release); |
| } |
| |
| /// Returns whether any critical process is currently dying. |
| pub fn get_critical_process_dying() -> bool { |
| CRITICAL_PROCESS_DYING.load(Ordering::Acquire) |
| } |
| |
| /// Record the dead process responsible for getting the root job killed. |
| pub fn critical_process_kill(dead_process: &ProcessDispatcher) { |
| ksync::lock!(let mut guard = CriticalProcessNameLock::lock()); |
| let token = guard.as_mut().token_mut(); |
| // SAFETY: `token` proves the lock is held. |
| let current_koid = unsafe { *CRITICAL_PROCESS_KOID.get(token) }; |
| if current_koid == ZX_KOID_INVALID { |
| let mut name = [0u8; ZX_MAX_NAME_LEN]; |
| let status = dead_process.get_name(&mut name); |
| debug_assert!(status.is_ok()); |
| // SAFETY: `token` proves exclusive access to the cell. |
| unsafe { |
| *CRITICAL_PROCESS_NAME.get_mut(token) = name; |
| *CRITICAL_PROCESS_KOID.get_mut(token) = dead_process.get_koid(); |
| } |
| } |
| } |
| |
| /// Returns the name of the critical process that caused root job termination. |
| pub fn get_critical_process_name() -> [u8; ZX_MAX_NAME_LEN] { |
| ksync::lock!(let guard = CriticalProcessNameLock::lock()); |
| let token = guard.token(); |
| // SAFETY: `token` proves the lock is held. |
| unsafe { *CRITICAL_PROCESS_NAME.get(token) } |
| } |
| |
| /// Returns the KOID of the critical process that caused root job termination. |
| pub fn get_critical_process_koid() -> zx_koid_t { |
| ksync::lock!(let guard = CriticalProcessNameLock::lock()); |
| let token = guard.token(); |
| // SAFETY: `token` proves the lock is held. |
| unsafe { *CRITICAL_PROCESS_KOID.get(token) } |
| } |
| |
| #[cfg(ktest)] |
| fn reset_critical_process_state_for_test() -> SavedCriticalProcessState { |
| ksync::lock!(let mut guard = CriticalProcessNameLock::lock()); |
| let token = guard.as_mut().token_mut(); |
| // SAFETY: `token` proves exclusive access to the cells. |
| let (name, koid) = unsafe { |
| let old_name = *CRITICAL_PROCESS_NAME.get(token); |
| let old_koid = *CRITICAL_PROCESS_KOID.get(token); |
| *CRITICAL_PROCESS_NAME.get_mut(token) = [0u8; ZX_MAX_NAME_LEN]; |
| *CRITICAL_PROCESS_KOID.get_mut(token) = ZX_KOID_INVALID; |
| (old_name, old_koid) |
| }; |
| let dying = CRITICAL_PROCESS_DYING.swap(false, Ordering::AcqRel); |
| SavedCriticalProcessState { name, koid, dying } |
| } |
| } |
| |
| #[cfg(ktest)] |
| #[must_use] |
| struct SavedCriticalProcessState { |
| name: [u8; ZX_MAX_NAME_LEN], |
| koid: zx_koid_t, |
| dying: bool, |
| } |
| |
| #[cfg(ktest)] |
| impl SavedCriticalProcessState { |
| fn restore(self) { |
| ksync::lock!(let mut guard = CriticalProcessNameLock::lock()); |
| let token = guard.as_mut().token_mut(); |
| // SAFETY: `token` proves exclusive access to the cells. |
| unsafe { |
| *CRITICAL_PROCESS_NAME.get_mut(token) = self.name; |
| *CRITICAL_PROCESS_KOID.get_mut(token) = self.koid; |
| } |
| CRITICAL_PROCESS_DYING.store(self.dying, Ordering::Release); |
| } |
| } |
| |
| impl Drop for RootJobObserver { |
| fn drop(&mut self) { |
| let mut out_signals: zx_signals_t = 0; |
| // SAFETY: self.signal_observer was registered with self.root_job. |
| unsafe { |
| self.root_job.remove_observer(self.signal_observer.as_mut_ptr(), &mut out_signals); |
| cpp_root_job_signal_observer_destroy(self.signal_observer.as_mut_ptr()); |
| } |
| } |
| } |
| |
| // Trampolines exported to C++: |
| |
| /// Called by C++ `SignalObserver::OnMatch` under `JobDispatcher` lock. |
| /// |
| /// # Safety |
| /// |
| /// `rust_ctx` must point to a live `RootJobObserver` if non-null. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "C" fn rust_root_job_observer_on_match( |
| rust_ctx: *mut c_void, |
| _signals: zx_signals_t, |
| ) { |
| // Remember, the root job's dispatcher lock is held for the duration of |
| // this method. Take care to avoid calling anything that might attempt to |
| // acquire that lock. |
| #[cfg(ktest)] |
| if !rust_ctx.is_null() { |
| // SAFETY: rust_ctx is guaranteed to point to a live RootJobObserver and this on_match |
| // callback is the only thing accessing the callback field. |
| let callback = unsafe { &mut (*(rust_ctx as *mut RootJobObserver)).callback }; |
| if let Some(callback) = callback.as_mut() { |
| callback(); |
| return; |
| } |
| } |
| #[cfg(not(ktest))] |
| let _ = rust_ctx; |
| RootJobObserver::halt(); |
| } |
| |
| /// Called by C++ `SignalObserver::OnCancel` under `JobDispatcher` lock. |
| /// |
| /// # Safety |
| /// |
| /// `rust_ctx` is the context pointer passed during observer creation. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "C" fn rust_root_job_observer_on_cancel( |
| _rust_ctx: *mut c_void, |
| _signals: zx_signals_t, |
| ) { |
| } |
| |
| /// Initializes a `RootJobObserver` in the provided `storage` from C++. |
| /// |
| /// # Safety |
| /// |
| /// `storage` must point to valid uninitialized memory of size and alignment matching `RootJobObserver`. |
| /// `root_job` must point to a live `JobDispatcher` whose refcount was exported. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "C" fn rust_root_job_observer_init( |
| storage: *mut MaybeUninit<RootJobObserver>, |
| root_job: *mut JobDispatcher, |
| root_job_handle: *mut c_void, |
| ) { |
| // SAFETY: root_job was exported with a transferred refcount and is not null. |
| let root_job_ptr = unsafe { RefPtr::from_raw(root_job) }; |
| |
| let root_job_handle_ref = core::ptr::NonNull::new(root_job_handle).map(|ptr| { |
| // SAFETY: The caller has ensured that root_job_handle points to a valid handle and there can |
| // be no other possible threads interacting with a handle table so the handle table lock is |
| // not required. |
| unsafe { HandleRef::from_raw(ptr) } |
| }); |
| |
| // SAFETY: storage is guaranteed by caller to point to valid uninitialized memory for RootJobObserver. |
| unsafe { RootJobObserver::init_in_storage(storage, root_job_ptr, root_job_handle_ref) }; |
| } |
| |
| /// Destroys a `RootJobObserver` initialized in `storage`. |
| /// |
| /// # Safety |
| /// |
| /// `storage` must point to an initialized `RootJobObserver`. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "C" fn rust_root_job_observer_destroy(storage: *mut RootJobObserver) { |
| // SAFETY: storage points to a live RootJobObserver. |
| unsafe { |
| core::ptr::drop_in_place(storage); |
| } |
| } |
| |
| /// Halts the platform via `RootJobObserver::halt()`. |
| #[unsafe(no_mangle)] |
| pub extern "C" fn rust_root_job_observer_halt() { |
| RootJobObserver::halt(); |
| } |
| |
| /// Notifies Rust that a critical process is dying. |
| #[unsafe(no_mangle)] |
| pub extern "C" fn rust_root_job_observer_set_critical_process_dying() { |
| RootJobObserver::set_critical_process_dying(); |
| } |
| |
| /// Checks whether any critical process is dying. |
| #[unsafe(no_mangle)] |
| pub extern "C" fn rust_root_job_observer_get_critical_process_dying() -> bool { |
| RootJobObserver::get_critical_process_dying() |
| } |
| |
| /// Records the dead process causing root job kill. |
| #[unsafe(no_mangle)] |
| pub extern "C" fn rust_root_job_observer_critical_process_kill(dead_process: &ProcessDispatcher) { |
| RootJobObserver::critical_process_kill(dead_process); |
| } |
| |
| /// Retrieves the critical process name. |
| /// |
| /// # Safety |
| /// |
| /// `out_name` must point to a buffer of at least `ZX_MAX_NAME_LEN` bytes. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "C" fn rust_root_job_observer_get_critical_process_name(out_name: *mut u8) { |
| let name = RootJobObserver::get_critical_process_name(); |
| // SAFETY: caller guarantees `out_name` has room for `ZX_MAX_NAME_LEN` bytes. |
| unsafe { |
| core::ptr::copy_nonoverlapping(name.as_ptr(), out_name, ZX_MAX_NAME_LEN); |
| } |
| } |
| |
| /// Retrieves the critical process KOID. |
| #[unsafe(no_mangle)] |
| pub extern "C" fn rust_root_job_observer_get_critical_process_koid() -> zx_koid_t { |
| RootJobObserver::get_critical_process_koid() |
| } |
| |
| /// In-tree kernel unit tests for `RootJobObserver`. |
| #[cfg(ktest)] |
| #[unittest::suite(name = "root_job_observer")] |
| mod tests { |
| use core::sync::atomic::{AtomicU32, Ordering}; |
| use fbl::RefPtr; |
| use unittest::expect_true; |
| use zx_status::Status; |
| |
| use super::super::handle::KernelHandle; |
| use super::super::job_dispatcher::JobDispatcher; |
| use super::super::process_dispatcher::ProcessDispatcher; |
| use super::super::root_job_observer_ffi::{ |
| cpp_test_process_is_dead, cpp_test_process_is_running, cpp_test_thread_is_dying_or_dead, |
| }; |
| use super::super::thread_dispatcher::ThreadDispatcher; |
| use super::RootJobObserver; |
| use crate::arch_rs::UserEntryState; |
| use crate::kernel::deadline::{Deadline, TimerSlack}; |
| use crate::kernel::event::Event; |
| use crate::platform_rs::timer::DurationMono; |
| |
| /// Create a process inside the given job. |
| fn create_process(parent_job: RefPtr<JobDispatcher>) -> KernelHandle<ProcessDispatcher> { |
| let (proc_handle, _rights, _vmar_handle, _vmar_rights) = |
| ProcessDispatcher::create(parent_job, b"unittest_process", 0) |
| .expect("failed to create process"); |
| proc_handle |
| } |
| |
| /// Create a suspended thread inside the given process. |
| fn create_thread(parent_process: RefPtr<ProcessDispatcher>) -> KernelHandle<ThreadDispatcher> { |
| let (thread_handle, _rights) = |
| ThreadDispatcher::create(parent_process, 0, b"unittest_thread") |
| .expect("failed to create thread"); |
| let thread = thread_handle.dispatcher(); |
| thread.initialize().expect("failed to initialize thread"); |
| thread.suspend().expect("failed to suspend thread"); |
| let entry = UserEntryState { pc: 0, sp: 0, arg1: 0, arg2: 0, tp: 0, abi_reg: 0 }; |
| thread.start(entry, true).expect("failed to start thread"); |
| thread_handle |
| } |
| |
| struct SendPtr<T>(*const T); |
| impl<T> Clone for SendPtr<T> { |
| fn clone(&self) -> Self { |
| *self |
| } |
| } |
| impl<T> Copy for SendPtr<T> {} |
| // SAFETY: Raw pointer wrapper used to pass pointers into test closures safely. |
| unsafe impl<T> Send for SendPtr<T> {} |
| unsafe impl<T> Sync for SendPtr<T> {} |
| |
| impl<T> SendPtr<T> { |
| fn get(self) -> *const T { |
| self.0 |
| } |
| } |
| |
| /// Exercise basic creation/destruction of the RootJobObserver. |
| #[test] |
| fn test_create_destroy() { |
| pin_init::stack_pin_init!(let event = Event::init_unsignaled()); |
| let root_job = JobDispatcher::create_root_job(); |
| let event_ptr = SendPtr(event.as_raw()); |
| |
| // Create and destroy the root job observer. |
| let observer = RootJobObserver::new_with_callback(root_job, None, move || { |
| let event_ptr = event_ptr; |
| // SAFETY: `event_ptr` points to the pinned `event` on the caller's stack frame. |
| let evt = unsafe { Event::from_raw_ref(event_ptr.get()) }; |
| evt.signal(); |
| }); |
| expect_true!(observer.is_ok()); |
| |
| // Ensure the callback fired. |
| expect_true!(event.wait_infinite().is_ok()); |
| } |
| |
| /// Ensure that the callback fires when the root job is killed. |
| #[test] |
| fn test_callback_fires_on_root_job_death() { |
| pin_init::stack_pin_init!(let event = Event::init_unsignaled()); |
| // Create the root job with a child process, and start watching it. |
| let root_job = JobDispatcher::create_root_job(); |
| let _child_process = create_process(root_job.clone()); |
| let event_ptr = SendPtr(event.as_raw()); |
| |
| let observer = RootJobObserver::new_with_callback(root_job.clone(), None, move || { |
| let event_ptr = event_ptr; |
| // SAFETY: `event_ptr` points to the pinned `event` on the caller's stack frame. |
| let evt = unsafe { Event::from_raw_ref(event_ptr.get()) }; |
| evt.signal(); |
| }); |
| expect_true!(observer.is_ok()); |
| |
| // Shouldn't be signalled yet. |
| let deadline = Deadline::after_mono(DurationMono::from_millis(1), TimerSlack::none()); |
| expect_true!(event.wait(&deadline) == Err(Status::TIMED_OUT)); |
| |
| // Kill the root job. |
| expect_true!(root_job.kill(1)); |
| |
| // Ensure we are signalled. |
| expect_true!(event.wait_infinite().is_ok()); |
| } |
| |
| /// Test that by the time the RootJobObserver callback fires due to the root job being killed, all of the root job's children have already been terminated. |
| #[test] |
| fn test_children_already_dead_when_callback_fires() { |
| pin_init::stack_pin_init!(let event = Event::init_unsignaled()); |
| // Create a new root job, containing a process and a thread. |
| let root_job = JobDispatcher::create_root_job(); |
| let child_process = create_process(root_job.clone()); |
| let child_thread = create_thread(child_process.dispatcher().clone()); |
| |
| let event_ptr = SendPtr(event.as_raw()); |
| let proc_ptr = SendPtr((&**child_process.dispatcher()) as *const ProcessDispatcher); |
| let thread_ptr = SendPtr((&**child_thread.dispatcher()) as *const ThreadDispatcher); |
| |
| // Create a root job observer. The callback ensures that the child process and thread are both dead when it fires. |
| let observer = RootJobObserver::new_with_callback(root_job.clone(), None, move || { |
| // SAFETY: pointers are valid for the test duration. |
| unsafe { |
| assert!(cpp_test_process_is_dead(proc_ptr.get())); |
| assert!(cpp_test_thread_is_dying_or_dead(thread_ptr.get())); |
| let evt = Event::from_raw_ref(event_ptr.get()); |
| evt.signal(); |
| } |
| }); |
| expect_true!(observer.is_ok()); |
| |
| // Ensure everything is running. |
| // SAFETY: pointers are valid. |
| unsafe { |
| expect_true!(cpp_test_process_is_running(proc_ptr.get())); |
| expect_true!(!cpp_test_thread_is_dying_or_dead(thread_ptr.get())); |
| } |
| |
| // Kill the parent job. |
| expect_true!(root_job.kill(1)); |
| |
| // Wait for the callback to fire. |
| expect_true!(event.wait_infinite().is_ok()); |
| } |
| |
| /// Ensure that the RootJobObserver callback fires when the root job has no children, even if the root job itself is not killed. |
| #[test] |
| fn test_callback_fires_when_no_children() { |
| pin_init::stack_pin_init!(let event = Event::init_unsignaled()); |
| // Create a new root job, containing a process and a thread. |
| let root_job = JobDispatcher::create_root_job(); |
| let child_process = create_process(root_job.clone()); |
| let child_thread = create_thread(child_process.dispatcher().clone()); |
| |
| let event_ptr = SendPtr(event.as_raw()); |
| let proc_ptr = SendPtr((&**child_process.dispatcher()) as *const ProcessDispatcher); |
| let thread_ptr = SendPtr((&**child_thread.dispatcher()) as *const ThreadDispatcher); |
| |
| // Create a root job observer. The callback ensures that the child process and thread are both dead when it fires. |
| let observer = RootJobObserver::new_with_callback(root_job, None, move || { |
| let (event_ptr, proc_ptr, thread_ptr) = (event_ptr, proc_ptr, thread_ptr); |
| // SAFETY: `event_ptr` points to the pinned `event` on the caller's stack frame. |
| unsafe { |
| assert!(cpp_test_process_is_dead(proc_ptr.get())); |
| assert!(cpp_test_thread_is_dying_or_dead(thread_ptr.get())); |
| let evt = Event::from_raw_ref(event_ptr.get()); |
| evt.signal(); |
| } |
| }); |
| expect_true!(observer.is_ok()); |
| |
| // Ensure everything is running. |
| // SAFETY: pointers are valid. |
| unsafe { |
| expect_true!(cpp_test_process_is_running(proc_ptr.get())); |
| expect_true!(!cpp_test_thread_is_dying_or_dead(thread_ptr.get())); |
| } |
| |
| // Kill the process. |
| child_process.dispatcher().kill(1); |
| |
| // Ensure the callback fires. |
| expect_true!(event.wait_infinite().is_ok()); |
| } |
| |
| /// Ensure that it's safe for multiple observers to observe termination of a root job. |
| #[test] |
| fn test_multiple_observers_one_job() { |
| static COUNT: AtomicU32 = AtomicU32::new(0); |
| COUNT.store(0, Ordering::SeqCst); |
| |
| // Create a new root job containing one process. |
| let root_job = JobDispatcher::create_root_job(); |
| let child_process = create_process(root_job.clone()); |
| |
| // Create two observers. |
| let observer1 = RootJobObserver::new_with_callback(root_job.clone(), None, || { |
| COUNT.fetch_add(1, Ordering::SeqCst); |
| }); |
| let observer2 = RootJobObserver::new_with_callback(root_job, None, || { |
| COUNT.fetch_add(1, Ordering::SeqCst); |
| }); |
| expect_true!(observer1.is_ok()); |
| expect_true!(observer2.is_ok()); |
| |
| // Kill the process. |
| expect_true!(COUNT.load(Ordering::SeqCst) == 0); |
| child_process.dispatcher().kill(1); |
| |
| // Ensure the callback fired twice. |
| expect_true!(COUNT.load(Ordering::SeqCst) == 2); |
| } |
| |
| /// Verifies critical process tracking records the dead process and ignores subsequent calls. |
| #[test] |
| fn test_critical_process_kill() { |
| let saved = RootJobObserver::reset_critical_process_state_for_test(); |
| |
| let root_job = JobDispatcher::create_root_job(); |
| let proc1 = create_process(root_job.clone()); |
| let koid1 = proc1.dispatcher().get_koid(); |
| |
| // Kill process 1 and record it. |
| RootJobObserver::critical_process_kill(proc1.dispatcher()); |
| expect_true!(RootJobObserver::get_critical_process_koid() == koid1); |
| let name1 = RootJobObserver::get_critical_process_name(); |
| expect_true!(&name1[..16] == b"unittest_process"); |
| |
| // Attempting to record a second process must not overwrite the initial dead process. |
| let (proc2, _rights, _vmar, _vmar_rights) = |
| ProcessDispatcher::create(root_job, b"second_process", 0) |
| .expect("failed to create proc2"); |
| let koid2 = proc2.dispatcher().get_koid(); |
| expect_true!(koid1 != koid2); |
| |
| RootJobObserver::critical_process_kill(proc2.dispatcher()); |
| expect_true!(RootJobObserver::get_critical_process_koid() == koid1); |
| let name2 = RootJobObserver::get_critical_process_name(); |
| expect_true!(&name2[..16] == b"unittest_process"); |
| |
| saved.restore(); |
| } |
| |
| /// Verifies setting and getting the critical process dying state. |
| #[test] |
| fn test_critical_process_dying_state() { |
| let saved = RootJobObserver::reset_critical_process_state_for_test(); |
| expect_true!(!RootJobObserver::get_critical_process_dying()); |
| RootJobObserver::set_critical_process_dying(); |
| expect_true!(RootJobObserver::get_critical_process_dying()); |
| saved.restore(); |
| } |
| |
| /// Verifies reading the critical process name and koid. |
| #[test] |
| fn test_critical_process_name_and_koid() { |
| let saved = RootJobObserver::reset_critical_process_state_for_test(); |
| let koid = RootJobObserver::get_critical_process_koid(); |
| let name = RootJobObserver::get_critical_process_name(); |
| expect_true!(koid == 0); |
| expect_true!(name[0] == 0); |
| saved.restore(); |
| } |
| |
| /// Exercise init_in_storage and destruction. |
| #[test] |
| fn test_init_in_storage() { |
| let root_job = JobDispatcher::create_root_job(); |
| let _child_process = create_process(root_job.clone()); |
| let mut storage = core::mem::MaybeUninit::<RootJobObserver>::uninit(); |
| // SAFETY: storage is properly allocated on the stack with matching size and alignment. |
| unsafe { RootJobObserver::init_in_storage(&mut storage, root_job, None) }; |
| // SAFETY: storage was initialized successfully. |
| unsafe { |
| core::ptr::drop_in_place(storage.as_mut_ptr()); |
| } |
| } |
| } |