1
0
Fork 0

Revert event loop APIs to use Weak instead of Arc

Previously the plugin wouldn't get dropped because the event loops kept
them alive. These APIs used Weak instead of Arc in the past precisely to
prevent this issue. Not sure why I changed that.
This commit is contained in:
Robbert van der Helm 2023-02-23 21:45:09 +01:00
parent 4564bf9027
commit 42a7bb37b4
8 changed files with 50 additions and 25 deletions

View file

@ -1,6 +1,6 @@
//! An internal event loop for spooling tasks to the/a GUI thread. //! An internal event loop for spooling tasks to the/a GUI thread.
use std::sync::Arc; use std::sync::Weak;
mod background_thread; mod background_thread;
@ -46,7 +46,7 @@ where
{ {
/// Create and start a new event loop. The thread this is called on will be designated as the /// Create and start a new event loop. The thread this is called on will be designated as the
/// main thread, so this should be called when constructing the wrapper. /// main thread, so this should be called when constructing the wrapper.
fn new_and_spawn(executor: Arc<E>) -> Self; fn new_and_spawn(executor: Weak<E>) -> Self;
/// Either post the function to the task queue so it can be delegated to the main thread, or /// Either post the function to the task queue so it can be delegated to the main thread, or
/// execute the task directly if this is the main thread. This function needs to be callable at /// execute the task directly if this is the main thread. This function needs to be callable at

View file

@ -19,7 +19,7 @@ pub(crate) struct BackgroundThread<T, E> {
/// The object that actually executes the task `T`. We'll send a weak reference to this to the /// The object that actually executes the task `T`. We'll send a weak reference to this to the
/// worker thread whenever a task needs to be executed. This allows multiple plugin instances to /// worker thread whenever a task needs to be executed. This allows multiple plugin instances to
/// share the same worker thread. /// share the same worker thread.
executor: Arc<E>, executor: Weak<E>,
/// A thread that act as our worker thread. When [`schedule()`][Self::schedule()] is called, /// A thread that act as our worker thread. When [`schedule()`][Self::schedule()] is called,
/// this thread will be woken up to execute the task on the executor. When the last worker /// this thread will be woken up to execute the task on the executor. When the last worker
/// thread handle gets dropped the thread is shut down. /// thread handle gets dropped the thread is shut down.
@ -52,7 +52,7 @@ where
T: Send + 'static, T: Send + 'static,
E: MainThreadExecutor<T> + 'static, E: MainThreadExecutor<T> + 'static,
{ {
pub fn get_or_create(executor: Arc<E>) -> Self { pub fn get_or_create(executor: Weak<E>) -> Self {
Self { Self {
executor, executor,
// The same worker thread can be shared by multiple instances. Lifecycle management // The same worker thread can be shared by multiple instances. Lifecycle management
@ -67,7 +67,7 @@ where
permit_alloc(|| { permit_alloc(|| {
self.worker_thread self.worker_thread
.tasks_sender .tasks_sender
.try_send(Message::Task((task, Arc::downgrade(&self.executor)))) .try_send(Message::Task((task, self.executor.clone())))
.is_ok() .is_ok()
}) })
} }

View file

@ -2,7 +2,7 @@
//! of a main thread does not exist there. Because of that, this mostly just serves as a way to //! 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. //! delegate expensive processing to another thread.
use std::sync::Arc; use std::sync::Weak;
use std::thread::{self, ThreadId}; use std::thread::{self, ThreadId};
use super::{BackgroundThread, EventLoop, MainThreadExecutor}; use super::{BackgroundThread, EventLoop, MainThreadExecutor};
@ -13,7 +13,7 @@ pub(crate) struct LinuxEventLoop<T, E> {
/// The thing that ends up executing these tasks. The tasks are usually executed from the worker /// The thing that ends up executing these tasks. The tasks are usually executed from the worker
/// thread, but if the current thread is the main thread then the task cna also be executed /// thread, but if the current thread is the main thread then the task cna also be executed
/// directly. /// directly.
executor: Arc<E>, executor: Weak<E>,
/// The actual background thread. The implementation is shared with the background thread used /// The actual background thread. The implementation is shared with the background thread used
/// in other backends. /// in other backends.
@ -29,7 +29,7 @@ where
T: Send + 'static, T: Send + 'static,
E: MainThreadExecutor<T> + 'static, E: MainThreadExecutor<T> + 'static,
{ {
fn new_and_spawn(executor: Arc<E>) -> Self { fn new_and_spawn(executor: Weak<E>) -> Self {
Self { Self {
executor: executor.clone(), executor: executor.clone(),
background_thread: BackgroundThread::get_or_create(executor), background_thread: BackgroundThread::get_or_create(executor),
@ -39,7 +39,13 @@ where
fn schedule_gui(&self, task: T) -> bool { fn schedule_gui(&self, task: T) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
self.executor.execute(task, true); match self.executor.upgrade() {
Some(executor) => executor.execute(task, true),
None => {
nih_debug_assert_failure!("GUI task was posted after the executor was dropped")
}
}
true true
} else { } else {
self.background_thread.schedule(task) self.background_thread.schedule(task)

View file

@ -9,7 +9,7 @@ use core_foundation::runloop::{
use crossbeam::channel::{self, Receiver, Sender}; use crossbeam::channel::{self, Receiver, Sender};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use std::os::raw::c_void; use std::os::raw::c_void;
use std::sync::Arc; use std::sync::Weak;
use super::{BackgroundThread, EventLoop, MainThreadExecutor}; use super::{BackgroundThread, EventLoop, MainThreadExecutor};
@ -24,7 +24,7 @@ pub(crate) struct MacOSEventLoop<T, E> {
/// The thing that ends up executing these tasks. The tasks are usually executed from the worker /// The thing that ends up executing these tasks. The tasks are usually executed from the worker
/// thread, but if the current thread is the main thread then the task cna also be executed /// thread, but if the current thread is the main thread then the task cna also be executed
/// directly. /// directly.
executor: Arc<E>, executor: Weak<E>,
/// A background thread for running tasks independently from the host's GUI thread. Useful for /// A background thread for running tasks independently from the host's GUI thread. Useful for
/// longer, blocking tasks. /// longer, blocking tasks.
@ -40,7 +40,7 @@ pub(crate) struct MacOSEventLoop<T, E> {
/// The data that is passed to the external run loop source callback function via a raw pointer. /// The data that is passed to the external run loop source callback function via a raw pointer.
/// The data is not accessed from the Rust side after creating it but it's kept here so as not /// The data is not accessed from the Rust side after creating it but it's kept here so as not
/// to get dropped. /// to get dropped.
_callback_data: Box<(Arc<E>, Receiver<T>)>, _callback_data: Box<(Weak<E>, Receiver<T>)>,
} }
impl<T, E> EventLoop<T, E> for MacOSEventLoop<T, E> impl<T, E> EventLoop<T, E> for MacOSEventLoop<T, E>
@ -48,7 +48,7 @@ where
T: Send + 'static, T: Send + 'static,
E: MainThreadExecutor<T> + 'static, E: MainThreadExecutor<T> + 'static,
{ {
fn new_and_spawn(executor: Arc<E>) -> Self { fn new_and_spawn(executor: Weak<E>) -> Self {
let (main_thread_sender, main_thread_receiver) = let (main_thread_sender, main_thread_receiver) =
channel::bounded::<T>(super::TASK_QUEUE_CAPACITY); channel::bounded::<T>(super::TASK_QUEUE_CAPACITY);
@ -88,7 +88,11 @@ where
fn schedule_gui(&self, task: T) -> bool { fn schedule_gui(&self, task: T) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
self.executor.execute(task, true); match self.executor.upgrade() {
Some(executor) => executor.execute(task, true),
None => nih_debug_assert_failure!("GUI task posted after the executor was dropped"),
}
true true
} else { } else {
// Only signal the main thread callback to be called if the task was added to the queue. // Only signal the main thread callback to be called if the task was added to the queue.
@ -131,7 +135,15 @@ where
T: Send + 'static, T: Send + 'static,
E: MainThreadExecutor<T> + 'static, E: MainThreadExecutor<T> + 'static,
{ {
let (executor, receiver) = unsafe { &*(info as *mut (Arc<E>, Receiver<T>)) }; let (executor, receiver) = unsafe { &*(info as *mut (Weak<E>, Receiver<T>)) };
let executor = match executor.upgrade() {
Some(executor) => executor,
None => {
nih_debug_assert_failure!("GUI task was posted after the executor was dropped");
return;
}
};
while let Ok(task) = receiver.try_recv() { while let Ok(task) = receiver.try_recv() {
executor.execute(task, true); executor.execute(task, true);
} }

View file

@ -5,7 +5,7 @@ use crossbeam::channel;
use std::ffi::{c_void, CString}; use std::ffi::{c_void, CString};
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Weak;
use std::thread::{self, ThreadId}; use std::thread::{self, ThreadId};
use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, PSTR, WPARAM}; use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, PSTR, WPARAM};
use windows::Win32::System::{ use windows::Win32::System::{
@ -37,7 +37,7 @@ pub(crate) struct WindowsEventLoop<T, E> {
/// The thing that ends up executing these tasks. The tasks are usually executed from the worker /// The thing that ends up executing these tasks. The tasks are usually executed from the worker
/// thread, but if the current thread is the main thread then the task cna also be executed /// thread, but if the current thread is the main thread then the task cna also be executed
/// directly. /// directly.
executor: Arc<E>, executor: Weak<E>,
/// The ID of the main thread. In practice this is the ID of the thread that created this task /// The ID of the main thread. In practice this is the ID of the thread that created this task
/// queue. /// queue.
@ -63,7 +63,7 @@ where
T: Send + 'static, T: Send + 'static,
E: MainThreadExecutor<T> + 'static, E: MainThreadExecutor<T> + 'static,
{ {
fn new_and_spawn(executor: Arc<E>) -> Self { fn new_and_spawn(executor: Weak<E>) -> Self {
let (tasks_sender, tasks_receiver) = channel::bounded(super::TASK_QUEUE_CAPACITY); let (tasks_sender, tasks_receiver) = channel::bounded(super::TASK_QUEUE_CAPACITY);
// Window classes need to have unique names or else multiple plugins loaded into the same // Window classes need to have unique names or else multiple plugins loaded into the same
@ -87,8 +87,7 @@ where
// can't pass the tasks queue and the executor to it directly, so this is a simple type // can't pass the tasks queue and the executor to it directly, so this is a simple type
// erased version of the polling loop. // erased version of the polling loop.
let callback: PollCallback = { let callback: PollCallback = {
let executor = Arc::downgrade(&executor); let executor = executor.clone();
Box::new(move || { Box::new(move || {
let executor = match executor.upgrade() { let executor = match executor.upgrade() {
Some(e) => e, Some(e) => e,
@ -137,7 +136,13 @@ where
fn schedule_gui(&self, task: T) -> bool { fn schedule_gui(&self, task: T) -> bool {
if self.is_main_thread() { if self.is_main_thread() {
self.executor.execute(task, true); match self.executor.upgrade() {
Some(executor) => executor.execute(task, true),
None => {
nih_debug_assert_failure!("GUI task was posted after the executor was dropped")
}
}
true true
} else { } else {
let success = self.tasks_sender.try_send(task).is_ok(); let success = self.tasks_sender.try_send(task).is_ok();

View file

@ -326,7 +326,7 @@ pub enum OutputParamEvent {
/// Because CLAP has this [`clap_host::request_host_callback()`] function, we don't need to use /// Because CLAP has this [`clap_host::request_host_callback()`] function, we don't need to use
/// `OsEventLoop` and can instead just request a main thread callback directly. /// `OsEventLoop` and can instead just request a main thread callback directly.
impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> { impl<P: ClapPlugin> EventLoop<Task<P>, Wrapper<P>> for Wrapper<P> {
fn new_and_spawn(_executor: std::sync::Arc<Self>) -> Self { fn new_and_spawn(_executor: Weak<Self>) -> Self {
panic!("What are you doing"); panic!("What are you doing");
} }
@ -713,7 +713,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// Same with the background thread // Same with the background thread
*wrapper.background_thread.borrow_mut() = *wrapper.background_thread.borrow_mut() =
Some(BackgroundThread::get_or_create(wrapper.clone())); Some(BackgroundThread::get_or_create(Arc::downgrade(&wrapper)));
wrapper wrapper
} }

View file

@ -248,7 +248,8 @@ impl<P: Plugin, B: Backend<P>> Wrapper<P, B> {
updated_state_receiver, updated_state_receiver,
}); });
*wrapper.event_loop.borrow_mut() = Some(OsEventLoop::new_and_spawn(wrapper.clone())); *wrapper.event_loop.borrow_mut() =
Some(OsEventLoop::new_and_spawn(Arc::downgrade(&wrapper)));
// The editor needs to be initialized later so the Async executor can work. // The editor needs to be initialized later so the Async executor can work.
*wrapper.editor.borrow_mut() = wrapper *wrapper.editor.borrow_mut() = wrapper

View file

@ -337,7 +337,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
// FIXME: Right now this is safe, but if we are going to have a singleton main thread queue // FIXME: Right now this is safe, but if we are going to have a singleton main thread queue
// serving multiple plugin instances, Arc can't be used because its reference count // serving multiple plugin instances, Arc can't be used because its reference count
// is separate from the internal COM-style reference count. // is separate from the internal COM-style reference count.
*wrapper.event_loop.borrow_mut() = Some(OsEventLoop::new_and_spawn(wrapper.clone())); *wrapper.event_loop.borrow_mut() =
Some(OsEventLoop::new_and_spawn(Arc::downgrade(&wrapper)));
// The editor also needs to be initialized later so the Async executor can work. // The editor also needs to be initialized later so the Async executor can work.
*wrapper.editor.borrow_mut() = wrapper *wrapper.editor.borrow_mut() = wrapper