summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-rw-r--r--extensions/common/one_shot_event.cc66
-rw-r--r--extensions/common/one_shot_event.h98
-rw-r--r--extensions/common/one_shot_event_unittest.cc111
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