RustAudio / vst-rs

VST 2.4 API implementation in rust. Create plugins or hosts. Previously rust-vst on the RustDSP group.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 the Plugin in the opcode branches that actually access the Plugin. 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 the PluginCache directly. Replace the get_cache() method on AEffect by methods for the individual fields, e.g. unsafe fn get_info(&mut self) -> &Info { &(*(self.user as *mut PluginCache)).info }.
  • For the info and params members of the PluginCache, 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 the Editor.

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.