Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: test/PausePoint.h

Issue 29370876: Issue #4692, #3595 - Stop sleeping in the implementation of `SetTimeout`
Patch Set: Created Jan. 9, 2017, 1:22 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/Timeout.cpp ('k') | test/PausePoint.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « src/Timeout.cpp ('k') | test/PausePoint.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld