AUv3 presets, thread safety and crashes when calling getCurrentPreset() in the AUv3 wrapper

Hi there

I have a question in relation to a crash we’ve observed in JUCE’s AUv3 wrapper. In an AUv3 with only one program/preset, we observe the AUv3 crashing in specific hosts on iOS when calling the following function of the JUCE AUv3 wrapper (juce_AUv3_Wrapper.mm line 524):

 AUAudioUnitPreset* getCurrentPreset() override
    {
        const int n = static_cast<int> ([factoryPresets.get() count]);
        const int idx = static_cast<int> (getAudioProcessor().getCurrentProgram());
    
        if (idx < n)
            return [factoryPresets.get() objectAtIndex:static_cast<unsigned int> (idx)];

        return nullptr;
    }

What happens is that n is set to 1, and idx is set to 0 (there is only one program which always has index 0). The AUv3 subsequently crashes on calling objectAtIndex with the following message:

*** Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array’

So the array has one element, but by the time we want to retrieve that element, the array seems empty. AFAIK this could happen if multiple threads are modifying the factoryPresets array; and I can’t see any mutex or other type of lock to prevent this from happening in JUCE.

Any ideas how this can be solved? Is this an issue that needs to be fixed in JUCE ?

In order for us to investigate this issue, we will need to reproduce the problem. Please can you let us know which hosts cause a problem? Do you see the same issue with any of the JUCE example projects (AudioPluginDemo etc.)?

Thanks for the reply @reuk. We’re seeing the issue in Loopy Pro. I am the developer of that app, so feel free to run any questions by me (there’s a 7-day trial so you can just grab it to try, if you wish, or let me know if you want an unlock code). Nothing unusual happening on my end that I can discover, and the issue doesn’t arise with non-JUCE-based AUv3s.

Just for a little extra information, @djb-2 was able to prevent crashes by copying the result of factoryPresets.get() at the start of getCurrentPreset(), then only interacting with the copy. This seems a reasonable partial solution to me, but the copy itself will need to be protected within a mutex as well.

I wasn’t able to reproduce the actual crash, but I think I can see what might be going wrong.

Please could you try applying this patch to your copy of JUCE, and checking whether it fixes the crash for you? Thanks!

0001-AUv3-Fix-race-on-factoryPresets.patch (4.8 KB)

@michaeltyson thanks much for chiming in!

@reuk : I tried the patch; I can’t reproduce the issue anymore with it applied so as far as I can tell it solved the issue!

As always, thanks much to the JUCE team for the quick response :+1:

1 Like

Thanks for testing, that’s available now:

2 Likes

Thanks!