JoshMarler / react-juce

Write cross-platform native apps with React.js and JUCE
https://docs.react-juce.dev
MIT License
763 stars 79 forks source link

ReactJUCEGainPlugin has a memory leak with CFString #318

Closed shih1 closed 4 months ago

shih1 commented 5 months ago

Hi!

I understand that this project support is in limbo but I still believe in the utility and that its something plugins could greatly benefit from so hopefully other believers can help me diagnose this issue :)

I was developing an independent plugin and noticed a memory leak. It seems related to invalidating the timeoutFunction when looping the Hermes JS engine.

I went ahead and tested the GainPlugin example and found a similar leak.

Profiled using leaks --atExit -- ./ReactJUCEGainPlugin on an M1 Mac.


Process:         ReactJUCEGainPlugin [94244]
Path:            /Users/USER/*/ReactJUCEGainPlugin.app/Contents/MacOS/ReactJUCEGainPlugin
Load Address:    0x102a1c000
Identifier:      com.yourcompany.GainPlugin
Version:         0.1.0 (0.1.0)
Code Type:       ARM64
Platform:        macOS
Parent Process:  leaks [94243]

Date/Time:       2024-04-06 22:23:45.449 +0800
Launch Time:     2024-04-06 22:23:39.933 +0800
OS Version:      macOS 12.6.5 (21G531)
Report Version:  7
Analysis Tool:   /Applications/Xcode.app/Contents/Developer/usr/bin/leaks
Analysis Tool Version:  Xcode 13.4.1 (13F100)

Physical footprint:         27.5M
Physical footprint (peak):  37.7M
----

leaks Report Version: 4.0
Process 94244: 20272 nodes malloced for 3255 KB
Process 94244: 1 leak for 48 total leaked bytes.

    1 (48 bytes) ROOT LEAK: <CFString 0x600002997900> [48]

I've narrowed down the problem code to EcmascriptEngine_Hermes.cpp and reviewed the clearing functionality and it all seems correct to me which makes me think that it could be some concurrency issue but I'm not entirely sure.

        {
            explicit TimeoutFunctionManager(jsi::Runtime &rt)
                : runtime(rt)
            {
            }

            ~TimeoutFunctionManager() override
            {
                clear();
            }

            void clear()
            {
                std::for_each(timeoutFunctions.cbegin(), timeoutFunctions.cend(), [this](const auto &tf)
                              { stopTimer(tf.first); });

                timeoutFunctions.clear();
            }

            jsi::Value clearTimeout(const int id)
            {
                stopTimer(id);

                if (const auto f = timeoutFunctions.find(id); f != timeoutFunctions.cend())
                    timeoutFunctions.erase(f);

                return jsi::Value();
            }

            jsi::Value newTimeout(jsi::Function f, const int timeoutMillis, std::vector<jsi::Value> args, const bool repeats = false)
            {
                static int nextId = 0;
                timeoutFunctions.emplace(nextId, TimeoutFunction(std::move(f), std::move(args), repeats));
                startTimer(nextId, timeoutMillis);

                return nextId++;

Any one experienced or with more knowledge than me have any clues? Any help is appreciated