juce::WeakReference atomicity

Say I have a juce::WeakReference<MyObject> as suggested in the documentation - what happens if I try to access a pointer returned by the WeakReference, if the original pointer is deleted by another thread in a multi-threaded situation?

    MyObject* n = new MyObject();
    WeakReference<MyObject> myObjectRef = n;

    MyObject* pointer1 = myObjectRef;  // returns a valid pointer to 'n'

    // some other thread deletes n at this point

    pointer1->doSomething(); // behaviour?

Is it even possible to safely use WeakReference in a multi-threaded environment without locking, if there are no guarantees that the underlying pointer is still valid right after checking if it is?

No, WeakReference was never designed to be thread-safe.

I figured. I was just a little confused, as the documentation indicates it may be used in a multi-threaded environment:

            // If you're planning on using your WeakReferences in a multi-threaded situation, you may choose
            // to create a WeakReference to the object here in the constructor, [...]

Sure - the message is a bit confusing, but the point is that you could use it as long as you lock around all the uses of WeakReference. It might be better if I change that comment to just warn that the whole thing is unsafe!

Thank you very much for the clarification :slight_smile:

Is it safe to copy WeakReferences onto other threads, provided that you don’t dereference them?

I have something like this in mind:

void messageThreadFunction(juce::WeakReference<SomeClass> w)
{
  threadPool.addJob([w](){
     // do stuff but don't dereference w
     juce::MessageManager::callAsync([w](){
         if (w) w->doSomething();
     });
  });
}

Since WeakReference uses ReferenceCountedObject internally, which itself if atomic, I’m thinking that this might be safe. But I’m really not sure.

The problem is, that the threadpool thread can continue to use w while the message thread proceeds and might destroy the object w is pointing to.

It is only safe, if no destruction of the referenced object can happen while doSomething() is running.

A typical solution is to use std::shared_ptr and std::weakPtr.
The weak_ptr is turned into a shared_ptr while you are using it and keeps the object alive:

auto object = std::make_shared<SomeClass>();

threadPool.addJob([object]{
    // do stuff with the object. It is safe because it is referenced
    object->doSomething();
}

// if you want to allow object to go out of scope:
std::weak_ptr<SomeClass> reference(object);

// object is allowed to go out of scope

threadPool.addJob([reference]{
    // lock converts the weak_ptr into a shared_ptr keeping the pointed object alive
    if (ptr = reference.lock()) 
        ptr->doSomething();
}
1 Like

I am familiar with the shared_ptr / weak_ptr paradigm, and I use that most of the time. In this case, I don’t think that this will work because it will lead to SomeClass being deleted on the other thread, which I definitely don’t want.

In this case, SomeClass is a Component. (I’m actually using Component::SafePointer rather than WeakReference. I should have mentioned this in the original post, though I don’t think that changes things so much).

I still wonder if my proposed solution is safe though? If I can guarantee that:

  • SomeClass only gets created and destroyed on the message thread
  • Component::SafePointer<SomeClass> only gets dereferenced on the message thread

Is it not safe to copy Component::SafePointer<SomeClass> between threads? Rationale: Copying the WeakReference around increments and decrements the (atomic) ref count of the internal SharedRef, but doesn’t do anything else.

If it is not safe I will find some other way around. I just want to know because with what I’m working on, this would be the most convenient solution.

It is perfectly safe to copy a WeakReference or SafePointer.

I just realise now that you delegate from the thread pool back to the message thread, which I missed at first reading.

So if creation and deletion is limited to the message thread, this is a safe usage.

1 Like

This solution @LiamG suggested is exactly what I planned to do.

void messageThreadFunction(juce::WeakReference<SomeClass> w)
{
  threadPool.addJob([w](){
     // do stuff but don't dereference w
     juce::MessageManager::callAsync([w](){
         if (w) w->doSomething();
     });
  });
}

But in this example a copy of a WeakReference is made on the worker thread right? On this line: juce::MessageManager::callAsync([w](){.

Is it safe to create a copy of a WeakReference on a worker thread. Provided that you only use the copied WeakReference on the main thread again. As it seems to be only doing a ReferenceCountedObject::incReferenceCount()?

Yes, this seems to be safe.