// Copyright 2023 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Worker thread abstraction use std::panic; use std::thread; use std::thread::JoinHandle; use std::thread::Thread; use crate::Error; use crate::Event; /// Wrapper object for creating a worker thread that can be stopped by signaling an event. pub struct WorkerThread { worker: Option<(Event, JoinHandle)>, } impl WorkerThread { /// Starts a worker thread named `thread_name` running the `thread_func` function. /// /// The `thread_func` implementation must monitor the provided `Event` and return from the /// thread when it is signaled. /// /// Call [`stop()`](Self::stop) to stop the thread. pub fn start(thread_name: impl Into, thread_func: F) -> Self where F: FnOnce(Event) -> T + Send + 'static, { let stop_event = Event::new().expect("Event::new() failed"); let thread_stop_event = stop_event.try_clone().expect("Event::try_clone() failed"); let thread_handle = thread::Builder::new() .name(thread_name.into()) .spawn(move || thread_func(thread_stop_event)) .expect("thread spawn failed"); WorkerThread { worker: Some((stop_event, thread_handle)), } } /// Stops the worker thread. /// /// Returns the value returned by the function running in the thread. pub fn stop(mut self) -> T { // The only time the internal `Option` should be `None` is in a `drop` after `stop`, so this // `expect()` should never fail. self.stop_internal().expect("invalid worker state") } // `stop_internal` accepts a reference so it can be called from `drop`. #[doc(hidden)] fn stop_internal(&mut self) -> Option { self.worker.take().map(|(stop_event, thread_handle)| { // There is nothing the caller can do to handle `stop_event.signal()` failure, and we // don't want to leave the thread running, so panic in that case. stop_event .signal() .expect("WorkerThread stop event signal failed"); match thread_handle.join() { Ok(v) => v, Err(e) => panic::resume_unwind(e), } }) } /// Signal thread's stop event. Unlike stop, the function doesn't wait /// on joining the thread. /// The function can be called multiple times. /// Calling `stop` or `drop` will internally signal the stop event again /// and join the thread. pub fn signal(&mut self) -> Result<(), Error> { if let Some((event, _)) = &mut self.worker { event.signal() } else { Ok(()) } } /// Returns a handle to the running thread. pub fn thread(&self) -> &Thread { // The only time the internal `Option` should be `None` is in a `drop` after `stop`, so this // `unwrap()` should never fail. self.worker.as_ref().unwrap().1.thread() } } impl Drop for WorkerThread { /// Stops the thread if the `WorkerThread` is dropped without calling [`stop()`](Self::stop). fn drop(&mut self) { let _ = self.stop_internal(); } }