(was: Re: Proposal to ban the usage of refcounted objects inside C++
lambdas in Gecko)

tl;dr: We should make DispatchToMainThread take already_AddRefed and
move away from raw ptrs for Dispatches in general.

So:

What I want to avoid is this pattern for runnables that hold
thread-restricted pointers (mostly MainThread-restricted, such as JS
callbacks):

     FooRunnable::FooRunnable(nsCOMPtr<nsIFoo>& aFoo)
     {
       mFoo.swap(aFoo); // Never AddRef/Release off MainThread
     }
...
    {
      nsRefPtr<FooRunnable> foo = new FooRunnable(myFoo);
      // RefCnt == 1, myFoo.get() == nullptr now
      NS_DispatchToMainThread(foo); // infallible (soon), takes a ref to foo 
(refcnt 2)

      // XXX what is foo's refcnt here?  You don't know!
    }

The reason is that foo may go out of scope here *after* it has Run() and
had the event ref dropped on mainthread.  I.e.: at the XXX comment, it
may "usually" be 2, but sometimes may be 1, and when foo goes out of
scope we delete it here, and violate thread-safety for mFoo.

Blindly taking an NS_DispatchToMainThread(new FooRunnable) and
converting it to the pattern above (with nsRefPtr) will introduce a
timing-based thread safety violation IF it holds thread-locked items
(which isn't super-common).  But also, it's extra thread-safe
addref/releasing when we don't need to.

A safe pattern is this:
    {
      nsRefPtr<FooRunnable> foo = new FooRunnable(myFoo);
      // RefCnt == 1, myFoo.get() == nullptr now
      // This requires adding a version of
      // NS_DispatchToMainThread(already_AddRefed<nsIRunnable>) or some such
      NS_DispatchToMainThread(foo.forget()); // infallible (soon)
      // foo is nullptr
    }

And to be honest, that's generally a better pattern for runnables
anyways - we *want* to give them away on Dispatch()/DispatchToMainThread().

Note that you can also do ehsan's trick for safety instead:

   ~FooRunnable()
   {
     if (!NS_IsMainThread()) {
       // get mainthread
       NS_ProxyRelease(mainthread, mFoo);
     }
   }

I don't love this though.  It adds almost-never-executed code, we
addref/release extra times, it could bitrot or forget to grow new
mainthread-only refs.  And it wastes code and adds semantic boilerplate
to ignore when reading code.

You could add a macro to build these and hide the boilerplate some, but
that's not wonderful either.

So, if I have my druthers:  (And really only #1 is needed, probably)

1) Add already_AddRefed variants of Dispatch/DispatchToMainThread, and
   convert things as needed to .forget() (Preferably most
   Dispatch/DispatchToMainThreads would become this.)  If the Dispatch()
   can fail and it's not a logic error somewhere (due to Dispatch to a
   doomed thread perhaps in some race conditions), then live with a leak
   - we can't send it there to die.  DispatchToMainThread() isn't
   affected by this and will be infallible soon.

2) If you're building a runnable with a complex lifetime and a
   MainThread or thread-locked object, also add the ProxyRelease
   destructor.  Otherwise consider simple boilerplate (for
   MainThread-only runnables) to do:
   
     ~FooRunnable() { if (!NS_IsMainThread()) { MOZ_CRASH(); } }

   (Perhaps DESTROYED_ON_MAINTHREAD_ONLY(FooRunnable)).  Might not be a
   bad thing to have in our pockets for other reasons.
   
3) Consider replacing nsCOMPtr<nsIFoo> mFoo in the runnable with
   nsReleaseOnMainThread<nsCOMPtr<nsIFoo>> mFoo and have
   ~nsReleaseOnMainThread() do NS_ProxyRelease, and also have
   nsReleaseOnMainThread block attempts to do assignments that AddRef
   (only set via swap or assign from already_AddRefed).  Unlike the
   MainThreadPtrHolder this could be created on other threads since it
   never AddRefs; it only inherits references and releases them on MainThread

I wonder if we could move to requiring already_AddRefed for
DispatchToMainThread (and Dispatch?), and thus block all attempts to do
DispatchToMainThread(new FooRunnable), etc.  :-)

-- 
Randell Jesup, Mozilla Corp
remove "news" for personal email
_______________________________________________
dev-platform mailing list
dev-platform@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-platform

Reply via email to