ListenerList crashes on AudioProcessor destruction

Hi all,

My plug-in has a DataModel class that acts as a wrapper around a ValueTree, with its own Listener class and ListenerList for callbacks when changes are made to the data model. It is set-up in a similar manner to the JUCE - SamplerPlugin demo project.
I have one main instance of the DataModel in my AudioProcessor which then gets copied around to other classes that then register as listeners to their copy. This way each class owns the DataModel object that it refers to and any Listeners and ListenerLists take care of themselves on destruction without any fuss (no dangling Listeners).

However, when I register the AudioProcessor as a Listener to the main DataModel instance it crashes (exc_bad_access) on destruction in this iterator within the ListenerList destructor:

/** Destructor. */
    ~ListenerList()
    {
        WrappedIterator::forEach (activeIterators, [&] (auto& iter)
        {
            iter.invalidate();
        });
    } 

Adding in a removeListener (this) to the processor’s destructor does not help either as a get
exc_bad_access during the iterator of the remove() function in ListenerList().

It is quite confusing because it works like a charm in every other GUI class on destruction.
Am I missing something obvious with the ListenerList clas?

Thanks a bunch!

-Cameron

It seems that the ListenerList has already been deleted when its destructor is called. You say the DataModel has its own ListenerList, and the model gets copied around, but the ListenerList class being non-copyable I can’t piece together the full picture yet.

If you can share a minimal example that reproduces the issue, I am happy to take a look.

Hi Attila, thanks for the response.
Since posting I changed my code slightly to not copy the DataModel around in an attempt to get rid of this issue. Now my DataModel is working as a ValueTreeWrapper for the ValueTree in APVTS, which I am passing around to the GUI and other processors. Though that has helped I still get the crash every now and then at seemly random times.
Here is an example of how I am using the DataModel.

A simplified version of DataModel:

class SamplerParameters : public ValueTree::Listener
{
public:
    SamplerParameters(const ValueTree& ss) : samplerState (ss)
    {
        jassert (samplerState. hasType (IDs::SAMPLER));
        samplerState.addListener(this);
    }
    
    class Listener
    {
    public:
        virtual ~Listener() noexcept = default;
        virtual void exampleCallback (int a) {}
    };
    
    void addListener (Listener* listener)
    {
        listenerList.add (listener);
    }
private:
    ValueTree samplerState;
    ListenerList<Listener> listenerList;
};

Use case in the editor:

// PluginEditor.h
class ExampleAudioProcessorEditor : public juce::AudioProcessorEditor, 
public SamplerParameters::Listener
{
public:
ExampleAudioProcessorEditor (ExampleAudioProcessor& ap) :
audioProcessor (ap), samplerParams (audioProcessor.getSamplerState())
{
  samplerParams.addListener(this);
}

void exampleCallback (int a) override
{
}
......
private:
ExampleAudioProcessor& audioProcessor;
SamplerParameters samplerParams;
};

// getSamplerState() function in AudioProcessor
ValueTree getSamplerState()
{
    return apvts.state.getChildWithName(IDs::SAMPLER);
}

I am also using a similar approach in my samplerProcessor.h and .cpp files. Instead, I just pass a reference to the APVTS, instead of the AudioProcessor.

Am I missing something?

Thanks so much for your help!

-Cameron

Your code sample is reasonable, and can’t see anything that would definitely cause a crash.

I’m guessing here, but in case you are accessing the ListenerList from the ValueTree::Listener callback, that might lead to a crash, as the ListenerList is destroyed before the ValueTree and you are not calling ValueTree::removeListener() in the destructor either.

I’m trying to ascertain whether there might be a bug in the ListenerList implementation but so far I haven’t been able to come up with a project that would produce a crash similar to yours, so for now, I’ll be assuming it’s fine.

I’m not sure if this has anything todo with it, but there is a potential race condition with the public access to AudioProcessorValueTreeState::state.

While any access to the value tree internally is secured by the “valueTreeChanging” CriticalSection, it is still possible to interact with it, without locking the CriticalSection.