Best practices for handling setStateInformation on background thread

Hi,

I’m developing an iOS AUv3 plug-in and have noticed that setStateInformation and prepareToPlay are being called on background threads in GarageBand (I haven’t tested this with other DAWs yet)

In my case (and likely most JUCE plug-ins), setStateInformation modifies the ValueTree. My UI, which observes the ValueTree, triggers repaints on changes in the ValueTree. This will lead to assertions and potential other issues. Additionally, there’s a risk of a data race if the UI modifies the ValueTree during a setStateInformation call. It feels like a difficult problem to solve, and I’m curious how others have addressed this issue.

I noticed that APVTS::replaceState uses a lock when replacing the ValueTree, but I’m not sure if this approach is entirely thread-safe, particularly if the UI modifies the ValueTree during replaceState.

I had a few ideas:

  1. Deferring setState using MessageManager::callAsync. This is not great because it will introduce a delay which could cause audio processing to begin before the state replace has completed. However, in practice this might be acceptable.
  2. Use a MessageManagerLock. Obviously, this is not great because it could deadlock. Also, this might not actually prevent all asserts, because some asserts trigger when code off the Message Thread, so a MessageManagerLock won’t prevent this assert.
  3. Add a global ValueTree lock mutex valueTreeLock which should guard any interaction with ValueTree’s. I don’t have a clear picture how this should be implemented (it will mess up my codebase) and I’m not sure how much data race proof this would be.
  4. Handle ValueTree callbacks on the UI asynchronously. This will prevent a lot of assets but won’t make it fully data race proof if the UI happens to mutate the ValueTree during state replacement.
  5. Ignore the problem and just wait for crashes :sunglasses:

I wish there was some kind of way to tell the host to call the API functions from the main thread.

Any advice?

Best,
Jelle

I think using callAsync with a copy of the state and do the ‘real’ state loading on the message thread is the only acceptable solution, and it might mean a block or two would be processed with the old state, but that’s not a issue.

There is however a bigger issue that happens with `getState’ - since you can’t defer that call, and need to have a state ready to give the host back.

1 Like

Thanks @eyalamir
I haven’t really thought about getState yet.

How about prepareToPlay?

I’ve never had issues with that - in my mind things needed in ‘prepareToPlay’ are the same as processBlock. If I need to ‘prepare’ things that are read from the UI state I would need some mechanism to sync that anyway.

So for example, say my UI state loaded a user sample that needs to be resampled to the current sample rate in ‘prepareToPlay’.

I usually design my program so that in prepare it just saves the current samplerate in an atomic, and then later on a Timer it sends some threaded worker to do the resampling… and then push it back to the audio thread using a queue of sorts.

getStateInformation is more challenging because on AU and AAX and it can be called from non-UI threads. With AAX I’ve found that you can actually just wait for a result from the message thread.

In Logic however that sometimes causes a deadlock, because Logic calls it while holding on to the UI thread. So for AU I’ve found that if the call to getState comes in a side thread, you have to have a cached version of your state ready to go.

(All of the above applies even if you’re not using APVTS BTW, it’s just a general design problem).

1 Like

I think using callAsync with a copy of the state and do the ‘real’ state loading on the message thread is the only acceptable solution, and it might mean a block or two would be processed with the old state, but that’s not a issue.

Just to add to this, you should spin at the top of your processBlock() when you’re in offline render mode if you choose this approach.

while (isNonRealtime() && myAtomicFlagWhileStateIsLoading.load())
    juce::Thread::sleep (1);
3 Likes

Thanks everyone for answering. This is really helpeful.

Thank you for explaining @eyalamir !

Nice one, although if some crazy host holds the main thread lock during processBlock this will spin forever.

Yes, great idea.

This is in general how we use the non-realtime flag. For example when waiting for some thread to finish work.

I do have to say you have to do this anyway regardless of async state loading or not, since setStateInformation is likely to be called in a separate thread than the processing thread, and could still be going while offline processing has started.

In practice though - in all DAWs I’ve tried, the host probably called setStateInformation long before offline processing has started.

I could imagine a situation such as a batch command line host, where the host would call setStateInformation and immediately run the processor - but in that case the host probably should call setState from the message thread as required by VST3 - and the plugin can then just load the state synchronously.

The only way this could spin forever is if state loading itself is frozen somehow. I don’t think it’s an actual issue.

@eyalamir The only way this could spin forever is if state loading itself is frozen somehow. I don’t think it’s an actual issue.

No I mean if:

a) the host calls processBlock from the main thread for offline rendering
or
b) the host calls processBlock from a background thread while holding the main thread lock

in both cases you’ll be waiting forever in processBlock for your callAsync to be handled

Both a and b are unlikely to happen but could in theory.

A could happen if the host called setState form a side thread, then immediately called processBlock to process offline on the main thread.

I do think that would be quite a dangerous behavior by the host, by both doing multithreading and expecting immediate response from the plugin to offline process with the result of that.

I’m not sure B can happen - or at least it cannot happen unless the host freezes the event loop completely regardless of what the plugin does, but then the host would freeze.

Yes it’s very unlikely… :wink:

Hi Jelle,

I’ve been working on this issue and wanted to share my approach. I’m using a combination of the MessageManagerLock and async calls within setStateInformation(). Initially, I tried deferring the entire call to the message thread, but when I tested it with Pluginval, the tests failed. I believe this was due to the delay, as the retrieved state didn’t match the expected value.

Relying solely on the lock wasn’t sufficient since some UI methods need to be called from the message thread. Now, what I’m doing is locking setStateInformation() with the MessageManagerLock to safely update my model, and then deferring all UI updates with async calls only when they are not called from the message thread.

I hope this helps!

Best,
Milan

1 Like

Thank you Milan,

Handling UI changes asynchronously is a good suggestion and I think it is good practice regardless of this issue.

I’ve currently implemented async handling (only when called from a background thread). It passes pluginval (with default level 5) so far and all GarageBand iOS assertions have been resolved. The JUCE forum community has proved itself to be extremely useful! Thanks a lot everyone.

Deferring fails on PluginVal because you load the parameters later, and the parameters fail check.

When I defer to the message thread - I still load the parameters from the chunk without deferring. The parameters are also atomic so there’s no race condition there with any other thread.

1 Like

@eyalamir
Interesting, do you use APVTS and if so do you call APVTS::replaceState synchronously on setStateInformation or do you manually assign parameters outside of APVTS?

But be careful when doing that:
Not all hosts set the non-realtime flag correctly.

I don’t use the APVTS, and you’ve just spotted one of the many reason why - it doesn’t let you extract the parameters from the state without replacing the whole state.

Writing simple XML/JSON<->parameters functions is trivial if you want to do it without the APVTS: