My VST3 plugin sometimes ruins Ableton Live projects

Some of our customers are experiencing problems with the VST3 version of our plugin in Ableton Live.

The plugin can be instantiated without problems, but for some users, reloading the project will cause “A serious program error has occurred. Live will shut down after this message box is closed.”

For one of the users it has happened on macOS 10.14 (Mojave) in Live 10.1.3.
The AU version seems to work fine on the same machine.

It’s an Instrument plugin, but it also has a MIDI output (from its built-in trigger pad and sequencer), the settings in Projucer look like this:

We haven’t been able to reproduce this problem on our own Macs (or we would probably have solved the problem already…), but the users who have encountered it say that they can reproduce it consistently.
We have received an Ableton crash report, but I haven’t managed to find any relevant information in it yet.

Has anyone seen this problem or have any idea how we could proceed to solve it?

Cheers!

Ask one of the users who can reproduce consistently to give you a set of exact steps needed to reproduce from a blank project. I’ve had similar problems before with not being able to reproduce a bug report, where it turned out that after getting very precise details from the user after the standard vague “it crashes when I do something”, that we could reproduce locally and begin to start debugging. For instance we had one recently where the initial bug report was “Loading a project up in Logic Pro crashes if I added your plugin, I can add the plugin fine in a new project but then it crashes when I reload”, where really the bug report should have been, “If I load a project in Logic Pro that has multiple instances of your plugin then it crashes”. A subtle difference that made all the difference when it came to reproducing.

4 Likes

I can finally reproduce the problem now, it seems that it only happens when our plugin is loaded together with other plugins.

Our plugin gets stuck when creating a MessageManagerLock.

The AudioProcessor calls the Editor to notify it about an updated state, and the Editor creates a MessageManagerLock before doing anything to its components. But it doesn’t get further than that.

void MainDisplay::displayModeChanged(DisplayMode newMode)
{
    const MessageManagerLock lock;
    ...
}

So it seems a deadlock occurs for some reason, but I’m not sure exactly why this happens.

And it seems weird that the Editor is called at all, since the document is still loading it shouldn’t have instantiated the Editor yet, right? But apparently it has…

Any ideas?

MessageManagerLock is a huge hammer. It literally posts a message to the message queue and waits until that message is executed. When executed, it blocks the message queue until it is destroyed. There are many situation where this can fail, e.g. when the message queue is not up and running.

Maybe you can find a way to asynchronously trigger whatever needs to be triggered in your editor? E.g. use AsyncUpdater to post-pone the actual update so that displayModeChanged() just stores the DisplayMode newMode somewhere and triggers the async update, then immediately returns.

Thanks!

What if I wrap whatever I’m doing inside MessageManager::callAsync()? i e

void MainDisplay::displayModeChanged(DisplayMode newMode)
{
    MessageManager::callAsync([=] () {
        auto comp = components_[previousMode_].get();
        if (comp != nullptr) {
            Desktop::getInstance().getAnimator().fadeOut(comp, SCREEN_FADE_TIME_MS);
        }

        comp = components_[newMode].get();
        if (comp != nullptr) {
            callAfterDelay(SCREEN_FADE_TIME_MS, [this, comp, newMode] () {
                if (newMode == previousMode_) {
                    Desktop::getInstance().getAnimator().fadeIn(comp, SCREEN_FADE_TIME_MS);
                }
            });
        }

        previousMode_ = newMode;
    });
}

I tried this now and it seems that Live longer crashes, but I don’t know if it will potentially cause other problems?

Yeah - callAsync is a vicious source of bugs. It looks a bit like what you are doing there might work ok … but what stops your components_ container being deleted? Is that being passing in by value and contains WeakReferences? If so maybe you’re ok there.

MessageManagerLock should be renamed ‘CreateSupportTicketDeadlock’.

And maybe if callAsync was renamed ‘attemptToUseAnObjectThatMightBeDeleted’ we’d have fewer bugs.

Also there’s a thread about why not to use AsyncUpdater either (we will be renaming that one IntroduceAShortRandomDelayOnWindows).

The safe solutions for notifying are to use atomics with a Timer on the UI thread, or a lock-free (wait free?) queue and a Timer.

1 Like

Honestly, AsyncUpdater is fine for GUI stuff. The advantage compared to callAsync is that if your target class goes out of scope and is deleted, the corresponding async update message will be invalidated as well

Well, there’s a long debate about replacing it for sending updates from the audio thread. It calls PostMessage on windows which appears to incur a random delay.

2 Likes

We’re talking about loading stuff, not async updates during processing, so I think AsyncUpdater is fine for this

1 Like

Haha, OK, I understand. :smiley:

Yeah, the primary component in the MainDisplay uses a timer and polls the state from the AudioProcessor at a chosen frame rate.

The mechanism above is just for switching out the components (between the primary screen, the demo/licensing screen, and the loading progress screen), which isn’t done very often. So I’m hoping that it shouldn’t cause more problems… But “hope” is of course never a good thing to base your design on.

If you poll anyway, why don’t you just set a flag in your callback and update the components when the next timer update arrives?
That’s the most safe solution

Right now the “FPS timers” are on the individual sub-component level, not in the parent, because not all display sub-components need to be updated regularly. But it might be a good idea to rethink this… Thanks!

So something like this?

void MainDisplay::displayModeChanged(DisplayMode newMode)
{
    ScopedLock lock(mutex_);
    newMode_ = newMode;
}

void MainDisplay::timerCallback()
{
    if (newMode_ != previousMode_) {
        // Do stuff with components

        ScopedLock lock(mutex_);
        previousMode_ = newMode_;
    }
}

CriticalSection mutex_;

Take a look at std::atomic<DisplayMode>.
Otherwise: Yes, just like that.