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

Issue 1082113004: BindToCurrentLoop should delete callback on original thread

Created:
5 years, 8 months ago by johnme
Modified:
4 years, 8 months ago
Reviewers:
danakj, xhwang
CC:
chromium-reviews, feature-media-reviews_chromium.org
Base URL:
https://chromium.googlesource.com/chromium/src.git@master
Target Ref:
refs/pending/heads/master
Project:
chromium
Visibility:
Public.

Description

BindToCurrentLoop should delete callback on original thread It was pointed out on https://codereview.chromium.org/1083883003 that the callback wrapped by media::base::BindToCurrentLoop can get deleted on an arbitrary thread. This patch makes the callback always be deleted on the thread from which BindToCurrentLoop was invoked. BUG=432381

Patch Set 1 #

Total comments: 6

Patch Set 2 : Alternative impl, with scoped_ptr<Callback, DeleteOnCurrentLoop>* #

Total comments: 20

Patch Set 3 : Original impl, with nits addressed #

Patch Set 4 : Use ThreadTaskRunnerHandle::Get() instead of MessageLoop::current()->task_runner() #

Patch Set 5 : Address danakj's review comments #

Patch Set 6 : Rebase #

Patch Set 7 : Rebase #

Unified diffs Side-by-side diffs Delta from patch set Stats (+236 lines, -19 lines) Patch
M media/base/BUILD.gn View 1 2 3 4 5 6 1 chunk +1 line, -0 lines 0 comments Download
M media/base/bind_to_current_loop.h View 1 2 3 4 5 6 3 chunks +85 lines, -19 lines 0 comments Download
A media/base/bind_to_current_loop.cc View 1 2 3 4 1 chunk +25 lines, -0 lines 0 comments Download
M media/base/bind_to_current_loop_unittest.cc View 1 2 3 4 5 6 2 chunks +124 lines, -0 lines 0 comments Download
M media/media.gyp View 1 2 3 4 5 6 1 chunk +1 line, -0 lines 0 comments Download

Messages

Total messages: 18 (1 generated)
johnme
danakj: please review patch (hopefully it addresses your comments in https://codereview.chromium.org/1083883003) xhwang: please give owners ...
5 years, 8 months ago (2015-04-24 18:35:58 UTC) #2
danakj
https://codereview.chromium.org/1082113004/diff/1/media/base/bind_to_current_loop.h File media/base/bind_to_current_loop.h (right): https://codereview.chromium.org/1082113004/diff/1/media/base/bind_to_current_loop.h#newcode67 media/base/bind_to_current_loop.h:67: } else if (!origin_loop_->DeleteSoon(from_here_, cb_ptr_)) { I think you ...
5 years, 8 months ago (2015-04-24 19:00:03 UTC) #3
xhwang
I like this idea of this CL. I'll wait for you to address danakj@'s comment ...
5 years, 8 months ago (2015-04-27 17:56:42 UTC) #4
johnme
https://codereview.chromium.org/1082113004/diff/1/media/base/bind_to_current_loop.h File media/base/bind_to_current_loop.h (right): https://codereview.chromium.org/1082113004/diff/1/media/base/bind_to_current_loop.h#newcode67 media/base/bind_to_current_loop.h:67: } else if (!origin_loop_->DeleteSoon(from_here_, cb_ptr_)) { On 2015/04/24 19:00:03, ...
5 years, 8 months ago (2015-04-27 18:32:33 UTC) #5
danakj
On Mon, Apr 27, 2015 at 11:32 AM, <johnme@chromium.org> wrote: > > > https://codereview.chromium.org/1082113004/diff/1/media/base/bind_to_current_loop.h > ...
5 years, 7 months ago (2015-04-29 20:42:54 UTC) #6
johnme
On 2015/04/29 20:42:54, danakj wrote: > On Mon, Apr 27, 2015 at 11:32 AM, johnme ...
5 years, 7 months ago (2015-04-29 21:18:46 UTC) #7
danakj
On Wed, Apr 29, 2015 at 2:18 PM, <johnme@chromium.org> wrote: > On 2015/04/29 20:42:54, danakj ...
5 years, 7 months ago (2015-04-29 21:22:44 UTC) #8
danakj
https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h File media/base/bind_to_current_loop.h (right): https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode23 media/base/bind_to_current_loop.h:23: // media::base::BindToCurrentLoop(base::Bind(&MyClass::MyMethod, this))); no base::? https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode71 media/base/bind_to_current_loop.h:71: // scoped_ptr<Foo, ...
5 years, 7 months ago (2015-04-29 23:35:02 UTC) #9
danakj
https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h File media/base/bind_to_current_loop.h (right): https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode79 media/base/bind_to_current_loop.h:79: if (origin_loop_->BelongsToCurrentThread()) { oh and no conditional posting please
5 years, 7 months ago (2015-04-29 23:37:34 UTC) #10
johnme
Addressed/replied to review comments - thanks :) https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h File media/base/bind_to_current_loop.h (right): https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode23 media/base/bind_to_current_loop.h:23: // media::base::BindToCurrentLoop(base::Bind(&MyClass::MyMethod, ...
5 years, 7 months ago (2015-04-30 16:15:53 UTC) #11
danakj
On Thu, Apr 30, 2015 at 9:15 AM, <johnme@chromium.org> wrote: > Addressed/replied to review comments ...
5 years, 7 months ago (2015-04-30 16:42:25 UTC) #12
johnme
On 2015/04/30 16:42:25, danakj wrote: > https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode72 > > media/base/bind_to_current_loop.h:72: struct MEDIA_EXPORT > > DeleteOnCurrentLoop ...
5 years, 7 months ago (2015-04-30 17:09:12 UTC) #13
danakj
On Thu, Apr 30, 2015 at 10:09 AM, <johnme@chromium.org> wrote: > On 2015/04/30 16:42:25, danakj ...
5 years, 7 months ago (2015-04-30 17:13:10 UTC) #14
danakj
On Thu, Apr 30, 2015 at 9:15 AM, <johnme@chromium.org> wrote: > > https://codereview.chromium.org/1082113004/diff/20001/media/base/bind_to_current_loop.h#newcode117 > media/base/bind_to_current_loop.h:117: ...
5 years, 7 months ago (2015-04-30 17:44:36 UTC) #15
johnme
On 2015/04/30 17:13:10, danakj wrote: > On Thu, Apr 30, 2015 at 10:09 AM, <mailto:johnme@chromium.org> ...
5 years, 7 months ago (2015-05-01 18:18:09 UTC) #16
danakj
On Fri, May 1, 2015 at 11:18 AM, <johnme@chromium.org> wrote: > On 2015/04/30 17:13:10, danakj ...
5 years, 7 months ago (2015-05-04 18:46:13 UTC) #17
johnme
5 years, 7 months ago (2015-05-05 16:01:54 UTC) #18
On 2015/05/04 18:46:13, danakj wrote:
> On Fri, May 1, 2015 at 11:18 AM, <mailto:johnme@chromium.org> wrote:
> 
> > On 2015/04/30 17:13:10, danakj wrote:
> >
> >> On Thu, Apr 30, 2015 at 10:09 AM, <mailto:johnme@chromium.org> wrote:
> >>
> >
> >  > On 2015/04/30 16:42:25, danakj wrote:
> >> >
> >>
> >
> >
> >
>
https://code.google.com/p/chromium/codesearch#chromium/src/content/public/bro...
> >
> >> >
> >> >> >
> >> >> > The original code was written by jam/darin:
> >> >> >
> >> >> > https://codereview.chromium.org/338065
> >> >> >
> >> >> > I'm guessing their reasoning is that we may as well delete it if we
> >> have
> >> >> > the chance, whereas if we delay it may no longer be possible to
> >> delete
> >> >> > it.
> >> >> >
> >> >> > I tried removing the conditional and always using DeleteSoon, but
> >> that
> >> >> > causes the following tests to fail:
> >> >> >     NullVideoSinkTest.BasicFunctionality
> >> >> >     VideoRendererImplTest.StartPlayingFrom_LowDelay
> >> >> >     VideoRendererImplTest.Underflow
> >> >> >
> >> >> > These unit tests each have a MessageLoop owned by the test class.
> >> They
> >> >> > wait for some event on the message loop, then return, which causes
> >> the
> >> >> > test class to be destroyed. Hence the MessageLoop is destroyed, and
> >> >> > hence the DeleteOnLoop scoped_ptr to the callback ends up being
> >> >> > destructed. With the conditional this is fine -- it's already on the
> >> >> > right thread, so it goes ahead and synchronously deletes the
> >> callback.
> >> >> > But without the conditional, it tries to send a DeleteSoon to the
> >> >> > being-deleted message loop, which fails and our LOG(FATAL) causes the
> >> >> > test to crash.
> >> >>
> >> >
> >> >
> >> >  Can we RunUntilIdle before exiting the test?
> >> >>
> >> >
> >> > No, I tried that :-| In fact, VideoRendererImplTest::Destroy() does that
> >> > already
> >> > at the end of each test before deleting the MessageLoop.
> >> >
> >>
> >
> >  That's weird, why would it not run the DeleteSoon?
> >>
> >
> > I'm not sure :-|
> >
> 
> Is it something you can figure out? I don't like blindly making changes.

Ok, it turns out that all three tests define mock methods, e.g.:

class NullVideoSinkTest : public testing::Test,
                          public VideoRendererSink::RenderCallback {
 public:
  ...
  // VideoRendererSink::RenderCallback implementation.
  MOCK_METHOD2(Render,
               scoped_refptr<VideoFrame>(base::TimeTicks, base::TimeTicks));
  MOCK_METHOD0(OnFrameDropped, void());

  MOCK_METHOD1(FrameReceived, void(const scoped_refptr<VideoFrame>&));

 protected:
  base::MessageLoop message_loop_;
  ...
};

Then the tests have code like:

TEST_F (NullVideoSinkTest, BasicFunctionality) {
    ...
    WaitableMessageLoopEvent event;
    EXPECT_CALL(*this, FrameReceived(test_frame))
        .WillOnce(RunClosure(event.GetClosure()));
    event.RunAndWait();
    ...
}

This is simply intended to spin a message loop until the mocked method is
called, then continue. But WaitableMessageLoopEvent::GetClosure is implemented
as:

base::Closure WaitableMessageLoopEvent::GetClosure() {
  ...
  return BindToCurrentLoop(base::Bind(
      &WaitableMessageLoopEvent::OnCallback, base::Unretained(this),
      PIPELINE_OK));
}

And, from debugging the lifetimes, it turns out that Google Mock's MOCK_METHODs
hold on to the RunClosure callback until the MOCK_METHOD is destroyed (i.e. when
NullVideoSinkTest or VideoRendererImplTest are destroyed).

The MessageLoop member of both test classes happens to be destroyed before the
MOCK_METHODs, hence when the MOCK_METHODs are destroyed, the callback returned
by BindToCurrentLoop finally gets destroyed, and DeleteOnLoop::Destruct fails to
call DeleteSoon because the MessageLoop no longer exists.

A reduced test case for all this is:

TEST_F(Foo, DestroyAfterMessageLoop) {
  base::Closure cb;
  {
    base::MessageLoop message_loop;
    cb = BindToCurrentLoop(base::Bind(&base::DoNothing));
  }
}

which hits the same LOG(FATAL) due to DeleteSoon failing.

It would be possible to fix these tests by ensuring the callback returned by
BindToCurrentLoop is destroyed before the MessageLoop is; however (as well as
the refactoring possibly being invasive), I'm not convinced there's actually
anything wrong with tests like my reduced DestroyAfterMessageLoop above. It
seems pretty reasonable to allow the thread that the callback is meant to be
destroyed on to keep a reference whose lifetime exceeds that of its message
loop, since the message loop won't be needed to destroy it as it's already on
the right thread. Not allowing this will likely just waste developer time
hunting down lifetime subtleties that only affect unit tests...

Powered by Google App Engine
This is Rietveld 408576698