juce::URL::createInputStream() inside a lambda leak?


#1

I’ve got the following code being used inside a lambda, that is being used with that new ThreadPool::addJob(std::function<>) that was added to the latest tip a couple days ago.

void AuthenticationInterface::initializeServerConnectionThread(ServerString& postData) {
    auto ai = this;
    juce::String post = postData.value();
    auto a = [ai, post]()
    {
        int statusCode = 0;
        juce::URL serverRequestURL = juce::URL(ServerConnection::GetChordieURL()).withPOSTData(post);
        juce::ScopedPointer<juce::InputStream> stream(serverRequestURL.createInputStream(true,
                                                                                         nullptr,
                                                                                         nullptr,
                                                                                         juce::String(),
                                                                                         20000,
                                                                                         //-1, //infinite timeout
                                                                                         nullptr,
                                                                                         &statusCode )
                                                      );
        juce::String reply = "FailedToConnect";
        bool result = false;
        if( stream != nullptr ) {
            reply = stream->readEntireStreamAsString();
            result = true;
        }
        ai->receiverCallback( reply );
        return result;
    };
    MatkatMusic::OneShotThreadPoolQueue::AddToQueue(a);
}
void OneShotThreadPoolQueue::AddToQueue(std::function<bool ()> lambda) //this is a static method
{
   auto instance = OneShotThreadPoolQueue::GetInstance();
//    auto job = new OneShotThreadPoolJob("new job", lambda);
//    instance->pool.addJob(job, true);
   struct LambdaJobWrapper : public juce::ThreadPoolJob
   {
       LambdaJobWrapper(std::function<bool()> j) : juce::ThreadPoolJob("lambda"), job(j) {}
       juce::ThreadPoolJob::JobStatus runJob() override
       {
           if( shouldExit() )
               return juce::ThreadPoolJob::JobStatus::jobNeedsRunningAgain;

           if( job() )
           {
               DBG( "LambdaJobWrapper::runJob() result: OK" );
           }
           else
           {
               DBG( "LambdaJobWrapper::runJob() result: FAIL" );
           }
           return juce::ThreadPoolJob::JobStatus::jobHasFinished;
       }
       std::function<bool()> job;
   };
   instance->pool.addJob(new LambdaJobWrapper(lambda), true);
}

I can’t seem to figure out why i’m getting a leak, other than that the ScopedPointer in the lambda never gets released. But i’m just guessing when I say that. When I profile, this is what I see:

any ideas what might be causing the leak?


#2

Are you sure it’s just the InputStream that’s leaking? Can you press continue a few times and see if there’s any other objects which may also be leaking?

One immediate thing that does jump out is the round-about way of capturing the this pointer. Unless you’re absolutely certain this object will live for the duration of your whole app this is likely to be problematic.
As a general rule, you should never capture this pointers unless this owns the thing that’s doing the capturing (this this is out of the question for async or threaded code).


#3

I’m absolutely certain that AuthenticationInterface object will live for the duration of my whole app. it’s the content Component for my app.

I let it run for a few more minutes, and it’s always that InputStream. I’ll make a test case and report back.


#4

Sorry, I don’t mean let it run, if you press the continue button you’ll either get more leak messages in the console or your app will exit.


#5

I’m not getting leak messages tho. I just noticed that the memory usage kept increasing as the app ran, and the Profiler reflects the memory leak accordingly…


#6

ok, here’s the test code for you juce devs. @dave96 @jules @fabian @ed95 no leak messages, but the memory usage keeps increasing. This should be a fun bug to solve!

Make a generic GUI project, and put this in MainComponent.h:

class MainContentComponent   : public Component, public Timer
{
public:
    //==============================================================================
    MainContentComponent();
    ~MainContentComponent() { stopTimer(); }

    void paint (Graphics&) override;
    void resized() override {}
    void timerCallback() override;
    void addJob(std::function<bool()> lambda);
private:
    ThreadPool pool;
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

put this in MainComponent.cpp:

MainContentComponent::MainContentComponent()
{
    setSize (600, 400);
    startTimer(1000 * 3);
}

void MainContentComponent::paint (Graphics& g)
{
    g.fillAll (Colour (0xff001F36));

    g.setFont (Font (16.0f));
    g.setColour (Colours::white);
    g.drawText ("Hello World!", getLocalBounds(), Justification::centred, true);
}


void MainContentComponent::timerCallback()
{
    auto a = []()
    {
        int statusCode = 0;
        juce::URL serverRequestURL = juce::URL("https://www.google.com/search?q=what+is+the+area+of+a+square&rlz=1C5CHFA_enUS699US702&oq=what+is+the+area+of+a+square&aqs=chrome.0.0l6.9325j0j8&sourceid=chrome&ie=UTF-8");
        ScopedPointer<InputStream> stream( serverRequestURL.createInputStream(true,
                                                          nullptr,
                                                          nullptr,
                                                          juce::String(),
                                                          20000,
                                                          //-1, //infinite timeout
                                                          nullptr,
                                                          &statusCode )
                                          );
        juce::String reply = "FailedToConnect";
        bool result = false;
        if( stream != nullptr ) {
            reply = stream->readEntireStreamAsString();
            result = true;
        }
        return result;
    };
    addJob(a);
}

void MainContentComponent::addJob(std::function<bool ()> lambda)
{
    struct LambdaJobWrapper : public juce::ThreadPoolJob
    {
        LambdaJobWrapper(std::function<bool()> j) : juce::ThreadPoolJob("lambda"), job(j) {}
        juce::ThreadPoolJob::JobStatus runJob() override
        {
            if( shouldExit() )
                return juce::ThreadPoolJob::JobStatus::jobNeedsRunningAgain;

            if( job() )
            {
                DBG( "LambdaJobWrapper::runJob() result: OK" );
            }
            else
            {
                DBG( "LambdaJobWrapper::runJob() result: FAIL" );
            }
            return juce::ThreadPoolJob::JobStatus::jobHasFinished;
        }
        std::function<bool()> job;
    };

    pool.addJob(new LambdaJobWrapper(lambda), true);
}

just want to add that I’m using Juce 4.3.1 here


Using a Timer to Repaint GUI
#7

just wanted to add that @Xenakios helped me out in the IRC channel for JUCE and this appears to be specific to OS X. Windows doesn’t have the same problem. I let it run for about 20 minutes and the memory usage climbed from 15mb to 115mb. It’s possible that it’s not a leak, just the heap for the app growing, perhaps. but that is way beyond my expertise and ability to figure out why.