Bouncing in Logic Pro causes plugin crash

Our customers repeatedly report a crash in Logic Pro at the end of an offline bounce. Unfortunately, we were not able to reproduce the issue. However, we received a crash report (EXC_BAD_ACCESS), as shown in the snippet below.

Thread 0::  Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib                 0x19fe70998 __bsdthread_create + 8
1   libsystem_pthread.dylib                0x19feb0348 _pthread_create + 1008
2   myPlugin                               0x40dc9af70 juce::Thread::createNativeThread(juce::Thread::Priority) (in myPlugin) (juce_Threads_mac.mm:143) + 4173680
3   myPlugin                               0x40dc9b1ac juce::Thread::startThread(juce::Thread::Priority) (in myPlugin) (juce_Thread.cpp:169) + 4174252
4   myPlugin                               0x40dd5718c AnalyzerProcessor::prepareToPlay(double, int) (in myPlugin) (AnalyzerProcessor.h:49) + 4944268
5   myPlugin                               0x40d8e6c1c juce::NodeStates::applySettings(juce::Nodes const&) (in myPlugin) (juce_AudioProcessorGraph.cpp:469) + 289820
6   myPlugin                               0x40d8e6598 juce::AudioProcessorGraph::Pimpl::handleAsyncUpdate() (in myPlugin) (juce_AudioProcessorGraph.cpp:1929) + 288152
7   myPlugin                               0x40dc4460c juce::LockingAsyncUpdater::Impl::messageCallback() (in myPlugin) (juce_LockingAsyncUpdater.cpp:84) + 3819020
8   myPlugin                               0x40dc4a1e0 juce::MessageQueue::runLoopCallback() (in myPlugin) (juce_MessageQueue_mac.h:104) + 3842528
9   CoreFoundation                         0x19ff99cd4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
10  CoreFoundation                         0x19ff99c68 __CFRunLoopDoSource0 + 172
11  CoreFoundation                         0x19ff99a38 __CFRunLoopDoSources0 + 332
12  CoreFoundation                         0x19ff98628 __CFRunLoopRun + 840
13  CoreFoundation                         0x19ff97c58 CFRunLoopRunSpecific + 572
14  HIToolbox                              0x1aba2c27c RunCurrentEventLoopInMode + 324
15  HIToolbox                              0x1aba2f4e8 ReceiveNextEventCommon + 676
16  HIToolbox                              0x1abbba484 _BlockUntilNextEventMatchingListInModeWithFilter + 76
17  AppKit                                 0x1a3ebfab4 _DPSNextEvent + 684
18  AppKit                                 0x1a485e5b0 -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 688
19  ViewBridge                             0x1a95870a4 __77-[NSViewServiceApplication vbNextEventMatchingMask:untilDate:inMode:dequeue:]_block_invoke + 148
20  ViewBridge                             0x1a9586e18 -[NSViewServiceApplication _withToxicEventMonitorPerform:] + 152
21  ViewBridge                             0x1a9586ff8 -[NSViewServiceApplication vbNextEventMatchingMask:untilDate:inMode:dequeue:] + 168
22  ViewBridge                             0x1a9574b34 -[NSViewServiceApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 100
23  AppKit                                 0x1a3eb2c64 -[NSApplication run] + 480
24  AppKit                                 0x1a3e8935c NSApplicationMain + 880
25  libxpc.dylib                           0x19fbc0a3c _xpc_objc_main + 816
26  libxpc.dylib                           0x19fbd0fc8 _xpc_main + 40
27  libxpc.dylib                           0x19fbc0568 xpc_main + 64
28  ViewBridge                             0x1a9570408 -[NSXPCSharedListener resume] + 32
29  ViewBridge                             0x1a958925c NSViewServiceMain + 360
30  AUHostingServiceXPC_arrow              0x1004096ac 0x100400000 + 38572
31  dyld                                   0x19fb0eb98 start + 6076

Thread 50 Crashed:: AUOOPRenderingServer-280009362
0   libsystem_platform.dylib               0x19fee82f8 _platform_memmove + 168
1   myPlugin                               0x40dd5722c AnalyzerProcessor::processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) (in myPlugin) (AnalyzerProcessor.h:61) + 4944428
2   myPlugin                               0x40d8ee3c4 juce::GraphRenderSequence<float>::NodeOp::process(juce::GraphRenderSequence<float>::Context const&) (in myPlugin) (juce_AudioProcessorGraph.cpp:886) + 320452
3   myPlugin                               0x40d8f9de0 juce::GraphRenderSequence<float>::perform(juce::AudioBuffer<float>&, juce::MidiBuffer&, juce::AudioPlayHead*) (in myPlugin) (juce_AudioProcessorGraph.cpp:566) + 368096
4   myPlugin                               0x40d8b53f4 JuceAU::processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) (in myPlugin) (juce_audio_plugin_client_AU_1.mm:2143) + 87028
5   myPlugin                               0x40d8ac9a8 JuceAU::Render(unsigned int&, AudioTimeStamp const&, unsigned int) (in myPlugin) (juce_audio_plugin_client_AU_1.mm:1532) + 51624
6   myPlugin                               0x40d8bc6d4 ausdk::AUBase::DoRenderBus(unsigned int&, AudioTimeStamp const&, unsigned int, ausdk::AUOutputElement&, unsigned int, AudioBufferList&) (in myPlugin) (AUBase.h:548) + 116436
7   myPlugin                               0x40d8bc2c0 ausdk::AUBase::DoRender(unsigned int&, AudioTimeStamp const&, unsigned int, unsigned int, AudioBufferList&) (in myPlugin) (AUBase.cpp:0) + 115392
8   myPlugin                               0x40d8c2a68 ausdk::AUMethodRender(void*, unsigned int*, AudioTimeStamp const*, unsigned int, unsigned int, AudioBufferList*) (in myPlugin) (AUPlugInDispatch.cpp:334) + 141928
9   AudioToolboxCore                       0x1a28b0bd4 AudioUnitRender + 432
10  AudioToolboxCore                       0x1a27f16e0 invocation function for block in AUAudioUnitV2Bridge_Renderer::renderBlock() + 608
11  AudioToolboxCore                       0x1a280d924 void* caulk::thread_proxy<std::__1::tuple<caulk::thread::attributes, AUOOPRenderingServer::AUOOPRenderingServer(int, int, int, std::__1::vector<AudioStreamBasicDescription, std::__1::allocator<AudioStreamBasicDescription>> const&, unsigned int, unsigned int, applesauce::xpc::dict const&, std::__1::shared_ptr<auoop::WorkgroupMirror>)::$_0, std::__1::tuple<>>>(void*) + 1712
12  libsystem_pthread.dylib                0x19feaec0c _pthread_start + 136
13  libsystem_pthread.dylib                0x19fea9b80 thread_start + 8

My first suspicion was that AnalyzerProcessor::processBlock gets called before AnalyzerProcessor::prepareToPlay, leading to access of uninitialized buffers in processBlock. So I started the Logic Pro in the debugger, set a breakpoint in AnalyzerProcessor::prepareToPlay and started a bounce.

I found out that prepareToPlay is called once at the end of the bounce, as shown in the stack trace below.

com.apple-main-thread:
AnalyzerProcessor::prepareToPlay(double, int) AnalyzerProcessor.h:41
juce::NodeStates::applySettings(const juce::Nodes &) juce_AudioProcessorGraph.cpp:479
juce::AudioProcessorGraph::Pimpl::handleAsyncUpdate() juce_AudioProcessorGraph.cpp:1929
juce::AudioProcessorGraph::Pimpl::rebuild(juce::AudioProcessorGraph::UpdateKind) juce_AudioProcessorGraph.cpp:1863
juce::AudioProcessorGraph::Pimpl::topologyChanged(juce::AudioProcessorGraph::UpdateKind) juce_AudioProcessorGraph.cpp:1924
juce::AudioProcessorGraph::Pimpl::addNode(std::unique_ptr<…>, std::optional<…>, juce::AudioProcessorGraph::UpdateKind) juce_AudioProcessorGraph.cpp:1753
juce::AudioProcessorGraph::addNode(std::unique_ptr<…>, std::optional<…>, juce::AudioProcessorGraph::UpdateKind) juce_AudioProcessorGraph.cpp:1997
PluginProcessor::prepareToPlay(double, int) PluginProcessor.cpp:147
JuceAU::SetProperty(unsigned int, unsigned int, unsigned int, const void *, unsigned int) juce_audio_plugin_client_AU_1.mm:800
ausdk::AUBase::DispatchSetProperty(unsigned int, unsigned int, unsigned int, const void *, unsigned int) AUBase.cpp:846
ausdk::AUMethodSetProperty(void *, unsigned int, unsigned int, unsigned int, const void *, unsigned int) AUPlugInDispatch.cpp:182

What’s interesting, though, is that on my machine, AnalyzerProcessor::prepareToPlay is called synchronously from the message thread while in the crash report it seems to be async.
Could that cause processBlock to be called before prepareToPlay?

And why is there an async call to AnalyzerProcessor::prepareToPlay in the crash report? Could it be that juce::AudioProcessorGraph::Pimpl::rebuild is called from a different thread than the message thread on the remote machine?

Any thoughts?

It’s certainly suspicious that the stack trace shows two different threads simultaneously in prepareToPlay and processBlock on (presumably) the same object instance. That’s very likely to cause problems.

I can’t think of a way to work around this using the current AudioProcessorGraph API, as the rebuild will always happen on the main thread, regardless of the thread that a calls prepareToPlay. That’s probably a bug in the current design - we should allow calling prepareToPlay from a background thread, so this function should block until the rebuild has completed. I’ve put together a potential fix, and I’ll update this thread if/when we merge it to the develop branch.

1 Like

It’s certainly suspicious that the stack trace shows two different threads simultaneously in prepareToPlay and processBlock on (presumably) the same object instance. That’s very likely to cause problems.

Unfortunately, our user was running two instances of our plugin so it could be two different objects.

Anyway, thank you very much for the quick reply and the fix! We will ship a plugin update then and see if the problem will be gone. I guess there is not much else we can do at the moment.

OK, happy to take another look if you’re still seeing problems after updating.

For now, a tentative fix is available on the develop branch:

1 Like