summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorjyasskin@chromium.org <jyasskin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-17 23:18:35 +0000
committerjyasskin@chromium.org <jyasskin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-17 23:18:35 +0000
commit4a10006a0baeb74011e938668e896fc18adafcd1 (patch)
tree2d5e076e933c2010295ea8122217be8885f64f77 /extensions
parent3d580f8a85ff79cd933fd48e957b40a7ba3ca78e (diff)
downloadchromium_src-4a10006a0baeb74011e938668e896fc18adafcd1.zip
chromium_src-4a10006a0baeb74011e938668e896fc18adafcd1.tar.gz
chromium_src-4a10006a0baeb74011e938668e896fc18adafcd1.tar.bz2
Add a non-blocking "OneShotEvent" class
to simplify code that needs to run after something has happened, and start using it for the ExtensionService's READY notification. This change doesn't, in itself replace any uses of NOTIFICATION_EXTENSIONS_READY, but it paves the way for doing so. BUG=240968 R=atwilson@chromium.org, kalman@chromium.org, mpcomplete@chromium.org Review URL: https://codereview.chromium.org/14757022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@200918 0039d316-1c4b-4281-b951-d872f2087c98
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