Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(268)

Issue 2885143005: Fixing race between ~AsyncInvoker and ~AsyncClosure, using ref-counting. (Closed)

Created:
3 years, 7 months ago by Taylor Brandstetter
Modified:
3 years, 4 months ago
Reviewers:
kwiberg-webrtc
CC:
webrtc-reviews_webrtc.org, tterriberry_mozilla.com, kwiberg-webrtc
Target Ref:
refs/heads/master
Project:
webrtc
Visibility:
Public.

Description

Fixing race between ~AsyncInvoker and ~AsyncClosure, using ref-counting. The AsyncInvoker destructor waits for all invoked tasks to be complete (in other words, all AsyncClosures to be destructed). They were using an event to wake up the destructor, but a race made it possible for this event to be dereferenced after it's destroyed. This CL makes the event reference counted, such that if the destructor runs right after AsyncClosure decrements "pending_invocations_", setting the event will be a no-op, and the event will be destructed in the AsyncClosure destructor. This CL also fixes a deadlock that may occur for "re-entrant" invocations. The deadlock occurs if the AsyncInvoker is destroyed on thread A while a task on thread B is running, which AsyncInvokes a task back on thread A. This was causing pending_invocations_ to end up negative, because an AsyncClosure that's never added to a thread's message queue (due to the "destroying_" flag) caused the count to be decremented but not incremented. BUG=webrtc:7656 Review-Url: https://codereview.webrtc.org/2885143005 Cr-Commit-Position: refs/heads/master@{#19278} Committed: https://chromium.googlesource.com/external/webrtc/+/3af63b0dc9d8444ca5480dfcccd1f87fa05eb232

Patch Set 1 #

Patch Set 2 : Rebase on revision that still had CancelInvoke, which is a test we need. #

Patch Set 3 : . #

Patch Set 4 : Remove extra increments of pending_invocations_ #

Patch Set 5 : Merging in https://codereview.webrtc.org/2885143006/ #

Patch Set 6 : Only clear current thread's message queue in destructor loop #

Total comments: 13

Patch Set 7 : Adding comments and switching to std::atomic. #

Total comments: 15

Patch Set 8 : Adding comments and addressing nit. #

Patch Set 9 : Add another TODO. #

Unified diffs Side-by-side diffs Delta from patch set Stats (+134 lines, -35 lines) Patch
M webrtc/rtc_base/asyncinvoker.h View 1 2 3 4 5 6 7 8 8 chunks +43 lines, -7 lines 0 comments Download
M webrtc/rtc_base/asyncinvoker.cc View 1 2 3 4 5 6 7 6 chunks +40 lines, -16 lines 0 comments Download
M webrtc/rtc_base/asyncinvoker-inl.h View 1 2 3 4 5 6 3 chunks +9 lines, -2 lines 0 comments Download
M webrtc/rtc_base/thread_unittest.cc View 1 2 3 4 2 chunks +42 lines, -10 lines 0 comments Download

Messages

Total messages: 18 (8 generated)
Taylor Brandstetter
kwiberg: I don't know how familiar you are with AsyncInvoker, but would you be able ...
3 years, 4 months ago (2017-08-04 00:04:53 UTC) #5
kwiberg-webrtc
lgtm, but see comments I'm not an AsyncInvoker expert by any means, but it certainly ...
3 years, 4 months ago (2017-08-04 09:28:09 UTC) #6
Taylor Brandstetter
Switched to std::atomic, take another look. https://codereview.webrtc.org/2885143005/diff/100001/webrtc/rtc_base/asyncinvoker.cc File webrtc/rtc_base/asyncinvoker.cc (right): https://codereview.webrtc.org/2885143005/diff/100001/webrtc/rtc_base/asyncinvoker.cc#newcode33 webrtc/rtc_base/asyncinvoker.cc:33: Thread::Current()->Clear(this); On 2017/08/04 ...
3 years, 4 months ago (2017-08-04 19:14:23 UTC) #7
kwiberg-webrtc
Lots of subtleties with std::atomic... :-) I'm by no means an expert, so I tried ...
3 years, 4 months ago (2017-08-06 04:17:13 UTC) #8
Taylor Brandstetter
https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc File webrtc/rtc_base/asyncinvoker.cc (right): https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc#newcode24 webrtc/rtc_base/asyncinvoker.cc:24: destroying_.store(true, std::memory_order_relaxed); On 2017/08/06 04:17:12, kwiberg-webrtc wrote: > Doesn't ...
3 years, 4 months ago (2017-08-07 20:28:12 UTC) #9
kwiberg-webrtc
lgtm https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc File webrtc/rtc_base/asyncinvoker.cc (right): https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc#newcode24 webrtc/rtc_base/asyncinvoker.cc:24: destroying_.store(true, std::memory_order_relaxed); On 2017/08/07 20:28:12, Taylor Brandstetter wrote: ...
3 years, 4 months ago (2017-08-08 22:22:39 UTC) #10
Taylor Brandstetter
https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc File webrtc/rtc_base/asyncinvoker.cc (right): https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinvoker.cc#newcode125 webrtc/rtc_base/asyncinvoker.cc:125: invoker_->pending_invocations_.fetch_add(1, std::memory_order_relaxed); On 2017/08/08 22:22:39, kwiberg-webrtc wrote: > On ...
3 years, 4 months ago (2017-08-08 23:42:16 UTC) #11
commit-bot: I haz the power
CQ is trying da patch. Follow status at: https://chromium-cq-status.appspot.com/v2/patch-status/codereview.webrtc.org/2885143005/160001
3 years, 4 months ago (2017-08-08 23:42:35 UTC) #14
commit-bot: I haz the power
Committed patchset #9 (id:160001) as https://chromium.googlesource.com/external/webrtc/+/3af63b0dc9d8444ca5480dfcccd1f87fa05eb232
3 years, 4 months ago (2017-08-09 00:59:54 UTC) #17
kwiberg-webrtc
3 years, 4 months ago (2017-08-09 08:41:57 UTC) #18
Message was sent while issue was closed.
https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinv...
File webrtc/rtc_base/asyncinvoker.cc (right):

https://codereview.webrtc.org/2885143005/diff/120001/webrtc/rtc_base/asyncinv...
webrtc/rtc_base/asyncinvoker.cc:125: invoker_->pending_invocations_.fetch_add(1,
std::memory_order_relaxed);
On 2017/08/08 23:42:16, Taylor Brandstetter wrote:
> On 2017/08/08 22:22:39, kwiberg-webrtc wrote:
> > On 2017/08/07 20:28:12, Taylor Brandstetter wrote:
> > > On 2017/08/06 04:17:12, kwiberg-webrtc wrote:
> > > > std::memory_order_acquire? (Because unlike a reference count, where you
> > never
> > > > increment from 0 to 1, pending_invocations_ can be incremented from 0 to
1
> > by
> > > > this operation, which makes it unsafe to reorder reads and writes from
> this
> > > > thread before this increment.)
> > > 
> > > This would only be an issue if one thread is incrementing the count from
> 0->1
> > > while another thread is destroying the AsyncInvoker. But that means one
> thread
> > > is calling "AsyncInvoke" while another thread is destroying the invoker,
> which
> > > is inherently unsafe.
> > > 
> > > The only time when a "safe" usage of this class could result in
AsyncInvoke
> > > being called during destruction is when the AsyncInvoke is happening from
> > > *within* an AsyncInvoked functor (the so-called "re-entrant"/"recursive"
> > > invocations). But in that case, the outer invocation is holding a
reference,
> > so
> > > it could only be incremented from 1->2. And it's not possible for the
> > decrement
> > > of pending_invocations_ to be reordered relative to the increment, so we
> don't
> > > have to worry about, say, it being decremented from 1->0 before being
> > > incremented from 1->2.
> > > 
> > > I added some more comments to the header to make it more clear what things
> are
> > > "safe" and how the class should be properly used.
> > 
> > Acknowledged. Ach, this is subtle... I have a strong feeling that the right
> > thing to do from a code maintenance perspective is to not have naked atomic
> > operations mixed in with a heap of other nontrivial code like this, but to
> > package them up in a clearly documented class. Compare with the current best
> > practice of handling heap object ownership in C++: always use a dedicated
> smart
> > pointer class, except when implementing smart pointers. Not because raw new
> and
> > delete are impossible to use correctly, but because they have sharp edges
that
> > we'd rather not deal with at the same time we're dealing with other
nontrivial
> > things.
> 
> Agreed. I can't think of a great way to split this functionality into an
> independent class at the moment, but I'll add a TODO comment for now and think
> about it.

I was thinking along these lines:

  // Instances of this class are initially unset. The flag is
  // set and tested with atomic instructions, which makes it
  // thread-safe, but there are no memory barriers that prevent
  // the setting and testing of the flag being reordered relative
  // to surrounding memory accesses. Informally, use this to
  // signal a condition when it doesn't matter if the signal
  // arrives a little early or a little late.
  class NonsynchronizingAtomicOneWayFlag {
    void Set();  // Atomically set the flag.
    bool IsSet();  // Has the flag been set?
  };

  // Instances of this class initially have a reference count of 1.
  // Incref() and Decref() calls can be freely mixed and are
  // thread-safe. Once the refcount goes to zero, the counter is
  // spent, and you may no longer call either Incref() or Decref();
  // this means that the caller needs to statically ensure that
  // the refcount is at least 1.
  class RefCounter {
    void Incref();  // Atomically increases the refcount by 1.
    bool Decref();  // Atomically decreases the refcount by 1,
                    // returning true if the refcount reached zero.
  };

pending_invocations_ is more complicated, so I won't try to design it here.

Powered by Google App Engine
This is Rietveld 408576698