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

Side by Side Diff: src/plugin/DetachedInitialization.h

Issue 29332660: Issue #3391 - Add detached initialization class
Patch Set: address comments Created Jan. 29, 2016, 3:37 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « adblockplus.gyp ('k') | test/plugin/DetachedInitializationTest.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 Eyeo GmbH
Oleksandr 2016/02/05 07:36:05 Nit: 2016
Eric 2016/02/08 17:34:07 Done.
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()
Oleksandr 2016/02/01 02:47:09 The double checked locking is really convoluted he
Eric 2016/02/03 14:19:01 [...]
Oleksandr 2016/02/05 07:36:05 I am not convinced converting passive threads into
Eric 2016/02/08 17:34:07 There's no other place to throw it. You can't thro
Eric 2016/02/08 17:34:07 It's not what we want, at least not now. The diff
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_
OLDNEW
« no previous file with comments | « adblockplus.gyp ('k') | test/plugin/DetachedInitializationTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld