Loading bulk PNGs in threads got unsafe when moved to Direct2D? [solved]

My plugin loads about 20 PNG files in threads at startup. Since switching to Direct2D, I’ve noticed rare crashes on startup.

I can repro it in Debug build in Windows with the minimal test code below.

  • This usually crashes in juce_HeapBlock (called via void addListener (DxgiAdapterListener& l) in juce_DirectX_windows.h), see screenshot
  • However it reliably succeeds if I force SoftwareImageType in createImageFromData in juce_PNGLoader.cpp (where the juce::Image is created). Also pretty slow though.
// add other includes as needed...
// sorry for grubby code, was trying to minimize the test case.
#include <future>
#include <thread>
#include <vector>

// f should point to a valid PNG file
void TestPNGLoader(const juce::File& f) {
    const int num_iters = 10000;
    std::vector<juce::Image> images{};
    std::vector<juce::FileInputStream*> streams;
    std::vector<std::future<juce::Image>> loaders{};
    for (int i = 0; i < num_iters; ++i) {
        auto stream = new juce::FileInputStream{f};
        auto cb = [stream] {
            return juce::ImageFileFormat::loadFrom(*stream);
        };
        streams.emplace_back(stream);
        loaders.emplace_back(std::async(std::launch::async, cb));
    }
    // store
    for (auto& loader : loaders) {
        images.push_back(loader.get());
    }
    // cleanup
    for (auto stream : streams) {
        delete stream;
    }
}

While I figure this out, I’ve made my resource loader single threaded, but this adds about a second to my load path. Am I doing something dodgy, or have I stumbled into a previously-unknown re-entrancy bug? If this is the wrong way, is there a better way to quickly bulk load resources? (edit: My backup plan is to somehow force the image load using SoftwareImageType, but then convert them to NativeImageType afterwards in a single thread - the PNG decode seems to be the slow bit).

Thanks.

Thanks for reporting and for providing a test case, that’s really helpful. I believe the problem is caused by a data race on the ListenerList data member of DxgiAdapters, which is defined in juce_DirectX_windows.h. Updating this data member to be a ThreadSafeListenerList<DxgiAdapterListener> appears to resolve the problem for me.

Hopefully we’ll publish this change shortly. In the meantime, please could you try making the same change, and check whether it solves the issue for you? Thanks!

Patching that in seems to resolve the problem in my plugin, thanks for the rapid turnaround!

Thanks for testing! This fix has now been published: