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

Unified Diff: src/Timeout.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/Scheduler.cpp ('k') | src/Timeout.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/Timeout.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/src/Timeout.h
@@ -0,0 +1,303 @@
+/*
+ * 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(TIMEOUT_H)
+#define TIMEOUT_H
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <v8.h>
+
+/**
+ * Callback function implementing "SetTimeout()" on the global JS object.
+ */
+v8::Handle<v8::Value> CallbackForSetTimeout(const v8::Arguments& arguments);
+
+/**
+ * Base class for aspect classes.
+ * Provides uniform accessor to the aspect.
+ * Instantiate using CRTP.
+ */
+template<class AspectType>
+class AspectBase
+{
+public:
+ AspectType& Aspect()
+ {
+ return *static_cast<AspectType*>(this);
+ }
+};
+
+/**
+ * Definitions for all aspect points used in DelayedExecutorAspected.
+ */
+struct DelayedExecutorAspectPoints
+{
+ template<class LockType>
+ void RunnableToSleeping(LockType& lock) {};
+
+ void RunFinish() {};
+};
+
+class DelayedExecutorNullAspect
+ : public AspectBase<DelayedExecutorNullAspect>,
+ public DelayedExecutorAspectPoints
+{};
+
+/**
+ * Execute a function after a certain time has elapsed.
+ * Or don't execute it if you change your mind beforehand.
+ *
+ * This is the functional equivalent of sleeping-then-executing,
+ * with the exception that we can wake up the sleep early and stop execution.
+ */
+template<class AspectType>
+class DelayedExecutorAspected
+ : protected AspectType
+{
+ enum State
+ {
+ Runnable, ///< The state at construction. Awaiting a command to run.
+ Sleeping, ///< Ordered to run, but the delay timer is still ticking.
+ Interrupted, ///< Sleep was interrupted
+ Running, ///< In the middle of execution.
+ Completed, ///< Execution completed.
+ Error ///< An error encountered anywhere. Disables all operations.
+ };
+
+ /**
+ * Internal state.
+ *
+ * \par State Machine
+ * - Initial state is always "Runnable".
+ * - Final states are "Completed" and "Error".
+ * - Every non-final state may transition to "Error".
+ * - The ordinary linear sequence:
+ * "Runnable" --> "Sleeping" --> "Running" --> "Completed".
+ * - Accelerated execution:
+ * "Runnable" --> "Running"
+ * - The interruption transitions:
+ * "Sleeping" --> "Interrupted" --> "Completed"
+ */
+ State state;
+
+ /**
+ * Mutex primarily protects the internal state machine.
+ */
+ std::mutex m;
+ /**
+ * Condition variable to wait on instead of sleeping.
+ */
+ std::condition_variable cv;
+ /**
+ * Lock type is unique for use with condition variable.
+ */
+ typedef std::unique_lock<std::mutex> LockType;
+ /**
+ * The function that may execute, or not.
+ */
+ std::function<void()> body;
+
+ /**
+ * Validation routine.
+ */
+ void AssertNotInErrorState()
+ {
+ if (state == Error)
+ {
+ throw std::runtime_error("May not perform operation in error state");
+ }
+ }
+
+ /**
+ * Run the body unconditionally.
+ * This function does not take responsibility for prior state validation.
+ */
+ void Run()
+ {
+ try
+ {
+ // Run body with mutex unlocked
+ body();
+ // Now that body has completed successfully we can change state
+ LockType lock(m);
+ state = Completed;
+ }
+ catch (...)
+ {
+ LockType lock(m);
+ state = Error;
+ }
+ Aspect().RunFinish();
+ }
+
+public:
+ /**
+ * Constructor starts the delay timer.
+ * If the delay is zero, execution begins immediately.
+ *
+ * @param body
+ * A function object to execute. May not be empty.
+ */
+ DelayedExecutorAspected(AspectType&& aspect, std::function<void()> body)
+ : AspectType(std::move(aspect)), state(Runnable), body(body)
+ {
+ if (!body)
+ {
+ throw std::logic_error("Body function may not be empty");
+ }
+ }
+
+ /**
+ * Run the function body immediately and block until it's done.
+ *
+ * If we are not runnable, this is an error.
+ */
+ void RunImmediately()
+ {
+ // Validate state and transition with mutex protection
+ {
+ LockType lock(m);
+ switch (state)
+ {
+ case Runnable:
+ case Sleeping:
+ state = Running;
+ break;
+ case Interrupted:
+ case Running:
+ case Completed:
+ throw std::runtime_error("May not run immediately unless state is runnable or sleeping");
+ case Error:
+ throw std::runtime_error("May not run immediately from the error state");
+ default:
+ throw std::logic_error("Unknown state");
+ }
+ }
+ // Run with mutex unlocked
+ Run();
+ }
+
+ /**
+ * Run the body after some period of time has elapsed.
+ */
+ template <class Rep, class Period>
+ void RunWithDelay(const std::chrono::duration<Rep, Period>& duration)
+ {
+ // Validate state and transition with mutex protection
+ {
+ LockType lock(m);
+ switch (state)
+ {
+ case Runnable:
+ state = Sleeping;
+ break;
+ case Sleeping:
+ case Interrupted:
+ case Running:
+ case Completed:
+ throw std::runtime_error("May not run with delay unless state is runnable");
+ case Error:
+ throw std::runtime_error("May not run with delay from the error state");
+ default:
+ throw std::logic_error("Unknown state");
+ }
+
+ /*
+ * Aspect point for beginning of sleep.
+ * Ideally, this point would be inside the condition variable as it
+ * transitions to waiting, immediately after it unlocks its mutex.
+ * Putting it right before the statement is a reasonable substitute.
+ * Note: aspect should unlock and re-lock mutex
+ */
+ Aspect().RunnableToSleeping(lock);
+
+ auto completed = [this]() -> bool
+ {
+ return state != Sleeping;
+ };
+ // Assert state == Sleeping
+ cv.wait_for(lock, duration, completed);
+ // Assert either the wait timed out or we are no longer in the sleeping state
+
+ switch (state)
+ {
+ case Sleeping:
+ // We timed out and sleep was completed. Time to run the body.
+ state = Running;
+ break;
+ case Interrupted:
+ // We were interrupted while sleeping, so we're all done.
+ state = Completed;
+ return;
+ default:
+ throw std::runtime_error("Unexpected state after finished sleeping");
+ }
+ }
+ // Assert state == Running
+ Run();
+ }
+
+ /**
+ * Stave off running our function body if that's still possible.
+ *
+ * If we are "Running", return false.
+ * If we are "Runnable" or "Sleeping", then we transition to "Completed"
+ * and return true; in this case the body never executes.
+ * If we are "Completed", we simply return true.
+ *
+ * @return
+ * - True if we have verified that execution has completed.
+ * - False if we cannot verify that execution has completed.
+ * - Note that we can return false here but execution can complete on its
+ * own before we receive our next call.
+ */
+ bool ForestallExecution()
+ {
+ LockType lock(m);
+ switch (state)
+ {
+ case Runnable:
+ case Sleeping:
+ state = Interrupted;
+ // fall through
+ case Interrupted: // permissible, but usually irrelevant
+ cv.notify_one();
+ return true;
+ case Running:
+ case Completed:
+ return false;
+ case Error:
+ throw std::runtime_error("May not forestall execution from the error state");
+ default:
+ throw std::logic_error("Unknown state");
+ }
+ }
+};
+
+class DelayedExecutor
+ : public DelayedExecutorAspected<DelayedExecutorNullAspect>
+{
+public:
+ typedef DelayedExecutorAspected<DelayedExecutorNullAspect> Base;
+ DelayedExecutor(std::function<void()> body)
+ : Base(DelayedExecutorNullAspect(), body)
+ {}
+};
+
+#endif
« no previous file with comments | « src/Scheduler.cpp ('k') | src/Timeout.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld