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

Delta Between Two Patch Sets: src/plugin/DetachedInitialization.h

Issue 29332660: Issue #3391 - Add detached initialization class
Left Patch Set: Created Dec. 14, 2015, 6:06 p.m.
Right Patch Set: fix copyright year Created Feb. 8, 2016, 5:33 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « adblockplus.gyp ('k') | test/plugin/DetachedInitializationTest.cpp » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 Eyeo GmbH 3 * Copyright (C) 2006-2016 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 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 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 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/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 #ifndef _DETACHED_INITIALIZATION_H_ 18 #ifndef _DETACHED_INITIALIZATION_H_
19 #define _DETACHED_INITIALIZATION_H_ 19 #define _DETACHED_INITIALIZATION_H_
20 20
21 #include <atomic> 21 #include <atomic>
22 #include <thread> 22 #include <thread>
23 #include <mutex> 23 #include <mutex>
24 #include <chrono> 24 #include <chrono>
25 #include <condition_variable> 25 #include <condition_variable>
26 26
27 /** 27 /**
28 * A detached initializer for static class members 28 * A detached initializer for static class members
sergei 2016/01/29 10:04:21 It would be much better to pull the changes of thi
Eric 2016/01/29 15:33:10 No need.
sergei 2016/02/09 17:38:14 Currently it's very inconvenient to review the cod
29 * 29 *
30 * Detached initializers use a separate thread to avoid delays at instantiation. 30 * Detached initializers use a separate thread to avoid delays at instantiation.
31 * Variables statically initialized in a DLL cannot, however, contain threads. 31 * Variables statically initialized in a DLL cannot, however, contain threads.
32 * Thus such initialization must occur in the constructor at the earliest. 32 * Thus such initialization must occur in the constructor at the earliest.
33 * 33 *
34 * This kind of initialization acts shares much in common with a singleton. 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. 35 * Rather than constructing a singleton, instead we execute an initialization fu nction.
36 * The multiprocessing and synchronization issues, however, are essentially iden tical. 36 * The multiprocessing and synchronization issues, however, are essentially iden tical.
37 * 37 *
38 * The designated usage of this class: 38 * The designated usage of this class:
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
83 * Synchronizes end of initialization with waiting for intialization to finish . 83 * Synchronizes end of initialization with waiting for intialization to finish .
84 */ 84 */
85 std::condition_variable cv; 85 std::condition_variable cv;
86 86
87 /** 87 /**
88 * If not null, an exception thrown by the initializer function 88 * If not null, an exception thrown by the initializer function
89 * Thrown in EnsureInitialized() if present. 89 * Thrown in EnsureInitialized() if present.
90 */ 90 */
91 std::exception_ptr initializerException; 91 std::exception_ptr initializerException;
92 92
93 typedef std::unique_lock<std::mutex> UniqueLockType; 93 typedef std::unique_lock<std::mutex> UniqueLockType;
sergei 2016/01/29 10:04:22 I think we don't need "Type" suffix.
Eric 2016/01/29 15:33:10 It's the C++ library convention, modified for our
sergei 2016/02/09 17:38:15 Acknowledged.
94 typedef std::lock_guard<std::mutex> LockGuardType; 94 typedef std::lock_guard<std::mutex> LockGuardType;
sergei 2016/01/29 10:04:22 UniqueLockType is used only once, LockGuardType is
Eric 2016/01/29 15:33:11 They're present because the code at the point of u
sergei 2016/02/09 17:38:14 That's very subjective, OK, let's leave them.
95 95
96 inline bool AlreadyInitialized() // noexcept 96 inline bool AlreadyInitialized() const // noexcept
sergei 2016/01/29 10:04:22 It seems this method should be `const`.
Eric 2016/01/29 15:33:10 Done.
97 { 97 {
98 // Memory fence ensures load starts after any other load or store 98 // Memory fence ensures load starts after any other load or store
99 return isInitializationComplete.load(std::memory_order_acquire); 99 return isInitializationComplete.load(std::memory_order_acquire);
sergei 2016/01/29 10:04:22 Do we really need to be so specific and not use a
Eric 2016/01/29 15:33:11 Memory fences are absolutely critical for correct
sergei 2016/02/09 17:38:14 That's clear that they are critical, why do we nee
100 } 100 }
101 101
102 inline bool AlreadyStartedInitializer() // noexcept 102 inline bool AlreadyStartedInitializer() const // noexcept
sergei 2016/01/29 10:04:20 It seems this method should be `const`.
Eric 2016/01/29 15:33:11 Done.
103 { 103 {
104 return hasInitializerStarted.load(std::memory_order_acquire); 104 return hasInitializerStarted.load(std::memory_order_acquire);
105 } 105 }
106 106
107 /** 107 /**
108 * Main function for thread 108 * Main function for thread
109 */ 109 */
110 void ThreadMain() 110 void ThreadMain()
111 { 111 {
112 try 112 try
113 { 113 {
114 F(); 114 F();
115 } 115 }
116 catch (std::exception& e) 116 catch (std::exception&)
sergei 2016/01/29 10:04:21 Compiler generates warning `e` is not used.
sergei 2016/01/29 10:04:23 Why is it not const reference?
Eric 2016/01/29 15:33:10 Done. It's fixed in the subsequent patch set, inc
sergei 2016/02/09 17:38:14 I see that in https://codereview.adblockplus.org/2
117 { 117 {
118 // Assert initializer threw an exception 118 // Assert initializer threw an exception
sergei 2016/01/29 10:04:22 This comment is not needed
sergei 2016/02/09 17:38:15 ignored?
119 initializerException = std::current_exception(); 119 initializerException = std::current_exception();
120 } 120 }
121 /* 121 /*
122 * Synchronize with reading the completion status in EnsureInitialized(). 122 * Synchronize with reading the completion status in EnsureInitialized().
123 * Serialize return from SpawnInitializer() and this function. 123 * Serialize return from SpawnInitializer() and this function.
124 */ 124 */
125 LockGuardType lg(mutex); 125 LockGuardType lg(mutex);
sergei 2016/01/29 10:04:20 it would be easier to read if name it `lock` not `
Eric 2016/01/29 15:33:11 Its name doesn't matter, since it's a sentry objec
126 isInitializationComplete.store(true, std::memory_order_release); 126 isInitializationComplete.store(true, std::memory_order_release);
127 cv.notify_all(); 127 cv.notify_all();
128 } 128 }
129 129
130 /** 130 /**
131 * "noexcept" version of EnsureInitialized() used to implement both it and the destructor 131 * "noexcept" version of EnsureInitialized() used to implement both it and the destructor
132 */ 132 */
133 void EnsureInitializedNoexcept() // noexcept 133 void EnsureInitializedNoexcept() // noexcept
134 { 134 {
135 try 135 try
136 { 136 {
137 if (AlreadyInitialized()) 137 if (AlreadyInitialized())
138 { 138 {
139 return; // fast-return path 139 return; // fast-return path
140 } 140 }
141 UniqueLockType ul(mutex); // won't throw because this class uses only scop ed locks 141 UniqueLockType ul(mutex); // won't throw because this class uses only scop ed locks
sergei 2016/01/29 10:04:23 it would be easier to read if name it `lock` not `
142 /* 142 /*
143 * We must always check initialization status under lock. 143 * We must always check initialization status under lock.
144 * The loop protects against spurious wake-up calls. 144 * The loop protects against spurious wake-up calls.
145 */ 145 */
146 while (!AlreadyInitialized()) 146 while (!AlreadyInitialized())
147 { 147 {
148 cv.wait(ul); // won't throw because mutex is locked 148 cv.wait(ul); // won't throw because mutex is locked
149 } 149 }
150 } 150 }
151 catch (...) 151 catch (...)
152 { 152 {
153 // Should not reach 153 // Should not reach
sergei 2016/01/29 10:04:21 It would be better to put `assert(false && "Should
Eric 2016/01/29 15:33:11 That would be unnecessary code bloat. There's a p
sergei 2016/02/09 17:38:15 It's not more than the comment, however it helps t
154 } 154 }
155 } 155 }
156 156
157 public: 157 public:
158 DetachedInitializer() 158 DetachedInitializer()
159 : hasInitializerStarted(false), isInitializationComplete(false) 159 : hasInitializerStarted(false), isInitializationComplete(false)
160 {} 160 {}
161 161
162 /** 162 /**
163 * \par Precondition 163 * \par Precondition
164 * - No external aliases to this instance exist except for the one in the in itializer thread 164 * - No external aliases to this instance exist except for the one in the in itializer thread
165 * 165 *
166 * \par Postcondition 166 * \par Postcondition
167 * - Ensures that initializer thread is not running before return. 167 * - Ensures that initializer thread is not running before return.
168 */ 168 */
169 ~DetachedInitializer() 169 ~DetachedInitializer()
170 { 170 {
171 /* 171 /*
172 * We don't need to acquire a lock here. 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. 173 * By precondition at most the initializer thread is running, and it doesn't touch this flag.
sergei 2016/01/29 10:04:23 We don't need this comment.
Eric 2016/01/29 15:33:10 We do.
174 */ 174 */
175 if (!AlreadyStartedInitializer()) 175 if (!AlreadyStartedInitializer())
176 { 176 {
177 return; 177 return;
178 } 178 }
179 // Wait for initializer to complete 179 // Wait for initializer to complete
180 EnsureInitialized(); 180 EnsureInitialized();
181 } 181 }
sergei 2016/01/29 10:04:21 We should wait in the destructor for the finishing
Eric 2016/01/29 15:33:10 Sergei, we _are_ waiting for the destructor to fin
sergei 2016/02/09 17:38:14 Right, overlooked. However, I would to still wait
182 182
183 /** 183 /**
184 * Start an initialization thread if not already initialized and no initializa tion thread exists. 184 * Start an initialization thread if not already initialized and no initializa tion thread exists.
185 * 185 *
186 * \par Postcondition 186 * \par Postcondition
187 * - if returns, AlreadyStartedInitializer is true 187 * - if returns, AlreadyStartedInitializer is true
188 * - if throws, AlreadyStartedInitializer is false 188 * - if throws, AlreadyStartedInitializer is false
sergei 2016/01/29 10:04:21 Why is it false and what happens in EnsureInitiali
Eric 2016/01/29 15:33:11 Trace the code. The only times this function can t
sergei 2016/02/09 17:38:13 Acknowledged.
189 */ 189 */
190 void SpawnInitializer() 190 void SpawnInitializer()
191 { 191 {
192 if (AlreadyInitialized() || AlreadyStartedInitializer()) 192 if (AlreadyInitialized() || AlreadyStartedInitializer())
193 { 193 {
194 return; // fast-return path 194 return; // fast-return path
195 } 195 }
196 // Assert we have neither completed nor started an initialization thread 196 // Assert we have neither completed nor started an initialization thread
197 /* 197 /*
198 * Try to obtain a lock, giving us permission to start the initialization th read. 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. 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. 200 * It is, however, guaranteed to succeed eventually.
201 */ 201 */
202 while (true) 202 while (true)
203 { 203 {
204 if (mutex.try_lock()) 204 if (mutex.try_lock())
205 { 205 {
206 // Assert mutex is locked 206 // Assert mutex is locked
207 LockGuardType lg(mutex, std::adopt_lock); 207 LockGuardType lg(mutex, std::adopt_lock);
sergei 2016/01/29 10:04:22 Why not just use `std::lock_guard<std::mutex> lock
Eric 2016/01/29 15:33:10 Because that would be incorrect code. There's a r
sergei 2016/02/09 17:38:15 What is the race condition here? If there is a rea
208 /* 208 /*
209 * Refresh our knowledge of whether the initializer has started, this ti me under lock. 209 * Refresh our knowledge of whether the initializer has started, this ti me under lock.
210 */ 210 */
211 if (AlreadyStartedInitializer()) 211 if (AlreadyStartedInitializer())
212 { 212 {
213 return; 213 return;
214 } 214 }
215 /* 215 /*
216 * This thread constructor is the only statement that can throw in this function. 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. 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. 218 * An exception here is thus a soft error, since a future invocation mig ht succeed.
219 */ 219 */
220 std::thread th([this]() -> void { ThreadMain(); }); 220 std::thread th([this]() -> void { ThreadMain(); });
sergei 2016/01/29 10:04:20 Can it be written down as `[this]{ ThreadMain(); }
221 // Memory fence ensures store ends before any other load or store 221 // Memory fence ensures store ends before any other load or store
222 hasInitializerStarted.store(true, std::memory_order_release); 222 hasInitializerStarted.store(true, std::memory_order_release);
223 th.detach(); // Won't throw because the thread is both valid and joinabl e 223 th.detach(); // Won't throw because the thread is both valid and joinabl e
sergei 2016/01/29 10:04:22 We should not do that, see comment for the destruc
Eric 2016/01/29 15:33:10 Calling 'detach()' here is the whole point of deta
sergei 2016/02/09 17:38:13 If the thread is a member of the class then initia
224 return; 224 return;
225 } 225 }
226 else 226 else
227 { 227 {
228 // Assert mutex is not locked 228 // Assert mutex is not locked
229 if (AlreadyStartedInitializer()) 229 if (AlreadyStartedInitializer())
230 { 230 {
231 return; // Some other thread might have started the thread in the inte rim 231 return; // Some other thread might have started the thread in the inte rim
232 } 232 }
233 // One microsecond is usually enough for a mutex to fully reset 233 // One microsecond is usually enough for a mutex to fully reset
234 std::this_thread::sleep_for(std::chrono::microseconds(1)); 234 std::this_thread::sleep_for(std::chrono::microseconds(1));
sergei 2016/01/29 10:04:23 std::this_thread::yield() would be better. FYI: ac
Eric 2016/01/29 15:33:11 Calling 'sleep()' is more conservative. This stat
sergei 2016/02/09 17:38:12 std::this_thread::yield() is more descriptive, so
235 } 235 }
236 } 236 }
237 } 237 }
238 238
239 /** 239 /**
240 * Ensure that initialization has completed, blocking if necessary until it is . 240 * Ensure that initialization has completed, blocking if necessary until it is .
241 * 241 *
242 * SpawnInitializer() must have returned prior to calling this function. 242 * SpawnInitializer() must have returned prior to calling this function.
243 * If not, the function will block indefinitely. 243 * If not, the function will block indefinitely.
244 * 244 *
245 * \throw exception 245 * \throw exception
246 * if the initializer threw an exception, it's rethrown here 246 * if the initializer threw an exception, it's rethrown here
247 */ 247 */
248 void EnsureInitialized() 248 void EnsureInitialized()
sergei 2016/01/29 10:04:21 If you require to call firstly SpawnInitializer()
sergei 2016/01/29 10:04:21 It would be better to name it WaitForInitCompletio
Eric 2016/01/29 15:33:11 As I've said so many times before, this is a not a
Eric 2016/01/29 15:33:11 Renaming suggestion rejected. The current name ref
sergei 2016/02/09 17:38:12 It does not ensure that something is initialized i
sergei 2016/02/09 17:38:13 If it's used merely two times then it might be not
249 { 249 {
250 EnsureInitializedNoexcept(); 250 EnsureInitializedNoexcept();
251 if (initializerException) 251 if (initializerException)
252 { 252 {
253 std::rethrow_exception(initializerException); 253 std::rethrow_exception(initializerException);
254 } 254 }
255 } 255 }
256 }; 256 };
257 257
258 #endif // _DETACHED_INITIALIZATION_H_ 258 #endif // _DETACHED_INITIALIZATION_H_
LEFTRIGHT

Powered by Google App Engine
This is Rietveld