| 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 |