Skip to content

Commit a9e9f87

Browse files
sammcCommit bot
authored andcommitted
Mojo: Cancel WaitingCallbacks when their HandleWrappers are closed.
Currently, if a JS connector is left to be garbage collected, the handle and the WaitingCallback both become ready to be collected at the same time. If the handle is collected first, this results in an asynchronous wait on a closed handle. With this change, WaitingCallback registers itself as an observer to be notified when the handle it's watching is closing and cancels the wait if the handle closes while the wait is in progress. BUG=406487 Review URL: https://codereview.chromium.org/534953002 Cr-Commit-Position: refs/heads/master@{#294775}
1 parent 9573ca9 commit a9e9f87

File tree

11 files changed

+178
-11
lines changed

11 files changed

+178
-11
lines changed

mojo/apps/js/test/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ test("mojo_apps_js_unittests") {
1717
]
1818

1919
sources = [
20+
"handle_unittest.cc",
2021
"js_to_cpp_unittest.cc",
2122
"run_apps_js_tests.cc",
2223
]

mojo/apps/js/test/handle_unittest.cc

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2014 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "mojo/bindings/js/handle.h"
6+
#include "mojo/bindings/js/handle_close_observer.h"
7+
#include "mojo/public/cpp/system/core.h"
8+
#include "testing/gtest/include/gtest/gtest.h"
9+
10+
namespace mojo {
11+
namespace js {
12+
13+
class HandleWrapperTest : public testing::Test,
14+
public gin::HandleCloseObserver {
15+
public:
16+
HandleWrapperTest() : closes_observed_(0) {}
17+
18+
virtual void OnWillCloseHandle() OVERRIDE { closes_observed_++; }
19+
20+
protected:
21+
int closes_observed_;
22+
23+
private:
24+
DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest);
25+
};
26+
27+
class TestHandleWrapper : public gin::HandleWrapper {
28+
public:
29+
explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {}
30+
31+
private:
32+
DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper);
33+
};
34+
35+
// Test that calling Close() on a HandleWrapper for an invalid handle does not
36+
// notify observers.
37+
TEST_F(HandleWrapperTest, CloseWithInvalidHandle) {
38+
{
39+
TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
40+
wrapper.AddCloseObserver(this);
41+
ASSERT_EQ(0, closes_observed_);
42+
wrapper.Close();
43+
EXPECT_EQ(0, closes_observed_);
44+
}
45+
EXPECT_EQ(0, closes_observed_);
46+
}
47+
48+
// Test that destroying a HandleWrapper for an invalid handle does not notify
49+
// observers.
50+
TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) {
51+
{
52+
TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
53+
wrapper.AddCloseObserver(this);
54+
ASSERT_EQ(0, closes_observed_);
55+
}
56+
EXPECT_EQ(0, closes_observed_);
57+
}
58+
59+
// Test that calling Close on a HandleWrapper for a valid handle notifies
60+
// observers once.
61+
TEST_F(HandleWrapperTest, CloseWithValidHandle) {
62+
{
63+
mojo::MessagePipe pipe;
64+
TestHandleWrapper wrapper(pipe.handle0.release().value());
65+
wrapper.AddCloseObserver(this);
66+
ASSERT_EQ(0, closes_observed_);
67+
wrapper.Close();
68+
EXPECT_EQ(1, closes_observed_);
69+
// Check that calling close again doesn't notify observers.
70+
wrapper.Close();
71+
EXPECT_EQ(1, closes_observed_);
72+
}
73+
// Check that destroying a closed HandleWrapper doesn't notify observers.
74+
EXPECT_EQ(1, closes_observed_);
75+
}
76+
77+
// Test that destroying a HandleWrapper for a valid handle notifies observers.
78+
TEST_F(HandleWrapperTest, DestroyWithValidHandle) {
79+
{
80+
mojo::MessagePipe pipe;
81+
TestHandleWrapper wrapper(pipe.handle0.release().value());
82+
wrapper.AddCloseObserver(this);
83+
ASSERT_EQ(0, closes_observed_);
84+
}
85+
EXPECT_EQ(1, closes_observed_);
86+
}
87+
88+
} // namespace js
89+
} // namespace mojo

mojo/bindings/js/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ source_set("js") {
99
"core.h",
1010
"handle.cc",
1111
"handle.h",
12+
"handle_close_observer.h",
1213
"support.cc",
1314
"support.h",
1415
"waiting_callback.cc",

mojo/bindings/js/handle.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "mojo/bindings/js/handle.h"
66

7+
#include "mojo/bindings/js/handle_close_observer.h"
8+
79
namespace gin {
810

911
gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin };
@@ -13,6 +15,27 @@ HandleWrapper::HandleWrapper(MojoHandle handle)
1315
}
1416

1517
HandleWrapper::~HandleWrapper() {
18+
NotifyCloseObservers();
19+
}
20+
21+
void HandleWrapper::Close() {
22+
NotifyCloseObservers();
23+
handle_.reset();
24+
}
25+
26+
void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) {
27+
close_observers_.AddObserver(observer);
28+
}
29+
30+
void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) {
31+
close_observers_.RemoveObserver(observer);
32+
}
33+
34+
void HandleWrapper::NotifyCloseObservers() {
35+
if (!handle_.is_valid())
36+
return;
37+
38+
FOR_EACH_OBSERVER(HandleCloseObserver, close_observers_, OnWillCloseHandle());
1639
}
1740

1841
v8::Handle<v8::Value> Converter<mojo::Handle>::ToV8(v8::Isolate* isolate,

mojo/bindings/js/handle.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
#ifndef MOJO_BINDINGS_JS_HANDLE_H_
66
#define MOJO_BINDINGS_JS_HANDLE_H_
77

8+
#include "base/observer_list.h"
89
#include "gin/converter.h"
910
#include "gin/handle.h"
1011
#include "gin/wrappable.h"
1112
#include "mojo/public/cpp/system/core.h"
1213

1314
namespace gin {
15+
class HandleCloseObserver;
1416

1517
// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle
1618
// is Closed when its JS object is garbage collected.
@@ -25,12 +27,18 @@ class HandleWrapper : public gin::Wrappable<HandleWrapper> {
2527

2628
mojo::Handle get() const { return handle_.get(); }
2729
mojo::Handle release() { return handle_.release(); }
28-
void Close() { handle_.reset(); }
30+
void Close();
31+
32+
void AddCloseObserver(HandleCloseObserver* observer);
33+
void RemoveCloseObserver(HandleCloseObserver* observer);
2934

3035
protected:
3136
HandleWrapper(MojoHandle handle);
3237
virtual ~HandleWrapper();
38+
void NotifyCloseObservers();
39+
3340
mojo::ScopedHandle handle_;
41+
ObserverList<HandleCloseObserver> close_observers_;
3442
};
3543

3644
// Note: It's important to use this converter rather than the one for
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2014 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef MOJO_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_
6+
#define MOJO_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_
7+
8+
namespace gin {
9+
10+
class HandleCloseObserver {
11+
public:
12+
virtual void OnWillCloseHandle() = 0;
13+
14+
protected:
15+
virtual ~HandleCloseObserver() {}
16+
};
17+
18+
} // namespace gin
19+
20+
#endif // MOJO_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_

mojo/bindings/js/support.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ namespace js {
2121

2222
namespace {
2323

24-
WaitingCallback* AsyncWait(const gin::Arguments& args, mojo::Handle handle,
24+
WaitingCallback* AsyncWait(const gin::Arguments& args,
25+
gin::Handle<gin::HandleWrapper> handle,
2526
MojoHandleSignals signals,
2627
v8::Handle<v8::Function> callback) {
2728
return WaitingCallback::Create(args.isolate(), callback, handle, signals)

mojo/bindings/js/waiting_callback.cc

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ gin::WrapperInfo WaitingCallback::kWrapperInfo = { gin::kEmbedderNativeGin };
2424
gin::Handle<WaitingCallback> WaitingCallback::Create(
2525
v8::Isolate* isolate,
2626
v8::Handle<v8::Function> callback,
27-
mojo::Handle handle,
27+
gin::Handle<gin::HandleWrapper> handle_wrapper,
2828
MojoHandleSignals signals) {
29-
gin::Handle<WaitingCallback> waiting_callback =
30-
gin::CreateHandle(isolate, new WaitingCallback(isolate, callback));
29+
gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle(
30+
isolate, new WaitingCallback(isolate, callback, handle_wrapper));
3131
waiting_callback->wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
32-
handle.value(),
32+
handle_wrapper->get().value(),
3333
signals,
3434
MOJO_DEADLINE_INDEFINITE,
3535
&WaitingCallback::CallOnHandleReady,
@@ -41,13 +41,17 @@ void WaitingCallback::Cancel() {
4141
if (!wait_id_)
4242
return;
4343

44+
handle_wrapper_->RemoveCloseObserver(this);
45+
handle_wrapper_ = NULL;
4446
Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
4547
wait_id_ = 0;
4648
}
4749

4850
WaitingCallback::WaitingCallback(v8::Isolate* isolate,
49-
v8::Handle<v8::Function> callback)
50-
: wait_id_() {
51+
v8::Handle<v8::Function> callback,
52+
gin::Handle<gin::HandleWrapper> handle_wrapper)
53+
: wait_id_(0), handle_wrapper_(handle_wrapper.get()) {
54+
handle_wrapper_->AddCloseObserver(this);
5155
v8::Handle<v8::Context> context = isolate->GetCurrentContext();
5256
runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
5357
GetWrapper(isolate)->SetHiddenValue(GetHiddenPropertyName(isolate), callback);
@@ -64,6 +68,8 @@ void WaitingCallback::CallOnHandleReady(void* closure, MojoResult result) {
6468

6569
void WaitingCallback::OnHandleReady(MojoResult result) {
6670
wait_id_ = 0;
71+
handle_wrapper_->RemoveCloseObserver(this);
72+
handle_wrapper_ = NULL;
6773

6874
if (!runner_)
6975
return;
@@ -80,5 +86,10 @@ void WaitingCallback::OnHandleReady(MojoResult result) {
8086
runner_->Call(callback, runner_->global(), 1, args);
8187
}
8288

89+
void WaitingCallback::OnWillCloseHandle() {
90+
Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
91+
OnHandleReady(MOJO_RESULT_INVALID_ARGUMENT);
92+
}
93+
8394
} // namespace js
8495
} // namespace mojo

mojo/bindings/js/waiting_callback.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@
88
#include "gin/handle.h"
99
#include "gin/runner.h"
1010
#include "gin/wrappable.h"
11+
#include "mojo/bindings/js/handle.h"
12+
#include "mojo/bindings/js/handle_close_observer.h"
1113
#include "mojo/public/c/environment/async_waiter.h"
1214
#include "mojo/public/cpp/system/core.h"
1315

1416
namespace mojo {
1517
namespace js {
1618

17-
class WaitingCallback : public gin::Wrappable<WaitingCallback> {
19+
class WaitingCallback : public gin::Wrappable<WaitingCallback>,
20+
public gin::HandleCloseObserver {
1821
public:
1922
static gin::WrapperInfo kWrapperInfo;
2023

2124
// Creates a new WaitingCallback.
2225
static gin::Handle<WaitingCallback> Create(
2326
v8::Isolate* isolate,
2427
v8::Handle<v8::Function> callback,
25-
mojo::Handle handle,
28+
gin::Handle<gin::HandleWrapper> handle_wrapper,
2629
MojoHandleSignals signals);
2730

2831
// Cancels the callback. Does nothing if a callback is not pending. This is
@@ -31,7 +34,9 @@ class WaitingCallback : public gin::Wrappable<WaitingCallback> {
3134
void Cancel();
3235

3336
private:
34-
WaitingCallback(v8::Isolate* isolate, v8::Handle<v8::Function> callback);
37+
WaitingCallback(v8::Isolate* isolate,
38+
v8::Handle<v8::Function> callback,
39+
gin::Handle<gin::HandleWrapper> handle_wrapper);
3540
virtual ~WaitingCallback();
3641

3742
// Callback from MojoAsyncWaiter. |closure| is the WaitingCallback.
@@ -40,9 +45,15 @@ class WaitingCallback : public gin::Wrappable<WaitingCallback> {
4045
// Invoked from CallOnHandleReady() (CallOnHandleReady() must be static).
4146
void OnHandleReady(MojoResult result);
4247

48+
// Invoked by the HandleWrapper if the handle is closed while this wait is
49+
// still in progress.
50+
virtual void OnWillCloseHandle() OVERRIDE;
51+
4352
base::WeakPtr<gin::Runner> runner_;
4453
MojoAsyncWaitID wait_id_;
4554

55+
gin::HandleWrapper* handle_wrapper_;
56+
4657
DISALLOW_COPY_AND_ASSIGN(WaitingCallback);
4758
};
4859

mojo/mojo_apps.gypi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
'mojo_js_lib',
7474
],
7575
'sources': [
76+
'apps/js/test/handle_unittest.cc',
7677
'apps/js/test/js_to_cpp_unittest.cc',
7778
'apps/js/test/run_apps_js_tests.cc',
7879
],

mojo/mojo_base.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@
506506
'bindings/js/core.h',
507507
'bindings/js/handle.cc',
508508
'bindings/js/handle.h',
509+
'bindings/js/handle_close_observer.h',
509510
'bindings/js/support.cc',
510511
'bindings/js/support.h',
511512
'bindings/js/waiting_callback.cc',

0 commit comments

Comments
 (0)