summaryrefslogtreecommitdiffstats
path: root/sync/internal_api/public/base
diff options
context:
space:
mode:
Diffstat (limited to 'sync/internal_api/public/base')
-rw-r--r--sync/internal_api/public/base/cancelation_observer.cc13
-rw-r--r--sync/internal_api/public/base/cancelation_observer.h25
-rw-r--r--sync/internal_api/public/base/cancelation_signal.cc52
-rw-r--r--sync/internal_api/public/base/cancelation_signal.h72
-rw-r--r--sync/internal_api/public/base/cancelation_signal_unittest.cc169
5 files changed, 331 insertions, 0 deletions
diff --git a/sync/internal_api/public/base/cancelation_observer.cc b/sync/internal_api/public/base/cancelation_observer.cc
new file mode 100644
index 0000000..f50b6a3
--- /dev/null
+++ b/sync/internal_api/public/base/cancelation_observer.cc
@@ -0,0 +1,13 @@
+// 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 "sync/internal_api/public/base/cancelation_observer.h"
+
+namespace syncer {
+
+CancelationObserver::CancelationObserver() {}
+
+CancelationObserver::~CancelationObserver() {}
+
+} // namespace syncer
diff --git a/sync/internal_api/public/base/cancelation_observer.h b/sync/internal_api/public/base/cancelation_observer.h
new file mode 100644
index 0000000..7e67787
--- /dev/null
+++ b/sync/internal_api/public/base/cancelation_observer.h
@@ -0,0 +1,25 @@
+// 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 SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_OBSERVER_H_
+#define SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_OBSERVER_H_
+
+#include "sync/base/sync_export.h"
+
+namespace syncer {
+
+// Interface for classes that handle signals from the CancelationSignal.
+class SYNC_EXPORT CancelationObserver {
+ public:
+ CancelationObserver();
+ virtual ~CancelationObserver() = 0;
+
+ // This may be called from a foreign thread while the CancelationSignal's lock
+ // is held. The callee should avoid performing slow or blocking operations.
+ virtual void OnSignalReceived() = 0;
+};
+
+} // namespace syncer
+
+#endif // SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_OBSERVER_H_
diff --git a/sync/internal_api/public/base/cancelation_signal.cc b/sync/internal_api/public/base/cancelation_signal.cc
new file mode 100644
index 0000000..94a479b
--- /dev/null
+++ b/sync/internal_api/public/base/cancelation_signal.cc
@@ -0,0 +1,52 @@
+// 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 "sync/internal_api/public/base/cancelation_signal.h"
+
+#include "base/logging.h"
+#include "sync/internal_api/public/base/cancelation_observer.h"
+
+namespace syncer {
+
+CancelationSignal::CancelationSignal()
+ : signalled_(false),
+ handler_(NULL) { }
+
+CancelationSignal::~CancelationSignal() {
+ DCHECK(!handler_);
+}
+
+bool CancelationSignal::TryRegisterHandler(CancelationObserver* handler) {
+ base::AutoLock lock(signal_lock_);
+ DCHECK(!handler_);
+
+ if (signalled_)
+ return false;
+
+ handler_ = handler;
+ return true;
+}
+
+void CancelationSignal::UnregisterHandler(CancelationObserver* handler) {
+ base::AutoLock lock(signal_lock_);
+ DCHECK_EQ(handler_, handler);
+ handler_ = NULL;
+}
+
+bool CancelationSignal::IsSignalled() {
+ base::AutoLock lock(signal_lock_);
+ return signalled_;
+}
+
+void CancelationSignal::Signal() {
+ base::AutoLock lock(signal_lock_);
+ DCHECK(!signalled_);
+
+ signalled_ = true;
+ if (handler_) {
+ handler_->OnSignalReceived();
+ }
+}
+
+} // namespace syncer
diff --git a/sync/internal_api/public/base/cancelation_signal.h b/sync/internal_api/public/base/cancelation_signal.h
new file mode 100644
index 0000000..a074b62
--- /dev/null
+++ b/sync/internal_api/public/base/cancelation_signal.h
@@ -0,0 +1,72 @@
+// 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 SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_SIGNAL_H_
+#define SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_SIGNAL_H_
+
+#include "base/synchronization/lock.h"
+#include "sync/base/sync_export.h"
+
+namespace syncer {
+
+class CancelationObserver;
+
+// This class is used to allow one thread to request that another abort and
+// return early.
+//
+// The signalling thread owns this class and my call Signal() at any time.
+// After that call, this class' IsSignalled() will always return true. The
+// intended use case is that the task intending to support early exit will
+// periodically check the value of IsSignalled() to see if it should return
+// early.
+//
+// The receiving task may also choose to register an CancelationObserver whose
+// OnSignalReceived() method will be executed on the signaller's thread when
+// Signal() is called. This may be used for sending an early Signal() to a
+// WaitableEvent. The registration of the handler is necessarily racy. If
+// Signal() is executes before TryRegisterHandler(), TryRegisterHandler() will
+// not perform any registration and return false. That function's caller must
+// handle this case.
+//
+// This class supports only one handler, though it could easily support multiple
+// observers if we found a use case for such a feature.
+class SYNC_EXPORT_PRIVATE CancelationSignal {
+ public:
+ CancelationSignal();
+ ~CancelationSignal();
+
+ // Tries to register a handler to be invoked when Signal() is called.
+ //
+ // If Signal() has already been called, returns false without registering
+ // the handler. Returns true when the registration is successful.
+ //
+ // If the registration was successful, the handler must be unregistered with
+ // UnregisterHandler before this CancelationSignal is destroyed.
+ bool TryRegisterHandler(CancelationObserver* handler);
+
+ // Unregisters the abort handler.
+ void UnregisterHandler(CancelationObserver* handler);
+
+ // Returns true if Signal() has been called.
+ bool IsSignalled();
+
+ // Sets the stop_requested_ flag and calls the OnSignalReceived() method of
+ // the registered handler, if there is one registered at the time.
+ // SignalReceived() will be called with the |signal_lock_| held.
+ void Signal();
+
+ private:
+ // Protects all members of this class.
+ base::Lock signal_lock_;
+
+ // True if Signal() has been invoked.
+ bool signalled_;
+
+ // The registered abort handler. May be NULL.
+ CancelationObserver* handler_;
+};
+
+} // namespace syncer
+
+#endif // SYNC_INTERNAL_API_PUBLIC_BASE_CANCELATION_SIGNAL_H_
diff --git a/sync/internal_api/public/base/cancelation_signal_unittest.cc b/sync/internal_api/public/base/cancelation_signal_unittest.cc
new file mode 100644
index 0000000..613e756
--- /dev/null
+++ b/sync/internal_api/public/base/cancelation_signal_unittest.cc
@@ -0,0 +1,169 @@
+// 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 "sync/internal_api/public/base/cancelation_signal.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "sync/internal_api/public/base/cancelation_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+class BlockingTask : public CancelationObserver {
+ public:
+ BlockingTask(CancelationSignal* cancel_signal);
+ virtual ~BlockingTask();
+
+ // Starts the |exec_thread_| and uses it to execute DoRun().
+ void RunAsync(base::WaitableEvent* task_done_signal);
+
+ // Blocks until canceled. Signals |task_done_signal| when finished.
+ void Run(base::WaitableEvent* task_done_signal);
+
+ // Implementation of CancelationObserver.
+ // Wakes up the thread blocked in Run().
+ virtual void OnSignalReceived() OVERRIDE;
+
+ // Checks if we ever did successfully start waiting for |event_|. Be careful
+ // with this. The flag itself is thread-unsafe, and the event that flips it
+ // is racy.
+ bool WasStarted();
+
+ private:
+ base::WaitableEvent event_;
+ base::Thread exec_thread_;
+ CancelationSignal* cancel_signal_;
+ bool was_started_;
+};
+
+BlockingTask::BlockingTask(CancelationSignal* cancel_signal)
+ : event_(true, false),
+ exec_thread_("BlockingTaskBackgroundThread"),
+ cancel_signal_(cancel_signal),
+ was_started_(false) { }
+
+BlockingTask::~BlockingTask() {}
+
+void BlockingTask::RunAsync(base::WaitableEvent* task_done_signal) {
+ exec_thread_.Start();
+ exec_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingTask::Run,
+ base::Unretained(this),
+ base::Unretained(task_done_signal)));
+}
+
+void BlockingTask::Run(base::WaitableEvent* task_done_signal) {
+ if (cancel_signal_->TryRegisterHandler(this)) {
+ DCHECK(!event_.IsSignaled());
+ was_started_ = true;
+ event_.Wait();
+ }
+ task_done_signal->Signal();
+}
+
+void BlockingTask::OnSignalReceived() {
+ event_.Signal();
+}
+
+bool BlockingTask::WasStarted() {
+ return was_started_;
+}
+
+class CancelationSignalTest : public ::testing::Test {
+ public:
+ CancelationSignalTest();
+ virtual ~CancelationSignalTest();
+
+ // Starts the blocking task on a background thread.
+ void StartBlockingTask();
+
+ // Cancels the blocking task.
+ void CancelBlocking();
+
+ // Verifies that the background task is not running. This could be beacause
+ // it was canceled early or because it was canceled after it was started.
+ //
+ // This method may block for a brief period of time while waiting for the
+ // background thread to make progress.
+ bool VerifyTaskDone();
+
+ // Verifies that the background task was canceled early.
+ //
+ // This method may block for a brief period of time while waiting for the
+ // background thread to make progress.
+ bool VerifyTaskNotStarted();
+
+ private:
+ base::MessageLoop main_loop_;
+
+ CancelationSignal signal_;
+ base::WaitableEvent task_done_event_;
+ BlockingTask blocking_task_;
+};
+
+CancelationSignalTest::CancelationSignalTest()
+ : task_done_event_(false, false), blocking_task_(&signal_) {}
+
+CancelationSignalTest::~CancelationSignalTest() {}
+
+void CancelationSignalTest::StartBlockingTask() {
+ blocking_task_.RunAsync(&task_done_event_);
+}
+
+void CancelationSignalTest::CancelBlocking() {
+ signal_.Signal();
+}
+
+bool CancelationSignalTest::VerifyTaskDone() {
+ // Wait until BlockingTask::Run() has finished.
+ task_done_event_.Wait();
+ return true;
+}
+
+bool CancelationSignalTest::VerifyTaskNotStarted() {
+ // Wait until BlockingTask::Run() has finished.
+ task_done_event_.Wait();
+
+ // Verify the background thread never started blocking.
+ return !blocking_task_.WasStarted();
+}
+
+class FakeCancelationObserver : public CancelationObserver {
+ virtual void OnSignalReceived() OVERRIDE { }
+};
+
+TEST(CancelationSignalTest_SingleThread, CheckFlags) {
+ FakeCancelationObserver observer;
+ CancelationSignal signal;
+
+ EXPECT_FALSE(signal.IsSignalled());
+ signal.Signal();
+ EXPECT_TRUE(signal.IsSignalled());
+ EXPECT_FALSE(signal.TryRegisterHandler(&observer));
+}
+
+// Send the cancelation signal before the task is started. This will ensure
+// that the task will never be attempted.
+TEST_F(CancelationSignalTest, CancelEarly) {
+ CancelBlocking();
+ StartBlockingTask();
+ EXPECT_TRUE(VerifyTaskNotStarted());
+}
+
+// Send the cancelation signal after the request to start the task has been
+// posted. This is racy. The signal to stop may arrive before the signal to
+// run the task. If that happens, we end up with another instance of the
+// CancelEarly test defined earlier. If the signal requesting a stop arrives
+// after the task has been started, it should end up stopping the task.
+TEST_F(CancelationSignalTest, Cancel) {
+ StartBlockingTask();
+ CancelBlocking();
+ EXPECT_TRUE(VerifyTaskDone());
+}
+
+} // namespace syncer