OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-2016 Eyeo GmbH |
| 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. |
| 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. |
| 13 * |
| 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ |
| 17 |
| 18 #ifndef _DETACHED_INITIALIZATION_H_ |
| 19 #define _DETACHED_INITIALIZATION_H_ |
| 20 |
| 21 #include <atomic> |
| 22 #include <thread> |
| 23 #include <mutex> |
| 24 #include <chrono> |
| 25 #include <condition_variable> |
| 26 |
| 27 /** |
| 28 * A detached initializer for static class members |
| 29 * |
| 30 * Detached initializers use a separate thread to avoid delays at instantiation. |
| 31 * Variables statically initialized in a DLL cannot, however, contain threads. |
| 32 * Thus such initialization must occur in the constructor at the earliest. |
| 33 * |
| 34 * This kind of initialization acts shares much in common with a singleton. |
| 35 * Rather than constructing a singleton, instead we execute an initialization fu
nction. |
| 36 * The multiprocessing and synchronization issues, however, are essentially iden
tical. |
| 37 * |
| 38 * The designated usage of this class: |
| 39 * 1) Define a static class member 'x' of this type with an initializer function
. |
| 40 * 2) Call 'x.SpawnInitializer()' in the constructors to run the initializer. |
| 41 * 3) Call 'x.EnsureInitialized()' before relying upon any of the side effects o
f the initializer. |
| 42 * |
| 43 * The implementation acts much like a multithreaded singleton initialization us
ing a double-checked lock. |
| 44 * See https://en.wikipedia.org/wiki/Double-checked_locking and http://preshing.
com/20130922/acquire-and-release-fences/ |
| 45 * The flag members of this class enable fast-return paths, that is, a return wi
thout obtaining a mutex lock. |
| 46 * |
| 47 * \tparam F |
| 48 * The initializer function |
| 49 * |
| 50 * \par Serialization Guarantees |
| 51 * The visible effect of these function returns are serialized: |
| 52 * - SpawnInitializer() before ThreadMain() |
| 53 * - ThreadMain() before EnsureInitialized() |
| 54 * - If ThreadMain() started, ThreadMain() before destructor |
| 55 */ |
| 56 template<void (&F)()> |
| 57 class DetachedInitializer |
| 58 { |
| 59 // "protected" declaration allows white-box subclass for testing |
| 60 protected: |
| 61 /** |
| 62 * Set to true after initializer thread successfully constructed; remains true
thereafter. |
| 63 */ |
| 64 std::atomic<bool> hasInitializerStarted; |
| 65 |
| 66 /** |
| 67 * Set to true after initializer function completes; remains true thereafter. |
| 68 */ |
| 69 std::atomic<bool> isInitializationComplete; |
| 70 |
| 71 /** |
| 72 * Synchronizes order of setting flags and completion of member functions |
| 73 * |
| 74 * Protects separate changes to state flags |
| 75 * - In SpawnInitializer(), only one initializer runs |
| 76 * - In ThreadMain() and EnsureInitialized(), initializer completes and wait
ers notified |
| 77 * Protects mutual changes to state flags |
| 78 * - Serialize setting flags true: hasInitializerStarted always before isIni
tializationComplete |
| 79 */ |
| 80 std::mutex mutex; |
| 81 |
| 82 /** |
| 83 * Synchronizes end of initialization with waiting for intialization to finish
. |
| 84 */ |
| 85 std::condition_variable cv; |
| 86 |
| 87 /** |
| 88 * If not null, an exception thrown by the initializer function |
| 89 * Thrown in EnsureInitialized() if present. |
| 90 */ |
| 91 std::exception_ptr initializerException; |
| 92 |
| 93 typedef std::unique_lock<std::mutex> UniqueLockType; |
| 94 typedef std::lock_guard<std::mutex> LockGuardType; |
| 95 |
| 96 inline bool AlreadyInitialized() const // noexcept |
| 97 { |
| 98 // Memory fence ensures load starts after any other load or store |
| 99 return isInitializationComplete.load(std::memory_order_acquire); |
| 100 } |
| 101 |
| 102 inline bool AlreadyStartedInitializer() const // noexcept |
| 103 { |
| 104 return hasInitializerStarted.load(std::memory_order_acquire); |
| 105 } |
| 106 |
| 107 /** |
| 108 * Main function for thread |
| 109 */ |
| 110 void ThreadMain() |
| 111 { |
| 112 try |
| 113 { |
| 114 F(); |
| 115 } |
| 116 catch (std::exception&) |
| 117 { |
| 118 // Assert initializer threw an exception |
| 119 initializerException = std::current_exception(); |
| 120 } |
| 121 /* |
| 122 * Synchronize with reading the completion status in EnsureInitialized(). |
| 123 * Serialize return from SpawnInitializer() and this function. |
| 124 */ |
| 125 LockGuardType lg(mutex); |
| 126 isInitializationComplete.store(true, std::memory_order_release); |
| 127 cv.notify_all(); |
| 128 } |
| 129 |
| 130 /** |
| 131 * "noexcept" version of EnsureInitialized() used to implement both it and the
destructor |
| 132 */ |
| 133 void EnsureInitializedNoexcept() // noexcept |
| 134 { |
| 135 try |
| 136 { |
| 137 if (AlreadyInitialized()) |
| 138 { |
| 139 return; // fast-return path |
| 140 } |
| 141 UniqueLockType ul(mutex); // won't throw because this class uses only scop
ed locks |
| 142 /* |
| 143 * We must always check initialization status under lock. |
| 144 * The loop protects against spurious wake-up calls. |
| 145 */ |
| 146 while (!AlreadyInitialized()) |
| 147 { |
| 148 cv.wait(ul); // won't throw because mutex is locked |
| 149 } |
| 150 } |
| 151 catch (...) |
| 152 { |
| 153 // Should not reach |
| 154 } |
| 155 } |
| 156 |
| 157 public: |
| 158 DetachedInitializer() |
| 159 : hasInitializerStarted(false), isInitializationComplete(false) |
| 160 {} |
| 161 |
| 162 /** |
| 163 * \par Precondition |
| 164 * - No external aliases to this instance exist except for the one in the in
itializer thread |
| 165 * |
| 166 * \par Postcondition |
| 167 * - Ensures that initializer thread is not running before return. |
| 168 */ |
| 169 ~DetachedInitializer() |
| 170 { |
| 171 /* |
| 172 * We don't need to acquire a lock here. |
| 173 * By precondition at most the initializer thread is running, and it doesn't
touch this flag. |
| 174 */ |
| 175 if (!AlreadyStartedInitializer()) |
| 176 { |
| 177 return; |
| 178 } |
| 179 // Wait for initializer to complete |
| 180 EnsureInitialized(); |
| 181 } |
| 182 |
| 183 /** |
| 184 * Start an initialization thread if not already initialized and no initializa
tion thread exists. |
| 185 * |
| 186 * \par Postcondition |
| 187 * - if returns, AlreadyStartedInitializer is true |
| 188 * - if throws, AlreadyStartedInitializer is false |
| 189 */ |
| 190 void SpawnInitializer() |
| 191 { |
| 192 if (AlreadyInitialized() || AlreadyStartedInitializer()) |
| 193 { |
| 194 return; // fast-return path |
| 195 } |
| 196 // Assert we have neither completed nor started an initialization thread |
| 197 /* |
| 198 * Try to obtain a lock, giving us permission to start the initialization th
read. |
| 199 * try_lock() is not reliable, that is, it can sometimes fail to lock when t
he mutex is unlocked. |
| 200 * It is, however, guaranteed to succeed eventually. |
| 201 */ |
| 202 while (true) |
| 203 { |
| 204 if (mutex.try_lock()) |
| 205 { |
| 206 // Assert mutex is locked |
| 207 LockGuardType lg(mutex, std::adopt_lock); |
| 208 /* |
| 209 * Refresh our knowledge of whether the initializer has started, this ti
me under lock. |
| 210 */ |
| 211 if (AlreadyStartedInitializer()) |
| 212 { |
| 213 return; |
| 214 } |
| 215 /* |
| 216 * This thread constructor is the only statement that can throw in this
function. |
| 217 * If it does throw, the initialization-started flag won't be set. |
| 218 * An exception here is thus a soft error, since a future invocation mig
ht succeed. |
| 219 */ |
| 220 std::thread th([this]() -> void { ThreadMain(); }); |
| 221 // Memory fence ensures store ends before any other load or store |
| 222 hasInitializerStarted.store(true, std::memory_order_release); |
| 223 th.detach(); // Won't throw because the thread is both valid and joinabl
e |
| 224 return; |
| 225 } |
| 226 else |
| 227 { |
| 228 // Assert mutex is not locked |
| 229 if (AlreadyStartedInitializer()) |
| 230 { |
| 231 return; // Some other thread might have started the thread in the inte
rim |
| 232 } |
| 233 // One microsecond is usually enough for a mutex to fully reset |
| 234 std::this_thread::sleep_for(std::chrono::microseconds(1)); |
| 235 } |
| 236 } |
| 237 } |
| 238 |
| 239 /** |
| 240 * Ensure that initialization has completed, blocking if necessary until it is
. |
| 241 * |
| 242 * SpawnInitializer() must have returned prior to calling this function. |
| 243 * If not, the function will block indefinitely. |
| 244 * |
| 245 * \throw exception |
| 246 * if the initializer threw an exception, it's rethrown here |
| 247 */ |
| 248 void EnsureInitialized() |
| 249 { |
| 250 EnsureInitializedNoexcept(); |
| 251 if (initializerException) |
| 252 { |
| 253 std::rethrow_exception(initializerException); |
| 254 } |
| 255 } |
| 256 }; |
| 257 |
| 258 #endif // _DETACHED_INITIALIZATION_H_ |
OLD | NEW |