diff --git a/src/event_loop/linux.rs b/src/event_loop/linux.rs index ae0be6cf..315dc374 100644 --- a/src/event_loop/linux.rs +++ b/src/event_loop/linux.rs @@ -2,11 +2,10 @@ //! of a main thread does not exist there. Because of that, this mostly just serves as a way to //! delegate expensive processing to another thread. -use crossbeam::channel; -use std::sync::{Arc, Weak}; -use std::thread::{self, JoinHandle, ThreadId}; +use std::sync::Arc; +use std::thread::{self, ThreadId}; -use super::{EventLoop, MainThreadExecutor}; +use super::{BackgroundThread, EventLoop, MainThreadExecutor}; use crate::util::permit_alloc; /// See [`EventLoop`][super::EventLoop]. @@ -16,25 +15,13 @@ pub(crate) struct LinuxEventLoop { /// directly. executor: Arc, + /// The actual background thread. The implementation is shared with the background thread used + /// in other backends. + background_thread: BackgroundThread, + /// The ID of the main thread. In practice this is the ID of the thread that created this task /// queue. main_thread_id: ThreadId, - - /// A thread that act as our worker thread. When [`schedule_gui()`][Self::schedule_gui()] is - /// called, this thread will be woken up to execute the task on the executor. This is wrapped in - /// an `Option` so the thread can be taken out of it and joined when this struct gets dropped. - worker_thread: Option>, - /// A channel for waking up the worker thread and having it perform one of the tasks from - /// [`Message`]. - tasks_sender: channel::Sender>, -} - -/// A message for communicating with the worker thread. -enum Message { - /// A new task for the event loop to execute. - Task(T), - /// Shut down the worker thread. - Shutdown, } impl EventLoop for LinuxEventLoop @@ -43,19 +30,10 @@ where E: MainThreadExecutor + 'static, { fn new_and_spawn(executor: Arc) -> Self { - let (tasks_sender, tasks_receiver) = channel::bounded(super::TASK_QUEUE_CAPACITY); - Self { executor: executor.clone(), + background_thread: BackgroundThread::get_or_create(executor), main_thread_id: thread::current().id(), - // With our drop implementation we guarantee that this thread never outlives this struct - worker_thread: Some( - thread::Builder::new() - .name(String::from("worker")) - .spawn(move || worker_thread(tasks_receiver, Arc::downgrade(&executor))) - .expect("Could not spawn worker thread"), - ), - tasks_sender, } } @@ -64,14 +42,14 @@ where self.executor.execute(task, true); true } else { - self.tasks_sender.try_send(Message::Task(task)).is_ok() + self.background_thread.schedule(task) } } fn schedule_background(&self, task: T) -> bool { // This event loop implementation already uses a thread that's completely decoupled from the // operating system's or the host's main thread, so we don't need _another_ thread here - self.tasks_sender.try_send(Message::Task(task)).is_ok() + self.background_thread.schedule(task) } fn is_main_thread(&self) -> bool { @@ -80,45 +58,3 @@ where permit_alloc(|| thread::current().id() == self.main_thread_id) } } - -impl Drop for LinuxEventLoop { - fn drop(&mut self) { - self.tasks_sender - .send(Message::Shutdown) - .expect("Failed while sending worker thread shutdown request"); - if let Some(join_handle) = self.worker_thread.take() { - join_handle.join().expect("Worker thread panicked"); - } - } -} - -/// The worker thread used in [`EventLoop`] that executes incoming tasks on the event loop's -/// executor. -fn worker_thread(tasks_receiver: channel::Receiver>, executor: Weak) -where - T: Send, - E: MainThreadExecutor, -{ - loop { - match tasks_receiver.recv() { - Ok(Message::Task(task)) => match executor.upgrade() { - Some(e) => e.execute(task, true), - None => { - nih_trace!( - "Received a new task but the executor is no longer alive, shutting down \ - worker" - ); - return; - } - }, - Ok(Message::Shutdown) => return, - Err(err) => { - nih_trace!( - "Worker thread got disconnected unexpectedly, shutting down: {}", - err - ); - return; - } - } - } -}