Windows native File Dialog stays open after closing Audio Editor

The destructor of juce::FileChooser does not close its open file chooser dialog.
(We updated JUCE to version 6 from 5 and this problem started to occur.)

Our VST plugin opens the native file chooser in windows. If the user decide to quite our plugin by removing it from the DAW the file chooser dialog stays open. In case the user choose a file everything works as expected. We use launchAsync to open the file chooser dialog and in case the plugin is terminated while the dialog is open the destructor of juce::FileChooser is called.

Just for testing I tried to use browseForFileToOpen and removed the plugin form the DAW (tested with Ableton Live). This leads to a crash of the DAW.

I tested to delete the file chooser object (just by a timer) during the dialog is open: The dialog stays open.
In the description of method juce::FileChooser::launchAsync its says:
“… To abort the file selection, simply delete the FileChooser object. …”
Somehow this does not work.

Hey JUCE team, to my understanding, this is a serious bug… are you fixing it?

1 Like

Thanks for raising this issue. The behaviour should be fixed on develop:

1 Like

That’s great - thank you so much!

Thank you for your fix. But there are still problems in Windows with DAW Ableton Live 11:

  1. If our plugin is removed while the dialog is still open (started via showDialog within a click handler) the juce leak detector recognize a leak of a juce::StringArray and of three juce::MouseInputSource. Additionally there are several access violations and the DAW Ableton Live 11 crashes.

  2. Using launchAsync just does nothing. No dialog is opened.

I tested this with the latest juce devleop branch (1ff7fc38b1782c86009c1d1edb8e31669afd4860).

Modal loops, such as those used by the synchronous FileChooser functions, are not supported in plugins for exactly this reason. It’s not possible for the plugin instance to interrupt the nested modal loop and exit cleanly. I’d highly recommend setting JUCE_MODAL_LOOPS_PERMITTED=0 in the preprocessor flags for plugin builds, to ensure that modal loops are not used.

How are you calling launchAsync? Note that the dialog will be automatically closed as soon as the FileChooser instance goes out of scope. Therefore, you should not do this:

juce::FileChooser chooser { ... };
// Opens the filechooser dialog.
chooser.launchAsync (...);
// 'chooser' goes out of scope here, immediately closing the dialog again.

Instead, you should ensure that the FileChooser lives for at least as long as the dialog should remain open. Consider adding it as a data member on your editor, or another similarly long-lived object.

1 Like

This is the class member we use:
std::unique_ptr <juce::FileChooser> m_fileChooser{};
In the click handler have this code:
m_fileChooser.reset(new juce::FileChooser(...));
m_fileChooser->launchAsync(...);

In juce 6.0.7 the same code launches the FileChooser, but the FileChooser stayes open after the plugin is removed.

As I recognized that launchAsync does not work with the latest juce develop branch I just tried showDialog.

I collected further details:
For the native windows FileChooser the method launchAsync throws this exception:
std::bad_weak_ptr at memory location 0x000000000014EA40.
Since this exceptions happens inside launchAsync which is called at the end of the click handler
it appeared to me like nothing happens.

If I only set useOSNativeDialogBox to false in the constructor call to juce::FileChooser the complete process with launchAsync works as expected with the non-native juce FileChooser opening. After choosing a file the non-native juce FileChooser closes as expected and returns the choosen file.

If I close the plugin during the non-native juce FileChooser is open the FileChooser closes too, and
I get a memory leak of one juce::StringArray.

OK, thanks for investigating. I’ll take a look shortly.

1 Like

This should be fixed on develop now:

2 Likes

Thank you. I will try it now.

I have tested your new commit fd2f866dd1de58344ec4ed27611cfd61643ae303.
Results:

  1. The exception is gone and the native Windows FileChooser opens. I could choose a file and the FileChooser returns this file.

  2. I removed the plugin while the native windows FileChooser was open. Then FileChooser was also closed.

:+1:

Small problems:

  1. The native Windows FileChooser creates a memory leak of 24 bytes for every open-cancel-cycle and open-choose-close-cycle (on Windows 64 Bit, Ableton Live 11). The non-native juce FileChooser has no memory leak.

  2. Both FileChooser (native and non-native) create a memory leak of a juce::StringArray instance if I remove the plugin while they are still open.

I think I see what’s going wrong here, I’ll push a fix shortly.

I can’t repro this - could you try replacing the JUCE_LEAK_DETECTOR in StringArray with a JUCE_HEAVYWEIGHT_LEAK_DETECTOR instead? This should give a stack trace pointing to where the leaked object is allocated.

1 Like

Thank you for your effort.

I will try this shortly.

I cannot use the JUCE_HEAVYWEIGHT_LEAK_DETECTOR for juce::StringArray
It is not available in juce_StringArray.h due to juce core include order.


Ok after a bit of hacking the include order and adding forward declaration I got this:

0: xxxx (Debug64): juce::SystemStats::getStackBacktrace + 0x71
1: xxxx (Debug64):  juce::HeavyweightLeakedObjectDetector<juce::StringArray>::HeavyweightLeakedObjectDetector<juce::StringArray> + 0x13
2: xxxx (Debug64): juce::StringArray::StringArray + 0x27
3: xxxx (Debug64): juce::ComponentPeer::DragInfo::DragInfo + 0x16
4: xxxx (Debug64): juce::HWNDComponentPeer::FileDropTarget::FileDropTarget + 0x4b
5: xxxx (Debug64): juce::HWNDComponentPeer::createWindow + 0x39d
6: xxxx (Debug64): juce::HWNDComponentPeer::createWindowCallback + 0x13
7: xxxx (Debug64): juce::HWNDComponentPeer::callFunctionIfNotLocked + 0x3c
8: xxxx (Debug64): juce::HWNDComponentPeer::HWNDComponentPeer + 0x203

This memory leak apperars if I close the plugin while native Windows FileChooser is open.

Thanks, I’ve pushed a fix for the first leak now:

I still can’t repro the second leak. If it’s not also reported by MSVC’s CRT leak checker, there’s a chance this is a false positive due to a JUCE component being retained by the system and freed after the plugin’s destructor has run.

Thank you for your fix.
I will test it.

I will check if the MSVC’s CRT leak checker reports something, too.

The first leak is gone. :+1:

The second leak seems to be a leak of FileDropTarget which contains DragInfo and DragInfo contains StringArray. I added HeavyweightLeakedObjectDetector to FileDropTarget to get this info:

The thread 0x569c has exited with code 0 (0x0).
*** Leaked objects detected: 1 instance(s) of class FileDropTarget

Backtrace 1
-----------------------------------------------------------------
0: xxxxxxxx (Debug64): juce::SystemStats::getStackBacktrace + 0x71
1: xxxxxxxx (Debug64): juce::HeavyweightLeakedObjectDetector<juce::HWNDComponentPeer::FileDropTarget>::HeavyweightLeakedObjectDetector<juce::HWNDComponentPeer::FileDropTarget> + 0x13
2: xxxxxxxx (Debug64): juce::HWNDComponentPeer::FileDropTarget::FileDropTarget + 0x65
3: xxxxxxxx (Debug64): juce::HWNDComponentPeer::createWindow + 0x39d
4: xxxxxxxx (Debug64): juce::HWNDComponentPeer::createWindowCallback + 0x13
5: xxxxxxxx (Debug64): juce::HWNDComponentPeer::callFunctionIfNotLocked + 0x3c
6: xxxxxxxx (Debug64): juce::HWNDComponentPeer::HWNDComponentPeer + 0x203
7: xxxxxxxx (Debug64): juce::Component::createNewPeer + 0x47
8: xxxxxxxx (Debug64): juce::Component::addToDesktop + 0x2ba
9: xxxxxxxx (Debug64): JuceVSTWrapper::EditorCompWrapper::attachToHost + 0x4f
10: xxxxxxxx (Debug64): JuceVSTWrapper::handleOpenEditor + 0x122
11: xxxxxxxx (Debug64): JuceVSTWrapper::dispatcher + 0x328
12: xxxxxxxx (Debug64): JuceVSTWrapper::dispatcherCB + 0x113
13: PyInit_live_io + 0x5b4e5f
15: PyInit_live_io + 0x5c7612
16: PyInit_live_io + 0x5ca392

Detected memory leaks!
Dumping objects ->
{3530439} normal block at 0x000000003D858B30, 80 bytes long.
 Data: <h  V            > 68 89 EE 56 FF 7F 00 00 01 00 00 00 CD CD CD CD 
Object dump complete.
'Ableton Live 11 Suite.exe' (Win32): Unloaded 'xxxxxxxx.dll'	

I guess the Data-Line is an output of the MSVC’s CRT leak checker.

That’s really strange - it looks like the peer being leaked is that of the main editor, rather than the FileChooser. Does this issue definitely only occur when you add a FileChooser to your editor?

I checked again: Just removing the plugin in the state just before one click to open the native FileChooser no memory leak occurs. Closing the plugin while the FileChooser is open the memory leak appears. Opening the FileChooser and closing it with cancel then removing the plugin no memory leak occurs.

I checked a bit more:
a)

  1. Opening the plugin is calling the constructor FileDropTarget once
  2. Removing the plugin is calling the destructor FileDropTarget once
  3. No leak

b)

  1. Opening the plugin is calling the constructor FileDropTarget once
  2. Opening the FileChoose another call to constructor FileDropTarget happend
  3. Closing the FileChooser the destructor of FileDropTarget is called
  4. Removing the plugin is calling the destructor FileDropTarget again
  5. No leak

c)

  1. Opening the plugin is calling the constructor FileDropTarget once
  2. Opening the FileChoose another call to constructor FileDropTarget happend
  3. Removing the plugin the destructor FileDropTarget is called once for the object the FileChooser has created. The object created during plugin start is not destructed.
  4. Memory leak appears