Here is code to reproduce the issue: juce_bugs/CubaseHang/Source at master · FigBug/juce_bugs · GitHub
The call stack looks like this:
> CubaseHang.vst3!juce::MessageManager::Lock::tryAcquire(bool lockIsMandatory) Line 338 C++
CubaseHang.vst3!juce::MessageManager::Lock::tryEnter() Line 330 C++
CubaseHang.vst3!juce::MessageManagerLock::attemptLock(juce::Thread * threadToCheck, juce::ThreadPoolJob * jobToCheck) Line 445 C++
CubaseHang.vst3!juce::MessageManagerLock::MessageManagerLock(juce::Thread * threadToCheck) Line 424 C++
CubaseHang.vst3!CubaseHangAudioProcessor::CubaseHangAudioProcessor() Line 26 C++
CubaseHang.vst3!createPluginFilter() Line 191 C++
CubaseHang.vst3!juce::createPluginFilterOfType(juce::AudioProcessor::WrapperType type) Line 35 C++
CubaseHang.vst3!juce::JucePluginCompatibility::getCompatibilityJSON(Steinberg::IBStream * stream) Line 3957 C++
[External Code]
MessageManager::Lock::tryAquire
will fail if no message manager exists.
bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
{
auto* mm = MessageManager::instance;
if (mm == nullptr)
{
jassertfalse;
return false;
}
MessageManagerLock::attemptLock
is an endless loop until it gets the lock, which is not possible.
// tryEnter may have a spurious abort (return false) so keep checking the condition
while ((threadToCheck == nullptr || ! threadToCheck->threadShouldExit())
&& (jobToCheck == nullptr || ! jobToCheck->shouldExit()))
{
if (mmLock.tryEnter())
break;
}
The following change fixes the hang:
diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
index df00976f7..c21450983 100644
--- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
+++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp
@@ -3954,6 +3954,8 @@ public:
tresult PLUGIN_API getCompatibilityJSON (IBStream* stream) override
{
+ ScopedJuceInitialiser_GUI libraryInitialiser;
+
auto filter = createPluginFilterOfType (AudioProcessor::WrapperType::wrapperType_VST3);
auto* extensions = dynamic_cast<const VST3ClientExtensions*> (filter.get());
However I question the design that requires initializing the GUI, creating an instance of the AudioProcessor to just return a 32 character string that the majority of plugins will never implement. Instead I think it would be a lot smarter to have a define like JUCE_VST3_REPLACES_VST2_ID. Then the function could become:
tresult PLUGIN_API getCompatibilityJSON (IBStream* stream) override
{
#ifndef JUCE_VST3_REPLACES_VST2_ID
return kResultFalse;
#else
DynamicObject::Ptr object { new DynamicObject };
// New iid is the ID of our Audio Effect class
object->setProperty ("New", String (VST3::UID (JuceVST3Component::iid).toString()));
object->setProperty ("Old", [&]
{
Array<var> oldArray;
for (const auto uid : StringArray::fromTokens (JUCE_VST3_REPLACES_VST2_ID, ",", ""))
{
// All UIDs returned from getCompatibleClasses should be 32 characters long
jassert (uid.length() == 32);
// All UIDs returned from getCompatibleClasses should be in hex notation
jassert (uid.containsOnly ("ABCDEF0123456789"));
oldArray.add (uid);
}
return oldArray;
}());
MemoryOutputStream memory;
JSON::writeToStream (memory, var { Array<var> { object.get() } });
return stream->write (memory.getMemoryBlock().getData(), (Steinberg::int32) memory.getDataSize());
#endif
}