Index: src/plugin/DetachedInitialization.h |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/src/plugin/DetachedInitialization.h |
@@ -0,0 +1,258 @@ |
+/* |
+ * This file is part of Adblock Plus <https://adblockplus.org/>, |
+ * Copyright (C) 2006-2016 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/>. |
+ */ |
+ |
+#ifndef _DETACHED_INITIALIZATION_H_ |
+#define _DETACHED_INITIALIZATION_H_ |
+ |
+#include <atomic> |
+#include <thread> |
+#include <mutex> |
+#include <chrono> |
+#include <condition_variable> |
+ |
+/** |
+ * A detached initializer for static class members |
+ * |
+ * Detached initializers use a separate thread to avoid delays at instantiation. |
+ * Variables statically initialized in a DLL cannot, however, contain threads. |
+ * Thus such initialization must occur in the constructor at the earliest. |
+ * |
+ * This kind of initialization acts shares much in common with a singleton. |
+ * Rather than constructing a singleton, instead we execute an initialization function. |
+ * The multiprocessing and synchronization issues, however, are essentially identical. |
+ * |
+ * The designated usage of this class: |
+ * 1) Define a static class member 'x' of this type with an initializer function. |
+ * 2) Call 'x.SpawnInitializer()' in the constructors to run the initializer. |
+ * 3) Call 'x.EnsureInitialized()' before relying upon any of the side effects of the initializer. |
+ * |
+ * The implementation acts much like a multithreaded singleton initialization using a double-checked lock. |
+ * See https://en.wikipedia.org/wiki/Double-checked_locking and http://preshing.com/20130922/acquire-and-release-fences/ |
+ * The flag members of this class enable fast-return paths, that is, a return without obtaining a mutex lock. |
+ * |
+ * \tparam F |
+ * The initializer function |
+ * |
+ * \par Serialization Guarantees |
+ * The visible effect of these function returns are serialized: |
+ * - SpawnInitializer() before ThreadMain() |
+ * - ThreadMain() before EnsureInitialized() |
+ * - If ThreadMain() started, ThreadMain() before destructor |
+ */ |
+template<void (&F)()> |
+class DetachedInitializer |
+{ |
+ // "protected" declaration allows white-box subclass for testing |
+protected: |
+ /** |
+ * Set to true after initializer thread successfully constructed; remains true thereafter. |
+ */ |
+ std::atomic<bool> hasInitializerStarted; |
+ |
+ /** |
+ * Set to true after initializer function completes; remains true thereafter. |
+ */ |
+ std::atomic<bool> isInitializationComplete; |
+ |
+ /** |
+ * Synchronizes order of setting flags and completion of member functions |
+ * |
+ * Protects separate changes to state flags |
+ * - In SpawnInitializer(), only one initializer runs |
+ * - In ThreadMain() and EnsureInitialized(), initializer completes and waiters notified |
+ * Protects mutual changes to state flags |
+ * - Serialize setting flags true: hasInitializerStarted always before isInitializationComplete |
+ */ |
+ std::mutex mutex; |
+ |
+ /** |
+ * Synchronizes end of initialization with waiting for intialization to finish. |
+ */ |
+ std::condition_variable cv; |
+ |
+ /** |
+ * If not null, an exception thrown by the initializer function |
+ * Thrown in EnsureInitialized() if present. |
+ */ |
+ std::exception_ptr initializerException; |
+ |
+ typedef std::unique_lock<std::mutex> UniqueLockType; |
+ typedef std::lock_guard<std::mutex> LockGuardType; |
+ |
+ inline bool AlreadyInitialized() const // noexcept |
+ { |
+ // Memory fence ensures load starts after any other load or store |
+ return isInitializationComplete.load(std::memory_order_acquire); |
+ } |
+ |
+ inline bool AlreadyStartedInitializer() const // noexcept |
+ { |
+ return hasInitializerStarted.load(std::memory_order_acquire); |
+ } |
+ |
+ /** |
+ * Main function for thread |
+ */ |
+ void ThreadMain() |
+ { |
+ try |
+ { |
+ F(); |
+ } |
+ catch (std::exception&) |
+ { |
+ // Assert initializer threw an exception |
+ initializerException = std::current_exception(); |
+ } |
+ /* |
+ * Synchronize with reading the completion status in EnsureInitialized(). |
+ * Serialize return from SpawnInitializer() and this function. |
+ */ |
+ LockGuardType lg(mutex); |
+ isInitializationComplete.store(true, std::memory_order_release); |
+ cv.notify_all(); |
+ } |
+ |
+ /** |
+ * "noexcept" version of EnsureInitialized() used to implement both it and the destructor |
+ */ |
+ void EnsureInitializedNoexcept() // noexcept |
+ { |
+ try |
+ { |
+ if (AlreadyInitialized()) |
+ { |
+ return; // fast-return path |
+ } |
+ UniqueLockType ul(mutex); // won't throw because this class uses only scoped locks |
+ /* |
+ * We must always check initialization status under lock. |
+ * The loop protects against spurious wake-up calls. |
+ */ |
+ while (!AlreadyInitialized()) |
+ { |
+ cv.wait(ul); // won't throw because mutex is locked |
+ } |
+ } |
+ catch (...) |
+ { |
+ // Should not reach |
+ } |
+ } |
+ |
+public: |
+ DetachedInitializer() |
+ : hasInitializerStarted(false), isInitializationComplete(false) |
+ {} |
+ |
+ /** |
+ * \par Precondition |
+ * - No external aliases to this instance exist except for the one in the initializer thread |
+ * |
+ * \par Postcondition |
+ * - Ensures that initializer thread is not running before return. |
+ */ |
+ ~DetachedInitializer() |
+ { |
+ /* |
+ * We don't need to acquire a lock here. |
+ * By precondition at most the initializer thread is running, and it doesn't touch this flag. |
+ */ |
+ if (!AlreadyStartedInitializer()) |
+ { |
+ return; |
+ } |
+ // Wait for initializer to complete |
+ EnsureInitialized(); |
+ } |
+ |
+ /** |
+ * Start an initialization thread if not already initialized and no initialization thread exists. |
+ * |
+ * \par Postcondition |
+ * - if returns, AlreadyStartedInitializer is true |
+ * - if throws, AlreadyStartedInitializer is false |
+ */ |
+ void SpawnInitializer() |
+ { |
+ if (AlreadyInitialized() || AlreadyStartedInitializer()) |
+ { |
+ return; // fast-return path |
+ } |
+ // Assert we have neither completed nor started an initialization thread |
+ /* |
+ * Try to obtain a lock, giving us permission to start the initialization thread. |
+ * try_lock() is not reliable, that is, it can sometimes fail to lock when the mutex is unlocked. |
+ * It is, however, guaranteed to succeed eventually. |
+ */ |
+ while (true) |
+ { |
+ if (mutex.try_lock()) |
+ { |
+ // Assert mutex is locked |
+ LockGuardType lg(mutex, std::adopt_lock); |
+ /* |
+ * Refresh our knowledge of whether the initializer has started, this time under lock. |
+ */ |
+ if (AlreadyStartedInitializer()) |
+ { |
+ return; |
+ } |
+ /* |
+ * This thread constructor is the only statement that can throw in this function. |
+ * If it does throw, the initialization-started flag won't be set. |
+ * An exception here is thus a soft error, since a future invocation might succeed. |
+ */ |
+ std::thread th([this]() -> void { ThreadMain(); }); |
+ // Memory fence ensures store ends before any other load or store |
+ hasInitializerStarted.store(true, std::memory_order_release); |
+ th.detach(); // Won't throw because the thread is both valid and joinable |
+ return; |
+ } |
+ else |
+ { |
+ // Assert mutex is not locked |
+ if (AlreadyStartedInitializer()) |
+ { |
+ return; // Some other thread might have started the thread in the interim |
+ } |
+ // One microsecond is usually enough for a mutex to fully reset |
+ std::this_thread::sleep_for(std::chrono::microseconds(1)); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Ensure that initialization has completed, blocking if necessary until it is. |
+ * |
+ * SpawnInitializer() must have returned prior to calling this function. |
+ * If not, the function will block indefinitely. |
+ * |
+ * \throw exception |
+ * if the initializer threw an exception, it's rethrown here |
+ */ |
+ void EnsureInitialized() |
+ { |
+ EnsureInitializedNoexcept(); |
+ if (initializerException) |
+ { |
+ std::rethrow_exception(initializerException); |
+ } |
+ } |
+}; |
+ |
+#endif // _DETACHED_INITIALIZATION_H_ |