Crash in URLConnectionState::run() while app is shutting down

When an app / plug-in is shut down while a network request is still in progress, it can crash in URLConnectionState::run (juce_mac_Network.mm).

The crash (SIGABRT) occurs in this line.

Right before the crash, Objective-C logs:

objc[31049]: Attempt to use unknown class 0x600000cb4ab0.

The address seems to point to an array:

(lldb) p (id)0x600000cb4ab0
(__NSArrayM *) $3 = 0x0000600000cb4ab0 @"1 element"

…but maybe that doesn’t mean a lot because we’re already in undefined behaviour.

Call stack:

A fix would be appreciated! :slightly_smiling_face: Or any advice on what I can do in my code to prevent this.

Bump

Can you put together a stripped down example which reliably reproduces the crash?

Seeing a very similar crash with the same exception during our tests

objc[31049]: Attempt to use unknown class

Did you have any luck figuring it out so far?

Bump. Seeing similar issue here… when deleting a VST3 filter from the AudioPluginHost. JUCE 6.0.1.

How are you managing the network request? I can trigger the crash, but only if I do the request on a background thread which I deliberately leak on shutdown. In normal circumstances where stopThread() is called everything behaves as expected.

Hello @ed95,

I am inviting myself in this topic because I am experiencing somewhat of a similar crash with URLConnectionState when trying to shutdown my app.

Essentially I’m running a download and I want to prematurely cancel it if the user decides to shutdown the app while it’s running. So I reset the unique_ptr in which the juce::URL::DownloadTask is stored. That leads me to a SIGABRT in some assembly code.

Here’s is the stack:

The failing line in URLConnection state:

And a very simple version of the class doing the download:

Header

#pragma once
#include <JuceHeader.h>

#include <atomic>
#include <memory>

class ContentManager : public juce::URL::DownloadTask::Listener
{
public:
    ContentManager();
    ~ContentManager();

    void checkAndUpdate();
    void cancelUpdate();

    bool isUpdateRunning();
    float getProgress();

    void finished(juce::URL::DownloadTask*, bool success) override;
    void progress(juce::URL::DownloadTask*, juce::int64 bytesDownloaded, juce::int64 totalLength) override;

private:
    juce::URL m_url;
    std::unique_ptr<juce::URL::DownloadTask> m_task;

    std::atomic<bool> m_running = { false };
    std::atomic<float> m_progress = { 0.0 };
};

CPP

#include "ContentManager.h"

namespace
{
    static std::string s_downloadURL = "https://somefileontheinternet.zip";
}

ContentManager::ContentManager()
: m_url(s_downloadURL)
, m_task(nullptr)
{
}

ContentManager::~ContentManager()
{
    if (isUpdateRunning())
        cancelUpdate();
}

void ContentManager::cancelUpdate()
{
    m_task.reset();
    m_running = false;
    m_progress = 0;
}

bool ContentManager::isUpdateRunning()
{
    return m_running.load();
}

float ContentManager::getProgress()
{
    return m_progress.load();
}

void ContentManager::checkAndUpdate()
{
    if (isUpdateRunning())
        return;

    m_running = true;

    m_task = m_url.downloadToFile(
        juce::File("/Users/somewhere/Desktop/somefile.zip"), juce::String(), this);
}

void ContentManager::finished(juce::URL::DownloadTask*, bool success)
{
    m_running = false;
    m_task.reset();
}

void ContentManager::progress(juce::URL::DownloadTask*, juce::int64 bytesDownloaded, juce::int64 totalLength)
{
    m_progress = totalLength != 0 ? static_cast<float>(bytesDownloaded) / static_cast<float>(totalLength) : 0;
}

I’m getting this crash in 6.0.1. Resetting the unique_ptr from the message thread (like a cancel button on the UI) seems to work alright, just this one case gives me a headache. Am I missing something ? Would you be able to give me a hand on that ?

Thanks !

That crash looks unrelated to the Obj-C++ issues that the OP is seeing.

When you say it’s working deleting the URL::DownloadTask on the message thread, how are you deleting it in the crashing code? On a background thread? If you could post the full stack trace instead of the cropped first image, and some more complete example code which reproduces the crash then that will help to track things down.

As mentionned this happens when shutting down the app. So the call initiates from the shutdown method in the application base class.

Here’s the stack:

The application shutdown also happens on the message thread so it’s unlikely to be related to threading - it’s hard to say without seeing the full code but it looks like you might be having some lifetime issues with the shared_ptr in your app.

Try stripping it down to the bare minimum code in order to narrow down where the crash is coming from - this code for example shuts down cleanly:

#pragma once

#include <JuceHeader.h>

//==============================================================================
class MainComponent  : public juce::Component,
                       public juce::URL::DownloadTask::Listener
{
public:
    //==============================================================================
    MainComponent()
    {
        task = url.downloadToFile (juce::File::getSpecialLocation (juce::File::userDesktopDirectory).getChildFile ("download.zip"),
                                   {},
                                   this);
        jassert (task != nullptr);
        
        setSize (600, 400);

    }

    //==============================================================================
    void paint (juce::Graphics& g) override
    {
        // (Our component is opaque, so we must completely fill the background with a solid colour)
        g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
    }
    
    //==============================================================================
    void finished (juce::URL::DownloadTask*, bool success) override
    {
        DBG ("finished");
        jassert (success);
    }
    
    void progress (juce::URL::DownloadTask*, juce::int64 bytesDownloaded, juce::int64 totalLength) override
    {
        DBG ("progress = " + juce::String ((float) bytesDownloaded / totalLength));
    }

private:
    //==============================================================================
    // Your private member variables go here...
    juce::URL url { "https://somefileontheinternet.zip" };
    std::unique_ptr<juce::URL::DownloadTask> task;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Another thread with yet another unresolved issue… :cry:

In my App, everything freezes when i delete the DownloadTask object. No crash, the App just freezes, something remains in loop somewhere. Anybody found a solution or a workaround?

Apparently, the DownloadTask object remains stuck in a while loop in the destructor, that’s the cause of the frozen App. The only workaround is to leave it die in a detached thread. It’s not a solution, but should the download fail for some reason, there would be a hanging thread that sooner or later will be wiped out by the garbage collector. Until someone finds a solution…

        #if JUCE_IOS
        std::thread([&] { taskProgress.reset(); }).detach();
        #else
        taskProgress.reset();
        #endif

One of the great things about JUCE is that you have access to the source code. For this type of problem I would look at the DownloadTask code to see what is expected to happen when you delete the DownloadTask. And from there you can use the debugger to examine things at runtime to determine where the failure is occuring. It looks like ~FallbackDownloadTask signals the thread to exit, cancels the stream, and then wait (indefinitely) for the thread to exit. So, it’s likely hanging at that last step. If this is the case, now you can start looking at the thread code itself, in FallbackDownloadTask::run(). You can use a combination of breakpoints and maybe add some logging, to determine what is failing there. That code is checking if it should exit, so the most likely problem is that one of the calls in that thread is blocking and not returning. Once you determine that, solutions can be considered. I hope this helps :slight_smile:

Thanks for your reply. What you’re suggesting is exactly what I have already done, used the debugger, put breakpoints, added logging, etc. and my conclusion is that the while loop in the destructor of FallbackDownloadTask never ends, but the hang is somewhere else buried in swift code that I can’t understand. I see something related to sessions, but I don’t know what it’s all about… seems like a session element managed by some underlying code that isn’t released.

You need to look at the code in the thread, as that is what is causing the destructor to hang. While it may be in swift code, you should be able to determine which is the last call made in the thread run function stack, which points to the call that is not returning. While that doesn’t solve the issue, it gives the spot where the problem exists, and people can think about a solution.

After my initial reply I noticed your other post, and now understand it is iOS specific, and is a likely a known issue. But having said that, I didn’t notice the other post history indicating what function in the thread run code is blocking.

Just hit this same issue (intermittently)
Looking at the lifecycle of my std::unique_ptr<j::InputStream> I see no reason it should cause any trouble and I’m not using any background threading. I create the InputStream, readEntireStreamAsString, then explicitly .reset() when I’m done… or just leave it to fall out of scope. Same (occasional) result.

The destruction of the InputStream triggers destruction of the NSURLConnectionState which I guess is happening in some background thread, because the crash is happening after my app has completely finished running, well after the InputStream object should have been destroyed.

I’ve tried first checking isExhausted() but it always seems to return false despite having previously run readEntireStreamAsString(). I think this is because readEntireStreamAsString() does not call pimpl->read() which is the only place the WebInputStream sets its finished flag. It instead uses the << operator to fill a MemoryOutputStream, and I’m damned if I can see where that operator is defined.

I vaguely remember having had crash issues in this area.

What I’m sure of, is that the culprit was some static object, in the networking code, probably specific to macOS.

Looking at the JUCE source code now, I see that both URLConnectionState and URLConnectionStatePreYosemite (and also BackgroundDownloadTask) initialize in their constructor a

    static DelegateClass cls;

could it be that the crash is triggered when this thing is being destroyed?

If this is not it, then I believe it’s something similar, because destruction of static objects happen at program end, which could explain why you see the following:

Sorry if I can’t recall anything more specific, that was really hard to track down and it was a while ago.