(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(nsCOMPtrnsIFoo aFoo)
{
mFoo.swap(aFoo); // Never AddRef/Release off MainThread
}
...
{
nsRefPtrFooRunnable 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:
{
nsRefPtrFooRunnable foo = new FooRunnable(myFoo);
// RefCnt == 1, myFoo.get() == nullptr now
// This requires adding a version of
// NS_DispatchToMainThread(already_AddRefednsIRunnable) 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 nsCOMPtrnsIFoo mFoo in the runnable with
nsReleaseOnMainThreadnsCOMPtrnsIFoo 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