Plugin crash on exit after converting to APVTS

#1

So the symptom is assert and crash upon exit, plugin executing in AudiPluginHost or as Standalone. Plugin runs fine before the exit event.

I’ve narrowed the problem down to the pattern of code generated by the GUI Builder. It allocates Sliders via ‘new’ in the Editor, and generates pointers as member variables.

I could switch over to use member Sliders without pointers (which seems to fix the issue), but then I’d lose the connection with GUI builder for my Editor, which I’m fond of.

So the question is what’s causing the crash on exit and can it be cured, without replacing these patterns? The plugin works fine until shutdown.

———

EditorComponent.cpp:

OutputVolumeSliderMemberName.reset ( new Slider ("OutputVolumeSliderName"));
addAndMakeVisible (OutputVolumeSliderMemberName.get());
OutputVolumeSliderMemberName->setRange (-30, 30, 0.1);
OutputVolumeSliderMemberName->setSliderStyle (Slider::LinearHorizontal);
OutputVolumeSliderMemberName->setTextBoxStyle (Slider::TextBoxLeft, false , 80, 20);
OutputVolumeSliderMemberName->setBounds (544, 64, 264, 24);
OutputVolumeSliderMemberName->setTextValueSuffix (" dB");
OutputVolumeSliderMemberName->attachToComponent(OutputVolumeSliderLabelMemberName.get(), true );
OutputVolumeSliderAttachment.reset (new SliderAttachment (processor.parameters, "OutputVolumeParamID",*OutputVolumeSliderMemberName)); 

EditorComponent.h

std::unique_ptr<Slider> OutputVolumeSliderMemberName;
std::unique_ptr<SliderAttachment> OutputVolumeSliderAttachment;

———

Here’s the assert that hits in juce_HeapBlock.h:

inline ElementType begin() const noexcept

{

return elements; `JUCE Message Thread (1): EXC_BAD_ACCESS (code=1, address=0x30)`

}

———

Here’s the first 20 lines of the stack trace.

JUCE Message Thread (1) Queue : com.apple.main-thread (serial)
#0 0x00000001006a2f8c in juce::HeapBlock&lt;juce::Slider::Listener*, false&gt;::operator juce::Slider::Listener**() const at /Users/me/JUCE/modules/juce_core/memory/juce_HeapBlock.h:182
#1 0x00000001006a2f55 in juce::ArrayBase&lt;juce::Slider::Listener*, juce::DummyCriticalSection&gt;::begin() const at /Users/me/JUCE/modules/juce_core/containers/juce_ArrayBase.h:164
#2 0x00000001006a33e5 in juce::Array&lt;juce::Slider::Listener*, juce::DummyCriticalSection, 0&gt;::removeFirstMatchingValue(juce::Slider::Listener*) at /Users/me/JUCE/modules/juce_core/containers/juce_Array.h:790
#3 0x0000000100545308 in juce::ListenerList&lt;juce::Slider::Listener, juce::Array&lt;juce::Slider::Listener*, juce::DummyCriticalSection, 0&gt; &gt;::remove(juce::Slider::Listener*) at /Users/me/JUCE/modules/juce_core/containers/juce_ListenerList.h:98
#4 0x000000010054528e in juce::Slider::removeListener(juce::Slider::Listener*) at /Users/me/JUCE/modules/juce_gui_basics/widgets/juce_Slider.cpp:1403
#5 0x00000001001d5140 in juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl::~Pimpl() at /Users/me/JUCE/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp:589
#6 0x00000001001cb9d5 in juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl::~Pimpl() at /Users/me/JUCE/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp:588
#7 0x00000001001cb9f9 in juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl::~Pimpl() at /Users/me/JUCE/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp:588
#8 0x00000001001a5e5c in std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl&gt;::operator()(juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl*) const [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2285
#9 0x00000001001a5e37 in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl&gt; &gt;::reset(juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl*) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2598
#10 0x00000001001a5dea in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl&gt; &gt;::~unique_ptr() [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2552
#11 0x00000001001a5dea in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment::Pimpl&gt; &gt;::~unique_ptr() [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2552
#12 0x00000001001a5dea in juce::AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() at /Users/me/JUCE/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp:626
#13 0x00000001001a5ea5 in juce::AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() at /Users/me/JUCE/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp:626
#14 0x0000000100037987 in std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment&gt;::operator()(juce::AudioProcessorValueTreeState::SliderAttachment*) const [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2285
#15 0x0000000100037963 in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment&gt; &gt;::reset(juce::AudioProcessorValueTreeState::SliderAttachment*) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2598
#16 0x00000001000378e6 in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment&gt; &gt;::~unique_ptr() [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2552
#17 0x00000001000378e6 in std::__1::unique_ptr&lt;juce::AudioProcessorValueTreeState::SliderAttachment, std::__1::default_delete&lt;juce::AudioProcessorValueTreeState::SliderAttachment&gt; &gt;::~unique_ptr() [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2552
#18 0x00000001000378e6 in EditorComponent::~EditorComponent() at /Users/me/Documents/JuceProjects/Project1/Source/EditorComponent.cpp:831
#19 0x0000000100037bf5 in EditorComponent::~EditorComponent() at /Users/me/Documents/JuceProjects/Project1/Source/EditorComponent.cpp:769
#20 0x0000000100037c79 in EditorComponent::~EditorComponent() at /Users/me/Documents/JuceProjects/Project1/Source/EditorComponent.cpp:769
0 Likes

#2

Is this definitely the order these members appear in the header file?

See also this thread: Closing the Plug-In makes the DAW crash

0 Likes

#3

Actually, not! (The user member variable section comes before the GUI Builder generated declarations.) However I just tried it with the order switched but it made no difference to the result.

0 Likes

#4

Read that other thread, thanks. Seems like it was only about the ordering, as you had already suggested. I also tried Scoped instead of Uniques, but no difference.

Is there maybe a manual shutdown sequence I could try? What does the stack trace suggest? Is it that the APVTS is trying to flush and delete Slider callbacks, but the Sliders have already been deleted?

0 Likes

#5

The stack trace suggests exactly the behaviour I was mentioning:
The editor stack is unrolled, the SliderAttachment goes out of scope, including it’s Pimpl pointer, which calls a removeListener on the Slider it listens to, and there it breaks. So it suggests, the Slider was deleted before the attachment.

Did you put anything in your destructor? Hint: leave it empty, then the natural order of destruction will take place, which is reverse order to creation.

0 Likes

#6

One interesting thing is that the Gui Builder created a destructor in the Editor which set all the Slider pointers to nullptr. I found and commented these out a couple days ago.

I also did a search for all other desctructors to make sure they are empty.

I’m trying to understand the order of creation. I think it’s:

1. First the APVTS is created in the processor, then in the Editor it's:
2. Editor's .h file
std::unique_ptr<Slider> OutputVolumeSliderMemberName;
3. 
typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
std::unique_ptr<SliderAttachment> OutputVolumeSliderAttachment;
4. Editor's .cpp file
OutputVolumeSliderMemberName.reset ( new Slider ("OutputVolumeSliderName"));
5. 
OutputVolumeSliderAttachment.reset ( new SliderAttachment (processor.parameters, "OutputVolumeParamID",*OutputVolumeSliderMemberName));

If I understand correctly, the problem is that upon unwinding, #4 is happening before #5? Or really, could it be any of items #1 thru #4 that’s happening before #5?

If the Slider was instead declared in the Editor’s header file, it would replace #2 and there’d be no #4.

I suspected #4 is the problem and I wanted to instead just bind the #2 pointer to a pre-declared Slider. That way I’d stay in the GUI Builder’s pattern and only modify one line per slider. I cannot figure out how to do that binding, seems all the obvious assignment operators are not available. Is there a way?

0 Likes

#7

Unfortunately the answer to that, that @jules has given in the past is, that the GUI builder is only kept around, but there are no plans to enhance that, nor the underlying factory ComponentBuilder.

With that void, many people started their own GUI editing tools, including my own ff_layouts module from 2016 and several more just recently.

But they come and go, in the end, everybody does their own thing. For indie developers, it is easy enough to code something together, using proportional layout using the Rectangle::removeFromLeft() etc methods or FlexBox and Grid.

For people, who want to offload the GUI design/fine tuning to designers away from the C++ developers, there is no solution available at the moment (apart from the few mentioned works in progress).

0 Likes

#8

Hi Daniel, I might’ve figured it out.

Since the Slider attachment objects are held by unique_ptrs, then by calling .reset() for each of them in the Editor’s destructor, the attachment objects within the APVTS are deleted in an orderly fashion, and I achieve a clean Editor shutdown.

Afterwards the APVTS parameters are still intact.

It might also be that setting each attachment to nullptr would have the same effect (and would also explain what I mentioned about the GUI Builder’s default destructors.)

0 Likes

#9

Sure, yes. You can get it to work…
I was merely commenting on what to expect from that workflow. Also with the Projucer overwriting your code changes, if you are not careful, I would no longer advertise using the Projucer GUI builder…

0 Likes

#10

Yes it is a workaround for sure. For now there are enough UserCode slots in the GUI code generator that I think I can get it to sync up.

Too bad about the GUI builder, I’ll have to check yours out. For me just getting started, being able to create and modify a basic UI on the fly with lots of params is motivational to use JUCE for plugin development. (At this early stage I’d rather focus on dsp than programmatic gui creation.) I wonder if there are some comprehensive wysiwyg frameworks out there that Juce can leverage.

0 Likes

#11

If you want to focus on dsp, I would suggest to ignore AudioProcessorEditor completely and use GenericAudioProcessorEditor.

Simply change in your AudioProcessor the following method:

AudioProcessorEditor * createEditor () override
{
    return new GenericAudioProcessorEditor (this);
}

And you get a GUI with a slider for each parameter. Once you are happy with your DSP, create a proper AudioProcessorEditor.

0 Likes