HTTP call from specific thread

Hello !

Context

I currently working on internet call to fetch a XML page that is launched only one time, during the plugin initialisation.

I would have some thread related question and be sure to use the best practices.
I didn’t find it on the forum so I don’t know if I missused the search feature or if it hasn’t been discussed, so it could maybe help other people.

Let’s say I’ve the following URL

auto url = URL("https://mywebsite/fileToFetch.xml");

I know that to fetch this file I’ve to do

url.readEntireXmlStream();

Even it it’s a simple call, I’m used to do that in specific thread in order to avoid any problem in the message thread. The result of the the fetch will be then send to the message thread using juce::MessageManager::callAsync

Simple static function solution

I’ve read that using a simple

juce::Thread::launch

Could cause some issue if the pluging is shutdown.

Custom class and juce::Thread inheritance solution

So I tried to create a custome class that inherit from juce::Thread and I implemented the run function like this:

void MyCustomClass::run() {
  if(threadShouldExit()) { return; }

  auto result = fetchXMLFile();

  auto weakRef = juce::WeakReference<MyCustomClass>(this);
  juce::MessageManager::callAsync([weakRef, result] {
    if (weakRef == nullptr) { return; }
    if (weakRef->callbackToSendResult) {
      weakRef->callbackToSendResult(result);
    }
  });
}

In the AudioPluginHost app (only for the moment), I encountered an issue where the thread was forcedly killed.

I tried to use stopThread(100); in my custom class destructor. Nothing changed.

I also tried to stop the thread after the xml sent via callback. Nothing changed too.

juce::ThreadPool solution

I also see that a good thing was to use juce::ThreadPool even if it seemed to be a little massive regarding the simple call I want to make, if it would be safe it would be perfect.

However, when I use it, once again I cannot close the thread and I see during the whole usage of my app the thread I created.

Questions

So, what would be the best practice to make a simple internet call to fetch a XML file ?

It seem to be a naive question but I’m really worried about this opened thread. I’ve the feeling I’m missing something obvious.

Feel free to forward any forum threads or documentations that I could have missed.

Thanks

I would say inheriting Thread should work. You could post extra info about your thread killing issues.

Have a look at URL::downloadToFile()

The returned DownloadTask already runs in a thread. And on some platforms it even uses OS methods for downloading.

Since you keep the DownloadTask as unique_ptr, it is automatically cancelled when the app/plugin is closed.

And you can make yourself a DownloadTask::Listener so you get a notification when the task has finished or failed.

1 Like

The program breaks at bool Thread::stopThread (const int timeOutMilliseconds) in Juce::Thread

if (isThreadRunning())
        {
            // very bad karma if this point is reached, as there are bound to be
            // locks and events left in silly states when a thread is killed by force..
            jassertfalse;
            Logger::writeToLog ("!! killing thread by force !!");

and it comes from my destructor

MyCustomClass::~ MyCustomClass() {
  stopThread(100);
}

I wonder if the problem doesn’t come from the fact that the AudioPluginHost opens and closes very quickly the plugin UI :thinking:
I’ve this assert when I running my plugin but I don’t see any UI on the screen.

For more context here are the .h and .cpp files:

MyCustomClass.h

class MyCustomClass: private juce::Thread {
public:
  
  MyCustomClass();
  ~MyCustomClass();

  void getResult(std::function<void(std::unique_ptr<XmlElement>)> completionHandler);

private:
  JUCE_DECLARE_WEAK_REFERENCEABLE(MyCustomClass);

  juce::URL url_;

  GetLastVersionCallback resultCallback_;

  std::optional<XML> fetch();

  void run() override;
};

MyCustomClass.cpp

MyCustomClass::MyCustomClass() :
juce::Thread("MyCustomClass network"),
url_("https://mywebsite/fileToFetch.xml")
{

}

MyCustomClass::~MyCustomClass() {
  stopThread(100);
}

void MyCustomClass::getResult(std::unique_ptr<XmlElement>)> completionHandler) {
  resultCallback_ = completionHandler;
  startThread();
}

void MyCustomClass::run() {
  if(threadShouldExit()) { return; }

  auto result = url.readEntireXmlStream();

  auto weakRef = juce::WeakReference<MyCustomClass>(this);
  juce::MessageManager::callAsync([weakRef, result] {
    if (weakRef == nullptr) { return; }
    if (weakRef->resultCallback_) {
      weakRef->resultCallback_(result);
    }
  });
}

As you can see, very simple so I’m really surprised I got an assert :thinking:

Thank you @daniel I wasn’t aware of this one and it’s really easier to use rather than a full class for my case.
I’m gonna test it :slight_smile:

Your Thread::run() implementation has to check threadShouldExit() periodically, not only once at the beginning and return if that returns true. It is a flag, that stopThread() is setting. Usually that is done in a loop while you do the work.

If your thread is busy for longer than the timeout you are setting in stopThread(), then stopThread kills the thread and raises that assert.

THIS ! This is the root of my issue! Thank you @daniel !

I hided a part of my code because I thought it wasn’t worth it but my run implementation is more than
auto result = url.readEntireXmlStream();. I create the WebInputStream by myself and it looked like this

const auto parameter = juce::URL::ParameterHandling::inAddress;
const auto inputStreamOptions = juce::URL::InputStreamOptions(parameter)
    .withConnectionTimeoutMs(1000)
    .withResponseHeaders (&responseHeaders)
    .withStatusCode (&statusCode);

auto stream = url_.createInputStream(inputStreamOptions);

The connectionTimeoutMs was 1000, way larger than the

stopThread(100);

I changed with 1000 ms for both

juce::URL::InputStreamOptions(parameter)
    .withConnectionTimeoutMs(1000)
    .withResponseHeaders (&responseHeaders)
    .withStatusCode (&statusCode);

and

stopThread(1000);

and it works like a charm now :slight_smile:

Thank you !