Bug 195605 - makeWeakPtr isn't thread-safe
Summary: makeWeakPtr isn't thread-safe
Status: RESOLVED WONTFIX
Alias: None
Product: WebKit
Classification: Unclassified
Component: Web Template Framework (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-03-11 21:53 PDT by Fujii Hironori
Modified: 2019-07-10 00:58 PDT (History)
10 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Fujii Hironori 2019-03-11 21:53:32 PDT
makeWeakPtr isn't thread-safe even though it is called in non main threads.
This can be problem if multiple threads would call makeWeakPtr simultaneously.

WeakPtrFactory::createWeakPtr reads/writes 'm_ref' member variable without locking mutex.
https://trac.webkit.org/browser/webkit/trunk/Source/WTF/wtf/WeakPtr.h?rev=242387#L109

> WeakPtr<T> createWeakPtr(T& ptr) const
> {
>     if (!m_ref)
>         m_ref = WeakReference<T>::create(&ptr);
>     return { makeRef(*m_ref) };
> }

The simple solution is making WeakPtrFactory::m_ref always have a reference to a instance of WeakReference by calling createWeakPtr in WeakPtrFactory ctor.
Comment 1 Ryosuke Niwa 2019-03-11 22:12:18 PDT
(In reply to Fujii Hironori from comment #0)
> makeWeakPtr isn't thread-safe even though it is called in non main threads.
> This can be problem if multiple threads would call makeWeakPtr
> simultaneously.
> 
> WeakPtrFactory::createWeakPtr reads/writes 'm_ref' member variable without
> locking mutex.
> https://trac.webkit.org/browser/webkit/trunk/Source/WTF/wtf/WeakPtr.
> h?rev=242387#L109
> 
> > WeakPtr<T> createWeakPtr(T& ptr) const
> > {
> >     if (!m_ref)
> >         m_ref = WeakReference<T>::create(&ptr);
> >     return { makeRef(*m_ref) };
> > }
> 
> The simple solution is making WeakPtrFactory::m_ref always have a reference
> to a instance of WeakReference by calling createWeakPtr in WeakPtrFactory
> ctor.

It's important that WeakReference<T> is constructed lazily. I think we should do make the assignment to m_ref atomic.

Maybe we can use std::atomic<WeakReference<T>*> m_ref then manually call ref() / deref()? I'm sure others have a better idea than this.
Comment 2 Chris Dumez 2019-03-11 22:27:46 PDT
Is this a problem with actual code right now? Can you provide an example? WeakPtr is normally not used across thread since you could check on one line that the weak pointer is not null and then dereference it on the next line, by which time your object may be dead already.
Comment 3 Fujii Hironori 2019-03-11 23:33:01 PDT
(In reply to Chris Dumez from comment #2)
> Is this a problem with actual code right now? Can you provide an example?
> WeakPtr is normally not used across thread since you could check on one line
> that the weak pointer is not null and then dereference it on the next line,
> by which time your object may be dead already.

WTF::WeakReference is a sub-class of ThreadSafeRefCounted. 

> class WeakReference : public ThreadSafeRefCounted<WeakReference<T>> {

And, WTF::WeakPtr doesn't have a method like weak_ptr::lock.

  std::weak_ptr::lock
  https://en.cppreference.com/w/cpp/memory/weak_ptr/lock

These two facts mean WTF::WeakPtr should be used in a single thread, but its WTF::WeakReference can be held in multiple threads by design.

And, it's easy to find code looks like the following under WebCore/platform.

> callOnMainThread([weakPtr = makeWeakPtr(*this)] {
Comment 4 Chris Dumez 2019-03-12 07:01:01 PDT
(In reply to Fujii Hironori from comment #3)
> (In reply to Chris Dumez from comment #2)
> > Is this a problem with actual code right now? Can you provide an example?
> > WeakPtr is normally not used across thread since you could check on one line
> > that the weak pointer is not null and then dereference it on the next line,
> > by which time your object may be dead already.
> 
> WTF::WeakReference is a sub-class of ThreadSafeRefCounted. 
> 
> > class WeakReference : public ThreadSafeRefCounted<WeakReference<T>> {
> 
> And, WTF::WeakPtr doesn't have a method like weak_ptr::lock.
> 
>   std::weak_ptr::lock
>   https://en.cppreference.com/w/cpp/memory/weak_ptr/lock
> 
> These two facts mean WTF::WeakPtr should be used in a single thread, but its
> WTF::WeakReference can be held in multiple threads by design.
> 
> And, it's easy to find code looks like the following under WebCore/platform.
> 
> > callOnMainThread([weakPtr = makeWeakPtr(*this)] {

So no concrete example where there is an actual problem? CallonMainThread() is frequently used on the main thread to call something asynchronously and does not imply multithreading. Also, it is ok to temporarily pass a weakptr to another thread (since ThreadSafeRefcounted) as long as it is created and used on the thread where the object lives (which may or may not be the main thread).
Comment 5 Geoffrey Garen 2019-03-12 10:03:46 PDT
Honestly, it's probably a bug that WeakReference is ThreadSafeRefCounted. (It should just be RefCounted.) None of the data in WeakPtr, WeakReference, or WeakPtrFactory is thread-safe.

I left WeakReference ThreadSafeRefCounted out of an abundance of caution, because it was originally written that way and I didn't know if there any clients needed the atomic reference count.

If we wanted a thread-safe approach to weak references, we could create one, and it would be a distinct class with a distinct implementation. (All operations would need to be atomic, get() and operator->() would need to return RefPtr, and operator*() would need to return Ref.)
Comment 6 Chris Dumez 2019-03-12 10:11:03 PDT
(In reply to Geoffrey Garen from comment #5)
> Honestly, it's probably a bug that WeakReference is ThreadSafeRefCounted.
> (It should just be RefCounted.) None of the data in WeakPtr, WeakReference,
> or WeakPtrFactory is thread-safe.
> 
> I left WeakReference ThreadSafeRefCounted out of an abundance of caution,
> because it was originally written that way and I didn't know if there any
> clients needed the atomic reference count.
> 
> If we wanted a thread-safe approach to weak references, we could create one,
> and it would be a distinct class with a distinct implementation. (All
> operations would need to be atomic, get() and operator->() would need to
> return RefPtr, and operator*() would need to return Ref.)

I have leveraged the fact that it is ThreadSafeRefcounted in my code for cases where I jump to a background thread and then back to the main thread. I only create the WeakPtr and use it on the main thread, which is where my object live, so this is safe. It is only safe though because it is ThreadSafeRefCounted.

That said, I agree with the closing on this bug and I do not think we should try and promote usage of WeakPtr from different threads.

Just because an object is ThreadSafeRefCounted does not imply its methods are thread safe and that you use it from different threads.
Comment 7 Alex Christensen 2019-03-12 11:28:29 PDT
Should we then make WeakReference RefCounted instead of ThreadSafeRefCounted and add assertions that WeakPtrs are made, used, and destroyed on the same thread?  Those both sound like improvements to me.  We'll need the move constructor to be able to be called from any thread for cases where we hop to a different thread then back.
Comment 8 Chris Dumez 2019-03-12 11:40:35 PDT
(In reply to Alex Christensen from comment #7)
> Should we then make WeakReference RefCounted instead of ThreadSafeRefCounted
> and add assertions that WeakPtrs are made, used, and destroyed on the same
> thread?  Those both sound like improvements to me.  We'll need the move
> constructor to be able to be called from any thread for cases where we hop
> to a different thread then back.

What about the very valid use case I mentioned earlier?
Comment 9 Alex Christensen 2019-03-12 17:29:28 PDT
It does need to be ThreadSafeRefCounted, but why don't we add assertions that all important use happens on the same thread?  I could imagine that preventing some strange crashes.
Comment 10 Ryosuke Niwa 2019-03-12 17:41:04 PDT
(In reply to Alex Christensen from comment #9)
> It does need to be ThreadSafeRefCounted, but why don't we add assertions
> that all important use happens on the same thread?  I could imagine that
> preventing some strange crashes.

I'd argue that we should do this for RefCounted: https://bugs.webkit.org/show_bug.cgi?id=195656
Comment 11 Geoffrey Garen 2019-03-13 10:41:39 PDT
Let's continue the more general per-object / per-type assertion discussion in bug 195656.
Comment 12 Fujii Hironori 2019-07-10 00:58:03 PDT
Bug 199517 – Add threading assertion to WTF::WeakPtr
Bug 199639 – Add threading assertion to WeakPtrFactory::createWeakPtr()