/*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <type_traits>
#include <mutex>
#include <boost/thread.hpp>
-#include "folly/Preprocessor.h"
-#include "folly/Traits.h"
+#include <folly/Preprocessor.h>
+#include <folly/SharedMutex.h>
+#include <folly/Traits.h>
namespace folly {
* Free function adaptors for std:: and boost::
*/
+// Android, OSX, and Cygwin don't have timed mutexes
+#if defined(ANDROID) || defined(__ANDROID__) || \
+ defined(__APPLE__) || defined(__CYGWIN__)
+# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 0
+#else
+# define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES 1
+#endif
+
/**
* Yields true iff T has .lock() and .unlock() member functions. This
* is done by simply enumerating the mutexes with this interface in
*/
template <class T>
struct HasLockUnlock {
- enum { value = IsOneOf<T,
- std::mutex, std::recursive_mutex,
- boost::mutex, boost::recursive_mutex, boost::shared_mutex
-#ifndef __APPLE__ // OSX doesn't have timed mutexes
- ,std::timed_mutex, std::recursive_timed_mutex,
- boost::timed_mutex, boost::recursive_timed_mutex
+ enum { value = IsOneOf<T
+ , std::mutex
+ , std::recursive_mutex
+ , boost::mutex
+ , boost::recursive_mutex
+ , boost::shared_mutex
+#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
+ , std::timed_mutex
+ , std::recursive_timed_mutex
+ , boost::timed_mutex
+ , boost::recursive_timed_mutex
#endif
- >::value };
+ >::value };
};
/**
- * Acquires a mutex for reading by calling .lock(). The exception is
- * boost::shared_mutex, which has a special read-lock primitive called
- * .lock_shared().
+ * Yields true iff T has .lock_shared() and .unlock_shared() member functions.
+ * This is done by simply enumerating the mutexes with this interface.
+ */
+template <class T>
+struct HasLockSharedUnlockShared {
+ enum { value = IsOneOf<T
+ , boost::shared_mutex
+ >::value };
+};
+
+/**
+ * Acquires a mutex for reading by calling .lock().
+ *
+ * This variant is not appropriate for shared mutexes.
*/
template <class T>
typename std::enable_if<
- HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
+ HasLockUnlock<T>::value && !HasLockSharedUnlockShared<T>::value>::type
acquireRead(T& mutex) {
mutex.lock();
}
/**
- * Special case for boost::shared_mutex.
+ * Acquires a mutex for reading by calling .lock_shared().
+ *
+ * This variant is not appropriate for nonshared mutexes.
*/
template <class T>
-typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
+typename std::enable_if<HasLockSharedUnlockShared<T>::value>::type
acquireRead(T& mutex) {
mutex.lock_shared();
}
/**
- * Acquires a mutex for reading with timeout by calling .timed_lock(). This
- * applies to three of the boost mutex classes as enumerated below.
+ * Acquires a mutex for reading and writing by calling .lock().
*/
template <class T>
-typename std::enable_if<std::is_same<T, boost::shared_mutex>::value, bool>::type
-acquireRead(T& mutex,
- unsigned int milliseconds) {
- return mutex.timed_lock_shared(boost::posix_time::milliseconds(milliseconds));
+typename std::enable_if<HasLockUnlock<T>::value>::type
+acquireReadWrite(T& mutex) {
+ mutex.lock();
}
+#if FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
/**
- * Acquires a mutex for reading and writing by calling .lock().
+ * Acquires a mutex for reading by calling .try_lock_shared_for(). This applies
+ * to boost::shared_mutex.
*/
template <class T>
-typename std::enable_if<HasLockUnlock<T>::value>::type
-acquireReadWrite(T& mutex) {
- mutex.lock();
+typename std::enable_if<
+ IsOneOf<T
+ , boost::shared_mutex
+ >::value, bool>::type
+acquireRead(T& mutex,
+ unsigned int milliseconds) {
+ return mutex.try_lock_shared_for(boost::chrono::milliseconds(milliseconds));
}
-#ifndef __APPLE__ // OSX doesn't have timed mutexes
/**
* Acquires a mutex for reading and writing with timeout by calling
* .try_lock_for(). This applies to two of the std mutex classes as
*/
template <class T>
typename std::enable_if<
- IsOneOf<T, std::timed_mutex, std::recursive_timed_mutex>::value, bool>::type
+ IsOneOf<T
+ , std::timed_mutex
+ , std::recursive_timed_mutex
+ >::value, bool>::type
acquireReadWrite(T& mutex,
unsigned int milliseconds) {
// work around try_lock_for bug in some gcc versions, see
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562
+ // TODO: Fixed in gcc-4.9.0.
return mutex.try_lock()
|| (milliseconds > 0 &&
mutex.try_lock_until(std::chrono::system_clock::now() +
/**
* Acquires a mutex for reading and writing with timeout by calling
- * .timed_lock(). This applies to three of the boost mutex classes as
+ * .try_lock_for(). This applies to three of the boost mutex classes as
* enumerated below.
*/
template <class T>
typename std::enable_if<
- IsOneOf<T, boost::shared_mutex, boost::timed_mutex,
- boost::recursive_timed_mutex>::value, bool>::type
+ IsOneOf<T
+ , boost::shared_mutex
+ , boost::timed_mutex
+ , boost::recursive_timed_mutex
+ >::value, bool>::type
acquireReadWrite(T& mutex,
unsigned int milliseconds) {
- return mutex.timed_lock(boost::posix_time::milliseconds(milliseconds));
+ return mutex.try_lock_for(boost::chrono::milliseconds(milliseconds));
}
-#endif // __APPLE__
+#endif // FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES
/**
* Releases a mutex previously acquired for reading by calling
*/
template <class T>
typename std::enable_if<
- HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
+ HasLockUnlock<T>::value && !HasLockSharedUnlockShared<T>::value>::type
releaseRead(T& mutex) {
mutex.unlock();
}
* Special case for boost::shared_mutex.
*/
template <class T>
-typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
+typename std::enable_if<HasLockSharedUnlockShared<T>::value>::type
releaseRead(T& mutex) {
mutex.unlock_shared();
}
* refer to the namespace detail below, which implements the
* primitives for mutexes in std and boost.
*/
-template <class T, class Mutex = boost::shared_mutex>
+template <class T, class Mutex = SharedMutex>
struct Synchronized {
/**
* Default constructor leaves both members call their own default
*/
Synchronized() = default;
+ private:
+ static constexpr bool nxCopyCtor{
+ std::is_nothrow_copy_constructible<T>::value};
+ static constexpr bool nxMoveCtor{
+ std::is_nothrow_move_constructible<T>::value};
+
+ /**
+ * Helper constructors to enable Synchronized for
+ * non-default constructible types T.
+ * Guards are created in actual public constructors and are alive
+ * for the time required to construct the object
+ */
+ template <typename Guard>
+ Synchronized(const Synchronized& rhs,
+ const Guard& /*guard*/) noexcept(nxCopyCtor)
+ : datum_(rhs.datum_) {}
+
+ template <typename Guard>
+ Synchronized(Synchronized&& rhs, const Guard& /*guard*/) noexcept(nxMoveCtor)
+ : datum_(std::move(rhs.datum_)) {}
+
+ public:
/**
* Copy constructor copies the data (with locking the source and
* all) but does NOT copy the mutex. Doing so would result in
* deadlocks.
*/
- Synchronized(const Synchronized& rhs) {
- auto guard = rhs.operator->();
- datum_ = rhs.datum_;
- }
+ Synchronized(const Synchronized& rhs) noexcept(nxCopyCtor)
+ : Synchronized(rhs, rhs.operator->()) {}
/**
* Move constructor moves the data (with locking the source and all)
* but does not move the mutex.
*/
- Synchronized(Synchronized&& rhs) {
- auto guard = rhs.operator->();
- datum_ = std::move(rhs.datum_);
- }
+ Synchronized(Synchronized&& rhs) noexcept(nxMoveCtor)
+ : Synchronized(std::move(rhs), rhs.operator->()) {}
/**
* Constructor taking a datum as argument copies it. There is no
* need to lock the constructing object.
*/
- explicit Synchronized(const T& rhs) : datum_(rhs) {}
+ explicit Synchronized(const T& rhs) noexcept(nxCopyCtor) : datum_(rhs) {}
/**
* Constructor taking a datum rvalue as argument moves it. Again,
* there is no need to lock the constructing object.
*/
- explicit Synchronized(T&& rhs) : datum_(std::move(rhs)) {}
+ explicit Synchronized(T&& rhs) noexcept(nxMoveCtor)
+ : datum_(std::move(rhs)) {}
+
+ /**
+ * Lets you construct non-movable types in-place. Use the constexpr
+ * instance `construct_in_place` as the first argument.
+ */
+ template <typename... Args>
+ explicit Synchronized(construct_in_place_t, Args&&... args)
+ : datum_(std::forward<Args>(args)...) {}
/**
* The canonical assignment operator only assigns the data, NOT the
return;
}
// Could not acquire the resource, pointer is null
- parent_ = NULL;
+ parent_ = nullptr;
}
/**
* SYNCHRONIZED below.
*/
T* operator->() {
- return parent_ ? &parent_->datum_ : NULL;
+ return parent_ ? &parent_->datum_ : nullptr;
}
/**
acquire();
}
ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
- if (parent->mutex_.timed_lock(
- boost::posix_time::milliseconds(milliseconds))) {
+ using namespace detail;
+ if (acquireRead(
+ parent->mutex_,
+ milliseconds)) {
parent_ = parent;
return;
}
// Could not acquire the resource, pointer is null
- parent_ = NULL;
+ parent_ = nullptr;
}
ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
}
const T* operator->() const {
- return parent_ ? &parent_->datum_ : NULL;
+ return parent_ ? &parent_->datum_ : nullptr;
}
struct Unsynchronizer {
* examples.
*/
#define SYNCHRONIZED(...) \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wshadow\"") \
if (bool SYNCHRONIZED_state = false) {} else \
for (auto SYNCHRONIZED_lockedPtr = \
(FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
for (auto& FB_ARG_1(__VA_ARGS__) = \
*SYNCHRONIZED_lockedPtr.operator->(); \
- !SYNCHRONIZED_state; SYNCHRONIZED_state = true)
+ !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
+ _Pragma("GCC diagnostic pop")
#define TIMED_SYNCHRONIZED(timeout, ...) \
if (bool SYNCHRONIZED_state = false) {} else \
for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
- for (auto name = *SYNCHRONIZED_state3.operator->(); \
+ for (auto& name = *SYNCHRONIZED_state3.operator->(); \
!SYNCHRONIZED_state; SYNCHRONIZED_state = true)
/**