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