Plugin trait should require Send
glowcoil opened this issue · comments
The changes in #65 went a long way to fixing the soundness issues brought up by #49. However, there are some remaining problems. The methods on the Plugin
trait are not called from multiple threads concurrently, but they are called from multiple threads sequentially:
- The
Plugin
struct is created on the UI thread - Setup methods (
get_parameter_object
,get_editor
) and suspended-state methods (init
, etc.) are called on the UI thread - Processing methods are called on one of potentially multiple audio threads
This constitutes moving the Plugin
struct between threads, so the trait should require implementation of Send
for the API to be sound. I don't believe the Editor
struct needs to implement Sync
, as it is created on the UI thread and then remains there.
Additionally, there is a problem on these lines, where mutable references are materialized to both the Plugin
and Editor
regardless of what thread dispatch
is being called from. This is undefined behavior, since it results in two &mut
references to the same values existing at the same time when dispatch
is called concurrently from both the UI and audio threads. These references should only be materialized when dispatch
is running on the proper thread.
Thanks for these well-spotted observations.
Even though it's technically a breaking change, I don't expect requiring plugins to be Send
to cause any big problems. Since it is derived automatically, most plugins will probably compile without any changes.
The multiple &mut
references to the Plugin
and PluginCache
in dispatch
are tricky. I suppose we can do something like:
- Only create the
&mut
to thePlugin
in the opcode branches that actually access thePlugin
. Since the opcodes that do this are never invoked concurrently, only one reference will ever exist. - Never create a reference to the
PluginCache
. When a reference to one of its members is needed, dereference the raw pointer to thePluginCache
directly. Replace theget_cache()
method onAEffect
by methods for the individual fields, e.g.unsafe fn get_info(&mut self) -> &Info { &(*(self.user as *mut PluginCache)).info }
. - For the
info
andparams
members of thePluginCache
, only ever create shared references, since these are used on multiple threads concurrently. - Only call the
Editor
access method (which will return&mut Editor
) in the opcode branches that access theEditor
.
Does this look like something that would be free of UB? Does calling &mut self
methods (of the AEffect
) on a raw pointer count as taking &mut
references to the AEffect
? If so, we could extract the access methods into top-level functions taking in *mut AEffect
.