Audio Unit setLatencySamples() Thread Safety Issue

I post it here because I don’t know whether it is a pluginval/JUCE/my issue.

Environment:

  • Github Runner Image macOS 15.1 Arm64 & AppleClang 16.0.0.16000026
  • Test repo JUCE version: 8.0.4
  • pluginval JUCE version: 8.0.3
  • How to reproduce: Build the test repo and the pluginval with Thread Sanitizer. Run pluginval to test the AU format.

The test repo is here:

It is very simple. Every 4800 samples it changes the plugin latency via async call:

void PluginProcessor::processBlock(juce::AudioBuffer<float> &buffer,
                                   juce::MidiBuffer &midiMessages) {
    // clear unused buffer here

    sampleNum += buffer.getNumSamples();
    if (sampleNum >= 4800) {
        sampleNum = 0;
        latency.store(10 - latency.load());
        triggerAsyncUpdate();
    }
}
void handleAsyncUpdate() override {
    setLatencySamples(latency.load());
}

And the error given by the action is:

WARNING: ThreadSanitizer: data race (pid=7800)
  Write of size 4 at 0x00010930f03c by main thread:
    #0 juce::AudioProcessor::setLatencySamples(int) juce_AudioProcessor.cpp:419 (ZL Test:arm64+0xd5cc4)
    #1 PluginProcessor::handleAsyncUpdate() PluginProcessor.h:58 (ZL Test:arm64+0x1134d8c)
    #2 non-virtual thunk to PluginProcessor::handleAsyncUpdate() PluginProcessor.h (ZL Test:arm64+0x1134de4)
    #3 juce::AsyncUpdater::AsyncUpdaterMessage::messageCallback() juce_AsyncUpdater.cpp:46 (ZL Test:arm64+0xe8c5a8)
    #4 juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93 (ZL Test:arm64+0xebe5a0)
    #5 juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104 (ZL Test:arm64+0xebe4d0)
    #6 juce::MessageQueue::runLoopSourceCallback(void*) juce_MessageQueue_mac.h:112 (ZL Test:arm64+0xebdf9c)
    #7 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ <null>:123738768 (CoreFoundation:arm64e+0x7dd30)
    #8 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100556940)
    #9 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005566e4)
    #10 main Main.cpp:173 (pluginval:arm64+0x100040010)
  Previous read of size 4 at 0x00010930f03c by thread T5:
    #0 juce::AudioProcessor::getLatencySamples() const juce_AudioProcessor.h:843 (ZL Test:arm64+0x7c734)
    #1 JuceAU::GetLatency() juce_audio_plugin_client_AU_1.mm:1207 (ZL Test:arm64+0x125f4)
    #2 ausdk::AUBase::DispatchGetProperty(unsigned int, unsigned int, unsigned int, void*) AUBase.cpp:543 (ZL Test:arm64+0x8a2bc)
    #3 ausdk::AUMethodGetProperty(void*, unsigned int, unsigned int, unsigned int, void*, unsigned int*) AUPlugInDispatch.cpp:157 (ZL Test:arm64+0x9d224)
    #4 AudioUnitGetProperty <null>:126877984 (AudioToolboxCore:arm64e+0x2cffb0)
    #5 juce::AudioUnitPluginInstance::prepareToPlay(double, int)::'lambda'()::operator()() const juce_AudioUnitPluginFormat.mm:1297 (pluginval:arm64+0x1008ffc04)
    #6 juce::AudioUnitPluginInstance::prepareToPlay(double, int) juce_AudioUnitPluginFormat.mm:1283 (pluginval:arm64+0x1006a7f6c)
    #7 callPrepareToPlayOnMessageThreadIfVST3(juce::AudioPluginInstance&, double, int) TestUtilities.h:201 (pluginval:arm64+0x100094e28)
    #8 AudioProcessingTest::runAudioProcessingTest(PluginTests&, juce::AudioPluginInstance&, bool) BasicTests.cpp:197 (pluginval:arm64+0x1000a0944)
    #9 AudioProcessingTest::runTest(PluginTests&, juce::AudioPluginInstance&) BasicTests.cpp:246 (pluginval:arm64+0x1000a079c)
    #10 PluginTests::testType(juce::PluginDescription const&) PluginTests.cpp:210 (pluginval:arm64+0x10007d700)
    #11 PluginTests::runTest() PluginTests.cpp:125 (pluginval:arm64+0x10007cae4)
    #12 juce::UnitTest::performTest(juce::UnitTestRunner*) juce_UnitTest.cpp:89 (pluginval:arm64+0x1002b7b10)
    #13 juce::UnitTestRunner::runTests(juce::Array<juce::UnitTest*, juce::DummyCriticalSection, 0> const&, long long) juce_UnitTest.cpp:177 (pluginval:arm64+0x1002b8e28)
    #14 runTests(PluginTests&, std::__1::function<void (juce::String const&)>) Validator.cpp:198 (pluginval:arm64+0x1000d2364)
    #15 validate(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String const&)>) Validator.cpp:216 (pluginval:arm64+0x1000ce138)
    #16 AsyncValidator::run() Validator.cpp:379 (pluginval:arm64+0x1000cdd38)
    #17 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()::operator()() const Validator.cpp:343 (pluginval:arm64+0x1000cdc48)
    #18 decltype(std::declval<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>()()) std::__1::__invoke[abi:ne180100]<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) invoke.h:344 (pluginval:arm64+0x1000cdb98)
    #19 void std::__1::__thread_execute[abi:ne180100]<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>&, std::__1::__tuple_indices<>) thread.h:199 (pluginval:arm64+0x1000cdb44)
    #20 void* std::__1::__thread_proxy[abi:ne180100]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>>(void*) thread.h:208 (pluginval:arm64+0x1000cd5d8)
  Location is heap block of size 624 at 0x00010930f000 allocated by main thread:
    #0 operator new(unsigned long) <null>:131081760 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x84210)
    #1 createPluginFilter() PluginProcessor.cpp:173 (ZL Test:arm64+0x1134ccc)
    #2 juce::createPluginFilterOfType(juce::AudioProcessor::WrapperType) juce_CreatePluginFilter.h:44 (ZL Test:arm64+0x14234)
    #3 AudioProcessorHolder::AudioProcessorHolder() juce_audio_plugin_client_AU_1.mm:126 (ZL Test:arm64+0xb8e0)
    #4 JuceAU::JuceAU(ComponentInstanceRecord*) juce_audio_plugin_client_AU_1.mm:150 (ZL Test:arm64+0xb144)
    #5 JuceAU::JuceAU(ComponentInstanceRecord*) juce_audio_plugin_client_AU_1.mm:154 (ZL Test:arm64+0xb0cc)
    #6 ausdk::APFactory<ausdk::AUBaseLookup, JuceAU>::Construct(void*, ComponentInstanceRecord*) ComponentBase.h:108 (ZL Test:arm64+0xb000)
    #7 ausdk::ComponentBase::AP_Open(void*, ComponentInstanceRecord*) ComponentBase.cpp:48 (ZL Test:arm64+0xa35f0)
    #8 APComponent::newInstance(unsigned int, bool, void (OpaqueAudioComponentInstance*, int) block_pointer) <null>:126880192 (AudioToolboxCore:arm64e+0x1047b0)
    #9 juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>) juce_AudioUnitPluginFormat.mm:2804 (pluginval:arm64+0x10067ffcc)
    #10 juce::AudioPluginFormat::handleMessage(juce::Message const&) juce_AudioPluginFormat.cpp:104 (pluginval:arm64+0x100605eb8)
    #11 juce::Message::messageCallback() juce_MessageListener.cpp:44 (pluginval:arm64+0x100557b3c)
    #12 juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93 (pluginval:arm64+0x100597b20)
    #13 juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104 (pluginval:arm64+0x100597a50)
    #14 juce::MessageQueue::runLoopSourceCallback(void*) juce_MessageQueue_mac.h:112 (pluginval:arm64+0x10059751c)
    #15 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ <null>:123733808 (CoreFoundation:arm64e+0x7dd30)
    #16 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100556940)
    #17 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005566e4)
    #18 main Main.cpp:173 (pluginval:arm64+0x100040010)
  Thread T5 (tid=30200, running) created by main thread at:
    #0 pthread_create <null>:131081760 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x309d8)
    #1 std::__1::__libcpp_thread_create[abi:ne180100](_opaque_pthread_t**, void* (*)(void*), void*) __threading_support:317 (pluginval:arm64+0x10009dd84)
    #2 std::__1::thread::thread<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'(), void>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) thread.h:218 (pluginval:arm64+0x1000cd3bc)
    #3 std::__1::thread::thread<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'(), void>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) thread.h:213 (pluginval:arm64+0x1000cceb8)
    #4 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:343 (pluginval:arm64+0x1000cccd0)
    #5 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:342 (pluginval:arm64+0x1000cc41c)
    #6 std::__1::__unique_if<AsyncValidator>::__unique_single std::__1::make_unique[abi:ne180100]<AsyncValidator, juce::String const&, PluginTests::Options&, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>>(juce::String const&, PluginTests::Options&, std::__1::function<void (juce::String)>&&, std::__1::function<void (juce::String, unsigned int)>&&, std::__1::function<void (juce::String const&)>&&) unique_ptr.h:597 (pluginval:arm64+0x1000c906c)
    #7 ValidationPass::ValidationPass(juce::String const&, PluginTests::Options, ValidationType, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:404 (pluginval:arm64+0x1000c8df8)
    #8 ValidationPass::ValidationPass(juce::String const&, PluginTests::Options, ValidationType, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:401 (pluginval:arm64+0x1000c95a0)
    #9 std::__1::__unique_if<ValidationPass>::__unique_single std::__1::make_unique[abi:ne180100]<ValidationPass, juce::String const&, PluginTests::Options&, ValidationType, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_0, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_1, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_2>(juce::String const&, PluginTests::Options&, ValidationType&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_0&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_1&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_2&&) unique_ptr.h:597 (pluginval:arm64+0x10000c308)
    #10 CommandLineValidator::validate(juce::String const&, PluginTests::Options) CommandLine.cpp:98 (pluginval:arm64+0x10000c1b4)
    #11 _ZZL18performCommandLineR20CommandLineValidatorRKN4juce12ArgumentListEENK3$_0clIS2_EEDaRKT_ CommandLine.cpp:495 (pluginval:arm64+0x1000147c4)
    #12 decltype(std::declval<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&>()(std::declval<juce::ArgumentList const&>())) std::__1::__invoke[abi:ne180100]<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&>(performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&) invoke.h:344 (pluginval:arm64+0x1000146d8)
    #13 void std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne180100]<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&>(performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&) invoke.h:419 (pluginval:arm64+0x10001462c)
    #14 std::__1::__function::__alloc_func<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0, std::__1::allocator<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0>, void (juce::ArgumentList const&)>::operator()[abi:ne180100](juce::ArgumentList const&) function.h:169 (pluginval:arm64+0x1000145c8)
    #15 std::__1::__function::__func<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0, std::__1::allocator<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0>, void (juce::ArgumentList const&)>::operator()(juce::ArgumentList const&) function.h:311 (pluginval:arm64+0x100012294)
    #16 std::__1::__function::__value_func<void (juce::ArgumentList const&)>::operator()[abi:ne180100](juce::ArgumentList const&) const function.h:428 (pluginval:arm64+0x100471560)
    #17 std::__1::function<void (juce::ArgumentList const&)>::operator()(juce::ArgumentList const&) const function.h:981 (pluginval:arm64+0x10047149c)
    #18 juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0::operator()() const juce_ConsoleApplication.cpp:351 (pluginval:arm64+0x1004713cc)
    #19 decltype(std::declval<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>()()) std::__1::__invoke[abi:ne180100]<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>(juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&) invoke.h:344 (pluginval:arm64+0x1004712e4)
    #20 int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:ne180100]<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>(juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&) invoke.h:411 (pluginval:arm64+0x100471240)
    #21 std::__1::__function::__alloc_func<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0, std::__1::allocator<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0>, int ()>::operator()[abi:ne180100]() function.h:169 (pluginval:arm64+0x1004711e4)
    #22 std::__1::__function::__func<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0, std::__1::allocator<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0>, int ()>::operator()() function.h:311 (pluginval:arm64+0x10046f498)
    #23 std::__1::__function::__value_func<int ()>::operator()[abi:ne180100]() const function.h:428 (pluginval:arm64+0x10046e3c8)
    #24 std::__1::function<int ()>::operator()() const function.h:981 (pluginval:arm64+0x10027f1e4)
    #25 juce::ConsoleApplication::invokeCatchingFailures(std::__1::function<int ()>&&) juce_ConsoleApplication.cpp:319 (pluginval:arm64+0x10027f0b0)
    #26 juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const juce_ConsoleApplication.cpp:348 (pluginval:arm64+0x10027f668)
    #27 performCommandLine(CommandLineValidator&, juce::ArgumentList const&) CommandLine.cpp:502 (pluginval:arm64+0x10000c7ac)
    #28 performCommandLine(CommandLineValidator&, juce::String const&) CommandLine.cpp:516 (pluginval:arm64+0x10000c528)
    #29 PluginValidatorApplication::handleAsyncUpdate() Main.cpp:167 (pluginval:arm64+0x100040a64)
    #30 non-virtual thunk to PluginValidatorApplication::handleAsyncUpdate() Main.cpp (pluginval:arm64+0x100040c20)
    #31 juce::AsyncUpdater::AsyncUpdaterMessage::messageCallback() juce_AsyncUpdater.cpp:46 (pluginval:arm64+0x10056b8f8)
    #32 juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93 (pluginval:arm64+0x100597b20)
    #33 juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104 (pluginval:arm64+0x100597a50)
    #34 juce::MessageQueue::runLoopSourceCallback(void*) juce_MessageQueue_mac.h:112 (pluginval:arm64+0x10059751c)
    #35 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ <null>:123746688 (CoreFoundation:arm64e+0x7dd30)
    #36 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100556940)
    #37 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005566e4)
    #38 main Main.cpp:173 (pluginval:arm64+0x100040010)
SUMMARY: ThreadSanitizer: data race juce_AudioProcessor.cpp:419 in juce::AudioProcessor::setLatencySamples(int)

AFAICT, it is because of the race of the variable latencySamples. Did I do something wrong? Is it correct to call setLatencySamples() on the message thread?

It is a bug/works as designed thing in Juce itself. The AudioProcessor private member latencySamples is a non-atomic int and is accessed without synchronization in AudioProcessor::setLatencySamples and AudioProcessor::getLatencySamples.

Thanks for the answer. Yes, I believe that is the problem. However, the VST3 does not have this issue (at least does not show up in pluginval). What’s more, when I test another plugin, it tells me the variable currentSampleRate also has a race condition (again only AU format has this problem).

WARNING: ThreadSanitizer: data race (pid=10596)
  Write of size 8 at 0x00010a510830 by thread T6:
    #0 juce::AudioProcessor::setRateAndBufferSizeDetails(double, int) juce_AudioProcessor.cpp:378 (pluginval:arm64+0x10060af60)
    #1 juce::AudioUnitPluginInstance::prepareToPlay(double, int) juce_AudioUnitPluginFormat.mm:1267 (pluginval:arm64+0x1006a7180)
    #2 callPrepareToPlayOnMessageThreadIfVST3(juce::AudioPluginInstance&, double, int) TestUtilities.h:201 (pluginval:arm64+0x100094128)
    #3 AutomationTest::runTest(PluginTests&, juce::AudioPluginInstance&) BasicTests.cpp:378 (pluginval:arm64+0x1000aa0b0)
    #4 PluginTests::testType(juce::PluginDescription const&) PluginTests.cpp:210 (pluginval:arm64+0x10007ca00)
    #5 PluginTests::runTest() PluginTests.cpp:125 (pluginval:arm64+0x10007bde4)
    #6 juce::UnitTest::performTest(juce::UnitTestRunner*) juce_UnitTest.cpp:89 (pluginval:arm64+0x1002b6e10)
    #7 juce::UnitTestRunner::runTests(juce::Array<juce::UnitTest*, juce::DummyCriticalSection, 0> const&, long long) juce_UnitTest.cpp:177 (pluginval:arm64+0x1002b8128)
    #8 runTests(PluginTests&, std::__1::function<void (juce::String const&)>) Validator.cpp:198 (pluginval:arm64+0x1000d1664)
    #9 validate(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String const&)>) Validator.cpp:216 (pluginval:arm64+0x1000cd438)
    #10 AsyncValidator::run() Validator.cpp:379 (pluginval:arm64+0x1000cd038)
    #11 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()::operator()() const Validator.cpp:343 (pluginval:arm64+0x1000ccf48)
    #12 decltype(std::declval<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>()()) std::__1::__invoke[abi:ne180100]<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) invoke.h:344 (pluginval:arm64+0x1000cce98)
    #13 void std::__1::__thread_execute[abi:ne180100]<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>&, std::__1::__tuple_indices<>) thread.h:199 (pluginval:arm64+0x1000cce44)
    #14 void* std::__1::__thread_proxy[abi:ne180100]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()>>(void*) thread.h:208 (pluginval:arm64+0x1000cc8d8)

  Previous read of size 8 at 0x00010a510830 by main thread:
    #0 juce::AudioProcessor::getSampleRate() const juce_AudioProcessor.h:822 (pluginval:arm64+0x1000b6824)
    #1 juce::AudioUnitPluginInstance::updateLatency() juce_AudioUnitPluginFormat.mm:1800 (pluginval:arm64+0x1009077c0)
    #2 juce::AudioUnitPluginInstance::respondToPropertyChange(AudioUnitProperty const&) juce_AudioUnitPluginFormat.mm:2092 (pluginval:arm64+0x1009b6f54)
    #3 juce::AudioUnitPluginInstance::eventCallback(AudioUnitEvent const&, float) juce_AudioUnitPluginFormat.mm:2058 (pluginval:arm64+0x1009b6c14)
    #4 juce::AudioUnitPluginInstance::eventListenerCallback(void*, void*, AudioUnitEvent const*, unsigned long long, float) juce_AudioUnitPluginFormat.mm:2108 (pluginval:arm64+0x1009b6a6c)
    #5 AUEventListener::MessagesAreAvailable() <null>:155203264 (AudioToolboxCore:arm64e+0xc1f4c)
    #6 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100555c40)
    #7 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005559e4)
    #8 main Main.cpp:173 (pluginval:arm64+0x10003f310)

  Location is heap block of size 1472 at 0x00010a510800 allocated by main thread:
    #0 malloc <null>:159393312 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x55fb4)
    #1 operator new(unsigned long) TestUtilities.cpp:76 (pluginval:arm64+0x1000bd270)
    #2 std::__1::__unique_if<juce::AudioUnitPluginInstance>::__unique_single std::__1::make_unique[abi:ne180100]<juce::AudioUnitPluginInstance, ComponentInstanceRecord*&>(ComponentInstanceRecord*&) unique_ptr.h:597 (pluginval:arm64+0x1009b4af4)
    #3 juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0::operator()(ComponentInstanceRecord*, int) const juce_AudioUnitPluginFormat.mm:2809 (pluginval:arm64+0x1009b47f0)
    #4 decltype(std::declval<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0&>()(std::declval<ComponentInstanceRecord*>(), std::declval<int>())) std::__1::__invoke[abi:ne180100]<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0&, ComponentInstanceRecord*, int>(juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0&, ComponentInstanceRecord*&&, int&&) invoke.h:344 (pluginval:arm64+0x1009b4758)
    #5 void std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne180100]<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0&, ComponentInstanceRecord*, int>(juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0&, ComponentInstanceRecord*&&, int&&) invoke.h:419 (pluginval:arm64+0x1009b466c)
    #6 std::__1::__function::__alloc_func<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0, std::__1::allocator<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0>, void (ComponentInstanceRecord*, int)>::operator()[abi:ne180100](ComponentInstanceRecord*&&, int&&) function.h:169 (pluginval:arm64+0x1009b45f8)
    #7 std::__1::__function::__func<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0, std::__1::allocator<juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>)::$_0>, void (ComponentInstanceRecord*, int)>::operator()(ComponentInstanceRecord*&&, int&&) function.h:311 (pluginval:arm64+0x1009b2480)
    #8 std::__1::__function::__value_func<void (ComponentInstanceRecord*, int)>::operator()[abi:ne180100](ComponentInstanceRecord*&&, int&&) const function.h:428 (pluginval:arm64+0x1007c6b94)
    #9 std::__1::function<void (ComponentInstanceRecord*, int)>::operator()(ComponentInstanceRecord*, int) const function.h:981 (pluginval:arm64+0x1007c6994)
    #10 juce::createAudioUnit(juce::VersionedAudioComponent, std::__1::function<void (ComponentInstanceRecord*, int)>) juce_AudioUnitPluginFormat.mm:521 (pluginval:arm64+0x10067f8f4)
    #11 juce::AudioUnitPluginFormat::createPluginInstance(juce::PluginDescription const&, double, int, std::__1::function<void (std::__1::unique_ptr<juce::AudioPluginInstance, std::__1::default_delete<juce::AudioPluginInstance>>, juce::String const&)>) juce_AudioUnitPluginFormat.mm:2804 (pluginval:arm64+0x10067f2cc)
    #12 juce::AudioPluginFormat::handleMessage(juce::Message const&) juce_AudioPluginFormat.cpp:104 (pluginval:arm64+0x1006051b8)
    #13 juce::Message::messageCallback() juce_MessageListener.cpp:44 (pluginval:arm64+0x100556e3c)
    #14 juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93 (pluginval:arm64+0x100596e20)
    #15 juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104 (pluginval:arm64+0x100596d50)
    #16 juce::MessageQueue::runLoopSourceCallback(void*) juce_MessageQueue_mac.h:112 (pluginval:arm64+0x10059681c)
    #17 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ <null>:152063120 (CoreFoundation:arm64e+0x7dd30)
    #18 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100555c40)
    #19 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005559e4)
    #20 main Main.cpp:173 (pluginval:arm64+0x10003f310)

  Thread T6 (tid=35967, running) created by main thread at:
    #0 pthread_create <null>:159393312 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x309d8)
    #1 std::__1::__libcpp_thread_create[abi:ne180100](_opaque_pthread_t**, void* (*)(void*), void*) __threading_support:317 (pluginval:arm64+0x10009d084)
    #2 std::__1::thread::thread<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'(), void>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) thread.h:218 (pluginval:arm64+0x1000cc6bc)
    #3 std::__1::thread::thread<AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'(), void>(AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>)::'lambda'()&&) thread.h:213 (pluginval:arm64+0x1000cc1b8)
    #4 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:343 (pluginval:arm64+0x1000cbfd0)
    #5 AsyncValidator::AsyncValidator(juce::String const&, PluginTests::Options, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:342 (pluginval:arm64+0x1000cb71c)
    #6 std::__1::__unique_if<AsyncValidator>::__unique_single std::__1::make_unique[abi:ne180100]<AsyncValidator, juce::String const&, PluginTests::Options&, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>>(juce::String const&, PluginTests::Options&, std::__1::function<void (juce::String)>&&, std::__1::function<void (juce::String, unsigned int)>&&, std::__1::function<void (juce::String const&)>&&) unique_ptr.h:597 (pluginval:arm64+0x1000c836c)
    #7 ValidationPass::ValidationPass(juce::String const&, PluginTests::Options, ValidationType, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:404 (pluginval:arm64+0x1000c80f8)
    #8 ValidationPass::ValidationPass(juce::String const&, PluginTests::Options, ValidationType, std::__1::function<void (juce::String)>, std::__1::function<void (juce::String, unsigned int)>, std::__1::function<void (juce::String const&)>) Validator.cpp:401 (pluginval:arm64+0x1000c88a0)
    #9 std::__1::__unique_if<ValidationPass>::__unique_single std::__1::make_unique[abi:ne180100]<ValidationPass, juce::String const&, PluginTests::Options&, ValidationType, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_0, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_1, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_2>(juce::String const&, PluginTests::Options&, ValidationType&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_0&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_1&&, CommandLineValidator::validate(juce::String const&, PluginTests::Options)::$_2&&) unique_ptr.h:597 (pluginval:arm64+0x10000b608)
    #10 CommandLineValidator::validate(juce::String const&, PluginTests::Options) CommandLine.cpp:98 (pluginval:arm64+0x10000b4b4)
    #11 _ZZL18performCommandLineR20CommandLineValidatorRKN4juce12ArgumentListEENK3$_0clIS2_EEDaRKT_ CommandLine.cpp:495 (pluginval:arm64+0x100013ac4)
    #12 decltype(std::declval<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&>()(std::declval<juce::ArgumentList const&>())) std::__1::__invoke[abi:ne180100]<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&>(performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&) invoke.h:344 (pluginval:arm64+0x1000139d8)
    #13 void std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne180100]<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&>(performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0&, juce::ArgumentList const&) invoke.h:419 (pluginval:arm64+0x10001392c)
    #14 std::__1::__function::__alloc_func<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0, std::__1::allocator<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0>, void (juce::ArgumentList const&)>::operator()[abi:ne180100](juce::ArgumentList const&) function.h:169 (pluginval:arm64+0x1000138c8)
    #15 std::__1::__function::__func<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0, std::__1::allocator<performCommandLine(CommandLineValidator&, juce::ArgumentList const&)::$_0>, void (juce::ArgumentList const&)>::operator()(juce::ArgumentList const&) function.h:311 (pluginval:arm64+0x100011594)
    #16 std::__1::__function::__value_func<void (juce::ArgumentList const&)>::operator()[abi:ne180100](juce::ArgumentList const&) const function.h:428 (pluginval:arm64+0x100470860)
    #17 std::__1::function<void (juce::ArgumentList const&)>::operator()(juce::ArgumentList const&) const function.h:981 (pluginval:arm64+0x10047079c)
    #18 juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0::operator()() const juce_ConsoleApplication.cpp:351 (pluginval:arm64+0x1004706cc)
    #19 decltype(std::declval<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>()()) std::__1::__invoke[abi:ne180100]<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>(juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&) invoke.h:344 (pluginval:arm64+0x1004705e4)
    #20 int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:ne180100]<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&>(juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0&) invoke.h:411 (pluginval:arm64+0x100470540)
    #21 std::__1::__function::__alloc_func<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0, std::__1::allocator<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0>, int ()>::operator()[abi:ne180100]() function.h:169 (pluginval:arm64+0x1004704e4)
    #22 std::__1::__function::__func<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0, std::__1::allocator<juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const::$_0>, int ()>::operator()() function.h:311 (pluginval:arm64+0x10046e798)
    #23 std::__1::__function::__value_func<int ()>::operator()[abi:ne180100]() const function.h:428 (pluginval:arm64+0x10046d6c8)
    #24 std::__1::function<int ()>::operator()() const function.h:981 (pluginval:arm64+0x10027e4e4)
    #25 juce::ConsoleApplication::invokeCatchingFailures(std::__1::function<int ()>&&) juce_ConsoleApplication.cpp:319 (pluginval:arm64+0x10027e3b0)
    #26 juce::ConsoleApplication::findAndRunCommand(juce::ArgumentList const&, bool) const juce_ConsoleApplication.cpp:348 (pluginval:arm64+0x10027e968)
    #27 performCommandLine(CommandLineValidator&, juce::ArgumentList const&) CommandLine.cpp:502 (pluginval:arm64+0x10000baac)
    #28 performCommandLine(CommandLineValidator&, juce::String const&) CommandLine.cpp:516 (pluginval:arm64+0x10000b828)
    #29 PluginValidatorApplication::handleAsyncUpdate() Main.cpp:167 (pluginval:arm64+0x10003fd64)
    #30 non-virtual thunk to PluginValidatorApplication::handleAsyncUpdate() Main.cpp (pluginval:arm64+0x10003ff20)
    #31 juce::AsyncUpdater::AsyncUpdaterMessage::messageCallback() juce_AsyncUpdater.cpp:46 (pluginval:arm64+0x10056abf8)
    #32 juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93 (pluginval:arm64+0x100596e20)
    #33 juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104 (pluginval:arm64+0x100596d50)
    #34 juce::MessageQueue::runLoopSourceCallback(void*) juce_MessageQueue_mac.h:112 (pluginval:arm64+0x10059681c)
    #35 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ <null>:152049280 (CoreFoundation:arm64e+0x7dd30)
    #36 juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277 (pluginval:arm64+0x100555c40)
    #37 juce::JUCEApplicationBase::main(int, char const**) juce_ApplicationBase.cpp:255 (pluginval:arm64+0x1005559e4)
    #38 main Main.cpp:173 (pluginval:arm64+0x10003f310)

SUMMARY: ThreadSanitizer: data race juce_AudioProcessor.cpp:378 in juce::AudioProcessor::setRateAndBufferSizeDetails(double, int)

I can only hope that the users/DAWs won’t do crazy tests like the pluginval :joy:

I think I won’t be able to fix all those data race. I will treat those warnings as false positives. Therefore, my temporary solution is to disable Automation test from pluginval.