For our more complex plugins, setting a state involves restoring data structures for our inter-plugin-communication implementation that will result in calling listener callbacks that often end up triggering component changes in the editor. Previously, we had no guarantees about threading here, so there were locks all over the place in the underlying implementation and juce::MessageManager::callAsync calls in nearly every editor-side listener.
I’m working on a rather big cleanup of the underlying implementation and decided to make the implementation entirely message thread bound, which streamlined the existing plugin code dramatically but leaves me with the issue that in some hosts, especially Pro Tools, setStateInformation is called from a non-message thread.
I now added three juce::MessageManagerLock instances around relevant sub-function calls in the setStateInformation implementation and it seems to work flawless in Pro Tools according to my current testing, so I’m relatively confident that this is a good solution.
Still, I’m a bit unsure if I overlooked some possible caveat here just taking that lock on the pro tools state restoration thread? I found an older forum thread about deadlocks in that area, but the creator of that topic used the message manager lock constructor overload that takes a juce::Thread instance pointer, while my implementation uses the argumentless constructor that possibly waits forever.
What I can say is that project closing scenarios can be fragile and need to be inspected.
I have a thought, but it only concerns getStateInformation, not setStateInformation.
The host tries to close the project on the message thread and receives the state from another thread. If you then perform a message manager lock, it results in a deadlock.
For this reason, I chose to avoid using any MML locks, because you can never be sure if the host for what ever reason choose to call set/getStateInformation at that point.
(If I remember correctly there was an option to force Pro Tools to do that on the message thread, but that might be deprecated)
Interesting enough, I could only observe setStateInformation, so the AAX SetChunk being called from a non-main tread while getStateInformation/GetChunk was always called from the main thread during my testing, so I only cared about message manager locking in setStateInformation for now.
However, there is this property in the the AAX format as you mentioned, which is documented like this
/** \brief Indicates whether the plug-in supports SetChunk and GetChunk calls on threads other than the main thread.
* It is actually important for plug-ins to support these calls on non-main threads, so that is the default.
* However, in response to a few companies having issues with this, we have decided to support this constraint
* for now.
*
* property value should be set to true if you need Chunk calls on the main thread.
*
* Values: 0 (off, default), 1 (on)
*
* \li Apply this property at the \ref AAX_IEffectDescriptor level
*/
AAX_eProperty_RequiresChunkCallsOnMainThread = 308,
This documentation indicates that both, SetChunk and GetChunk calls could be expected from threads other than the main thread. So I’m wondering if anyone knows about certain interactions in Pro Tools that would lead to GetChunk calls coming from a non-main thread?
It’s also interesting to consider just setting this flag, but from what I get from reading the docs, there is a strong preferrence expressed by Avid here to just make plugins support setting that flag and a wording like “we have decided to support this constraint for now” sounds like that could change at some time.
I’m really undecided on what to decide here, as according to my testing limited scope message manager locks in setStateInformation and none at all in getStateInformation seems to work just great, so don’t want to over-complicate things too
By the way, is there any other host besided pro-tools known to call these functions from non-main threads?
AU/CLAP/VST3 hosts are explicitly forbidden from doing this. Not to say they can’t or won’t, but just that the plugin API specifies not to.
btw I think the reason they have worded it that way is because this is actually a really useful thing for users, blocking the main thread for state serialization/deserialization is a bad experience when you have hundreds of plugin instances that all require time slices on the main thread…
Totally agree on this one, this is why I actually like my approach on only taking the message manager lock in a few sub-function calls in the state restoring implementation which might end up calling editor-side listener interfaces, which will probably update components, most of the heavy deserialization lifting would be done without holding the lock.
be careful! in some hosts this could lead to a dead-lock. especially with AudioUnits (in case you support them). try running pluginval with high strictness. if i remember correctly digital performer using AU did dead-lock in real world
Yes, we ship all plugins as VST, VST3, AU and AAX. Pro Tools was just the only host that I happened to know that calls this funciton from a different thread all the time, therefore I focused on it at first. But I’m basically interested in all known cases where doing that caused someone troubles. Will definetively have a closer look at digital performer then!
So I tested a bit with Digital Performer 11 and AU and could not see any non-message thread callback nor trigger any deadlock with my current approach. Do you remember any more details on how this locked up in a real-world scenario?
Alright, finally found a case where I could trigger a deadlock and that’s running Apple auval with the args -strict -stress -v. This will make it apply a state from a non-main thread while, according to the call stacks observable in the debugger the main thread just waits for a thread to join without driving the message loop. This came up in our CI since auval is invoked there through pluginval 1.0.3 which applies this exact set of flags.
This does not happen with auval running without the strict and stress flag, so it might be up to discussion if a command line app driving no message loop at all on the main thread is close to any real-world scenario, but having a plugin not surviving auval in strict mode seems undesirable. So I’m gonna have to look for some more sophisticated solution here
Perhaps you could call another function to do the work? Then, if on the message thread, call that fuunction directly with the given data, and if not on the message thread, then copy the data to a member and call that function asynchronously using that copy of the data instead?
Yeah, that’s the obvious approach here but in a real-world scenario it’s a bit tricky with our connected smart series plug-ins.
Since all our plug-ins listen to each other and the UI side group views dynamically update to display the existence and adapt to the state of other plugin instances, setting the state of plugin instance A can trigger a listener callback in plugin instance B.
This also means, setting each plugins state mutates the a shared state between all instances. For this reason, it’s very desirable that the mutation of this shared state appears just in the order of the changes coming in from various plugin instances being restored. Deferring that state mutation into some async callback really makes the implementation much more complicated and a central point of my current clean up that lead to this question was to reduce the edge cases for such scenarios. But it’s not impossible to do.
What I find most challenging here is that the plugin state of course also contains a set of processing parameters and a whole processor state. I think that this state must be applied synchronously to be set up correctly before a prepare callback comes in after applying a state. So if the whole state is applied later asynchronoulsy, there are chances that the plugin is prepared before the new state has been applied async. And now I’m possibly setting part of my state information synchronously and defer other parts async, which I would have liked to avoid at all cost if possible
I still didn’t understand the actual need to sync and lock the message thread.
With interconnected plugins it might be possible you’ll have some “latency” applying changes.
The MessageThread in most cases is linked with UI.
And it’s always possible each instance will load out of sync so I don’t understand why order matters here..
A possible approach (singleton example to simplify it) would be a set function on a singleton with atomic way of updating the state and then notifying UI to update on messagethread.
You can expand that with other ideas. But since the message thread is heavily used I’d just use callAsync once I have some state needed to be updated on UI
For this reason, it’s very desirable that the mutation of this shared state appears just in the order of the changes coming in from various plugin instances being restored.
This problem (that Pro Tools calls setState from another thread) has been discussed multiple times on the forum already. I find it interesting that the problem doesn’t seem to have an obvious solution. i truly wonder how popular plugins handle this, or if they don’t handle this at all… and just perform main thread stuff on this background thread even though it’s not really safe to do so.
The same problem can apply to getState by the way, and I have seen AU hosts do that. But you can solve that by keeping a thread-safe cache of your state.