Proper way to unload an AudioPluginInstance (and the dylib on 4.2)


#1

Before juce 4.2 when the last AudioPluginsInstance was unloaded, the dylib was really unloaded from memory.
Now, with juce 4.2 lastest tip, when the last plugin instance is unloaded, the dynlib is not unloaded, so if you recompile or delete the plugin from disk, it will be no refreshed until you restart the app.
The same happens on juce plugin host example.

Is there a proper way to unload pluginsInstances, and if is the last instance of the plugin then unload the dylib?

I’m on osx 10.11
Thanks.


#2

Is that true…?
We don’t do any kind of reference-counting ourselves, so the DLL should be unloaded by the OS as soon as no more plugin instances have it open. Are you sure it’s something we changed?


#3

Hi Jules,
Yes, my app is a host to analize audio, I use it to test my plugins, and I work a lot with it. I recompile the plugin while the analizer app is open, then unload the plugin in the app and reload it again.
Yesterday I update to juce 4.2, and the plugins does not refresh any more, I have to restart the app to see the new changes in the plugin. The same happens to the plugin host example.

To compile juce 4.2, yesterday I updated to last xcode and last sdk on 10.11 from xcode 6 and 10.10 sdk.
I have test it with vst 2.4 plugins. I you need more info let me know.
Thanks


#4

The close() method in the Mac VST2 code hasn’t been changed for 2 years… Something may have changed, but I think you might be looking in the wrong place for the cause (?)


#5

I have noticed a strange behavior,
I’ve created a new blank plugin with projucer (vst and vst3).
in the processorEditor I’ve changed the paint method:

 void CompresorAudioProcessorEditor::paint (Graphics& g)
    {
        g.fillAll (Colours::white);

        g.setColour (Colours::black);
        g.setFont (15.0f);
        
        String a = String (*jucePlugInClientCurrentWrapperType);
        
        g.drawFittedText ("Hello World " + a, getLocalBounds(), Justification::centred, 1);
    }

I open the new plugin in “juce plugin host example” the vst instance and it paints “Helo Wolrd 1” then the vst3: “Hello World 2”. Ok for now is every fine.
Then close the gui of both, open again the gui, vst3 and vst2 shows the same “Hello World 1


#6

I think this should be fixed on the latest tip as we now statically link the shared code.


#7

That’s right.

BTW there’s a static method for getting that value, you shouldn’t call it directly. Use PluginHostType::getPluginLoadedAs() instead.


#8

Hi guys!
I’ve downloaded the lastest tip, clean and compile the host and plugins, but the bugs persists (the plugin host doesn’t unload the dylib when last plugin instance is unloaded, and the problem with hello world 1 & 2, now using PluginHostType::getPluginLoadedAs())


#9

I think the only way it could mix up the host type would be if the host is loading both the VST2 and VST3 from the same binary… Aren’t you duplicating the binary file as two separate copies?


#10

Have you pulled the latest tip AND re-introjuced & re-compiled the projucer? Then re-save your projects.


#11

resaved&clean&compiled host and plugins with 4.2.1 (projucer 4.2.1):

  • The first problem is not solved, the dylib remains loaded until you restart the host.
  • The mix on plugin type now is fixed.

Thanks


#12

Hi guys,
in 4.2.3 the problem still persists. When you unload the last plugin instance from your daw/host, the plugin doesn’t really unload, it stays in memory.
All cached images, and all static data, stays in memory.

before juce 4.2, this did not happens. (I can confirm this with Reaper and any juce based host)


#13

Hi Xeneize,

This seems more like an OS X bug/change. I’ve rolled back to JUCE 4.0.3 and could not get JUCE plug-ins to unload in either Reaper or the PlugIn Host. Strange…

I did some more debugging and found out that since OS X 10.11 the retain count immediately after loading a dylib is 2?!? For example, print out the retain count right after the loading of a VST (line juce_VSTPluginFormat.cpp:489)

DBG (CFGetRetainCount (bundleRef));

It returns 2 on OS X 10.11. I’m not sure why there has been this change in OS X. You can, however, force unloading by commenting out this line.

That’s dangerous though to enable for all JUCE users as other code may also be loading the bundle. Not quite sure how to fix this.


#14

Hi Fabian,

I’ve been using OSX 10.11 since JUCE 4.0, then not OS updates, and I can confirm that happens since Juce 4.2.
Because, pre 4.2, I had no need to restart the host every plugin recompile, only I had to unload all plugin instances.

Maybe it’s the way projucer generated by the project or the bundle? I know that some of this has changed since 4.2, but I’m not sure what.


#15

Like I said, I tried a clean juce 4.0.3 (with recompiling projucer) and the bundle definitely does not unload. You must be something different.


#16

Fabian, forgive me for insisting, Is there any chance to solve this?
Now I’m working in a project where a really need it solved, I use a static class (singletone) that has a ReleasePool (with a timer)
First problem is that Singletone destructor is never called until you close the host.
and the other problem is that ReleasePool timer stop working when you remove the last plugin instance, but then you add a new plugin instance again, the Timer doesn’t start again.

ReleasePool and the singleton class works fine in a gui or console app.

class Singleton
{
public:
    static Singleton* getInstance()
    {
        static Singleton instance {}; 
        return &instance;
    };

    void doSomething () { pool.add (aSharedPtr) };
    
private:
    Singleton (Singleton&) = delete;        
    void operator=(Singleton&) = delete;    
    
    Singleton ()
    {
        std::cout << "Singleton()" << std::endl;
    };
    
    ~Singleton()
    {
        std::cout << "~Singleton()" << std::endl;
    };
 ReleasePool pool;
};

The releasepool

template <typename objType>
class ReleasePool : private Timer
{
public:
    ReleasePool ()
    {
        startTimer (5000);
    };
    
    
    void add (const std::shared_ptr<objType>& object)
    {
        if (object == nullptr)
            return;
    
        std::lock_guard<std::mutex> lock (m);
        pool.emplace_back (object);
    };
    

private:

    void timerCallback() override
    {
        std::lock_guard<std::mutex> lock (m);


        pool.erase (
            std::remove_if (
                pool.begin(),
                pool.end(),
                [] (std::shared_ptr<objType>& object) { return object.use_count() <= 1; }
            ),
            pool.end());

    };
    
    std::vector<std::shared_ptr<objType>> pool;
    std::mutex m;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReleasePool)
};

thanks!


#17

I’ve rewritten these classes to not use static/singleton class, so, now, It’s not a bug that bothers me. Anyway it would be a good fix.

Thanks!


#18

Let me take the opportunity to comment on this: OS X does something slightly strange with bundles on newer OS X versions - when you load a bundle, the caller will get a reference to the bundle (as expected), but there is also an internal bundle cache which will also hold a reference to the bundle. Therefore, on newer OS X versions, after loading the bundle, the reference count will be 2 and not 1.

This has nothing to do with the JUCE version. As stated above, if you go back to JUCE 4.0.3 you will have the same behaviour.

The bundle cache is there so that you can get a bundle by it’s identifier, i.e. CFBundleGetBundleWithIdentifier. Eventually, when new bundle’s are loaded, the OS will release the bundle in the bundle cache, but JUCE has no control over this.

Therefore, in general, it’s bad if your code relies on being unloaded at a certain time.


#20

Thanks Fabian for the explanation, now is more clear.
About the code, yes, there was a leak.
I’ve using a static class only for DynamicLibrary, to does not open twice a dll, anyway, the OS handles this, on OSX works fine, not tested in windows yet.