1 | /* |
2 | * Copyright 2011-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | /** |
17 | * This module implements a Synchronized abstraction useful in |
18 | * mutex-based concurrency. |
19 | * |
20 | * The Synchronized<T, Mutex> class is the primary public API exposed by this |
21 | * module. See folly/docs/Synchronized.md for a more complete explanation of |
22 | * this class and its benefits. |
23 | */ |
24 | |
25 | #pragma once |
26 | |
27 | #include <folly/Function.h> |
28 | #include <folly/Likely.h> |
29 | #include <folly/LockTraits.h> |
30 | #include <folly/Preprocessor.h> |
31 | #include <folly/SharedMutex.h> |
32 | #include <folly/Traits.h> |
33 | #include <folly/Utility.h> |
34 | #include <folly/container/Foreach.h> |
35 | #include <folly/functional/ApplyTuple.h> |
36 | #include <glog/logging.h> |
37 | |
38 | #include <array> |
39 | #include <mutex> |
40 | #include <tuple> |
41 | #include <type_traits> |
42 | #include <utility> |
43 | |
44 | namespace folly { |
45 | |
46 | template <class LockedType, class Mutex, class LockPolicy> |
47 | class LockedPtrBase; |
48 | template <class LockedType, class LockPolicy> |
49 | class LockedPtr; |
50 | |
51 | /** |
52 | * Public version of LockInterfaceDispatcher that contains the MutexLevel enum |
53 | * for the passed in mutex type |
54 | * |
55 | * This is decoupled from MutexLevelValueImpl in LockTraits.h because this |
56 | * ensures that a heterogenous mutex with a different API can be used. For |
57 | * example - if a mutex does not have a lock_shared() method but the |
58 | * LockTraits specialization for it supports a static non member |
59 | * lock_shared(Mutex&) it can be used as a shared mutex and will provide |
60 | * rlock() and wlock() functions. |
61 | */ |
62 | template <class Mutex> |
63 | using MutexLevelValue = detail::MutexLevelValueImpl< |
64 | true, |
65 | LockTraits<Mutex>::is_shared, |
66 | LockTraits<Mutex>::is_upgrade>; |
67 | |
68 | /** |
69 | * SynchronizedBase is a helper parent class for Synchronized<T>. |
70 | * |
71 | * It provides wlock() and rlock() methods for shared mutex types, |
72 | * or lock() methods for purely exclusive mutex types. |
73 | */ |
74 | template <class Subclass, detail::MutexLevel level> |
75 | class SynchronizedBase; |
76 | |
77 | /** |
78 | * SynchronizedBase specialization for shared mutex types. |
79 | * |
80 | * This class provides wlock() and rlock() methods for acquiring the lock and |
81 | * accessing the data. |
82 | */ |
83 | template <class Subclass> |
84 | class SynchronizedBase<Subclass, detail::MutexLevel::SHARED> { |
85 | public: |
86 | using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>; |
87 | using ConstWLockedPtr = |
88 | ::folly::LockedPtr<const Subclass, LockPolicyExclusive>; |
89 | using ConstLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyShared>; |
90 | |
91 | using TryWLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>; |
92 | using ConstTryWLockedPtr = |
93 | ::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>; |
94 | using TryRLockedPtr = ::folly::LockedPtr<const Subclass, LockPolicyTryShared>; |
95 | |
96 | /** |
97 | * Acquire an exclusive lock, and return a LockedPtr that can be used to |
98 | * safely access the datum. |
99 | * |
100 | * LockedPtr offers operator -> and * to provide access to the datum. |
101 | * The lock will be released when the LockedPtr is destroyed. |
102 | */ |
103 | LockedPtr wlock() { |
104 | return LockedPtr(static_cast<Subclass*>(this)); |
105 | } |
106 | ConstWLockedPtr wlock() const { |
107 | return ConstWLockedPtr(static_cast<const Subclass*>(this)); |
108 | } |
109 | |
110 | /** |
111 | * Attempts to acquire the lock in exclusive mode. If acquisition is |
112 | * unsuccessful, the returned LockedPtr will be null. |
113 | * |
114 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
115 | * validity.) |
116 | */ |
117 | TryWLockedPtr tryWLock() { |
118 | return TryWLockedPtr{static_cast<Subclass*>(this)}; |
119 | } |
120 | ConstTryWLockedPtr tryWLock() const { |
121 | return ConstTryWLockedPtr{static_cast<const Subclass*>(this)}; |
122 | } |
123 | |
124 | /** |
125 | * Acquire a read lock, and return a ConstLockedPtr that can be used to |
126 | * safely access the datum. |
127 | */ |
128 | ConstLockedPtr rlock() const { |
129 | return ConstLockedPtr(static_cast<const Subclass*>(this)); |
130 | } |
131 | |
132 | /** |
133 | * Attempts to acquire the lock in shared mode. If acquisition is |
134 | * unsuccessful, the returned LockedPtr will be null. |
135 | * |
136 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
137 | * validity.) |
138 | */ |
139 | TryRLockedPtr tryRLock() const { |
140 | return TryRLockedPtr{static_cast<const Subclass*>(this)}; |
141 | } |
142 | |
143 | /** |
144 | * Attempts to acquire the lock, or fails if the timeout elapses first. |
145 | * If acquisition is unsuccessful, the returned LockedPtr will be null. |
146 | * |
147 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
148 | * validity.) |
149 | */ |
150 | template <class Rep, class Period> |
151 | LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) { |
152 | return LockedPtr(static_cast<Subclass*>(this), timeout); |
153 | } |
154 | template <class Rep, class Period> |
155 | LockedPtr wlock(const std::chrono::duration<Rep, Period>& timeout) const { |
156 | return LockedPtr(static_cast<const Subclass*>(this), timeout); |
157 | } |
158 | |
159 | /** |
160 | * Attempts to acquire the lock, or fails if the timeout elapses first. |
161 | * If acquisition is unsuccessful, the returned LockedPtr will be null. |
162 | * |
163 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
164 | * validity.) |
165 | */ |
166 | template <class Rep, class Period> |
167 | ConstLockedPtr rlock( |
168 | const std::chrono::duration<Rep, Period>& timeout) const { |
169 | return ConstLockedPtr(static_cast<const Subclass*>(this), timeout); |
170 | } |
171 | |
172 | /** |
173 | * Invoke a function while holding the lock exclusively. |
174 | * |
175 | * A reference to the datum will be passed into the function as its only |
176 | * argument. |
177 | * |
178 | * This can be used with a lambda argument for easily defining small critical |
179 | * sections in the code. For example: |
180 | * |
181 | * auto value = obj.withWLock([](auto& data) { |
182 | * data.doStuff(); |
183 | * return data.getValue(); |
184 | * }); |
185 | */ |
186 | template <class Function> |
187 | auto withWLock(Function&& function) { |
188 | return function(*wlock()); |
189 | } |
190 | template <class Function> |
191 | auto withWLock(Function&& function) const { |
192 | return function(*wlock()); |
193 | } |
194 | |
195 | /** |
196 | * Invoke a function while holding the lock exclusively. |
197 | * |
198 | * This is similar to withWLock(), but the function will be passed a |
199 | * LockedPtr rather than a reference to the data itself. |
200 | * |
201 | * This allows scopedUnlock() to be called on the LockedPtr argument if |
202 | * desired. |
203 | */ |
204 | template <class Function> |
205 | auto withWLockPtr(Function&& function) { |
206 | return function(wlock()); |
207 | } |
208 | template <class Function> |
209 | auto withWLockPtr(Function&& function) const { |
210 | return function(wlock()); |
211 | } |
212 | |
213 | /** |
214 | * Invoke a function while holding an the lock in shared mode. |
215 | * |
216 | * A const reference to the datum will be passed into the function as its |
217 | * only argument. |
218 | */ |
219 | template <class Function> |
220 | auto withRLock(Function&& function) const { |
221 | return function(*rlock()); |
222 | } |
223 | |
224 | template <class Function> |
225 | auto withRLockPtr(Function&& function) const { |
226 | return function(rlock()); |
227 | } |
228 | }; |
229 | |
230 | /** |
231 | * SynchronizedBase specialization for upgrade mutex types. |
232 | * |
233 | * This class provides all the functionality provided by the SynchronizedBase |
234 | * specialization for shared mutexes and a ulock() method that returns an |
235 | * upgrade lock RAII proxy |
236 | */ |
237 | template <class Subclass> |
238 | class SynchronizedBase<Subclass, detail::MutexLevel::UPGRADE> |
239 | : public SynchronizedBase<Subclass, detail::MutexLevel::SHARED> { |
240 | public: |
241 | using UpgradeLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyUpgrade>; |
242 | using ConstUpgradeLockedPtr = |
243 | ::folly::LockedPtr<const Subclass, LockPolicyUpgrade>; |
244 | |
245 | using TryUpgradeLockedPtr = |
246 | ::folly::LockedPtr<Subclass, LockPolicyTryUpgrade>; |
247 | using ConstTryUpgradeLockedPtr = |
248 | ::folly::LockedPtr<const Subclass, LockPolicyTryUpgrade>; |
249 | |
250 | /** |
251 | * Acquire an upgrade lock and return a LockedPtr that can be used to safely |
252 | * access the datum |
253 | * |
254 | * And the const version |
255 | */ |
256 | UpgradeLockedPtr ulock() { |
257 | return UpgradeLockedPtr(static_cast<Subclass*>(this)); |
258 | } |
259 | |
260 | /** |
261 | * Attempts to acquire the lock in upgrade mode. If acquisition is |
262 | * unsuccessful, the returned LockedPtr will be null. |
263 | * |
264 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
265 | * validity.) |
266 | */ |
267 | TryUpgradeLockedPtr tryULock() { |
268 | return TryUpgradeLockedPtr{static_cast<Subclass*>(this)}; |
269 | } |
270 | |
271 | /** |
272 | * Acquire an upgrade lock and return a LockedPtr that can be used to safely |
273 | * access the datum |
274 | * |
275 | * And the const version |
276 | */ |
277 | template <class Rep, class Period> |
278 | UpgradeLockedPtr ulock(const std::chrono::duration<Rep, Period>& timeout) { |
279 | return UpgradeLockedPtr(static_cast<Subclass*>(this), timeout); |
280 | } |
281 | |
282 | /** |
283 | * Invoke a function while holding the lock. |
284 | * |
285 | * A reference to the datum will be passed into the function as its only |
286 | * argument. |
287 | * |
288 | * This can be used with a lambda argument for easily defining small critical |
289 | * sections in the code. For example: |
290 | * |
291 | * auto value = obj.withULock([](auto& data) { |
292 | * data.doStuff(); |
293 | * return data.getValue(); |
294 | * }); |
295 | * |
296 | * This is probably not the function you want. If the intent is to read the |
297 | * data object and determine whether you should upgrade to a write lock then |
298 | * the withULockPtr() method should be called instead, since it gives access |
299 | * to the LockedPtr proxy (which can be upgraded via the |
300 | * moveFromUpgradeToWrite() method) |
301 | */ |
302 | template <class Function> |
303 | auto withULock(Function&& function) { |
304 | return function(*ulock()); |
305 | } |
306 | |
307 | /** |
308 | * Invoke a function while holding the lock exclusively. |
309 | * |
310 | * This is similar to withULock(), but the function will be passed a |
311 | * LockedPtr rather than a reference to the data itself. |
312 | * |
313 | * This allows scopedUnlock() and getUniqueLock() to be called on the |
314 | * LockedPtr argument. |
315 | * |
316 | * This also allows you to upgrade the LockedPtr proxy to a write state so |
317 | * that changes can be made to the underlying data |
318 | */ |
319 | template <class Function> |
320 | auto withULockPtr(Function&& function) { |
321 | return function(ulock()); |
322 | } |
323 | }; |
324 | |
325 | /** |
326 | * SynchronizedBase specialization for non-shared mutex types. |
327 | * |
328 | * This class provides lock() methods for acquiring the lock and accessing the |
329 | * data. |
330 | */ |
331 | template <class Subclass> |
332 | class SynchronizedBase<Subclass, detail::MutexLevel::UNIQUE> { |
333 | public: |
334 | using LockedPtr = ::folly::LockedPtr<Subclass, LockPolicyExclusive>; |
335 | using ConstLockedPtr = |
336 | ::folly::LockedPtr<const Subclass, LockPolicyExclusive>; |
337 | |
338 | using TryLockedPtr = ::folly::LockedPtr<Subclass, LockPolicyTryExclusive>; |
339 | using ConstTryLockedPtr = |
340 | ::folly::LockedPtr<const Subclass, LockPolicyTryExclusive>; |
341 | |
342 | /** |
343 | * Acquire a lock, and return a LockedPtr that can be used to safely access |
344 | * the datum. |
345 | */ |
346 | LockedPtr lock() { |
347 | return LockedPtr(static_cast<Subclass*>(this)); |
348 | } |
349 | |
350 | /** |
351 | * Acquire a lock, and return a ConstLockedPtr that can be used to safely |
352 | * access the datum. |
353 | */ |
354 | ConstLockedPtr lock() const { |
355 | return ConstLockedPtr(static_cast<const Subclass*>(this)); |
356 | } |
357 | |
358 | /** |
359 | * Attempts to acquire the lock in exclusive mode. If acquisition is |
360 | * unsuccessful, the returned LockedPtr will be null. |
361 | * |
362 | * (Use LockedPtr::operator bool() or LockedPtr::isNull() to check for |
363 | * validity.) |
364 | */ |
365 | TryLockedPtr tryLock() { |
366 | return TryLockedPtr{static_cast<Subclass*>(this)}; |
367 | } |
368 | ConstTryLockedPtr tryLock() const { |
369 | return ConstTryLockedPtr{static_cast<const Subclass*>(this)}; |
370 | } |
371 | |
372 | /** |
373 | * Attempts to acquire the lock, or fails if the timeout elapses first. |
374 | * If acquisition is unsuccessful, the returned LockedPtr will be null. |
375 | */ |
376 | template <class Rep, class Period> |
377 | LockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) { |
378 | return LockedPtr(static_cast<Subclass*>(this), timeout); |
379 | } |
380 | |
381 | /** |
382 | * Attempts to acquire the lock, or fails if the timeout elapses first. |
383 | * If acquisition is unsuccessful, the returned LockedPtr will be null. |
384 | */ |
385 | template <class Rep, class Period> |
386 | ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const { |
387 | return ConstLockedPtr(static_cast<const Subclass*>(this), timeout); |
388 | } |
389 | |
390 | /** |
391 | * Invoke a function while holding the lock. |
392 | * |
393 | * A reference to the datum will be passed into the function as its only |
394 | * argument. |
395 | * |
396 | * This can be used with a lambda argument for easily defining small critical |
397 | * sections in the code. For example: |
398 | * |
399 | * auto value = obj.withLock([](auto& data) { |
400 | * data.doStuff(); |
401 | * return data.getValue(); |
402 | * }); |
403 | */ |
404 | template <class Function> |
405 | auto withLock(Function&& function) { |
406 | return function(*lock()); |
407 | } |
408 | template <class Function> |
409 | auto withLock(Function&& function) const { |
410 | return function(*lock()); |
411 | } |
412 | |
413 | /** |
414 | * Invoke a function while holding the lock exclusively. |
415 | * |
416 | * This is similar to withWLock(), but the function will be passed a |
417 | * LockedPtr rather than a reference to the data itself. |
418 | * |
419 | * This allows scopedUnlock() and getUniqueLock() to be called on the |
420 | * LockedPtr argument. |
421 | */ |
422 | template <class Function> |
423 | auto withLockPtr(Function&& function) { |
424 | return function(lock()); |
425 | } |
426 | template <class Function> |
427 | auto withLockPtr(Function&& function) const { |
428 | return function(lock()); |
429 | } |
430 | }; |
431 | |
432 | /** |
433 | * Synchronized<T> encapsulates an object of type T (a "datum") paired |
434 | * with a mutex. The only way to access the datum is while the mutex |
435 | * is locked, and Synchronized makes it virtually impossible to do |
436 | * otherwise. The code that would access the datum in unsafe ways |
437 | * would look odd and convoluted, thus readily alerting the human |
438 | * reviewer. In contrast, the code that uses Synchronized<T> correctly |
439 | * looks simple and intuitive. |
440 | * |
441 | * The second parameter must be a mutex type. Any mutex type supported by |
442 | * LockTraits<Mutex> can be used. By default any class with lock() and |
443 | * unlock() methods will work automatically. LockTraits can be specialized to |
444 | * teach Synchronized how to use other custom mutex types. See the |
445 | * documentation in LockTraits.h for additional details. |
446 | * |
447 | * Supported mutexes that work by default include std::mutex, |
448 | * std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex, |
449 | * folly::SharedMutex, folly::RWSpinLock, and folly::SpinLock. |
450 | * Include LockTraitsBoost.h to get additional LockTraits specializations to |
451 | * support the following boost mutex types: boost::mutex, |
452 | * boost::recursive_mutex, boost::shared_mutex, boost::timed_mutex, and |
453 | * boost::recursive_timed_mutex. |
454 | */ |
455 | template <class T, class Mutex = SharedMutex> |
456 | struct Synchronized : public SynchronizedBase< |
457 | Synchronized<T, Mutex>, |
458 | MutexLevelValue<Mutex>::value> { |
459 | private: |
460 | using Base = |
461 | SynchronizedBase<Synchronized<T, Mutex>, MutexLevelValue<Mutex>::value>; |
462 | static constexpr bool nxCopyCtor{ |
463 | std::is_nothrow_copy_constructible<T>::value}; |
464 | static constexpr bool nxMoveCtor{ |
465 | std::is_nothrow_move_constructible<T>::value}; |
466 | |
467 | // used to disable copy construction and assignment |
468 | class NonImplementedType; |
469 | |
470 | public: |
471 | using LockedPtr = typename Base::LockedPtr; |
472 | using ConstLockedPtr = typename Base::ConstLockedPtr; |
473 | using DataType = T; |
474 | using MutexType = Mutex; |
475 | |
476 | /** |
477 | * Default constructor leaves both members call their own default |
478 | * constructor. |
479 | */ |
480 | Synchronized() = default; |
481 | |
482 | public: |
483 | /** |
484 | * Copy constructor; deprecated |
485 | * |
486 | * Enabled only when the data type is copy-constructible. |
487 | * |
488 | * Takes a shared-or-exclusive lock on the source mutex while performing the |
489 | * copy-construction of the destination data from the source data. No lock is |
490 | * taken on the destination mutex. |
491 | * |
492 | * May throw even when the data type is is nothrow-copy-constructible because |
493 | * acquiring a lock may throw. |
494 | */ |
495 | /* implicit */ Synchronized(typename std::conditional< |
496 | std::is_copy_constructible<T>::value, |
497 | const Synchronized&, |
498 | NonImplementedType>::type rhs) /* may throw */ |
499 | : Synchronized(rhs.copy()) {} |
500 | |
501 | /** |
502 | * Move constructor; deprecated |
503 | * |
504 | * Move-constructs from the source data without locking either the source or |
505 | * the destination mutex. |
506 | * |
507 | * Semantically, assumes that the source object is a true rvalue and therefore |
508 | * that no synchronization is required for accessing it. |
509 | */ |
510 | Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor) |
511 | : Synchronized(std::move(rhs.datum_)) {} |
512 | |
513 | /** |
514 | * Constructor taking a datum as argument copies it. There is no |
515 | * need to lock the constructing object. |
516 | */ |
517 | explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {} |
518 | |
519 | /** |
520 | * Constructor taking a datum rvalue as argument moves it. Again, |
521 | * there is no need to lock the constructing object. |
522 | */ |
523 | explicit Synchronized(T&& rhs) noexcept(nxMoveCtor) |
524 | : datum_(std::move(rhs)) {} |
525 | |
526 | /** |
527 | * Lets you construct non-movable types in-place. Use the constexpr |
528 | * instance `in_place` as the first argument. |
529 | */ |
530 | template <typename... Args> |
531 | explicit Synchronized(in_place_t, Args&&... args) |
532 | : datum_(std::forward<Args>(args)...) {} |
533 | |
534 | /** |
535 | * Lets you construct the synchronized object and also pass construction |
536 | * parameters to the underlying mutex if desired |
537 | */ |
538 | template <typename... DatumArgs, typename... MutexArgs> |
539 | Synchronized( |
540 | std::piecewise_construct_t, |
541 | std::tuple<DatumArgs...> datumArgs, |
542 | std::tuple<MutexArgs...> mutexArgs) |
543 | : Synchronized{std::piecewise_construct, |
544 | std::move(datumArgs), |
545 | std::move(mutexArgs), |
546 | make_index_sequence<sizeof...(DatumArgs)>{}, |
547 | make_index_sequence<sizeof...(MutexArgs)>{}} {} |
548 | |
549 | /** |
550 | * Copy assignment operator; deprecated |
551 | * |
552 | * Enabled only when the data type is copy-constructible and move-assignable. |
553 | * |
554 | * Move-assigns from a copy of the source data. |
555 | * |
556 | * Takes a shared-or-exclusive lock on the source mutex while copying the |
557 | * source data to a temporary. Takes an exclusive lock on the destination |
558 | * mutex while move-assigning from the temporary. |
559 | * |
560 | * This technique consts an extra temporary but avoids the need to take locks |
561 | * on both mutexes together. |
562 | */ |
563 | Synchronized& operator=(typename std::conditional< |
564 | std::is_copy_constructible<T>::value && |
565 | std::is_move_assignable<T>::value, |
566 | const Synchronized&, |
567 | NonImplementedType>::type rhs) { |
568 | return *this = rhs.copy(); |
569 | } |
570 | |
571 | /** |
572 | * Move assignment operator; deprecated |
573 | * |
574 | * Takes an exclusive lock on the destination mutex while move-assigning the |
575 | * destination data from the source data. The source mutex is not locked or |
576 | * otherwise accessed. |
577 | * |
578 | * Semantically, assumes that the source object is a true rvalue and therefore |
579 | * that no synchronization is required for accessing it. |
580 | */ |
581 | Synchronized& operator=(Synchronized&& rhs) { |
582 | return *this = std::move(rhs.datum_); |
583 | } |
584 | |
585 | /** |
586 | * Lock object, assign datum. |
587 | */ |
588 | Synchronized& operator=(const T& rhs) { |
589 | if (&datum_ != &rhs) { |
590 | auto guard = operator->(); |
591 | datum_ = rhs; |
592 | } |
593 | return *this; |
594 | } |
595 | |
596 | /** |
597 | * Lock object, move-assign datum. |
598 | */ |
599 | Synchronized& operator=(T&& rhs) { |
600 | if (&datum_ != &rhs) { |
601 | auto guard = operator->(); |
602 | datum_ = std::move(rhs); |
603 | } |
604 | return *this; |
605 | } |
606 | |
607 | /** |
608 | * Acquire an appropriate lock based on the context. |
609 | * |
610 | * If the mutex is a shared mutex, and the Synchronized instance is const, |
611 | * this acquires a shared lock. Otherwise this acquires an exclusive lock. |
612 | * |
613 | * In general, prefer using the explicit rlock() and wlock() methods |
614 | * for read-write locks, and lock() for purely exclusive locks. |
615 | * |
616 | * contextualLock() is primarily intended for use in other template functions |
617 | * that do not necessarily know the lock type. |
618 | */ |
619 | LockedPtr contextualLock() { |
620 | return LockedPtr(this); |
621 | } |
622 | ConstLockedPtr contextualLock() const { |
623 | return ConstLockedPtr(this); |
624 | } |
625 | template <class Rep, class Period> |
626 | LockedPtr contextualLock(const std::chrono::duration<Rep, Period>& timeout) { |
627 | return LockedPtr(this, timeout); |
628 | } |
629 | template <class Rep, class Period> |
630 | ConstLockedPtr contextualLock( |
631 | const std::chrono::duration<Rep, Period>& timeout) const { |
632 | return ConstLockedPtr(this, timeout); |
633 | } |
634 | /** |
635 | * contextualRLock() acquires a read lock if the mutex type is shared, |
636 | * or a regular exclusive lock for non-shared mutex types. |
637 | * |
638 | * contextualRLock() when you know that you prefer a read lock (if |
639 | * available), even if the Synchronized<T> object itself is non-const. |
640 | */ |
641 | ConstLockedPtr contextualRLock() const { |
642 | return ConstLockedPtr(this); |
643 | } |
644 | template <class Rep, class Period> |
645 | ConstLockedPtr contextualRLock( |
646 | const std::chrono::duration<Rep, Period>& timeout) const { |
647 | return ConstLockedPtr(this, timeout); |
648 | } |
649 | |
650 | /** |
651 | * This accessor offers a LockedPtr. In turn, LockedPtr offers |
652 | * operator-> returning a pointer to T. The operator-> keeps |
653 | * expanding until it reaches a pointer, so syncobj->foo() will lock |
654 | * the object and call foo() against it. |
655 | * |
656 | * NOTE: This API is planned to be deprecated in an upcoming diff. |
657 | * Prefer using lock(), wlock(), or rlock() instead. |
658 | */ |
659 | LockedPtr operator->() { |
660 | return LockedPtr(this); |
661 | } |
662 | |
663 | /** |
664 | * Obtain a ConstLockedPtr. |
665 | * |
666 | * NOTE: This API is planned to be deprecated in an upcoming diff. |
667 | * Prefer using lock(), wlock(), or rlock() instead. |
668 | */ |
669 | ConstLockedPtr operator->() const { |
670 | return ConstLockedPtr(this); |
671 | } |
672 | |
673 | /** |
674 | * Attempts to acquire for a given number of milliseconds. If |
675 | * acquisition is unsuccessful, the returned LockedPtr is nullptr. |
676 | * |
677 | * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead. |
678 | * In the future it will be marked with a deprecation attribute to emit |
679 | * build-time warnings, and then it will be removed entirely. |
680 | */ |
681 | LockedPtr timedAcquire(unsigned int milliseconds) { |
682 | return LockedPtr(this, std::chrono::milliseconds(milliseconds)); |
683 | } |
684 | |
685 | /** |
686 | * Attempts to acquire for a given number of milliseconds. If |
687 | * acquisition is unsuccessful, the returned ConstLockedPtr is nullptr. |
688 | * |
689 | * NOTE: This API is deprecated. Use lock(), wlock(), or rlock() instead. |
690 | * In the future it will be marked with a deprecation attribute to emit |
691 | * build-time warnings, and then it will be removed entirely. |
692 | */ |
693 | ConstLockedPtr timedAcquire(unsigned int milliseconds) const { |
694 | return ConstLockedPtr(this, std::chrono::milliseconds(milliseconds)); |
695 | } |
696 | |
697 | /** |
698 | * Swaps with another Synchronized. Protected against |
699 | * self-swap. Only data is swapped. Locks are acquired in increasing |
700 | * address order. |
701 | */ |
702 | void swap(Synchronized& rhs) { |
703 | if (this == &rhs) { |
704 | return; |
705 | } |
706 | if (this > &rhs) { |
707 | return rhs.swap(*this); |
708 | } |
709 | auto guard1 = operator->(); |
710 | auto guard2 = rhs.operator->(); |
711 | |
712 | using std::swap; |
713 | swap(datum_, rhs.datum_); |
714 | } |
715 | |
716 | /** |
717 | * Swap with another datum. Recommended because it keeps the mutex |
718 | * held only briefly. |
719 | */ |
720 | void swap(T& rhs) { |
721 | LockedPtr guard(this); |
722 | |
723 | using std::swap; |
724 | swap(datum_, rhs); |
725 | } |
726 | |
727 | /** |
728 | * Assign another datum and return the original value. Recommended |
729 | * because it keeps the mutex held only briefly. |
730 | */ |
731 | T exchange(T&& rhs) { |
732 | swap(rhs); |
733 | return std::move(rhs); |
734 | } |
735 | |
736 | /** |
737 | * Copies datum to a given target. |
738 | */ |
739 | void copyInto(T& target) const { |
740 | ConstLockedPtr guard(this); |
741 | target = datum_; |
742 | } |
743 | |
744 | /** |
745 | * Returns a fresh copy of the datum. |
746 | */ |
747 | T copy() const { |
748 | ConstLockedPtr guard(this); |
749 | return datum_; |
750 | } |
751 | |
752 | private: |
753 | template <class LockedType, class MutexType, class LockPolicy> |
754 | friend class folly::LockedPtrBase; |
755 | template <class LockedType, class LockPolicy> |
756 | friend class folly::LockedPtr; |
757 | |
758 | /** |
759 | * Helper constructors to enable Synchronized for |
760 | * non-default constructible types T. |
761 | * Guards are created in actual public constructors and are alive |
762 | * for the time required to construct the object |
763 | */ |
764 | Synchronized( |
765 | const Synchronized& rhs, |
766 | const ConstLockedPtr& /*guard*/) noexcept(nxCopyCtor) |
767 | : datum_(rhs.datum_) {} |
768 | |
769 | Synchronized(Synchronized&& rhs, const LockedPtr& /*guard*/) noexcept( |
770 | nxMoveCtor) |
771 | : datum_(std::move(rhs.datum_)) {} |
772 | |
773 | template < |
774 | typename... DatumArgs, |
775 | typename... MutexArgs, |
776 | std::size_t... IndicesOne, |
777 | std::size_t... IndicesTwo> |
778 | Synchronized( |
779 | std::piecewise_construct_t, |
780 | std::tuple<DatumArgs...> datumArgs, |
781 | std::tuple<MutexArgs...> mutexArgs, |
782 | index_sequence<IndicesOne...>, |
783 | index_sequence<IndicesTwo...>) |
784 | : datum_{std::get<IndicesOne>(std::move(datumArgs))...}, |
785 | mutex_{std::get<IndicesTwo>(std::move(mutexArgs))...} {} |
786 | |
787 | // Synchronized data members |
788 | T datum_; |
789 | mutable Mutex mutex_; |
790 | }; |
791 | |
792 | template <class SynchronizedType, class LockPolicy> |
793 | class ScopedUnlocker; |
794 | |
795 | namespace detail { |
796 | /* |
797 | * A helper alias that resolves to "const T" if the template parameter |
798 | * is a const Synchronized<T>, or "T" if the parameter is not const. |
799 | */ |
800 | template <class SynchronizedType> |
801 | using SynchronizedDataType = typename std::conditional< |
802 | std::is_const<SynchronizedType>::value, |
803 | typename SynchronizedType::DataType const, |
804 | typename SynchronizedType::DataType>::type; |
805 | /* |
806 | * A helper alias that resolves to a ConstLockedPtr if the template parameter |
807 | * is a const Synchronized<T>, or a LockedPtr if the parameter is not const. |
808 | */ |
809 | template <class SynchronizedType> |
810 | using LockedPtrType = typename std::conditional< |
811 | std::is_const<SynchronizedType>::value, |
812 | typename SynchronizedType::ConstLockedPtr, |
813 | typename SynchronizedType::LockedPtr>::type; |
814 | |
815 | template < |
816 | typename Synchronized, |
817 | typename LockFunc, |
818 | typename TryLockFunc, |
819 | typename... Args> |
820 | class SynchronizedLocker { |
821 | public: |
822 | using LockedPtr = invoke_result_t<LockFunc&, Synchronized&, const Args&...>; |
823 | |
824 | template <typename LockFuncType, typename TryLockFuncType, typename... As> |
825 | SynchronizedLocker( |
826 | Synchronized& sync, |
827 | LockFuncType&& lockFunc, |
828 | TryLockFuncType tryLockFunc, |
829 | As&&... as) |
830 | : synchronized{sync}, |
831 | lockFunc_{std::forward<LockFuncType>(lockFunc)}, |
832 | tryLockFunc_{std::forward<TryLockFuncType>(tryLockFunc)}, |
833 | args_{std::forward<As>(as)...} {} |
834 | |
835 | auto lock() const { |
836 | auto args = std::tuple<const Args&...>{args_}; |
837 | return apply(lockFunc_, std::tuple_cat(std::tie(synchronized), args)); |
838 | } |
839 | auto tryLock() const { |
840 | return tryLockFunc_(synchronized); |
841 | } |
842 | |
843 | private: |
844 | Synchronized& synchronized; |
845 | LockFunc lockFunc_; |
846 | TryLockFunc tryLockFunc_; |
847 | std::tuple<Args...> args_; |
848 | }; |
849 | |
850 | template < |
851 | typename Synchronized, |
852 | typename LockFunc, |
853 | typename TryLockFunc, |
854 | typename... Args> |
855 | auto makeSynchronizedLocker( |
856 | Synchronized& synchronized, |
857 | LockFunc&& lockFunc, |
858 | TryLockFunc&& tryLockFunc, |
859 | Args&&... args) { |
860 | using LockFuncType = std::decay_t<LockFunc>; |
861 | using TryLockFuncType = std::decay_t<TryLockFunc>; |
862 | return SynchronizedLocker< |
863 | Synchronized, |
864 | LockFuncType, |
865 | TryLockFuncType, |
866 | std::decay_t<Args>...>{synchronized, |
867 | std::forward<LockFunc>(lockFunc), |
868 | std::forward<TryLockFunc>(tryLockFunc), |
869 | std::forward<Args>(args)...}; |
870 | } |
871 | |
872 | /** |
873 | * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe |
874 | * manner. |
875 | * |
876 | * The function uses the "smart and polite" algorithm from this link |
877 | * http://howardhinnant.github.io/dining_philosophers.html#Polite |
878 | * |
879 | * The gist of the algorithm is that it locks a mutex, then tries to lock the |
880 | * other mutexes in a non-blocking manner. If all the locks succeed, we are |
881 | * done, if not, we release the locks we have held, yield to allow other |
882 | * threads to continue and then block on the mutex that we failed to acquire. |
883 | * |
884 | * This allows dynamically yielding ownership of all the mutexes but one, so |
885 | * that other threads can continue doing work and locking the other mutexes. |
886 | * See the benchmarks in folly/test/SynchronizedBenchmark.cpp for more. |
887 | */ |
888 | template <typename... SynchronizedLocker> |
889 | auto lock(SynchronizedLocker... lockersIn) |
890 | -> std::tuple<typename SynchronizedLocker::LockedPtr...> { |
891 | // capture the list of lockers as a tuple |
892 | auto lockers = std::forward_as_tuple(lockersIn...); |
893 | |
894 | // make a list of null LockedPtr instances that we will return to the caller |
895 | auto lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{}; |
896 | |
897 | // start by locking the first thing in the list |
898 | std::get<0>(lockedPtrs) = std::get<0>(lockers).lock(); |
899 | auto indexLocked = 0; |
900 | |
901 | while (true) { |
902 | auto couldLockAll = true; |
903 | |
904 | for_each(lockers, [&](auto& locker, auto index) { |
905 | // if we should try_lock on the current locker then do so |
906 | if (index != indexLocked) { |
907 | auto lockedPtr = locker.tryLock(); |
908 | |
909 | // if we were unable to lock this mutex, |
910 | // |
911 | // 1. release all the locks, |
912 | // 2. yield control to another thread to be nice |
913 | // 3. block on the mutex we failed to lock, acquire the lock |
914 | // 4. break out and set the index of the current mutex to indicate |
915 | // which mutex we have locked |
916 | if (!lockedPtr) { |
917 | // writing lockedPtrs = decltype(lockedPtrs){} does not compile on |
918 | // gcc, I believe this is a bug D7676798 |
919 | lockedPtrs = std::tuple<typename SynchronizedLocker::LockedPtr...>{}; |
920 | |
921 | std::this_thread::yield(); |
922 | fetch(lockedPtrs, index) = locker.lock(); |
923 | indexLocked = index; |
924 | couldLockAll = false; |
925 | |
926 | return loop_break; |
927 | } |
928 | |
929 | // else store the locked mutex in the list we return |
930 | fetch(lockedPtrs, index) = std::move(lockedPtr); |
931 | } |
932 | |
933 | return loop_continue; |
934 | }); |
935 | |
936 | if (couldLockAll) { |
937 | return lockedPtrs; |
938 | } |
939 | } |
940 | } |
941 | |
942 | template <typename Synchronized, typename... Args> |
943 | auto wlock(Synchronized& synchronized, Args&&... args) { |
944 | return detail::makeSynchronizedLocker( |
945 | synchronized, |
946 | [](auto& s, auto&&... a) { |
947 | return s.wlock(std::forward<decltype(a)>(a)...); |
948 | }, |
949 | [](auto& s) { return s.tryWLock(); }, |
950 | std::forward<Args>(args)...); |
951 | } |
952 | template <typename Synchronized, typename... Args> |
953 | auto rlock(Synchronized& synchronized, Args&&... args) { |
954 | return detail::makeSynchronizedLocker( |
955 | synchronized, |
956 | [](auto& s, auto&&... a) { |
957 | return s.rlock(std::forward<decltype(a)>(a)...); |
958 | }, |
959 | [](auto& s) { return s.tryRLock(); }, |
960 | std::forward<Args>(args)...); |
961 | } |
962 | template <typename Synchronized, typename... Args> |
963 | auto ulock(Synchronized& synchronized, Args&&... args) { |
964 | return detail::makeSynchronizedLocker( |
965 | synchronized, |
966 | [](auto& s, auto&&... a) { |
967 | return s.ulock(std::forward<decltype(a)>(a)...); |
968 | }, |
969 | [](auto& s) { return s.tryULock(); }, |
970 | std::forward<Args>(args)...); |
971 | } |
972 | template <typename Synchronized, typename... Args> |
973 | auto lock(Synchronized& synchronized, Args&&... args) { |
974 | return detail::makeSynchronizedLocker( |
975 | synchronized, |
976 | [](auto& s, auto&&... a) { |
977 | return s.lock(std::forward<decltype(a)>(a)...); |
978 | }, |
979 | [](auto& s) { return s.tryLock(); }, |
980 | std::forward<Args>(args)...); |
981 | } |
982 | |
983 | } // namespace detail |
984 | |
985 | /** |
986 | * A helper base class for implementing LockedPtr. |
987 | * |
988 | * The main reason for having this as a separate class is so we can specialize |
989 | * it for std::mutex, so we can expose a std::unique_lock to the caller |
990 | * when std::mutex is being used. This allows callers to use a |
991 | * std::condition_variable with the mutex from a Synchronized<T, std::mutex>. |
992 | * |
993 | * We don't use std::unique_lock with other Mutex types since it makes the |
994 | * LockedPtr class slightly larger, and it makes the logic to support |
995 | * ScopedUnlocker slightly more complicated. std::mutex is the only one that |
996 | * really seems to benefit from the unique_lock. std::condition_variable |
997 | * itself only supports std::unique_lock<std::mutex>, so there doesn't seem to |
998 | * be any real benefit to exposing the unique_lock with other mutex types. |
999 | * |
1000 | * Note that the SynchronizedType template parameter may or may not be const |
1001 | * qualified. |
1002 | */ |
1003 | template <class SynchronizedType, class Mutex, class LockPolicy> |
1004 | class LockedPtrBase { |
1005 | public: |
1006 | using MutexType = Mutex; |
1007 | friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>; |
1008 | |
1009 | /** |
1010 | * Friend all instantiations of LockedPtr and LockedPtrBase |
1011 | */ |
1012 | template <typename S, typename L> |
1013 | friend class folly::LockedPtr; |
1014 | template <typename S, typename M, typename L> |
1015 | friend class LockedPtrBase; |
1016 | |
1017 | /** |
1018 | * Destructor releases. |
1019 | */ |
1020 | ~LockedPtrBase() { |
1021 | if (parent_) { |
1022 | LockPolicy::unlock(parent_->mutex_); |
1023 | } |
1024 | } |
1025 | |
1026 | /** |
1027 | * Unlock the synchronized data. |
1028 | * |
1029 | * The LockedPtr can no longer be dereferenced after unlock() has been |
1030 | * called. isValid() will return false on an unlocked LockedPtr. |
1031 | * |
1032 | * unlock() can only be called on a LockedPtr that is valid. |
1033 | */ |
1034 | void unlock() { |
1035 | DCHECK(parent_ != nullptr); |
1036 | LockPolicy::unlock(parent_->mutex_); |
1037 | parent_ = nullptr; |
1038 | } |
1039 | |
1040 | protected: |
1041 | LockedPtrBase() {} |
1042 | explicit LockedPtrBase(SynchronizedType* parent) : parent_(parent) { |
1043 | DCHECK(parent); |
1044 | if (!LockPolicy::lock(parent_->mutex_)) { |
1045 | parent_ = nullptr; |
1046 | } |
1047 | } |
1048 | template <class Rep, class Period> |
1049 | LockedPtrBase( |
1050 | SynchronizedType* parent, |
1051 | const std::chrono::duration<Rep, Period>& timeout) { |
1052 | if (LockPolicy::try_lock_for(parent->mutex_, timeout)) { |
1053 | this->parent_ = parent; |
1054 | } |
1055 | } |
1056 | LockedPtrBase(LockedPtrBase&& rhs) noexcept |
1057 | : parent_{exchange(rhs.parent_, nullptr)} {} |
1058 | LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept { |
1059 | assignImpl(*this, rhs); |
1060 | return *this; |
1061 | } |
1062 | |
1063 | /** |
1064 | * Templated move construct and assignment operators |
1065 | * |
1066 | * These allow converting LockedPtr types that have the same unlocking |
1067 | * policy to each other. This allows us to write code like |
1068 | * |
1069 | * auto wlock = sync.wlock(); |
1070 | * wlock.unlock(); |
1071 | * |
1072 | * auto ulock = sync.ulock(); |
1073 | * wlock = ulock.moveFromUpgradeToWrite(); |
1074 | */ |
1075 | template <typename LockPolicyType> |
1076 | LockedPtrBase( |
1077 | LockedPtrBase<SynchronizedType, Mutex, LockPolicyType>&& rhs) noexcept |
1078 | : parent_{exchange(rhs.parent_, nullptr)} {} |
1079 | template <typename LockPolicyType> |
1080 | LockedPtrBase& operator=( |
1081 | LockedPtrBase<SynchronizedType, Mutex, LockPolicyType>&& rhs) noexcept { |
1082 | assignImpl(*this, rhs); |
1083 | return *this; |
1084 | } |
1085 | |
1086 | /** |
1087 | * Implementation for the assignment operator |
1088 | */ |
1089 | template <typename LockPolicyLhs, typename LockPolicyRhs> |
1090 | void assignImpl( |
1091 | LockedPtrBase<SynchronizedType, Mutex, LockPolicyLhs>& lhs, |
1092 | LockedPtrBase<SynchronizedType, Mutex, LockPolicyRhs>& rhs) noexcept { |
1093 | if (lhs.parent_) { |
1094 | LockPolicy::unlock(lhs.parent_->mutex_); |
1095 | } |
1096 | |
1097 | lhs.parent_ = exchange(rhs.parent_, nullptr); |
1098 | } |
1099 | |
1100 | using UnlockerData = SynchronizedType*; |
1101 | |
1102 | /** |
1103 | * Get a pointer to the Synchronized object from the UnlockerData. |
1104 | * |
1105 | * In the generic case UnlockerData is just the Synchronized pointer, |
1106 | * so we return it as is. (This function is more interesting in the |
1107 | * std::mutex specialization below.) |
1108 | */ |
1109 | static SynchronizedType* getSynchronized(UnlockerData data) { |
1110 | return data; |
1111 | } |
1112 | |
1113 | UnlockerData releaseLock() { |
1114 | DCHECK(parent_ != nullptr); |
1115 | auto current = parent_; |
1116 | parent_ = nullptr; |
1117 | LockPolicy::unlock(current->mutex_); |
1118 | return current; |
1119 | } |
1120 | void reacquireLock(UnlockerData&& data) { |
1121 | DCHECK(parent_ == nullptr); |
1122 | parent_ = data; |
1123 | LockPolicy::lock(parent_->mutex_); |
1124 | } |
1125 | |
1126 | SynchronizedType* parent_ = nullptr; |
1127 | }; |
1128 | |
1129 | /** |
1130 | * LockedPtrBase specialization for use with std::mutex. |
1131 | * |
1132 | * When std::mutex is used we use a std::unique_lock to hold the mutex. |
1133 | * This makes it possible to use std::condition_variable with a |
1134 | * Synchronized<T, std::mutex>. |
1135 | */ |
1136 | template <class SynchronizedType, class LockPolicy> |
1137 | class LockedPtrBase<SynchronizedType, std::mutex, LockPolicy> { |
1138 | public: |
1139 | using MutexType = std::mutex; |
1140 | friend class folly::ScopedUnlocker<SynchronizedType, LockPolicy>; |
1141 | |
1142 | /** |
1143 | * Friend all instantiations of LockedPtr and LockedPtrBase |
1144 | */ |
1145 | template <typename S, typename L> |
1146 | friend class folly::LockedPtr; |
1147 | template <typename S, typename M, typename L> |
1148 | friend class LockedPtrBase; |
1149 | |
1150 | /** |
1151 | * Destructor releases. |
1152 | */ |
1153 | ~LockedPtrBase() { |
1154 | // The std::unique_lock will automatically release the lock when it is |
1155 | // destroyed, so we don't need to do anything extra here. |
1156 | } |
1157 | |
1158 | LockedPtrBase(LockedPtrBase&& rhs) noexcept |
1159 | : lock_{std::move(rhs.lock_)}, parent_{exchange(rhs.parent_, nullptr)} {} |
1160 | LockedPtrBase& operator=(LockedPtrBase&& rhs) noexcept { |
1161 | assignImpl(*this, rhs); |
1162 | return *this; |
1163 | } |
1164 | |
1165 | /** |
1166 | * Templated move construct and assignment operators |
1167 | * |
1168 | * These allow converting LockedPtr types that have the same unlocking |
1169 | * policy to each other. |
1170 | */ |
1171 | template <typename LockPolicyType> |
1172 | LockedPtrBase(LockedPtrBase<SynchronizedType, std::mutex, LockPolicyType>&& |
1173 | other) noexcept |
1174 | : lock_{std::move(other.lock_)}, |
1175 | parent_{exchange(other.parent_, nullptr)} {} |
1176 | template <typename LockPolicyType> |
1177 | LockedPtrBase& operator=( |
1178 | LockedPtrBase<SynchronizedType, std::mutex, LockPolicyType>&& |
1179 | rhs) noexcept { |
1180 | assignImpl(*this, rhs); |
1181 | return *this; |
1182 | } |
1183 | |
1184 | /** |
1185 | * Implementation for the assignment operator |
1186 | */ |
1187 | template <typename LockPolicyLhs, typename LockPolicyRhs> |
1188 | void assignImpl( |
1189 | LockedPtrBase<SynchronizedType, std::mutex, LockPolicyLhs>& lhs, |
1190 | LockedPtrBase<SynchronizedType, std::mutex, LockPolicyRhs>& |
1191 | rhs) noexcept { |
1192 | lhs.lock_ = std::move(rhs.lock_); |
1193 | lhs.parent_ = exchange(rhs.parent_, nullptr); |
1194 | } |
1195 | |
1196 | /** |
1197 | * Get a reference to the std::unique_lock. |
1198 | * |
1199 | * This is provided so that callers can use Synchronized<T, std::mutex> |
1200 | * with a std::condition_variable. |
1201 | * |
1202 | * While this API could be used to bypass the normal Synchronized APIs and |
1203 | * manually interact with the underlying unique_lock, this is strongly |
1204 | * discouraged. |
1205 | */ |
1206 | std::unique_lock<std::mutex>& getUniqueLock() { |
1207 | return lock_; |
1208 | } |
1209 | |
1210 | /** |
1211 | * Unlock the synchronized data. |
1212 | * |
1213 | * The LockedPtr can no longer be dereferenced after unlock() has been |
1214 | * called. isValid() will return false on an unlocked LockedPtr. |
1215 | * |
1216 | * unlock() can only be called on a LockedPtr that is valid. |
1217 | */ |
1218 | void unlock() { |
1219 | DCHECK(parent_ != nullptr); |
1220 | lock_.unlock(); |
1221 | parent_ = nullptr; |
1222 | } |
1223 | |
1224 | protected: |
1225 | LockedPtrBase() {} |
1226 | explicit LockedPtrBase(SynchronizedType* parent) |
1227 | : lock_{parent->mutex_, std::adopt_lock}, parent_{parent} { |
1228 | DCHECK(parent); |
1229 | if (!LockPolicy::lock(parent_->mutex_)) { |
1230 | parent_ = nullptr; |
1231 | lock_.release(); |
1232 | } |
1233 | } |
1234 | |
1235 | using UnlockerData = |
1236 | std::pair<std::unique_lock<std::mutex>, SynchronizedType*>; |
1237 | |
1238 | static SynchronizedType* getSynchronized(const UnlockerData& data) { |
1239 | return data.second; |
1240 | } |
1241 | |
1242 | UnlockerData releaseLock() { |
1243 | DCHECK(parent_ != nullptr); |
1244 | UnlockerData data(std::move(lock_), parent_); |
1245 | parent_ = nullptr; |
1246 | data.first.unlock(); |
1247 | return data; |
1248 | } |
1249 | void reacquireLock(UnlockerData&& data) { |
1250 | lock_ = std::move(data.first); |
1251 | lock_.lock(); |
1252 | parent_ = data.second; |
1253 | } |
1254 | |
1255 | // The specialization for std::mutex does have to store slightly more |
1256 | // state than the default implementation. |
1257 | std::unique_lock<std::mutex> lock_; |
1258 | SynchronizedType* parent_ = nullptr; |
1259 | }; |
1260 | |
1261 | /** |
1262 | * This class temporarily unlocks a LockedPtr in a scoped manner. |
1263 | */ |
1264 | template <class SynchronizedType, class LockPolicy> |
1265 | class ScopedUnlocker { |
1266 | public: |
1267 | explicit ScopedUnlocker(LockedPtr<SynchronizedType, LockPolicy>* p) |
1268 | : ptr_(p), data_(ptr_->releaseLock()) {} |
1269 | ScopedUnlocker(const ScopedUnlocker&) = delete; |
1270 | ScopedUnlocker& operator=(const ScopedUnlocker&) = delete; |
1271 | ScopedUnlocker(ScopedUnlocker&& other) noexcept |
1272 | : ptr_(exchange(other.ptr_, nullptr)), data_(std::move(other.data_)) {} |
1273 | ScopedUnlocker& operator=(ScopedUnlocker&& other) = delete; |
1274 | |
1275 | ~ScopedUnlocker() { |
1276 | if (ptr_) { |
1277 | ptr_->reacquireLock(std::move(data_)); |
1278 | } |
1279 | } |
1280 | |
1281 | /** |
1282 | * Return a pointer to the Synchronized object used by this ScopedUnlocker. |
1283 | */ |
1284 | SynchronizedType* getSynchronized() const { |
1285 | return LockedPtr<SynchronizedType, LockPolicy>::getSynchronized(data_); |
1286 | } |
1287 | |
1288 | private: |
1289 | using Data = typename LockedPtr<SynchronizedType, LockPolicy>::UnlockerData; |
1290 | LockedPtr<SynchronizedType, LockPolicy>* ptr_{nullptr}; |
1291 | Data data_; |
1292 | }; |
1293 | |
1294 | /** |
1295 | * A LockedPtr keeps a Synchronized<T> object locked for the duration of |
1296 | * LockedPtr's existence. |
1297 | * |
1298 | * It provides access the datum's members directly by using operator->() and |
1299 | * operator*(). |
1300 | * |
1301 | * The LockPolicy parameter controls whether or not the lock is acquired in |
1302 | * exclusive or shared mode. |
1303 | */ |
1304 | template <class SynchronizedType, class LockPolicy> |
1305 | class LockedPtr : public LockedPtrBase< |
1306 | SynchronizedType, |
1307 | typename SynchronizedType::MutexType, |
1308 | LockPolicy> { |
1309 | private: |
1310 | using Base = LockedPtrBase< |
1311 | SynchronizedType, |
1312 | typename SynchronizedType::MutexType, |
1313 | LockPolicy>; |
1314 | using UnlockerData = typename Base::UnlockerData; |
1315 | // CDataType is the DataType with the appropriate const-qualification |
1316 | using CDataType = detail::SynchronizedDataType<SynchronizedType>; |
1317 | // Enable only if the unlock policy of the other LockPolicy is the same as |
1318 | // ours |
1319 | template <typename LockPolicyOther> |
1320 | using EnableIfSameUnlockPolicy = std::enable_if_t<std::is_same< |
1321 | typename LockPolicy::UnlockPolicy, |
1322 | typename LockPolicyOther::UnlockPolicy>::value>; |
1323 | |
1324 | // friend other LockedPtr types |
1325 | template <typename SynchronizedTypeOther, typename LockPolicyOther> |
1326 | friend class LockedPtr; |
1327 | |
1328 | public: |
1329 | using DataType = typename SynchronizedType::DataType; |
1330 | using MutexType = typename SynchronizedType::MutexType; |
1331 | using Synchronized = typename std::remove_const<SynchronizedType>::type; |
1332 | friend class ScopedUnlocker<SynchronizedType, LockPolicy>; |
1333 | |
1334 | /** |
1335 | * Creates an uninitialized LockedPtr. |
1336 | * |
1337 | * Dereferencing an uninitialized LockedPtr is not allowed. |
1338 | */ |
1339 | LockedPtr() {} |
1340 | |
1341 | /** |
1342 | * Takes a Synchronized<T> and locks it. |
1343 | */ |
1344 | explicit LockedPtr(SynchronizedType* parent) : Base(parent) {} |
1345 | |
1346 | /** |
1347 | * Takes a Synchronized<T> and attempts to lock it, within the specified |
1348 | * timeout. |
1349 | * |
1350 | * Blocks until the lock is acquired or until the specified timeout expires. |
1351 | * If the timeout expired without acquiring the lock, the LockedPtr will be |
1352 | * null, and LockedPtr::isNull() will return true. |
1353 | */ |
1354 | template <class Rep, class Period> |
1355 | LockedPtr( |
1356 | SynchronizedType* parent, |
1357 | const std::chrono::duration<Rep, Period>& timeout) |
1358 | : Base(parent, timeout) {} |
1359 | |
1360 | /** |
1361 | * Move constructor. |
1362 | */ |
1363 | LockedPtr(LockedPtr&& rhs) noexcept = default; |
1364 | template < |
1365 | typename LockPolicyType, |
1366 | EnableIfSameUnlockPolicy<LockPolicyType>* = nullptr> |
1367 | LockedPtr(LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept |
1368 | : Base{std::move(other)} {} |
1369 | |
1370 | /** |
1371 | * Move assignment operator. |
1372 | */ |
1373 | LockedPtr& operator=(LockedPtr&& rhs) noexcept = default; |
1374 | template < |
1375 | typename LockPolicyType, |
1376 | EnableIfSameUnlockPolicy<LockPolicyType>* = nullptr> |
1377 | LockedPtr& operator=( |
1378 | LockedPtr<SynchronizedType, LockPolicyType>&& other) noexcept { |
1379 | Base::operator=(std::move(other)); |
1380 | return *this; |
1381 | } |
1382 | |
1383 | /* |
1384 | * Copy constructor and assignment operator are deleted. |
1385 | */ |
1386 | LockedPtr(const LockedPtr& rhs) = delete; |
1387 | LockedPtr& operator=(const LockedPtr& rhs) = delete; |
1388 | |
1389 | /** |
1390 | * Destructor releases. |
1391 | */ |
1392 | ~LockedPtr() {} |
1393 | |
1394 | /** |
1395 | * Check if this LockedPtr is uninitialized, or points to valid locked data. |
1396 | * |
1397 | * This method can be used to check if a timed-acquire operation succeeded. |
1398 | * If an acquire operation times out it will result in a null LockedPtr. |
1399 | * |
1400 | * A LockedPtr is always either null, or holds a lock to valid data. |
1401 | * Methods such as scopedUnlock() reset the LockedPtr to null for the |
1402 | * duration of the unlock. |
1403 | */ |
1404 | bool isNull() const { |
1405 | return this->parent_ == nullptr; |
1406 | } |
1407 | |
1408 | /** |
1409 | * Explicit boolean conversion. |
1410 | * |
1411 | * Returns !isNull() |
1412 | */ |
1413 | explicit operator bool() const { |
1414 | return this->parent_ != nullptr; |
1415 | } |
1416 | |
1417 | /** |
1418 | * Access the locked data. |
1419 | * |
1420 | * This method should only be used if the LockedPtr is valid. |
1421 | */ |
1422 | CDataType* operator->() const { |
1423 | return &this->parent_->datum_; |
1424 | } |
1425 | |
1426 | /** |
1427 | * Access the locked data. |
1428 | * |
1429 | * This method should only be used if the LockedPtr is valid. |
1430 | */ |
1431 | CDataType& operator*() const { |
1432 | return this->parent_->datum_; |
1433 | } |
1434 | |
1435 | /** |
1436 | * Temporarily unlock the LockedPtr, and reset it to null. |
1437 | * |
1438 | * Returns an helper object that will re-lock and restore the LockedPtr when |
1439 | * the helper is destroyed. The LockedPtr may not be dereferenced for as |
1440 | * long as this helper object exists. |
1441 | */ |
1442 | ScopedUnlocker<SynchronizedType, LockPolicy> scopedUnlock() { |
1443 | return ScopedUnlocker<SynchronizedType, LockPolicy>(this); |
1444 | } |
1445 | |
1446 | /*************************************************************************** |
1447 | * Upgrade lock methods. |
1448 | * These are disabled via SFINAE when the mutex is not an upgrade mutex. |
1449 | **************************************************************************/ |
1450 | /** |
1451 | * Move the locked ptr from an upgrade state to an exclusive state. The |
1452 | * current lock is left in a null state. |
1453 | */ |
1454 | template < |
1455 | typename SyncType = SynchronizedType, |
1456 | typename = typename std::enable_if< |
1457 | LockTraits<typename SyncType::MutexType>::is_upgrade>::type> |
1458 | LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive> |
1459 | moveFromUpgradeToWrite() { |
1460 | return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToExclusive>( |
1461 | exchange(this->parent_, nullptr)); |
1462 | } |
1463 | |
1464 | /** |
1465 | * Move the locked ptr from an exclusive state to an upgrade state. The |
1466 | * current lock is left in a null state. |
1467 | */ |
1468 | template < |
1469 | typename SyncType = SynchronizedType, |
1470 | typename = typename std::enable_if< |
1471 | LockTraits<typename SyncType::MutexType>::is_upgrade>::type> |
1472 | LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade> |
1473 | moveFromWriteToUpgrade() { |
1474 | return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToUpgrade>( |
1475 | exchange(this->parent_, nullptr)); |
1476 | } |
1477 | |
1478 | /** |
1479 | * Move the locked ptr from an upgrade state to a shared state. The |
1480 | * current lock is left in a null state. |
1481 | */ |
1482 | template < |
1483 | typename SyncType = SynchronizedType, |
1484 | typename = typename std::enable_if< |
1485 | LockTraits<typename SyncType::MutexType>::is_upgrade>::type> |
1486 | LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared> |
1487 | moveFromUpgradeToRead() { |
1488 | return LockedPtr<SynchronizedType, LockPolicyFromUpgradeToShared>( |
1489 | exchange(this->parent_, nullptr)); |
1490 | } |
1491 | |
1492 | /** |
1493 | * Move the locked ptr from an exclusive state to a shared state. The |
1494 | * current lock is left in a null state. |
1495 | */ |
1496 | template < |
1497 | typename SyncType = SynchronizedType, |
1498 | typename = typename std::enable_if< |
1499 | LockTraits<typename SyncType::MutexType>::is_upgrade>::type> |
1500 | LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared> |
1501 | moveFromWriteToRead() { |
1502 | return LockedPtr<SynchronizedType, LockPolicyFromExclusiveToShared>( |
1503 | exchange(this->parent_, nullptr)); |
1504 | } |
1505 | }; |
1506 | |
1507 | /** |
1508 | * Helper functions that should be passed to either a lock() or synchronized() |
1509 | * invocation, these return implementation defined structs that will be used |
1510 | * to lock the synchronized instance appropriately. |
1511 | * |
1512 | * lock(wlock(one), rlock(two), wlock(three)); |
1513 | * synchronized([](auto one, two) { ... }, wlock(one), rlock(two)); |
1514 | * |
1515 | * For example in the above rlock() produces an implementation defined read |
1516 | * locking helper instance and wlock() a write locking helper |
1517 | * |
1518 | * Subsequent arguments passed to these locking helpers, after the first, will |
1519 | * be passed by const-ref to the corresponding function on the synchronized |
1520 | * instance. This means that if the function accepts these parameters by |
1521 | * value, they will be copied. Note that it is not necessary that the primary |
1522 | * locking function will be invoked at all (for eg. the implementation might |
1523 | * just invoke the try*Lock() method) |
1524 | * |
1525 | * // Try to acquire the lock for one second |
1526 | * synchronized([](auto) { ... }, wlock(one, 1s)); |
1527 | * |
1528 | * // The timed lock acquire might never actually be called, if it is not |
1529 | * // needed by the underlying deadlock avoiding algorithm |
1530 | * synchronized([](auto, auto) { ... }, rlock(one), wlock(two, 1s)); |
1531 | * |
1532 | * Note that the arguments passed to to *lock() calls will be passed by |
1533 | * const-ref to the function invocation, as the implementation might use them |
1534 | * many times |
1535 | */ |
1536 | template <typename D, typename M, typename... Args> |
1537 | auto wlock(Synchronized<D, M>& synchronized, Args&&... args) { |
1538 | return detail::wlock(synchronized, std::forward<Args>(args)...); |
1539 | } |
1540 | template <typename D, typename M, typename... Args> |
1541 | auto wlock(const Synchronized<D, M>& synchronized, Args&&... args) { |
1542 | return detail::wlock(synchronized, std::forward<Args>(args)...); |
1543 | } |
1544 | template <typename Data, typename Mutex, typename... Args> |
1545 | auto rlock(const Synchronized<Data, Mutex>& synchronized, Args&&... args) { |
1546 | return detail::rlock(synchronized, std::forward<Args>(args)...); |
1547 | } |
1548 | template <typename D, typename M, typename... Args> |
1549 | auto ulock(Synchronized<D, M>& synchronized, Args&&... args) { |
1550 | return detail::ulock(synchronized, std::forward<Args>(args)...); |
1551 | } |
1552 | template <typename D, typename M, typename... Args> |
1553 | auto lock(Synchronized<D, M>& synchronized, Args&&... args) { |
1554 | return detail::lock(synchronized, std::forward<Args>(args)...); |
1555 | } |
1556 | template <typename D, typename M, typename... Args> |
1557 | auto lock(const Synchronized<D, M>& synchronized, Args&&... args) { |
1558 | return detail::lock(synchronized, std::forward<Args>(args)...); |
1559 | } |
1560 | |
1561 | /** |
1562 | * Acquire locks for multiple Synchronized<> objects, in a deadlock-safe |
1563 | * manner. |
1564 | * |
1565 | * Wrap the synchronized instances with the appropriate locking strategy by |
1566 | * using one of the four strategies - folly::lock (exclusive acquire for |
1567 | * exclusive only mutexes), folly::rlock (shared acquire for shareable |
1568 | * mutexes), folly::wlock (exclusive acquire for shareable mutexes) or |
1569 | * folly::ulock (upgrade acquire for upgrade mutexes) (see above) |
1570 | * |
1571 | * The locks will be acquired and the passed callable will be invoked with the |
1572 | * LockedPtr instances in the order that they were passed to the function |
1573 | */ |
1574 | template <typename Func, typename... SynchronizedLockers> |
1575 | decltype(auto) synchronized(Func&& func, SynchronizedLockers&&... lockers) { |
1576 | return apply( |
1577 | std::forward<Func>(func), |
1578 | lock(std::forward<SynchronizedLockers>(lockers)...)); |
1579 | } |
1580 | |
1581 | /** |
1582 | * Acquire locks on many lockables or synchronized instances in such a way |
1583 | * that the sequence of calls within the function does not cause deadlocks. |
1584 | * |
1585 | * This can often result in a performance boost as compared to simply |
1586 | * acquiring your locks in an ordered manner. Even for very simple cases. |
1587 | * The algorithm tried to adjust to contention by blocking on the mutex it |
1588 | * thinks is the best fit, leaving all other mutexes open to be locked by |
1589 | * other threads. See the benchmarks in folly/test/SynchronizedBenchmark.cpp |
1590 | * for more |
1591 | * |
1592 | * This works differently as compared to the locking algorithm in libstdc++ |
1593 | * and is the recommended way to acquire mutexes in a generic order safe |
1594 | * manner. Performance benchmarks show that this does better than the one in |
1595 | * libstdc++ even for the simple cases |
1596 | * |
1597 | * Usage is the same as std::lock() for arbitrary lockables |
1598 | * |
1599 | * folly::lock(one, two, three); |
1600 | * |
1601 | * To make it work with folly::Synchronized you have to specify how you want |
1602 | * the locks to be acquired, use the folly::wlock(), folly::rlock(), |
1603 | * folly::ulock() and folly::lock() helpers defined below |
1604 | * |
1605 | * auto [one, two] = lock(folly::wlock(a), folly::rlock(b)); |
1606 | * |
1607 | * Note that you can/must avoid the folly:: namespace prefix on the lock() |
1608 | * function if you use the helpers, ADL lookup is done to find the lock function |
1609 | * |
1610 | * This will execute the deadlock avoidance algorithm and acquire a write lock |
1611 | * for a and a read lock for b |
1612 | */ |
1613 | template <typename LockableOne, typename LockableTwo, typename... Lockables> |
1614 | void lock(LockableOne& one, LockableTwo& two, Lockables&... lockables) { |
1615 | auto locker = [](auto& lockable) { |
1616 | using Lockable = std::remove_reference_t<decltype(lockable)>; |
1617 | return detail::makeSynchronizedLocker( |
1618 | lockable, |
1619 | [](auto& l) { return std::unique_lock<Lockable>{l}; }, |
1620 | [](auto& l) { |
1621 | auto lock = std::unique_lock<Lockable>{l, std::defer_lock}; |
1622 | lock.try_lock(); |
1623 | return lock; |
1624 | }); |
1625 | }; |
1626 | auto locks = lock(locker(one), locker(two), locker(lockables)...); |
1627 | |
1628 | // release ownership of the locks from the RAII lock wrapper returned by the |
1629 | // function above |
1630 | for_each(locks, [&](auto& lock) { lock.release(); }); |
1631 | } |
1632 | |
1633 | /** |
1634 | * Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe |
1635 | * manner. |
1636 | * |
1637 | * The locks are acquired in order from lowest address to highest address. |
1638 | * (Note that this is not necessarily the same algorithm used by std::lock().) |
1639 | * For parameters that are const and support shared locks, a read lock is |
1640 | * acquired. Otherwise an exclusive lock is acquired. |
1641 | * |
1642 | * use lock() with folly::wlock(), folly::rlock() and folly::ulock() for |
1643 | * arbitrary locking without causing a deadlock (as much as possible), with the |
1644 | * same effects as std::lock() |
1645 | */ |
1646 | template <class Sync1, class Sync2> |
1647 | std::tuple<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>> |
1648 | acquireLocked(Sync1& l1, Sync2& l2) { |
1649 | if (static_cast<const void*>(&l1) < static_cast<const void*>(&l2)) { |
1650 | auto p1 = l1.contextualLock(); |
1651 | auto p2 = l2.contextualLock(); |
1652 | return std::make_tuple(std::move(p1), std::move(p2)); |
1653 | } else { |
1654 | auto p2 = l2.contextualLock(); |
1655 | auto p1 = l1.contextualLock(); |
1656 | return std::make_tuple(std::move(p1), std::move(p2)); |
1657 | } |
1658 | } |
1659 | |
1660 | /** |
1661 | * A version of acquireLocked() that returns a std::pair rather than a |
1662 | * std::tuple, which is easier to use in many places. |
1663 | */ |
1664 | template <class Sync1, class Sync2> |
1665 | std::pair<detail::LockedPtrType<Sync1>, detail::LockedPtrType<Sync2>> |
1666 | acquireLockedPair(Sync1& l1, Sync2& l2) { |
1667 | auto lockedPtrs = acquireLocked(l1, l2); |
1668 | return {std::move(std::get<0>(lockedPtrs)), |
1669 | std::move(std::get<1>(lockedPtrs))}; |
1670 | } |
1671 | |
1672 | /************************************************************************ |
1673 | * NOTE: All APIs below this line will be deprecated in upcoming diffs. |
1674 | ************************************************************************/ |
1675 | |
1676 | // Non-member swap primitive |
1677 | template <class T, class M> |
1678 | void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) { |
1679 | lhs.swap(rhs); |
1680 | } |
1681 | |
1682 | /** |
1683 | * Disambiguate the name var by concatenating the line number of the original |
1684 | * point of expansion. This avoids shadowing warnings for nested |
1685 | * SYNCHRONIZEDs. The name is consistent if used multiple times within |
1686 | * another macro. |
1687 | * Only for internal use. |
1688 | */ |
1689 | #define SYNCHRONIZED_VAR(var) FB_CONCATENATE(SYNCHRONIZED_##var##_, __LINE__) |
1690 | |
1691 | /** |
1692 | * SYNCHRONIZED is the main facility that makes Synchronized<T> |
1693 | * helpful. It is a pseudo-statement that introduces a scope where the |
1694 | * object is locked. Inside that scope you get to access the unadorned |
1695 | * datum. |
1696 | * |
1697 | * Example: |
1698 | * |
1699 | * Synchronized<vector<int>> svector; |
1700 | * ... |
1701 | * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... } |
1702 | * or |
1703 | * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... } |
1704 | * |
1705 | * Refer to folly/docs/Synchronized.md for a detailed explanation and more |
1706 | * examples. |
1707 | */ |
1708 | #define SYNCHRONIZED(...) \ |
1709 | FOLLY_PUSH_WARNING \ |
1710 | FOLLY_GNU_DISABLE_WARNING("-Wshadow") \ |
1711 | FOLLY_MSVC_DISABLE_WARNING(4189) /* initialized but unreferenced */ \ |
1712 | FOLLY_MSVC_DISABLE_WARNING(4456) /* declaration hides local */ \ |
1713 | FOLLY_MSVC_DISABLE_WARNING(4457) /* declaration hides parameter */ \ |
1714 | FOLLY_MSVC_DISABLE_WARNING(4458) /* declaration hides member */ \ |
1715 | FOLLY_MSVC_DISABLE_WARNING(4459) /* declaration hides global */ \ |
1716 | FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \ |
1717 | if (bool SYNCHRONIZED_VAR(state) = false) { \ |
1718 | } else \ |
1719 | for (auto SYNCHRONIZED_VAR(lockedPtr) = \ |
1720 | (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).operator->(); \ |
1721 | !SYNCHRONIZED_VAR(state); \ |
1722 | SYNCHRONIZED_VAR(state) = true) \ |
1723 | for (auto& FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ |
1724 | *SYNCHRONIZED_VAR(lockedPtr).operator->(); \ |
1725 | !SYNCHRONIZED_VAR(state); \ |
1726 | SYNCHRONIZED_VAR(state) = true) \ |
1727 | FOLLY_POP_WARNING |
1728 | |
1729 | #define TIMED_SYNCHRONIZED(timeout, ...) \ |
1730 | if (bool SYNCHRONIZED_VAR(state) = false) { \ |
1731 | } else \ |
1732 | for (auto SYNCHRONIZED_VAR(lockedPtr) = \ |
1733 | (FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__))).timedAcquire(timeout); \ |
1734 | !SYNCHRONIZED_VAR(state); \ |
1735 | SYNCHRONIZED_VAR(state) = true) \ |
1736 | for (auto FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)) = \ |
1737 | (!SYNCHRONIZED_VAR(lockedPtr) \ |
1738 | ? nullptr \ |
1739 | : SYNCHRONIZED_VAR(lockedPtr).operator->()); \ |
1740 | !SYNCHRONIZED_VAR(state); \ |
1741 | SYNCHRONIZED_VAR(state) = true) |
1742 | |
1743 | /** |
1744 | * Similar to SYNCHRONIZED, but only uses a read lock. |
1745 | */ |
1746 | #define SYNCHRONIZED_CONST(...) \ |
1747 | SYNCHRONIZED( \ |
1748 | FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \ |
1749 | as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__)))) |
1750 | |
1751 | /** |
1752 | * Similar to TIMED_SYNCHRONIZED, but only uses a read lock. |
1753 | */ |
1754 | #define TIMED_SYNCHRONIZED_CONST(timeout, ...) \ |
1755 | TIMED_SYNCHRONIZED( \ |
1756 | timeout, \ |
1757 | FB_VA_GLUE(FB_ARG_1, (__VA_ARGS__)), \ |
1758 | as_const(FB_VA_GLUE(FB_ARG_2_OR_1, (__VA_ARGS__)))) |
1759 | |
1760 | /** |
1761 | * Synchronizes two Synchronized objects (they may encapsulate |
1762 | * different data). Synchronization is done in increasing address of |
1763 | * object order, so there is no deadlock risk. |
1764 | */ |
1765 | #define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \ |
1766 | if (bool SYNCHRONIZED_VAR(state) = false) { \ |
1767 | } else \ |
1768 | for (auto SYNCHRONIZED_VAR(ptrs) = acquireLockedPair(e1, e2); \ |
1769 | !SYNCHRONIZED_VAR(state); \ |
1770 | SYNCHRONIZED_VAR(state) = true) \ |
1771 | for (auto& n1 = *SYNCHRONIZED_VAR(ptrs).first; !SYNCHRONIZED_VAR(state); \ |
1772 | SYNCHRONIZED_VAR(state) = true) \ |
1773 | for (auto& n2 = *SYNCHRONIZED_VAR(ptrs).second; \ |
1774 | !SYNCHRONIZED_VAR(state); \ |
1775 | SYNCHRONIZED_VAR(state) = true) |
1776 | |
1777 | } /* namespace folly */ |
1778 | |