forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mojo: Fix nested SyncHandleWatcher Wait
We defer event callback list modification until the outermost Wait() unwinds, but we need to ensure that the internal WaitSet has events removed immediately within UnregisterEvent if the event has no more valid callbacks registered. This allows callers to make the assumption that exclusively registered events are not referenced beyond a call to UnregisterEvent, which is an assumption made by production code. Adds tests for SyncHandleRegistry event watching too. BUG=761097 Change-Id: Ibbf6eee335879bba6b732637944338f046bf37cb Reviewed-on: https://chromium-review.googlesource.com/646491 Reviewed-by: Yuzhu Shen <yzshen@chromium.org> Commit-Queue: Ken Rockot <rockot@chromium.org> Cr-Commit-Position: refs/heads/master@{#499116}
- Loading branch information
Showing
3 changed files
with
279 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
257 changes: 257 additions & 0 deletions
257
mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
// Copyright 2017 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "mojo/public/cpp/bindings/sync_handle_registry.h" | ||
#include "base/bind.h" | ||
#include "base/memory/ptr_util.h" | ||
#include "base/memory/ref_counted.h" | ||
#include "base/synchronization/waitable_event.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
namespace mojo { | ||
|
||
class SyncHandleRegistryTest : public testing::Test { | ||
public: | ||
SyncHandleRegistryTest() : registry_(SyncHandleRegistry::current()) {} | ||
|
||
const scoped_refptr<SyncHandleRegistry>& registry() { return registry_; } | ||
|
||
private: | ||
scoped_refptr<SyncHandleRegistry> registry_; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(SyncHandleRegistryTest); | ||
}; | ||
|
||
TEST_F(SyncHandleRegistryTest, DuplicateEventRegistration) { | ||
bool called1 = false; | ||
bool called2 = false; | ||
auto callback = [](bool* called) { *called = true; }; | ||
auto callback1 = base::Bind(callback, &called1); | ||
auto callback2 = base::Bind(callback, &called2); | ||
|
||
base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
registry()->RegisterEvent(&e, callback1); | ||
registry()->RegisterEvent(&e, callback2); | ||
|
||
const bool* stop_flags[] = {&called1, &called2}; | ||
registry()->Wait(stop_flags, 2); | ||
|
||
EXPECT_TRUE(called1); | ||
EXPECT_TRUE(called2); | ||
registry()->UnregisterEvent(&e, callback1); | ||
|
||
called1 = false; | ||
called2 = false; | ||
|
||
registry()->Wait(stop_flags, 2); | ||
|
||
EXPECT_FALSE(called1); | ||
EXPECT_TRUE(called2); | ||
|
||
registry()->UnregisterEvent(&e, callback2); | ||
} | ||
|
||
TEST_F(SyncHandleRegistryTest, UnregisterDuplicateEventInNestedWait) { | ||
base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool called1 = false; | ||
bool called2 = false; | ||
bool called3 = false; | ||
auto callback1 = base::Bind([](bool* called) { *called = true; }, &called1); | ||
auto callback2 = base::Bind( | ||
[](base::WaitableEvent* e, const base::Closure& other_callback, | ||
scoped_refptr<SyncHandleRegistry> registry, bool* called) { | ||
registry->UnregisterEvent(e, other_callback); | ||
*called = true; | ||
}, | ||
&e, callback1, registry(), &called2); | ||
auto callback3 = base::Bind([](bool* called) { *called = true; }, &called3); | ||
|
||
registry()->RegisterEvent(&e, callback1); | ||
registry()->RegisterEvent(&e, callback2); | ||
registry()->RegisterEvent(&e, callback3); | ||
|
||
const bool* stop_flags[] = {&called1, &called2, &called3}; | ||
registry()->Wait(stop_flags, 3); | ||
|
||
// We don't make any assumptions about the order in which callbacks run, so | ||
// we can't check |called1| - it may or may not get set depending on internal | ||
// details. All we know is |called2| should be set, and a subsequent wait | ||
// should definitely NOT set |called1|. | ||
EXPECT_TRUE(called2); | ||
EXPECT_TRUE(called3); | ||
|
||
called1 = false; | ||
called2 = false; | ||
called3 = false; | ||
|
||
registry()->UnregisterEvent(&e, callback2); | ||
registry()->Wait(stop_flags, 3); | ||
|
||
EXPECT_FALSE(called1); | ||
EXPECT_FALSE(called2); | ||
EXPECT_TRUE(called3); | ||
} | ||
|
||
TEST_F(SyncHandleRegistryTest, UnregisterAndRegisterForNewEventInCallback) { | ||
auto e = base::MakeUnique<base::WaitableEvent>( | ||
base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool called = false; | ||
base::Closure callback_holder; | ||
auto callback = base::Bind( | ||
[](std::unique_ptr<base::WaitableEvent>* e, | ||
base::Closure* callback_holder, | ||
scoped_refptr<SyncHandleRegistry> registry, bool* called) { | ||
EXPECT_FALSE(*called); | ||
|
||
registry->UnregisterEvent(e->get(), *callback_holder); | ||
e->reset(); | ||
*called = true; | ||
|
||
base::WaitableEvent nested_event( | ||
base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool nested_called = false; | ||
auto nested_callback = | ||
base::Bind([](bool* called) { *called = true; }, &nested_called); | ||
registry->RegisterEvent(&nested_event, nested_callback); | ||
const bool* stop_flag = &nested_called; | ||
registry->Wait(&stop_flag, 1); | ||
registry->UnregisterEvent(&nested_event, nested_callback); | ||
}, | ||
&e, &callback_holder, registry(), &called); | ||
callback_holder = callback; | ||
|
||
registry()->RegisterEvent(e.get(), callback); | ||
|
||
const bool* stop_flag = &called; | ||
registry()->Wait(&stop_flag, 1); | ||
EXPECT_TRUE(called); | ||
} | ||
|
||
TEST_F(SyncHandleRegistryTest, UnregisterAndRegisterForSameEventInCallback) { | ||
base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool called = false; | ||
base::Closure callback_holder; | ||
auto callback = base::Bind( | ||
[](base::WaitableEvent* e, base::Closure* callback_holder, | ||
scoped_refptr<SyncHandleRegistry> registry, bool* called) { | ||
EXPECT_FALSE(*called); | ||
|
||
registry->UnregisterEvent(e, *callback_holder); | ||
*called = true; | ||
|
||
bool nested_called = false; | ||
auto nested_callback = | ||
base::Bind([](bool* called) { *called = true; }, &nested_called); | ||
registry->RegisterEvent(e, nested_callback); | ||
const bool* stop_flag = &nested_called; | ||
registry->Wait(&stop_flag, 1); | ||
registry->UnregisterEvent(e, nested_callback); | ||
|
||
EXPECT_TRUE(nested_called); | ||
}, | ||
&e, &callback_holder, registry(), &called); | ||
callback_holder = callback; | ||
|
||
registry()->RegisterEvent(&e, callback); | ||
|
||
const bool* stop_flag = &called; | ||
registry()->Wait(&stop_flag, 1); | ||
EXPECT_TRUE(called); | ||
} | ||
|
||
TEST_F(SyncHandleRegistryTest, RegisterDuplicateEventFromWithinCallback) { | ||
base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool called = false; | ||
int call_count = 0; | ||
auto callback = base::Bind( | ||
[](base::WaitableEvent* e, scoped_refptr<SyncHandleRegistry> registry, | ||
bool* called, int* call_count) { | ||
// Don't re-enter. | ||
++(*call_count); | ||
if (*called) | ||
return; | ||
|
||
*called = true; | ||
|
||
bool called2 = false; | ||
auto callback2 = | ||
base::Bind([](bool* called) { *called = true; }, &called2); | ||
registry->RegisterEvent(e, callback2); | ||
|
||
const bool* stop_flag = &called2; | ||
registry->Wait(&stop_flag, 1); | ||
|
||
registry->UnregisterEvent(e, callback2); | ||
}, | ||
&e, registry(), &called, &call_count); | ||
|
||
registry()->RegisterEvent(&e, callback); | ||
|
||
const bool* stop_flag = &called; | ||
registry()->Wait(&stop_flag, 1); | ||
|
||
EXPECT_TRUE(called); | ||
EXPECT_EQ(2, call_count); | ||
|
||
registry()->UnregisterEvent(&e, callback); | ||
} | ||
|
||
TEST_F(SyncHandleRegistryTest, UnregisterUniqueEventInNestedWait) { | ||
auto e1 = base::MakeUnique<base::WaitableEvent>( | ||
base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::NOT_SIGNALED); | ||
base::WaitableEvent e2(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
bool called1 = false; | ||
bool called2 = false; | ||
auto callback1 = base::Bind([](bool* called) { *called = true; }, &called1); | ||
auto callback2 = base::Bind( | ||
[](std::unique_ptr<base::WaitableEvent>* e1, | ||
const base::Closure& other_callback, | ||
scoped_refptr<SyncHandleRegistry> registry, bool* called) { | ||
// Prevent re-entrancy. | ||
if (*called) | ||
return; | ||
|
||
registry->UnregisterEvent(e1->get(), other_callback); | ||
*called = true; | ||
e1->reset(); | ||
|
||
// Nest another wait. | ||
bool called3 = false; | ||
auto callback3 = | ||
base::Bind([](bool* called) { *called = true; }, &called3); | ||
base::WaitableEvent e3(base::WaitableEvent::ResetPolicy::MANUAL, | ||
base::WaitableEvent::InitialState::SIGNALED); | ||
registry->RegisterEvent(&e3, callback3); | ||
|
||
// This nested Wait() must not attempt to wait on |e1| since it has | ||
// been unregistered. This would crash otherwise, since |e1| has been | ||
// deleted. See http://crbug.com/761097. | ||
const bool* stop_flags[] = {&called3}; | ||
registry->Wait(stop_flags, 1); | ||
|
||
EXPECT_TRUE(called3); | ||
registry->UnregisterEvent(&e3, callback3); | ||
}, | ||
&e1, callback1, registry(), &called2); | ||
|
||
registry()->RegisterEvent(e1.get(), callback1); | ||
registry()->RegisterEvent(&e2, callback2); | ||
|
||
const bool* stop_flags[] = {&called1, &called2}; | ||
registry()->Wait(stop_flags, 2); | ||
|
||
EXPECT_TRUE(called2); | ||
|
||
registry()->UnregisterEvent(&e2, callback2); | ||
} | ||
|
||
} // namespace mojo |