Wavelab and Cubase crash with JUCE demo plugin

Tested across 6 machines running OS X versions from 10.8.5 - 10.9.3 using the latest JUCE demo plugin from today (commit: 0147476), and loaded using the introducer built from the same commit, I am seeing crashes happen with a very simple recipe in both WaveLab and Cubase.

The ability to make it crash seems largely dependant on the version of WaveLab or Cubase you are running (I haven't yet tested Nuendo, but I wouldn’t be surprised if this was happening there also).

WaveLab versions tested:

7.2.1
8.0.0
8.0.1
8.0.2
8.0.3

Cubase versions tested:

7.5.2
7.0.5

The recipe is the same in both applications; open the DAW, load the demo, click just once in the body of the plugin (not the title bar), and quit using the WaveLab/Cubase menu.

Some of the crash logs do differ and occasionally the application will hang for a while before finally crashing. I have found crashing the plugin to be most reproducible in WaveLab 7.2.1 and 8.0.0, on one machine loading the 32bit variant of WaveLab made it more likely to crash, on other machines I found particular versions show this bug 100% or 0% of the time, others show the bug intermittently, it's not yet clear why this is. 

The bug was first reported to me by a developer at Steinberg, and further testing shows this to occur in the JUCE demo. I will of course be contacting them with these findings but I thought you would like to know.

I also have a whole load of crash logs if you would like me to send them your way? although the crash looks like it might be caused from a dangling pointer, it has a tendency to change, however we normally arrive at 'objc_msgSend' or  '_class_initialize' from the 'libobjc.A.dylib' library, I saw no more clues from debugging unfortunately.

If you would like any more information then let me know, I am happy to help in whatever way I can, I could for example put you in contact with the developer I spoke with at Steinberg.


Kind regards.
Anthony Nicholls.

Yeah, this sounds like an old and well-known but unfixable issue.

I've explained this many times on the forum, but here it is again... The problem happens when the host closes the plugin's GUI and then immediately unloads the plugin DLL without allowing the message loop to run in between. Because the plugin uses various internal obj-C classes in its NSView/NSWindow had a reference to, then after the GUI has been closed, the OS still has reference-counted pointers to those obj-C objects. Normally, shortly after the GUI closes, some kind of OS timer runs and cleans-up these windowing objects, which involves calls methods on the objects involved. Because the implementation of those objects is inside the plugin's DLL, then if the DLL has been unloaded before the OS does this clean-up work, then it will be calling dangling pointers to the code that has now been unloaded. Hence you get these bizarre crashes that seem to be caused by the plugin but which happen after the plugin has already been completely unloaded from memory.

In tracktion and other hosting work that I've done, I always avoid this by enforcing a couple of seconds of delay between closing a GUI and unloading the plugins, even when the app is quitting. But there's nothing I can do in the plugin itself to fix this - the only reason that non-juce plugins survive is because they tend to not use obj-C classes internally.

Thank you very much for clearing that up, and apologies for making you go over it again. I suspected that it would likely be out of your hands. If you don't mind I'll pass these comments onto Steinberg.

Thanks again.
Anthony.

Hi, 

Thanks for directing me to this post from yesterday Jules but enforcing the wait isn't working in my case, and it doesn't address why XCode 4.5.1 compiles show no bug but xcode 5.1.1 compiles do. My code is based around the juce audio host demo, it's been tweaked a lot but the basic framework is the same. 

During shutdown objects start being deleted and the relevant code goes something like this:

 

GraphDocumentComponent::~GraphDocumentComponent()

{

if( keyboardComp != nullptr ) {

        keyState.removeListener (&fgraph.getGraphPlayer()->getMidiMessageCollector());

    }

    

fgraph.clear();

 

detachFromAudioDeviceManager();

    

deleteStageManagerMidiDevice();

    

deleteAllChildren();

    

}

 

FilterGraph::~FilterGraph()

{

amperagePedal.close();

graphPlayer->setProcessor(nullptr);

agraph.clear();

}

 

void FilterGraph::clear()

{

PluginWindow::closeAllCurrentlyOpenWindows();

DBG("FilterGraph::clear, waiting after window closings");

    

//    MessageManager*

//    mm = MessageManager::getInstanceWithoutCreating();

    {

ScopedPointer<MessageManager>

    mm = MessageManager::getInstance();

    

    if( mm != nullptr ) {

        mm->runDispatchLoopUntil(2000);

    } else {

        DBG("FilterGraph::clear, couldn't obtain MessageManager");

    }

    }

    

agraph.clear();

changed();

//DBG("changed: clear");

DBG("FilterGraph::clear, waiting aftergraph clear.");

 

//MessageManager*

//mm = MessageManager::getInstanceWithoutCreating();

    {

ScopedPointer<MessageManager>

    mm = MessageManager::getInstance();

    

    if( mm != nullptr ) {

        mm->runDispatchLoopUntil(2000);

    } else {

        DBG("FilterGraph::clear, couldn't obtain MessageManager");

    }

    }

 

} // method

 

I realize I'm calling agraph.clear() twice but I don't think it's making the problem worse, I'll check today. The reason I'm going for a new instance of the message manager is because during shutdown it's the only way to enforce the delay. The MessageManager::getInstanceWithoutCreating(); doesn't result in any delay at all when a shutdown is happening. I'm guessing the message manager is already shutdown by that point in time? The delay does happen when running if the user elects to load a new graph and the clear() is called, but not during shutdown so I was sort of forced to get a new instance.

Bottom line is I've done my best so far to enforce the time delay between plugin window closings and plugin deletions but the EXC_BAD_ACCESS is still occuring.

And what are your instincts saying about the fact that xcode 4.5.1 compiles and 5.1.1 compiles are so different?

Thanks,
Kurt

 

Wow, that code looks pretty ropey! You shouldn't be messing with the MessageManager directly, and you really really shouldn't delete it! 

The way to enforece a delay is not to run the message loop explicitly - instead, make sure that your plugin gets deleted later with a timer. It's really easy, e.g. here's a bit of code I've used myself:

    struct DelayedPluginDeleter  : private Timer,
                                   private DeletedAtShutdown
    {
        DelayedPluginDeleter (AudioPluginInstance* p)  : plugin (p)
        {
            startTimer (1000);
        }

        void timerCallback() override
        {
            delete this;
        }

        ScopedPointer<AudioPluginInstance> plugin;
    };

 

Yep, it's rope-a-dope code but in self-defense I've been having to shotgun various trials and techniques while hunting this problem down. I do go back and clean things up when I find something that works...Any thoughts on why xcode 4.5 and 5.1 are behaving so differently?

 

Hey Jules, 

How is the way to use this delaying in a plugin? :) 

So the main question is, where I should place such a method to delay the closing. And can I use this one from you? 

Best 

Max

 

That depends entirely on how your app is designed..

You can of course use my example code above, but you'll need to understand what's going on and work out for yourself how best to use it in your app.

Hello, 

So I have programmed a VST plugin with an editor, or what do you mean with design? 

So where in the VST is the position to do this delaying. 

 

This thread is talking about writing a host, not a plugin.

Aww shit. Sorry this was my fauld. 

So in the plugin is no way to prevent this crashing when closing the plugin in the host? 

Perhaps with a delaying you talked about? 

I already explained this lots of times, including earlier in this thread - I don't have time to go over it all yet again..

FWIW , Something that could be helpful

Here is an answer from Arne @ Steinberg 


AppKit has recently changed (10.7 or 10.8) that if you close a window the view which is first responder will be called later again (I don't know the reason). Some hosts like the VST3PluginTestHost and some version of Cubase unloads the bundle executable directly when closing the window. Newer versions of the VST3PluginTestHost (not released) or Cubase (released) will delay unloading the executable so that this crash won't happen. cheers arne
 

to this question from Urs


Hi all, our derivate of NSView which we use in both VST2 64 bit and VST3 32/64 bit implements - (BOOL)acceptsFirstResponder { return YES; } However, whenever one clicks into the NSView and quits VST3PluginTestHost (possibly other hosts as well), the application crashes - after our stuff was fully deallocated and even after static memory is freed. If we return NO then this doesn't happen. If one clicks into another application window, e.g. a different plug-in, it doesn't happen. Does anyone have any ideas why this is? Does one have to explicitely lose key focus on destruction or something? Thanks,
 

If the shutdown call is made in the main message thread then could the message queue be flushed before you return from the shutdown call to block the unloading of the DLL until the OS has does its cleanup?

Also if there is an Apple demo plugin with a gui then it should object-c classes, so if this also crashes on shutdown then we could more easily push the problem back onto Steinberg.