diff options
Diffstat (limited to 'sync/internal_api/public/base')
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 |