BR: Latest JUCE plugins won't scan in Cubase 12

Running the latest juce from develop and Cubase 12, if I try and scan my plugin it deadlocks when I try and grab a MessageManagerLock because there is no MessageManager. Not sure how there isn’t a MessageManager by the time my constructor for my plugin runs. This only seems to happen in Cubase, other hosts are fine.

It was working fine on commit: 01394526075499f4542abd30e16bec2ec3d71e85

1 Like

There was a lot of change to the MessageManager in commit 33e81616ade84f40ccfec662c167b1b8dc772640, I wonder if that’s something to look at.

This is a serious problem for us and our shipping plugins. We’ve reverted to an older JUCE version for now, but that introduced other problems, so we need a fix for this missing MessageManager ASAP.

2 Likes

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
    }
8 Likes

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.

Yes, please, some plugins are really heavy to initialize.

2 Likes

I’ve tried to repro this (in Nuendo 12.0.50.387 on macOS 13.3.1) by doing rm ~/Library/Preferences/Nuendo\ 12/Nuendo\ VST3\ Cache\ \(arm64\)/vst3plugins.xml and running Nuendo but my plugin scanned and worked fine.

@yairadix Are you trying to lock the MessageManager in your Editor constructor? Because the issue, since the new commit, is that the MessageManager doesn’t exist for this specific case, and thus it can’t be locked.

1 Like

Added the following in the editor:

    {
        juce::MessageManagerLock testLock;
        juce::FileOutputStream (juce::File::createTempFile ("_debug.txt"))
            .writeText (
                juce::String ("Lock ") + (testLock.lockWasGained() ? "gained :)" : "not gained :/"),
                false,
                false,
                "\n");
    }

Indeed for the JUCE demo plugin it doesn’t work.
Added logs at beginning and end of getCompatibilityJSON and it gets called in the plugin scanning but never reaches completion.
When trying this for AA2 it did work (perhaps because it’s an ARA plugin it is different?)