Index: test/PausePoint.h |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/test/PausePoint.h |
@@ -0,0 +1,173 @@ |
+/* |
+ * This file is part of Adblock Plus <https://adblockplus.org/>, |
+ * Copyright (C) 2006-2017 Eyeo GmbH |
+ * |
+ * Adblock Plus is free software: you can redistribute it and/or modify |
+ * it under the terms of the GNU General Public License version 3 as |
+ * published by the Free Software Foundation. |
+ * |
+ * Adblock Plus is distributed in the hope that it will be useful, |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+ * GNU General Public License for more details. |
+ * |
+ * You should have received a copy of the GNU General Public License |
+ * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
+ */ |
+ |
+#if !defined(PAUSE_POINT_H) |
+#define PAUSE_POINT_H |
+ |
+#include <condition_variable> |
+#include <mutex> |
+#include <stdexcept> |
+ |
+/** |
+ * An asymmetric rendezvous class |
+ * |
+ * This is a multithreading rendezvous class with assymmetric actors |
+ * called "director" and "puppet". |
+ * This class is designed for unit testing. |
+ * In this case the director is the unit test itself |
+ * and the puppets are other threads that the director spawns and controls. |
+ * The goal this class supports is to tame non-deterministic execution order |
+ * by serializing execution segments in different threads into a known order. |
+ * |
+ * This is a simple, two-party rendezvous. |
+ * As with any rendezvous, the first party to hit a pause point blocks. |
+ * When the second party hits the pause point, it blocks if needed, |
+ * after which the director runs and the puppet blocks. |
+ * The director must then explicitly unblock the puppet and allow it to run. |
+ * By using multiple pause points in different threads, |
+ * the director can create arbitrary execution order between its threads. |
+ * |
+ * See boost::barrier and boost::latch for other kinds of rendezvous classes. |
+ * Note, however, that both of them are symmetric and not exact analogues. |
+ * |
+ * Please note this is a simple implementation, but sufficient for simple tests. |
+ * It's not meant to be idiot-proof, nor of library quality. |
+ * It is manifestly possible to write a unit test that hangs or deadlocks; |
+ * it is the responsibility of the author to implement their tests correctly. |
+ * One obvious deficiency is that it presents a single interface to two different roles. |
+ * |
+ * === Actors |
+ * - director: calls WaitUntilPaused(), Resume() |
+ * - puppet: calls Pause() |
+ * |
+ * === States |
+ * - idle: puppet is not paused, director is not waiting |
+ * - paused: puppet is paused, director is not waiting |
+ * - waiting: director is waiting for the puppet to pause |
+ * |
+ * === Transitions |
+ * - state idle |
+ * - event pause |
+ * - new state paused. Puppet call to Pause() block. |
+ * - event wait |
+ * - new state waiting. Director call to WaitUntilPaused() blocks. |
+ * - event resume |
+ * - ERROR. May not call Resume() when the puppet is not blocked. |
+ * This class treats this case as a logic error, a flaw in the unit test |
+ * - state paused |
+ * - event pause |
+ * - ERROR. May only call Pause() from the puppet thread. |
+ * - event wait |
+ * - Self-transition (state remains paused). |
+ * This transition happens when the director calls WaitUntilPaused() |
+ * while the puppet is paused. |
+ * In this case the call returns immediately and does not block. |
+ * - event resume |
+ * - new state idle. Unblock puppet call to Pause(). |
+ * - state waiting |
+ * - event pause |
+ * - new state paused. Director call to WaitUntilPaused() unblocks. |
+ * - event wait |
+ * - ERROR. May only call WaitUntilPaused() from the director thread. |
+ * - event resume |
+ * - ERROR. May only call Resume() from the director thread. |
+ */ |
+class PausePoint |
+{ |
+ std::mutex m; |
+ std::condition_variable cv; |
+ typedef std::unique_lock<std::mutex> UniqueLockType; |
+ enum State { Idle, Paused, Waiting }; |
+ State state; |
+ |
+public: |
+ PausePoint(); |
+ |
+ /** |
+ * Puppet: Pause here until the director orders us to resume. |
+ */ |
+ void Pause(); |
+ |
+ /** |
+ * Director: Wait here until the puppet hits the pause point. |
+ */ |
+ void WaitUntilPaused(); |
+ |
+ /** |
+ * Director: Order the puppet to resume execution. |
+ */ |
+ void Resume(); |
+ |
+ /** |
+ * Director: Call Resume() and then pause briefly. |
+ * |
+ * This function accommodates the case where we would prefer to pause execution |
+ * inside of a multiprocessing primitive such as `condition_variable::wait`. |
+ * Since that's impossible, we pause as soon as we can before it and |
+ * then resume with a tiny delay. |
+ * This makes the puppet thread the only thread available for execution |
+ * and we'll reach the blocking point at the primitive with high reliability. |
+ * It's still possible that, for whatever reason, the puppet thread does not execute |
+ * and we end up with a failed or hanging test, but that situation should be rare. |
+ * |
+ * The long-term solution to this problem requires "expanded" versions of primitives |
+ * that permit placing pause points on specific internal state transitions. |
+ */ |
+ void ResumeAndSleep(unsigned int ms = 10); |
+}; |
+ |
+/** |
+ * A sentry that temporarily unlocks a mutex in the middle of a locked scope. |
+ * |
+ * \par Contract |
+ * - Ownership of the constructor argument and any associated mutex transfer |
+ * to this object for the duration of its life span. |
+ * (If not, we might enable an exception thrown in the destructor.) |
+ */ |
+template<class L> |
+class AntiLock |
+{ |
+ L& lock; |
+public: |
+ /** |
+ * \par Precondition |
+ * - argument lock must be locked |
+ */ |
+ AntiLock(L& lock) |
+ : lock(lock) |
+ { |
+ try |
+ { |
+ lock.unlock(); |
+ } |
+ catch (...) |
+ { |
+ /* |
+ * Any throw will be std::system_error, meaning the mutex was not locked. |
+ * We want std::logic_error instead, since this means incorrect usage. |
+ */ |
+ throw std::logic_error("Must construct AntiLock with a locked argument."); |
+ } |
+ }; |
+ |
+ ~AntiLock() |
+ { |
+ lock.lock(); |
+ }; |
+}; |
+ |
+#endif |