blob: c1cb720a56b413a5598e44f6001668ba58315352 [file] [log] [blame]
// Copyright (c) 2012 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 "base/synchronization/waitable_event_watcher.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
// The main thread types on which each waitable event should be tested.
const test::ScopedTaskEnvironment::MainThreadType testing_main_threads[] = {
test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
test::ScopedTaskEnvironment::MainThreadType::IO,
#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop.
test::ScopedTaskEnvironment::MainThreadType::UI,
#endif
};
void QuitWhenSignaled(WaitableEvent* event) {
RunLoop::QuitCurrentWhenIdleDeprecated();
}
class DecrementCountContainer {
public:
explicit DecrementCountContainer(int* counter) : counter_(counter) {}
void OnWaitableEventSignaled(WaitableEvent* object) {
// NOTE: |object| may be already deleted.
--(*counter_);
}
private:
int* counter_;
};
} // namespace
class WaitableEventWatcherTest
: public testing::TestWithParam<
test::ScopedTaskEnvironment::MainThreadType> {};
TEST_P(WaitableEventWatcherTest, BasicSignalManual) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
// A manual-reset event that is not yet signaled.
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
event.Signal();
RunLoop().Run();
EXPECT_TRUE(event.IsSignaled());
}
TEST_P(WaitableEventWatcherTest, BasicSignalAutomatic) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
event.Signal();
RunLoop().Run();
// The WaitableEventWatcher consumes the event signal.
EXPECT_FALSE(event.IsSignaled());
}
TEST_P(WaitableEventWatcherTest, BasicCancel) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
// A manual-reset event that is not yet signaled.
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
watcher.StopWatching();
}
TEST_P(WaitableEventWatcherTest, CancelAfterSet) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
// A manual-reset event that is not yet signaled.
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
WaitableEventWatcher watcher;
int counter = 1;
DecrementCountContainer delegate(&counter);
WaitableEventWatcher::EventCallback callback = BindOnce(
&DecrementCountContainer::OnWaitableEventSignaled, Unretained(&delegate));
watcher.StartWatching(&event, std::move(callback),
SequencedTaskRunnerHandle::Get());
event.Signal();
// Let the background thread do its business
PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
watcher.StopWatching();
RunLoop().RunUntilIdle();
// Our delegate should not have fired.
EXPECT_EQ(1, counter);
}
TEST_P(WaitableEventWatcherTest, OutlivesTaskEnvironment) {
// Simulate a task environment that dies before an WaitableEventWatcher. This
// ordinarily doesn't happen when people use the Thread class, but it can
// happen when people use the Singleton pattern or atexit.
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
{
std::unique_ptr<WaitableEventWatcher> watcher;
{
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
watcher = std::make_unique<WaitableEventWatcher>();
watcher->StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
}
}
}
TEST_P(WaitableEventWatcherTest, SignaledAtStartManual) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
RunLoop().Run();
EXPECT_TRUE(event.IsSignaled());
}
TEST_P(WaitableEventWatcherTest, SignaledAtStartAutomatic) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
RunLoop().Run();
// The watcher consumes the event signal.
EXPECT_FALSE(event.IsSignaled());
}
TEST_P(WaitableEventWatcherTest, StartWatchingInCallback) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
WaitableEventWatcher watcher;
watcher.StartWatching(
&event,
BindOnce(
[](WaitableEventWatcher* watcher, WaitableEvent* event) {
// |event| is manual, so the second watcher will run
// immediately.
watcher->StartWatching(event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
},
&watcher),
SequencedTaskRunnerHandle::Get());
event.Signal();
RunLoop().Run();
}
TEST_P(WaitableEventWatcherTest, MultipleWatchersManual) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
int counter1 = 0;
int counter2 = 0;
auto callback = [](RunLoop* run_loop, int* counter, WaitableEvent* event) {
++(*counter);
run_loop->QuitWhenIdle();
};
RunLoop run_loop;
WaitableEventWatcher watcher1;
watcher1.StartWatching(
&event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter1)),
SequencedTaskRunnerHandle::Get());
WaitableEventWatcher watcher2;
watcher2.StartWatching(
&event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter2)),
SequencedTaskRunnerHandle::Get());
event.Signal();
run_loop.Run();
EXPECT_EQ(1, counter1);
EXPECT_EQ(1, counter2);
EXPECT_TRUE(event.IsSignaled());
}
// Tests that only one async waiter gets called back for an auto-reset event.
TEST_P(WaitableEventWatcherTest, MultipleWatchersAutomatic) {
test::ScopedTaskEnvironment scoped_task_environment(GetParam());
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
int counter1 = 0;
int counter2 = 0;
auto callback = [](RunLoop** run_loop, int* counter, WaitableEvent* event) {
++(*counter);
(*run_loop)->QuitWhenIdle();
};
// The same RunLoop instance cannot be Run more than once, and it is
// undefined which watcher will get called back first. Have the callback
// dereference this pointer to quit the loop, which will be updated on each
// Run.
RunLoop* current_run_loop;
WaitableEventWatcher watcher1;
watcher1.StartWatching(
&event,
BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter1)),
SequencedTaskRunnerHandle::Get());
WaitableEventWatcher watcher2;
watcher2.StartWatching(
&event,
BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter2)),
SequencedTaskRunnerHandle::Get());
event.Signal();
{
RunLoop run_loop;
current_run_loop = &run_loop;
run_loop.Run();
}
// Only one of the waiters should have been signaled.
EXPECT_TRUE((counter1 == 1) ^ (counter2 == 1));
EXPECT_FALSE(event.IsSignaled());
event.Signal();
{
RunLoop run_loop;
current_run_loop = &run_loop;
run_loop.Run();
}
EXPECT_FALSE(event.IsSignaled());
// The other watcher should have been signaled.
EXPECT_EQ(1, counter1);
EXPECT_EQ(1, counter2);
}
// To help detect errors around deleting WaitableEventWatcher, an additional
// bool parameter is used to test sleeping between watching and deletion.
class WaitableEventWatcherDeletionTest
: public testing::TestWithParam<
std::tuple<test::ScopedTaskEnvironment::MainThreadType, bool>> {};
TEST_P(WaitableEventWatcherDeletionTest, DeleteUnder) {
test::ScopedTaskEnvironment::MainThreadType main_thread_type;
bool delay_after_delete;
std::tie(main_thread_type, delay_after_delete) = GetParam();
// Delete the WaitableEvent out from under the Watcher. This is explictly
// allowed by the interface.
test::ScopedTaskEnvironment scoped_task_environment(main_thread_type);
{
WaitableEventWatcher watcher;
auto* event = new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
watcher.StartWatching(event, BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
if (delay_after_delete) {
// On Windows that sleep() improves the chance to catch some problems.
// It postpones the dtor |watcher| (which immediately cancel the waiting)
// and gives some time to run to a created background thread.
// Unfortunately, that thread is under OS control and we can't
// manipulate it directly.
PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
}
delete event;
}
}
TEST_P(WaitableEventWatcherDeletionTest, SignalAndDelete) {
test::ScopedTaskEnvironment::MainThreadType main_thread_type;
bool delay_after_delete;
std::tie(main_thread_type, delay_after_delete) = GetParam();
// Signal and immediately delete the WaitableEvent out from under the Watcher.
test::ScopedTaskEnvironment scoped_task_environment(main_thread_type);
{
WaitableEventWatcher watcher;
auto event = std::make_unique<WaitableEvent>(
WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
watcher.StartWatching(event.get(), BindOnce(&QuitWhenSignaled),
SequencedTaskRunnerHandle::Get());
event->Signal();
event.reset();
if (delay_after_delete) {
// On Windows that sleep() improves the chance to catch some problems.
// It postpones the dtor |watcher| (which immediately cancel the waiting)
// and gives some time to run to a created background thread.
// Unfortunately, that thread is under OS control and we can't
// manipulate it directly.
PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
}
// Wait for the watcher callback.
RunLoop().Run();
}
}
// Tests deleting the WaitableEventWatcher between signaling the event and
// when the callback should be run.
TEST_P(WaitableEventWatcherDeletionTest, DeleteWatcherBeforeCallback) {
test::ScopedTaskEnvironment::MainThreadType main_thread_type;
bool delay_after_delete;
std::tie(main_thread_type, delay_after_delete) = GetParam();
test::ScopedTaskEnvironment scoped_task_environment(main_thread_type);
scoped_refptr<SingleThreadTaskRunner> task_runner =
ThreadTaskRunnerHandle::Get();
// Flag used to esnure that the |watcher_callback| never runs.
bool did_callback = false;
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
auto watcher = std::make_unique<WaitableEventWatcher>();
// Queue up a series of tasks:
// 1. StartWatching the WaitableEvent
// 2. Signal the event (which will result in another task getting posted to
// the |task_runner|)
// 3. Delete the WaitableEventWatcher
// 4. WaitableEventWatcher callback should run (from #2)
WaitableEventWatcher::EventCallback watcher_callback = BindOnce(
[](bool* did_callback, WaitableEvent*) {
*did_callback = true;
},
Unretained(&did_callback));
task_runner->PostTask(
FROM_HERE, BindOnce(IgnoreResult(&WaitableEventWatcher::StartWatching),
Unretained(watcher.get()), Unretained(&event),
std::move(watcher_callback), task_runner));
task_runner->PostTask(FROM_HERE,
BindOnce(&WaitableEvent::Signal, Unretained(&event)));
task_runner->DeleteSoon(FROM_HERE, std::move(watcher));
if (delay_after_delete) {
task_runner->PostTask(FROM_HERE, BindOnce(&PlatformThread::Sleep,
TimeDelta::FromMilliseconds(30)));
}
RunLoop().RunUntilIdle();
EXPECT_FALSE(did_callback);
}
INSTANTIATE_TEST_CASE_P(,
WaitableEventWatcherTest,
testing::ValuesIn(testing_main_threads));
INSTANTIATE_TEST_CASE_P(
,
WaitableEventWatcherDeletionTest,
testing::Combine(testing::ValuesIn(testing_main_threads), testing::Bool()));
} // namespace base