Async Menu and AudioProcessorEditor Destruction Lifecycle [Resolved]

Hi

I have a problem with async menus and destroy lifecycle for my AudioProcessorEditor. Short version is that in the event you close (destroy) an AudioProcessorEditor with a menu open and that menu has a callback, the menu will be asynchronously called after the AudioProcessorEditor is destroyed, and there is no way with current code to avoid that that I have found (I do find a super gross way below).

Longer explanation:

In my code I do

auto p = juce::PopupMenu();
// build
p.showMenuAsync(options, [this](int) { this->whatever(); }

This requires me to make sure this lasts longer than the menu, which i assiduously do.

But there’s one point where i can’t do this and its a bit odd but it is a real problem.

Imagine I click a button to run that code, so the menu is showing. Now, if I close the UI with the menu open then the callback is called after the destruction happens. Specifically if, say, in Reaper I open the menu then press the “FX” button to close the UI, I get a call to ~AudioProcessorEditor() before the menu callbacks are called.

So I thought “I should call juce::PopupMenu::dismissAllActiveMenus() when I close”. So I do that.

Dissapointingly, I found that juce::PopupMenu::dismissAllActiveMenus() does close the windows but leaves the callbacks still scheduled.

So if I put a print in the destructor of my menu bound object, a print in the menu callback, and a print around dismissAll I get this order

Dismiss All Active Menus
Back from Dismiss
Destroying Oscillator Menu 0x7fecf2b18c70
Calling endHover on 0x7fecf2b18c70

That is obviously not the order I want. I also tried adding a call to juce::ModalComponentManager::getInstance()->cancelAllModalComponents(); but that still didn’t cancel my callbacks. The integer value my callback is passed doesn’t let me differentiate between a cancel and a close. So I’m stuck. Right now if you close surge with a menu open you will get a memory scribble.

So: How do I close an async juce::PopupMenu where i have done a showAsync in my editor destructor so as to not get a crash? Any thoughts welcome.

I can solve this problem with the following wildly unsatisfying fix

  1. Patch juce::ModalComponentManager so handleAsyncUpdate is public
  2. In my editor destructor codepath, before i clean up my objects, call
    juce::PopupMenu::dismissAllActiveMenus();
    juce::ModalComponentManager::getInstance()->handleAsyncUpdate();

that call to handleAsyncUpdate causes the callbacks to be closed when I dismiss them there, so they don’t get asynchronously triggered after the entire UI is torn down.

I guess I have a fork of juce so I could do that. But… i dunno. Right? Seems off!

Thanks to the team over in the audio programmer discord I have a resolution

The answer, basically, is never bind [this] in a menu lambda since juce doesn’t let you handle the lifecycle in this and only this case. But instead use the juce::Component::SafePointer wrapper.

1 Like

In the event someone stumbles over this thread in the distant future, here’s the commit that changes from [this] to [that = SafePtr(this)] in surge

Sounds like a good thing to do.
Would simply calling juce::PopupMenu::dismissAllActiveMenus(); in the editor destructor also work for you? Or does it still asynchronously trigger the callback? (Not on my computer atm, cannot verify)

Yeah dismiss doesn’t Call them just schedules them so the order is wrong even with the explicit call

Good to know, thanks!