dcc5f7dd775cedb0138a6c392e6630f234d7af57
[folly.git] / folly / Synchronized.h
1 /*
2  * Copyright 2015 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 /**
18  * This module implements a Synchronized abstraction useful in
19  * mutex-based concurrency.
20  *
21  * @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com)
22  */
23
24 #ifndef SYNCHRONIZED_H_
25 #define SYNCHRONIZED_H_
26
27 #include <type_traits>
28 #include <mutex>
29 #include <boost/thread.hpp>
30 #include <folly/Preprocessor.h>
31 #include <folly/Traits.h>
32
33 namespace folly {
34
35 namespace detail {
36 enum InternalDoNotUse {};
37
38 /**
39  * Free function adaptors for std:: and boost::
40  */
41
42 // Android, OSX, and Cygwin don't have timed mutexes
43 #if defined(ANDROID) || defined(__ANDROID__) || \
44     defined(__APPLE__) || defined(__CYGWIN__)
45 # define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 0
46 #else
47 # define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 1
48 #endif
49
50 /**
51  * Yields true iff T has .lock() and .unlock() member functions. This
52  * is done by simply enumerating the mutexes with this interface in
53  * std and boost.
54  */
55 template <class T>
56 struct HasLockUnlock {
57   enum { value = IsOneOf<T
58       , std::mutex
59       , std::recursive_mutex
60       , boost::mutex
61       , boost::recursive_mutex
62       , boost::shared_mutex
63 #if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
64       , std::timed_mutex
65       , std::recursive_timed_mutex
66       , boost::timed_mutex
67       , boost::recursive_timed_mutex
68 #endif
69       >::value };
70 };
71
72 /**
73  * Yields true iff T has .lock_shared() and .unlock_shared() member functions.
74  * This is done by simply enumerating the mutexes with this interface.
75  */
76 template <class T>
77 struct HasLockSharedUnlockShared {
78   enum { value = IsOneOf<T
79       , boost::shared_mutex
80       >::value };
81 };
82
83 /**
84  * Acquires a mutex for reading by calling .lock().
85  *
86  * This variant is not appropriate for shared mutexes.
87  */
88 template <class T>
89 typename std::enable_if<
90   HasLockUnlock<T>::value && !HasLockSharedUnlockShared<T>::value>::type
91 acquireRead(T& mutex) {
92   mutex.lock();
93 }
94
95 /**
96  * Acquires a mutex for reading by calling .lock_shared().
97  *
98  * This variant is not appropriate for nonshared mutexes.
99  */
100 template <class T>
101 typename std::enable_if<HasLockSharedUnlockShared<T>::value>::type
102 acquireRead(T& mutex) {
103   mutex.lock_shared();
104 }
105
106 /**
107  * Acquires a mutex for reading and writing by calling .lock().
108  */
109 template <class T>
110 typename std::enable_if<HasLockUnlock<T>::value>::type
111 acquireReadWrite(T& mutex) {
112   mutex.lock();
113 }
114
115 #if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
116 /**
117  * Acquires a mutex for reading by calling .try_lock_shared_for(). This applies
118  * to boost::shared_mutex.
119  */
120 template <class T>
121 typename std::enable_if<
122   IsOneOf<T
123       , boost::shared_mutex
124       >::value, bool>::type
125 acquireRead(T& mutex,
126             unsigned int milliseconds) {
127   return mutex.try_lock_shared_for(boost::chrono::milliseconds(milliseconds));
128 }
129
130 /**
131  * Acquires a mutex for reading and writing with timeout by calling
132  * .try_lock_for(). This applies to two of the std mutex classes as
133  * enumerated below.
134  */
135 template <class T>
136 typename std::enable_if<
137   IsOneOf<T
138       , std::timed_mutex
139       , std::recursive_timed_mutex
140       >::value, bool>::type
141 acquireReadWrite(T& mutex,
142                  unsigned int milliseconds) {
143   // work around try_lock_for bug in some gcc versions, see
144   // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
145   // TODO: Fixed in gcc-4.9.0.
146   return mutex.try_lock()
147       || (milliseconds > 0 &&
148           mutex.try_lock_until(std::chrono::system_clock::now() +
149                                std::chrono::milliseconds(milliseconds)));
150 }
151
152 /**
153  * Acquires a mutex for reading and writing with timeout by calling
154  * .try_lock_for(). This applies to three of the boost mutex classes as
155  * enumerated below.
156  */
157 template <class T>
158 typename std::enable_if<
159   IsOneOf<T
160       , boost::shared_mutex
161       , boost::timed_mutex
162       , boost::recursive_timed_mutex
163       >::value, bool>::type
164 acquireReadWrite(T& mutex,
165                  unsigned int milliseconds) {
166   return mutex.try_lock_for(boost::chrono::milliseconds(milliseconds));
167 }
168 #endif // FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
169
170 /**
171  * Releases a mutex previously acquired for reading by calling
172  * .unlock(). The exception is boost::shared_mutex, which has a
173  * special primitive called .unlock_shared().
174  */
175 template <class T>
176 typename std::enable_if<
177   HasLockUnlock<T>::value && !HasLockSharedUnlockShared<T>::value>::type
178 releaseRead(T& mutex) {
179   mutex.unlock();
180 }
181
182 /**
183  * Special case for boost::shared_mutex.
184  */
185 template <class T>
186 typename std::enable_if<HasLockSharedUnlockShared<T>::value>::type
187 releaseRead(T& mutex) {
188   mutex.unlock_shared();
189 }
190
191 /**
192  * Releases a mutex previously acquired for reading-writing by calling
193  * .unlock().
194  */
195 template <class T>
196 typename std::enable_if<HasLockUnlock<T>::value>::type
197 releaseReadWrite(T& mutex) {
198   mutex.unlock();
199 }
200
201 } // namespace detail
202
203 /**
204  * Synchronized<T> encapsulates an object of type T (a "datum") paired
205  * with a mutex. The only way to access the datum is while the mutex
206  * is locked, and Synchronized makes it virtually impossible to do
207  * otherwise. The code that would access the datum in unsafe ways
208  * would look odd and convoluted, thus readily alerting the human
209  * reviewer. In contrast, the code that uses Synchronized<T> correctly
210  * looks simple and intuitive.
211  *
212  * The second parameter must be a mutex type. Supported mutexes are
213  * std::mutex, std::recursive_mutex, std::timed_mutex,
214  * std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex,
215  * boost::shared_mutex, boost::timed_mutex,
216  * boost::recursive_timed_mutex, and the folly/RWSpinLock.h
217  * classes.
218  *
219  * You may define Synchronized support by defining 4-6 primitives in
220  * the same namespace as the mutex class (found via ADL).  The
221  * primitives are: acquireRead, acquireReadWrite, releaseRead, and
222  * releaseReadWrite. Two optional primitives for timout operations are
223  * overloads of acquireRead and acquireReadWrite. For signatures,
224  * refer to the namespace detail below, which implements the
225  * primitives for mutexes in std and boost.
226  */
227 template <class T, class Mutex = boost::shared_mutex>
228 struct Synchronized {
229   /**
230    * Default constructor leaves both members call their own default
231    * constructor.
232    */
233   Synchronized() = default;
234
235  private:
236   static constexpr bool nxCopyCtor{
237       std::is_nothrow_copy_constructible<T>::value};
238   static constexpr bool nxMoveCtor{
239       std::is_nothrow_move_constructible<T>::value};
240
241   /**
242    * Helper constructors to enable Synchronized for
243    * non-default constructible types T.
244    * Guards are created in actual public constructors and are alive
245    * for the time required to construct the object
246    */
247   template <typename Guard>
248   Synchronized(const Synchronized& rhs,
249                const Guard& /*guard*/) noexcept(nxCopyCtor)
250       : datum_(rhs.datum_) {}
251
252   template <typename Guard>
253   Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor)
254       : datum_(std::move(rhs.datum_)) {}
255
256  public:
257   /**
258    * Copy constructor copies the data (with locking the source and
259    * all) but does NOT copy the mutex. Doing so would result in
260    * deadlocks.
261    */
262   Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
263       : Synchronized(rhs, rhs.operator->()) {}
264
265   /**
266    * Move constructor moves the data (with locking the source and all)
267    * but does not move the mutex.
268    */
269   Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
270       : Synchronized(std::move(rhs), rhs.operator->()) {}
271
272   /**
273    * Constructor taking a datum as argument copies it. There is no
274    * need to lock the constructing object.
275    */
276   explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
277
278   /**
279    * Constructor taking a datum rvalue as argument moves it. Again,
280    * there is no need to lock the constructing object.
281    */
282   explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
283       : datum_(std::move(rhs)) {}
284
285   /**
286    * The canonical assignment operator only assigns the data, NOT the
287    * mutex. It locks the two objects in ascending order of their
288    * addresses.
289    */
290   Synchronized& operator=(const Synchronized& rhs) {
291     if (this == &rhs) {
292       // Self-assignment, pass.
293     } else if (this < &rhs) {
294       auto guard1 = operator->();
295       auto guard2 = rhs.operator->();
296       datum_ = rhs.datum_;
297     } else {
298       auto guard1 = rhs.operator->();
299       auto guard2 = operator->();
300       datum_ = rhs.datum_;
301     }
302     return *this;
303   }
304
305   /**
306    * Move assignment operator, only assigns the data, NOT the
307    * mutex. It locks the two objects in ascending order of their
308    * addresses.
309    */
310   Synchronized& operator=(Synchronized&& rhs) {
311     if (this == &rhs) {
312       // Self-assignment, pass.
313     } else if (this < &rhs) {
314       auto guard1 = operator->();
315       auto guard2 = rhs.operator->();
316       datum_ = std::move(rhs.datum_);
317     } else {
318       auto guard1 = rhs.operator->();
319       auto guard2 = operator->();
320       datum_ = std::move(rhs.datum_);
321     }
322     return *this;
323   }
324
325   /**
326    * Lock object, assign datum.
327    */
328   Synchronized& operator=(const T& rhs) {
329     auto guard = operator->();
330     datum_ = rhs;
331     return *this;
332   }
333
334   /**
335    * Lock object, move-assign datum.
336    */
337   Synchronized& operator=(T&& rhs) {
338     auto guard = operator->();
339     datum_ = std::move(rhs);
340     return *this;
341   }
342
343   /**
344    * A LockedPtr lp keeps a modifiable (i.e. non-const)
345    * Synchronized<T> object locked for the duration of lp's
346    * existence. Because of this, you get to access the datum's methods
347    * directly by using lp->fun().
348    */
349   struct LockedPtr {
350     /**
351      * Found no reason to leave this hanging.
352      */
353     LockedPtr() = delete;
354
355     /**
356      * Takes a Synchronized and locks it.
357      */
358     explicit LockedPtr(Synchronized* parent) : parent_(parent) {
359       acquire();
360     }
361
362     /**
363      * Takes a Synchronized and attempts to lock it for some
364      * milliseconds. If not, the LockedPtr will be subsequently null.
365      */
366     LockedPtr(Synchronized* parent, unsigned int milliseconds) {
367       using namespace detail;
368       if (acquireReadWrite(parent->mutex_, milliseconds)) {
369         parent_ = parent;
370         return;
371       }
372       // Could not acquire the resource, pointer is null
373       parent_ = nullptr;
374     }
375
376     /**
377      * This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
378      * everything properly, but does not lock the parent because it
379      * "knows" someone else will lock it. Please do not use.
380      */
381     LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
382         : parent_(parent) {
383     }
384
385     /**
386      * Copy ctor adds one lock.
387      */
388     LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
389       acquire();
390     }
391
392     /**
393      * Assigning from another LockedPtr results in freeing the former
394      * lock and acquiring the new one. The method works with
395      * self-assignment (does nothing).
396      */
397     LockedPtr& operator=(const LockedPtr& rhs) {
398       if (parent_ != rhs.parent_) {
399         if (parent_) parent_->mutex_.unlock();
400         parent_ = rhs.parent_;
401         acquire();
402       }
403       return *this;
404     }
405
406     /**
407      * Destructor releases.
408      */
409     ~LockedPtr() {
410       using namespace detail;
411       if (parent_) releaseReadWrite(parent_->mutex_);
412     }
413
414     /**
415      * Safe to access the data. Don't save the obtained pointer by
416      * invoking lp.operator->() by hand. Also, if the method returns a
417      * handle stored inside the datum, don't use this idiom - use
418      * SYNCHRONIZED below.
419      */
420     T* operator->() {
421       return parent_ ? &parent_->datum_ : nullptr;
422     }
423
424     /**
425      * This class temporarily unlocks a LockedPtr in a scoped
426      * manner. It is used inside of the UNSYNCHRONIZED macro.
427      */
428     struct Unsynchronizer {
429       explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
430         using namespace detail;
431         releaseReadWrite(parent_->parent_->mutex_);
432       }
433       Unsynchronizer(const Unsynchronizer&) = delete;
434       Unsynchronizer& operator=(const Unsynchronizer&) = delete;
435       ~Unsynchronizer() {
436         parent_->acquire();
437       }
438       LockedPtr* operator->() const {
439         return parent_;
440       }
441     private:
442       LockedPtr* parent_;
443     };
444     friend struct Unsynchronizer;
445     Unsynchronizer typeHackDoNotUse();
446
447     template <class P1, class P2>
448     friend void lockInOrder(P1& p1, P2& p2);
449
450   private:
451     void acquire() {
452       using namespace detail;
453       if (parent_) acquireReadWrite(parent_->mutex_);
454     }
455
456     // This is the entire state of LockedPtr.
457     Synchronized* parent_;
458   };
459
460   /**
461    * ConstLockedPtr does exactly what LockedPtr does, but for const
462    * Synchronized objects. Of interest is that ConstLockedPtr only
463    * uses a read lock, which is faster but more restrictive - you only
464    * get to call const methods of the datum.
465    *
466    * Much of the code between LockedPtr and
467    * ConstLockedPtr is identical and could be factor out, but there
468    * are enough nagging little differences to not justify the trouble.
469    */
470   struct ConstLockedPtr {
471     ConstLockedPtr() = delete;
472     explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
473       acquire();
474     }
475     ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
476         : parent_(parent) {
477     }
478     ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
479       acquire();
480     }
481     explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
482       acquire();
483     }
484     ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
485       using namespace detail;
486       if (acquireRead(
487             parent->mutex_,
488             milliseconds)) {
489         parent_ = parent;
490         return;
491       }
492       // Could not acquire the resource, pointer is null
493       parent_ = nullptr;
494     }
495
496     ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
497       if (parent_ != rhs.parent_) {
498         if (parent_) parent_->mutex_.unlock_shared();
499         parent_ = rhs.parent_;
500         acquire();
501       }
502     }
503     ~ConstLockedPtr() {
504       using namespace detail;
505       if (parent_) releaseRead(parent_->mutex_);
506     }
507
508     const T* operator->() const {
509       return parent_ ? &parent_->datum_ : nullptr;
510     }
511
512     struct Unsynchronizer {
513       explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
514         using namespace detail;
515         releaseRead(parent_->parent_->mutex_);
516       }
517       Unsynchronizer(const Unsynchronizer&) = delete;
518       Unsynchronizer& operator=(const Unsynchronizer&) = delete;
519       ~Unsynchronizer() {
520         using namespace detail;
521         acquireRead(parent_->parent_->mutex_);
522       }
523       ConstLockedPtr* operator->() const {
524         return parent_;
525       }
526     private:
527       ConstLockedPtr* parent_;
528     };
529     friend struct Unsynchronizer;
530     Unsynchronizer typeHackDoNotUse();
531
532     template <class P1, class P2>
533     friend void lockInOrder(P1& p1, P2& p2);
534
535   private:
536     void acquire() {
537       using namespace detail;
538       if (parent_) acquireRead(parent_->mutex_);
539     }
540
541     const Synchronized* parent_;
542   };
543
544   /**
545    * This accessor offers a LockedPtr. In turn. LockedPtr offers
546    * operator-> returning a pointer to T. The operator-> keeps
547    * expanding until it reaches a pointer, so syncobj->foo() will lock
548    * the object and call foo() against it.
549   */
550   LockedPtr operator->() {
551     return LockedPtr(this);
552   }
553
554   /**
555    * Same, for constant objects. You will be able to invoke only const
556    * methods.
557    */
558   ConstLockedPtr operator->() const {
559     return ConstLockedPtr(this);
560   }
561
562   /**
563    * Attempts to acquire for a given number of milliseconds. If
564    * acquisition is unsuccessful, the returned LockedPtr is NULL.
565    */
566   LockedPtr timedAcquire(unsigned int milliseconds) {
567     return LockedPtr(this, milliseconds);
568   }
569
570   /**
571    * As above, for a constant object.
572    */
573   ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
574     return ConstLockedPtr(this, milliseconds);
575   }
576
577   /**
578    * Used by SYNCHRONIZED_DUAL.
579    */
580   LockedPtr internalDoNotUse() {
581     return LockedPtr(this, detail::InternalDoNotUse());
582   }
583
584   /**
585    * ditto
586    */
587   ConstLockedPtr internalDoNotUse() const {
588     return ConstLockedPtr(this, detail::InternalDoNotUse());
589   }
590
591   /**
592    * Sometimes, although you have a mutable object, you only want to
593    * call a const method against it. The most efficient way to achieve
594    * that is by using a read lock. You get to do so by using
595    * obj.asConst()->method() instead of obj->method().
596    */
597   const Synchronized& asConst() const {
598     return *this;
599   }
600
601   /**
602    * Swaps with another Synchronized. Protected against
603    * self-swap. Only data is swapped. Locks are acquired in increasing
604    * address order.
605    */
606   void swap(Synchronized& rhs) {
607     if (this == &rhs) {
608       return;
609     }
610     if (this > &rhs) {
611       return rhs.swap(*this);
612     }
613     auto guard1 = operator->();
614     auto guard2 = rhs.operator->();
615
616     using std::swap;
617     swap(datum_, rhs.datum_);
618   }
619
620   /**
621    * Swap with another datum. Recommended because it keeps the mutex
622    * held only briefly.
623    */
624   void swap(T& rhs) {
625     LockedPtr guard = operator->();
626
627     using std::swap;
628     swap(datum_, rhs);
629   }
630
631   /**
632    * Copies datum to a given target.
633    */
634   void copy(T* target) const {
635     ConstLockedPtr guard = operator->();
636     *target = datum_;
637   }
638
639   /**
640    * Returns a fresh copy of the datum.
641    */
642   T copy() const {
643     ConstLockedPtr guard = operator->();
644     return datum_;
645   }
646
647 private:
648   T datum_;
649   mutable Mutex mutex_;
650 };
651
652 // Non-member swap primitive
653 template <class T, class M>
654 void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
655   lhs.swap(rhs);
656 }
657
658 /**
659  * SYNCHRONIZED is the main facility that makes Synchronized<T>
660  * helpful. It is a pseudo-statement that introduces a scope where the
661  * object is locked. Inside that scope you get to access the unadorned
662  * datum.
663  *
664  * Example:
665  *
666  * Synchronized<vector<int>> svector;
667  * ...
668  * SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
669  * or
670  * SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
671  *
672  * Refer to folly/docs/Synchronized.md for a detailed explanation and more
673  * examples.
674  */
675 #define SYNCHRONIZED(...)                                       \
676   _Pragma("GCC diagnostic push")                                \
677   _Pragma("GCC diagnostic ignored \"-Wshadow\"")                \
678   if (bool SYNCHRONIZED_state = false) {} else                  \
679     for (auto SYNCHRONIZED_lockedPtr =                          \
680            (FB_ARG_2_OR_1(__VA_ARGS__)).operator->();           \
681          !SYNCHRONIZED_state; SYNCHRONIZED_state = true)        \
682       for (auto& FB_ARG_1(__VA_ARGS__) =                        \
683              *SYNCHRONIZED_lockedPtr.operator->();              \
684            !SYNCHRONIZED_state; SYNCHRONIZED_state = true)      \
685   _Pragma("GCC diagnostic pop")
686
687 #define TIMED_SYNCHRONIZED(timeout, ...)                           \
688   if (bool SYNCHRONIZED_state = false) {} else                     \
689     for (auto SYNCHRONIZED_lockedPtr =                             \
690            (FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout);     \
691          !SYNCHRONIZED_state; SYNCHRONIZED_state = true)           \
692       for (auto FB_ARG_1(__VA_ARGS__) =                            \
693              SYNCHRONIZED_lockedPtr.operator->();                  \
694            !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
695
696 /**
697  * Similar to SYNCHRONIZED, but only uses a read lock.
698  */
699 #define SYNCHRONIZED_CONST(...)                         \
700   SYNCHRONIZED(FB_ARG_1(__VA_ARGS__),                   \
701                (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
702
703 /**
704  * Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
705  */
706 #define TIMED_SYNCHRONIZED_CONST(timeout, ...)                  \
707   TIMED_SYNCHRONIZED(timeout, FB_ARG_1(__VA_ARGS__),            \
708                      (FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
709
710 /**
711  * Temporarily disables synchronization inside a SYNCHRONIZED block.
712  */
713 #define UNSYNCHRONIZED(name)                                    \
714   for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse())      \
715          SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr);          \
716        !SYNCHRONIZED_state; SYNCHRONIZED_state = true)          \
717     for (auto name = *SYNCHRONIZED_state3.operator->();         \
718          !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
719
720 /**
721  * Locks two objects in increasing order of their addresses.
722  */
723 template <class P1, class P2>
724 void lockInOrder(P1& p1, P2& p2) {
725   if (static_cast<const void*>(p1.operator->()) >
726       static_cast<const void*>(p2.operator->())) {
727     p2.acquire();
728     p1.acquire();
729   } else {
730     p1.acquire();
731     p2.acquire();
732   }
733 }
734
735 /**
736  * Synchronizes two Synchronized objects (they may encapsulate
737  * different data). Synchronization is done in increasing address of
738  * object order, so there is no deadlock risk.
739  */
740 #define SYNCHRONIZED_DUAL(n1, e1, n2, e2)                       \
741   if (bool SYNCHRONIZED_state = false) {} else                  \
742     for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse();       \
743          !SYNCHRONIZED_state; SYNCHRONIZED_state = true)        \
744       for (auto& n1 = *SYNCHRONIZED_lp1.operator->();           \
745            !SYNCHRONIZED_state;  SYNCHRONIZED_state = true)     \
746         for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse();   \
747              !SYNCHRONIZED_state;  SYNCHRONIZED_state = true)   \
748           for (auto& n2 = *SYNCHRONIZED_lp2.operator->();       \
749                !SYNCHRONIZED_state; SYNCHRONIZED_state = true)  \
750             if ((::folly::lockInOrder(                          \
751                    SYNCHRONIZED_lp1, SYNCHRONIZED_lp2),         \
752                  false)) {}                                     \
753             else
754
755 } /* namespace folly */
756
757 #endif // SYNCHRONIZED_H_