diff options
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/common/one_shot_event.cc | 66 | ||||
-rw-r--r-- | extensions/common/one_shot_event.h | 98 | ||||
-rw-r--r-- | extensions/common/one_shot_event_unittest.cc | 111 |
3 files changed, 275 insertions, 0 deletions
diff --git a/extensions/common/one_shot_event.cc b/extensions/common/one_shot_event.cc new file mode 100644 index 0000000..14ab4ed --- /dev/null +++ b/extensions/common/one_shot_event.cc @@ -0,0 +1,66 @@ +// Copyright 2013 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 "extensions/common/one_shot_event.h" + +#include "base/callback.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/task_runner.h" + +using base::TaskRunner; + +namespace extensions { + +struct OneShotEvent::TaskInfo { + TaskInfo() {} + TaskInfo(const tracked_objects::Location& from_here, + const scoped_refptr<TaskRunner>& runner, + const base::Closure& task) + : from_here(from_here), runner(runner), task(task) { + CHECK(runner); // Detect mistakes with a decent stack frame. + } + tracked_objects::Location from_here; + scoped_refptr<TaskRunner> runner; + base::Closure task; +}; + +OneShotEvent::OneShotEvent() : signaled_(false) {} +OneShotEvent::~OneShotEvent() {} + +void OneShotEvent::Post(const tracked_objects::Location& from_here, + const base::Closure& task) const { + Post(from_here, task, base::MessageLoopProxy::current()); +} + +void OneShotEvent::Post(const tracked_objects::Location& from_here, + const base::Closure& task, + const scoped_refptr<TaskRunner>& runner) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (is_signaled()) { + runner->PostTask(from_here, task); + } else { + tasks_.push_back(TaskInfo(from_here, runner, task)); + } +} + +void OneShotEvent::Signal() { + DCHECK(thread_checker_.CalledOnValidThread()); + + CHECK(!signaled_) << "Only call Signal once."; + + signaled_ = true; + // After this point, a call to Post() from one of the queued tasks + // could proceed immediately, but the fact that this object is + // single-threaded prevents that from being relevant. + + // We could randomize tasks_ in debug mode in order to check that + // the order doesn't matter... + for (size_t i = 0; i < tasks_.size(); ++i) { + tasks_[i].runner->PostTask(tasks_[i].from_here, tasks_[i].task); + } +} + +} // namespace extensions diff --git a/extensions/common/one_shot_event.h b/extensions/common/one_shot_event.h new file mode 100644 index 0000000..17ab5c8 --- /dev/null +++ b/extensions/common/one_shot_event.h @@ -0,0 +1,98 @@ +// Copyright 2013 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. + +#ifndef EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ +#define EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ + +#include <vector> + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" + +namespace base { +class TaskRunner; +} + +namespace tracked_objects { +class Location; +} + +namespace extensions { + +// This class represents an event that's expected to happen once. It +// allows clients to guarantee that code is run after the OneShotEvent +// is signaled. If the OneShotEvent is destroyed before it's +// signaled, the delayed closures are destroyed without being run. +// +// This class is similar to a WaitableEvent combined with several +// WaitableEventWatchers, but using it is simpler. +// +// This class is not thread-safe, and must be used from a single thread. +class OneShotEvent { + public: + OneShotEvent(); + ~OneShotEvent(); + + // True if Signal has been called. This function is mostly for + // migrating old code; usually calling Post() unconditionally will + // result in more readable code. + bool is_signaled() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return signaled_; + } + + // Causes is_signaled() to return true and all queued tasks to be + // run in an arbitrary order. This method must only be called once. + void Signal(); + + // Scheduled |task| to be called on |runner| after is_signaled() + // becomes true. Inside |task|, if this OneShotEvent is still + // alive, CHECK(is_signaled()) will never fail (which implies that + // OneShotEvent::Reset() doesn't exist). + // + // If |*this| is destroyed before being released, none of these + // tasks will be executed. + // + // Omitting the |runner| argument indicates that |task| should run + // on MessageLoopProxy::current(). + // + // Tasks may be run in an arbitrary order, not just FIFO. Tasks + // will never be called on the current thread before this function + // returns. Beware that there's no simple way to wait for all tasks + // on a OneShotEvent to complete, so it's almost never safe to use + // base::Unretained() when creating one. + // + // Const because Post() doesn't modify the logical state of this + // object (which is just the is_signaled() bit). + void Post(const tracked_objects::Location& from_here, + const base::Closure& task) const; + void Post(const tracked_objects::Location& from_here, + const base::Closure& task, + const scoped_refptr<base::TaskRunner>& runner) const; + + private: + struct TaskInfo; + + base::ThreadChecker thread_checker_; + + bool signaled_; + + // The task list is mutable because it's not part of the logical + // state of the object. This lets us return const references to the + // OneShotEvent to clients that just want to run tasks through it + // without worrying that they'll signal the event. + // + // Optimization note: We could reduce the size of this class to a + // single pointer by storing |signaled_| in the low bit of a + // pointer, and storing the size and capacity of the array (if any) + // on the far end of the pointer. + mutable std::vector<TaskInfo> tasks_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_ diff --git a/extensions/common/one_shot_event_unittest.cc b/extensions/common/one_shot_event_unittest.cc new file mode 100644 index 0000000..ec2ddfd --- /dev/null +++ b/extensions/common/one_shot_event_unittest.cc @@ -0,0 +1,111 @@ +// Copyright 2013 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 "extensions/common/one_shot_event.h" + +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/test/test_simple_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace { + +void Increment(int* i) { ++*i; } + +TEST(OneShotEventTest, RecordsSignal) { + OneShotEvent event; + EXPECT_FALSE(event.is_signaled()); + event.Signal(); + EXPECT_TRUE(event.is_signaled()); +} + +TEST(OneShotEventTest, CallsQueue) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + EXPECT_EQ(0U, runner->GetPendingTasks().size()); + event.Signal(); + ASSERT_EQ(2U, runner->GetPendingTasks().size()); + EXPECT_NE(runner->GetPendingTasks()[0].location.line_number(), + runner->GetPendingTasks()[1].location.line_number()) + << "Make sure FROM_HERE is propagated."; + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + EXPECT_EQ(2, i); +} + +TEST(OneShotEventTest, CallsAfterSignalDontRunInline) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + + event.Signal(); + event.Post(FROM_HERE, base::Bind(&Increment, &i), runner); + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + EXPECT_EQ(1, i); +} + +TEST(OneShotEventTest, PostDefaultsToCurrentMessageLoop) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + MessageLoop loop; + int runner_i = 0; + int loop_i = 0; + + event.Post(FROM_HERE, base::Bind(&Increment, &runner_i), runner); + event.Post(FROM_HERE, base::Bind(&Increment, &loop_i)); + event.Signal(); + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, runner_i); + runner->RunPendingTasks(); + EXPECT_EQ(1, runner_i); + EXPECT_EQ(0, loop_i); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, loop_i); +} + +void CheckSignaledAndPostIncrement( + OneShotEvent* event, + const scoped_refptr<base::TaskRunner>& runner, + int* i) { + EXPECT_TRUE(event->is_signaled()); + event->Post(FROM_HERE, base::Bind(&Increment, i), runner); +} + +TEST(OneShotEventTest, IsSignaledAndPostsFromCallbackWork) { + OneShotEvent event; + scoped_refptr<base::TestSimpleTaskRunner> runner( + new base::TestSimpleTaskRunner); + int i = 0; + + event.Post(FROM_HERE, + base::Bind(&CheckSignaledAndPostIncrement, &event, runner, &i), + runner); + EXPECT_EQ(0, i); + event.Signal(); + + // CheckSignaledAndPostIncrement is queued on |runner|. + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + // Increment is queued on |runner|. + EXPECT_EQ(1U, runner->GetPendingTasks().size()); + EXPECT_EQ(0, i); + runner->RunPendingTasks(); + // Increment has run. + EXPECT_EQ(0U, runner->GetPendingTasks().size()); + EXPECT_EQ(1, i); +} + +} // namespace +} // namespace extensions |