Problem hosting non-native audio unit on M1 if they're loaded as first plugin

Hi,
something goes wrong when instantiating an audio unit with no arm64 slice, if the plugin is the first one to be instantiated.

Reproducible in the AudioPluginHost, latest commit on develop ( 938c66c83):

  1. open AudioPluginHost with an empty graph
  2. load one of those AU (for example Analog Lab 4, Absynth 5, Retrologue, GSI VB III…)
  3. app is stuck at juce::AudioUnitPluginInstance::prepareToPlay(double, int) (juce_AudioUnitPluginFormat.mm:1342 ) waiting for a mutex
#0	0x00000001b2a85718 in __psynch_mutexwait ()
#1	0x000000010608c008 in _pthread_mutex_firstfit_lock_wait ()
#2	0x000000010608bf34 in _pthread_mutex_firstfit_lock_slow ()
#3	0x00000001b4a54708 in HALB_Mutex::Lock() ()
#4	0x00000001b465d28c in HALC_ProxyIOContext::GetPropertyData(AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int, unsigned int&, void*) const ()
#5	0x00000001b47857c8 in HALC_ShellObject::GetPropertyData(unsigned int, AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int, unsigned int&, void*) const ()
#6	0x00000001b4629e64 in HAL_HardwarePlugIn_ObjectGetPropertyData(AudioHardwarePlugInInterface**, unsigned int, AudioObjectPropertyAddress const*, unsigned int, void const*, unsigned int*, void*) ()
#7	0x00000001b4742690 in HALPlugIn::ObjectGetPropertyData(HALObject const&, AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int&, void*) const ()
#8	0x00000001b449f604 in AudioObjectGetPropertyData ()
#9	0x00000001b41d7d04 in std::__1::__function::__func<auoop::WorkgroupManager_macOS::setHALDeviceList(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&)::$_7, std::__1::allocator<auoop::WorkgroupManager_macOS::setHALDeviceList(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&)::$_7>, bool (auoop::WorkgroupManager_Base::State&)>::operator()(auoop::WorkgroupManager_Base::State&) ()
#10	0x00000001b41d6bf8 in auoop::WorkgroupManager_Base::mutateWorkgroups(std::__1::function<bool (auoop::WorkgroupManager_Base::State&)>&&) ()
#11	0x00000001b41d67e8 in auoop::WorkgroupManager_macOS::handleHALDeviceListChange() ()
#12	0x00000001b41d6510 in auoop::gWorkgroupManager() ()
#13	0x00000001b4092538 in auoop::RenderPipePool::registerUser(AUOOPRenderClientUser const&, auoop::RenderPipeConfig const&) ()
#14	0x00000001b40f0400 in -[AUAudioUnit_XPC allocateRenderResourcesAndReturnError:] ()
#15	0x00000001b40e467c in int caulk::function_ref<int ()>::functor_invoker<AUv3InstanceBase::Initialize()::'lambda'()>(caulk::details::erased_callable<int ()> const&) ()
#16	0x00000001b40dbda8 in AUv3InstanceBase::NSExceptionBarrier(caulk::function_ref<int ()>) ()
#17	0x00000001b40e4624 in AUv3InstanceBase::Initialize() ()
#18	0x00000001b4212998 in AUIB_Initialize(void*) ()
#19	0x00000001b42258dc in AudioUnitInitialize ()
#20	0x0000000102aa37f8 in juce::AudioUnitPluginInstance::prepareToPlay(double, int) at /Users/matteo/Workspace/_Libraries/JUCE/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm:1342

I’m not seeing this on the latest develop. I’m testing on an M1 Pro running macOS 12.4.

I built and ran the AudioPluginHost as a universal binary. I also built the DSPModulePluginDemo, and ensured it had only an x86_64 segment using lipo. I’m able to load the plugin without problems. I also tried installing Analog Lab 4 and testing with that. file confirms that the bundle is x86_64-only. The AudioPluginHost is able to open the plugin correctly, and is able to load a saved project containing only an instance of Analog Lab 4 AU.

Edit: by adding a breakpoint on the AudioUnitInitialize call, I’m able to trigger the deadlock. The stacks look like this:

Main thread --------------------------------------------------------------------

                __psynch_mutexwait 0x000000019843d738
                _pthread_mutex_firstfit_lock_wait 0x0000000198475384
                _pthread_mutex_firstfit_lock_slow 0x0000000198472cf8
halb lock     ->HALB_Mutex::Lock() 0x000000019a40c898
                HALC_ProxyIOContext::GetPropertyData(AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int, unsigned int&, void*) const 0x000000019a014ef4
                HALC_ShellObject::GetPropertyData(unsigned int, AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int, unsigned int&, void*) const 0x000000019a13d638
                HAL_HardwarePlugIn_ObjectGetPropertyData(AudioHardwarePlugInInterface**, unsigned int, AudioObjectPropertyAddress const*, unsigned int, void const*, unsigned int*, void*) 0x0000000199fe1acc
                HALPlugIn::ObjectGetPropertyData(HALObject const&, AudioObjectPropertyAddress const&, unsigned int, void const*, unsigned int&, void*) const 0x000000019a0fa500
                AudioObjectGetPropertyData 0x0000000199e57704
                std::__1::__function::__func<auoop::WorkgroupManager_macOS::setHALDeviceList(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&)::$_7, std::__1::allocator<auoop::WorkgroupManager_macOS::setHALDeviceList(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&)::$_7>, bool (auoop::WorkgroupManager_Base::State&)>::operator()(auoop::WorkgroupManager_Base::State&) 0x0000000199b8f604
                auoop::WorkgroupManager_Base::mutateWorkgroups(std::__1::function<bool (auoop::WorkgroupManager_Base::State&)>&&) 0x0000000199b8e4f8
                auoop::WorkgroupManager_macOS::handleHALDeviceListChange() 0x0000000199b8e0e8
                auoop::gWorkgroupManager() 0x0000000199b8de10
                auoop::RenderPipePool::registerUser(AUOOPRenderClientUser const&, auoop::RenderPipeConfig const&) 0x0000000199a49e38
                -[AUAudioUnit_XPC allocateRenderResourcesAndReturnError:] 0x0000000199aa7d00
                int caulk::function_ref<int ()>::functor_invoker<AUv3InstanceBase::Initialize()::'lambda'()>(caulk::details::erased_callable<int ()> const&) 0x0000000199a9bf7c
                AUv3InstanceBase::NSExceptionBarrier(caulk::function_ref<int ()>) 0x0000000199a936a8
                AUv3InstanceBase::Initialize() 0x0000000199a9bf24
                AUIB_Initialize(void*) 0x0000000199bca298
                AudioUnitInitialize 0x0000000199bdd1dc
                juce::AudioUnitPluginInstance::prepareToPlay(double, int) juce_AudioUnitPluginFormat.mm:1342
                juce::AudioProcessorGraph::Node::prepare(double, int, juce::AudioProcessorGraph *, juce::AudioProcessor::ProcessingPrecision) juce_AudioProcessorGraph.cpp:875
callback lock ->juce::AudioProcessorGraph::buildRenderingSequence() juce_AudioProcessorGraph.cpp:1296
                juce::AudioProcessorGraph::handleAsyncUpdate() juce_AudioProcessorGraph.cpp:1307
                juce::updateOnMessageThread(juce::AsyncUpdater &) juce_AudioProcessorGraph.cpp:32
                juce::AudioProcessorGraph::topologyChanged() juce_AudioProcessorGraph.cpp:959
                juce::AudioProcessorGraph::addNode(std::unique_ptr<…>, juce::AudioProcessorGraph::NodeID) juce_AudioProcessorGraph.cpp:1015
                PluginGraph::createNodeFromXml(const juce::XmlElement &) PluginGraph.cpp:457
                PluginGraph::restoreFromXml(const juce::XmlElement &) PluginGraph.cpp:520
                PluginGraph::loadDocument(const juce::File &) PluginGraph.cpp:231
                auto juce::FileBasedDocument::Pimpl::loadFrom(juce::File const&, bool, bool)::'lambda'(juce::File const&, auto const&)::operator()<void juce::FileBasedDocument::Pimpl::loadFromImpl<'lambda'(juce::File const&, auto const&)>(juce::FileBasedDocument::Pimpl::SafeParentPointer, juce::File const&, bool, bool, auto&&, std::__1::function<void (juce::Result)>)::'lambda0'(juce::Result)>(juce::File const&, auto const&) const juce_FileBasedDocument.cpp:104
                juce::FileBasedDocument::Pimpl::loadFromImpl<…>(juce::FileBasedDocument::Pimpl::SafeParentPointer, const juce::File &, bool, bool, auto &&, std::function<…>) juce_FileBasedDocument.cpp:377
                juce::FileBasedDocument::Pimpl::loadFrom(const juce::File &, bool, bool) juce_FileBasedDocument.cpp:100
                juce::FileBasedDocument::loadFrom(const juce::File &, bool, bool) juce_FileBasedDocument.cpp:989
                PluginHostApp::handleAsyncUpdate() HostStartup.cpp:214
                juce::AsyncUpdater::AsyncUpdaterMessage::messageCallback() juce_AsyncUpdater.cpp:34
                juce::MessageQueue::deliverNextMessage() juce_osx_MessageQueue.h:81
                juce::MessageQueue::runLoopCallback() juce_osx_MessageQueue.h:92
                juce::MessageQueue::runLoopSourceCallback(void *) juce_osx_MessageQueue.h:100
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 0x0000000198545034
                __CFRunLoopDoSource0 0x0000000198544f80
                __CFRunLoopDoSources0 0x0000000198544c80
                __CFRunLoopRun 0x0000000198543600
                CFRunLoopRunSpecific 0x0000000198542b24
                RunCurrentEventLoopInMode 0x00000001a117b338
                ReceiveNextEventCommon 0x00000001a117b0b4
                _BlockUntilNextEventMatchingListInModeWithFilter 0x00000001a117ae68
                _DPSNextEvent 0x000000019b0a978c
                -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] 0x000000019b0a8084
                -[NSApplication run] 0x000000019b09a250
                juce::MessageManager::runDispatchLoop() juce_mac_MessageManager.mm:359
                juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:262
                juce::JUCEApplicationBase::main(int, const char **) juce_ApplicationBase.cpp:240
                main HostStartup.cpp:393
                start 0x0000000108c1908c

Audio thread -------------------------------------------------------------------

                __psynch_mutexwait 0x000000019843d738
                _pthread_mutex_firstfit_lock_wait 0x0000000198475384
                _pthread_mutex_firstfit_lock_slow 0x0000000198472cf8
                juce::CriticalSection::enter() const juce_posix_SharedCode.h:39
                juce::GenericScopedLock::GenericScopedLock(const juce::CriticalSection &) juce_ScopedLock.h:67
callback lock ->juce::GenericScopedLock::GenericScopedLock(const juce::CriticalSection &) juce_ScopedLock.h:67
                juce::AudioProcessorPlayer::audioDeviceIOCallbackWithContext(const float **, int, float **, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioProcessorPlayer.cpp:270
                juce::AudioDeviceManager::audioDeviceIOCallbackInt(const float **, int, float **, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioDeviceManager.cpp:921
                juce::AudioDeviceManager::CallbackHandler::audioDeviceIOCallbackWithContext(const float **, int, float **, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioDeviceManager.cpp:79
halb lock?    ->juce::CoreAudioClasses::CoreAudioInternal::audioCallback(const AudioTimeStamp *, const AudioBufferList *, AudioBufferList *) juce_mac_CoreAudio.cpp:786
                juce::CoreAudioClasses::CoreAudioInternal::audioIOProc(unsigned int, const AudioTimeStamp *, const AudioBufferList *, const AudioTimeStamp *, AudioBufferList *, const AudioTimeStamp *, void *) juce_mac_CoreAudio.cpp:893
                HALC_ProxyIOContext::IOWorkLoop() 0x000000019a017db4
                invocation function for block in HALC_ProxyIOContext::HALC_ProxyIOContext(unsigned int, unsigned int) 0x000000019a015efc
                HALB_IOThread::Entry(void*) 0x000000019a1e2304
                _pthread_start 0x000000019847826c

I’ll try to find a solution.

I’ve pushed a sequence of changes to the AudioProcessorGraph, tidying and simplifying the code, and improving the threading model. This bug should be fixed as a result of these changes.

The change sequence starts here:

…and ends here:

Please try out the changes and let us know if you still run into problems. Thanks!