add LockTraits
authorAdam Simpkins <simpkins@fb.com>
Wed, 6 Jul 2016 01:26:31 +0000 (18:26 -0700)
committerFacebook Github Bot 7 <facebook-github-bot-7-bot@fb.com>
Wed, 6 Jul 2016 01:38:29 +0000 (18:38 -0700)
Summary:
This adds a new LockTraits template class, for specifying how to use arbitrary
custom mutex types.

The goal of this new class is to replace the acquireRead(), acquireReadWrite(),
releaseRead(), and releaseReadWrite() functions currently defined in
Synchronized.h.  I have not replaced these functions yet in this diff, but will
do so in a follow-up diff.

LockTraits has a few advantages over the existing methods:
* It provides mechanisms for telling if a given mutex supports shared access
  and timed access.
* The default LockTraits implementation automatically figures out the correct
  behavior for classes that define lock(), unlock(), methods.  It automatically
  detects sharing and timed support based on the presence of lock_shared() and
  try_lock_for() methods.

LockTraits can be specialized for custom lock types that do not conform with
the lock method names used by the C++ standard.  This does differ slightly from
the argument dependent lookup mechanism used by the acquireRead() functions.

Reviewed By: yfeldblum

Differential Revision: D3504625

fbshipit-source-id: 40320997e9ae2147baecd10b70e8dc06a35e49e1

folly/LockTraits.h [new file with mode: 0644]
folly/LockTraitsBoost.h [new file with mode: 0644]
folly/Makefile.am
folly/Synchronized.h
folly/test/LockTraitsTest.cpp [new file with mode: 0644]
folly/test/Makefile.am

diff --git a/folly/LockTraits.h b/folly/LockTraits.h
new file mode 100644 (file)
index 0000000..3b0d21a
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This module provides a traits class for describing properties about mutex
+ * classes.
+ *
+ * This is a primitive for building higher-level abstractions that can work
+ * with a variety of mutex classes.  For instance, this allows
+ * folly::Synchronized to support a number of different mutex types.
+ */
+#pragma once
+
+#include <chrono>
+#include <type_traits>
+
+// Android, OSX, and Cygwin don't have timed mutexes
+#if defined(ANDROID) || defined(__ANDROID__) || defined(__APPLE__) || \
+    defined(__CYGWIN__)
+#define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 0
+#else
+#define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 1
+#endif
+
+namespace folly {
+namespace detail {
+
+/**
+ * An internal helper class for identifying if a lock type supports
+ * lock_shared() and try_lock_for() methods.
+ */
+template <class Mutex>
+class LockTraitsImpl {
+ private:
+  // Helper functions for implementing the traits using SFINAE
+  template <class T>
+  static auto timed_lock_test(T*) -> typename std::is_same<
+      decltype(std::declval<T>().try_lock_for(std::chrono::milliseconds(0))),
+      bool>::type;
+  template <class T>
+  static std::false_type timed_lock_test(...);
+
+  template <class T>
+  static auto lock_shared_test(T*) -> typename std::
+      is_same<decltype(std::declval<T>().lock_shared()), void>::type;
+  template <class T>
+  static std::false_type lock_shared_test(...);
+
+ public:
+  static constexpr bool has_timed_lock =
+      decltype(timed_lock_test<Mutex>(0))::value;
+  static constexpr bool has_lock_shared =
+      decltype(lock_shared_test<Mutex>(0))::value;
+};
+
+template <class Mutex>
+struct LockTraitsUniqueBase {
+  /**
+   * Acquire the lock exclusively.
+   */
+  static void lock(Mutex& mutex) {
+    mutex.lock();
+  }
+
+  /**
+   * Release an exclusively-held lock.
+   */
+  static void unlock(Mutex& mutex) {
+    mutex.unlock();
+  }
+};
+
+template <class Mutex>
+struct LockTraitsSharedBase : public LockTraitsUniqueBase<Mutex> {
+  /**
+   * Acquire the lock in shared (read) mode.
+   */
+  static void lock_shared(Mutex& mutex) {
+    mutex.lock_shared();
+  }
+
+  /**
+   * Release a lock held in shared mode.
+   */
+  static void unlock_shared(Mutex& mutex) {
+    mutex.unlock_shared();
+  }
+};
+
+template <class Mutex, bool is_shared, bool is_timed>
+struct LockTraitsBase {};
+
+template <class Mutex>
+struct LockTraitsBase<Mutex, false, false>
+    : public LockTraitsUniqueBase<Mutex> {
+  static constexpr bool is_shared = false;
+  static constexpr bool is_timed = false;
+};
+
+template <class Mutex>
+struct LockTraitsBase<Mutex, true, false> : public LockTraitsSharedBase<Mutex> {
+  static constexpr bool is_shared = true;
+  static constexpr bool is_timed = false;
+};
+
+template <class Mutex>
+struct LockTraitsBase<Mutex, false, true> : public LockTraitsUniqueBase<Mutex> {
+  static constexpr bool is_shared = false;
+  static constexpr bool is_timed = true;
+
+  /**
+   * Acquire the lock exclusively, with a timeout.
+   *
+   * Returns true or false indicating if the lock was acquired or not.
+   */
+  template <class Rep, class Period>
+  static bool try_lock_for(
+      Mutex& mutex,
+      const std::chrono::duration<Rep, Period>& timeout) {
+    return mutex.try_lock_for(timeout);
+  }
+};
+
+template <class Mutex>
+struct LockTraitsBase<Mutex, true, true> : public LockTraitsSharedBase<Mutex> {
+  static constexpr bool is_shared = true;
+  static constexpr bool is_timed = true;
+
+  /**
+   * Acquire the lock exclusively, with a timeout.
+   *
+   * Returns true or false indicating if the lock was acquired or not.
+   */
+  template <class Rep, class Period>
+  static bool try_lock_for(
+      Mutex& mutex,
+      const std::chrono::duration<Rep, Period>& timeout) {
+    return mutex.try_lock_for(timeout);
+  }
+
+  /**
+   * Acquire the lock in shared (read) mode, with a timeout.
+   *
+   * Returns true or false indicating if the lock was acquired or not.
+   */
+  template <class Rep, class Period>
+  static bool try_lock_shared_for(
+      Mutex& mutex,
+      const std::chrono::duration<Rep, Period>& timeout) {
+    return mutex.try_lock_shared_for(timeout);
+  }
+};
+} // detail
+
+/**
+ * LockTraits describes details about a particular mutex type.
+ *
+ * The default implementation automatically attempts to detect traits
+ * based on the presence of various member functions.
+ *
+ * You can specialize LockTraits to provide custom behavior for lock
+ * classes that do not use the standard method names
+ * (lock()/unlock()/lock_shared()/unlock_shared()/try_lock_for())
+ *
+ *
+ * LockTraits contains the following members variables:
+ * - static constexpr bool is_shared
+ *   True if the lock supports separate shared vs exclusive locking states.
+ * - static constexpr bool is_timed
+ *   True if the lock supports acquiring the lock with a timeout.
+ *
+ * The following static methods always exist:
+ * - lock(Mutex& mutex)
+ * - unlock(Mutex& mutex)
+ *
+ * The following static methods may exist, depending on is_shared and
+ * is_timed:
+ * - try_lock_for(Mutex& mutex, <std_chrono_duration> timeout)
+ * - lock_shared(Mutex& mutex)
+ * - unlock_shared(Mutex& mutex)
+ * - try_lock_shared_for(Mutex& mutex, <std_chrono_duration> timeout)
+ */
+template <class Mutex>
+struct LockTraits : public detail::LockTraitsBase<
+                        Mutex,
+                        detail::LockTraitsImpl<Mutex>::has_lock_shared,
+                        detail::LockTraitsImpl<Mutex>::has_timed_lock> {};
+
+/**
+ * If the lock is a shared lock, acquire it in shared mode.
+ * Otherwise, for plain (exclusive-only) locks, perform a normal acquire.
+ */
+template <class Mutex>
+typename std::enable_if<LockTraits<Mutex>::is_shared>::type
+lock_shared_or_unique(Mutex& mutex) {
+  LockTraits<Mutex>::lock_shared(mutex);
+}
+template <class Mutex>
+typename std::enable_if<!LockTraits<Mutex>::is_shared>::type
+lock_shared_or_unique(Mutex& mutex) {
+  LockTraits<Mutex>::lock(mutex);
+}
+
+/**
+ * If the lock is a shared lock, try to acquire it in shared mode, for up to
+ * the given timeout.  Otherwise, for plain (exclusive-only) locks, try to
+ * perform a normal acquire.
+ *
+ * Returns true if the lock was acquired, or false on time out.
+ */
+template <class Mutex, class Rep, class Period>
+typename std::enable_if<LockTraits<Mutex>::is_shared, bool>::type
+try_lock_shared_or_unique_for(
+    Mutex& mutex,
+    const std::chrono::duration<Rep, Period>& timeout) {
+  return LockTraits<Mutex>::try_lock_shared_for(mutex, timeout);
+}
+template <class Mutex, class Rep, class Period>
+typename std::enable_if<!LockTraits<Mutex>::is_shared, bool>::type
+try_lock_shared_or_unique_for(
+    Mutex& mutex,
+    const std::chrono::duration<Rep, Period>& timeout) {
+  return LockTraits<Mutex>::try_lock_for(mutex, timeout);
+}
+
+/**
+ * Release a lock acquired with lock_shared_or_unique()
+ */
+template <class Mutex>
+typename std::enable_if<LockTraits<Mutex>::is_shared>::type
+unlock_shared_or_unique(Mutex& mutex) {
+  LockTraits<Mutex>::unlock_shared(mutex);
+}
+template <class Mutex>
+typename std::enable_if<!LockTraits<Mutex>::is_shared>::type
+unlock_shared_or_unique(Mutex& mutex) {
+  LockTraits<Mutex>::unlock(mutex);
+}
+} // folly
diff --git a/folly/LockTraitsBoost.h b/folly/LockTraitsBoost.h
new file mode 100644 (file)
index 0000000..dd9a17b
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file contains LockTraits specializations for boost mutex types.
+ *
+ * These need to be specialized simply due to the fact that the timed
+ * methods take boost::chrono arguments instead of std::chrono.
+ */
+#pragma once
+
+#include <folly/LockTraits.h>
+
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+
+#include <boost/thread.hpp>
+
+namespace folly {
+
+/**
+ * LockTraits specialization for boost::shared_mutex
+ */
+template <>
+struct LockTraits<boost::shared_mutex>
+    : public folly::detail::LockTraitsSharedBase<boost::shared_mutex> {
+  static constexpr bool is_shared = true;
+  static constexpr bool is_timed = true;
+
+  static bool try_lock_for(
+      boost::shared_mutex& mutex,
+      std::chrono::milliseconds timeout) {
+    // Convert the std::chrono argument to boost::chrono
+    return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count()));
+  }
+
+  static bool try_lock_shared_for(
+      boost::shared_mutex& mutex,
+      std::chrono::milliseconds timeout) {
+    // Convert the std::chrono argument to boost::chrono
+    return mutex.try_lock_shared_for(
+        boost::chrono::milliseconds(timeout.count()));
+  }
+};
+
+/**
+ * LockTraits specialization for boost::timed_mutex
+ */
+template <>
+struct LockTraits<boost::timed_mutex>
+    : public folly::detail::LockTraitsUniqueBase<boost::timed_mutex> {
+  static constexpr bool is_shared = false;
+  static constexpr bool is_timed = true;
+
+  static bool try_lock_for(
+      boost::timed_mutex& mutex,
+      std::chrono::milliseconds timeout) {
+    // Convert the std::chrono argument to boost::chrono
+    return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count()));
+  }
+};
+
+/**
+ * LockTraits specialization for boost::recursive_timed_mutex
+ */
+template <>
+struct LockTraits<boost::recursive_timed_mutex>
+    : public folly::detail::LockTraitsUniqueBase<boost::recursive_timed_mutex> {
+  static constexpr bool is_shared = false;
+  static constexpr bool is_timed = true;
+
+  static bool try_lock_for(
+      boost::recursive_timed_mutex& mutex,
+      std::chrono::milliseconds timeout) {
+    // Convert the std::chrono argument to boost::chrono
+    return mutex.try_lock_for(boost::chrono::milliseconds(timeout.count()));
+  }
+};
+} // folly
+
+#endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
index 998ea6d..9df86b5 100644 (file)
@@ -229,6 +229,8 @@ nobase_follyinclude_HEADERS = \
        Lazy.h \
        LifoSem.h \
        Likely.h \
+       LockTraits.h \
+       LockTraitsBoost.h \
        Logging.h \
        MacAddress.h \
        Malloc.h \
index 4b6a25b..f9fee2a 100644 (file)
 
 #pragma once
 
-#include <type_traits>
-#include <mutex>
 #include <boost/thread.hpp>
+#include <folly/LockTraits.h>
 #include <folly/Preprocessor.h>
 #include <folly/SharedMutex.h>
 #include <folly/Traits.h>
+#include <mutex>
+#include <type_traits>
+
+// Temporarily provide FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES under the legacy
+// FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES name.  This definition will be
+// removed shortly in an upcoming diff to make Synchronized fully utilize
+// LockTraits.
+#define FOLLY_SYNCHRONIZED_HAVE_TIMED_MUTEXES \
+  FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
 
 namespace folly {
 
@@ -39,14 +47,6 @@ enum InternalDoNotUse {};
  * 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
diff --git a/folly/test/LockTraitsTest.cpp b/folly/test/LockTraitsTest.cpp
new file mode 100644 (file)
index 0000000..df43a8a
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/LockTraits.h>
+#include <folly/LockTraitsBoost.h>
+
+#include <gtest/gtest.h>
+#include <mutex>
+
+#include <folly/RWSpinLock.h>
+#include <folly/SharedMutex.h>
+#include <folly/SpinLock.h>
+
+using namespace folly;
+
+TEST(LockTraits, std_mutex) {
+  using traits = LockTraits<std::mutex>;
+  static_assert(!traits::is_timed, "std:mutex is not a timed lock");
+  static_assert(!traits::is_shared, "std:mutex is not a shared lock");
+
+  std::mutex mutex;
+  traits::lock(mutex);
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, SharedMutex) {
+  using traits = LockTraits<SharedMutex>;
+  static_assert(traits::is_timed, "SharedMutex is a timed lock");
+  static_assert(traits::is_shared, "SharedMutex is a shared lock");
+
+  SharedMutex mutex;
+  traits::lock(mutex);
+  traits::unlock(mutex);
+
+  traits::lock_shared(mutex);
+  traits::lock_shared(mutex);
+  traits::unlock_shared(mutex);
+  traits::unlock_shared(mutex);
+
+  lock_shared_or_unique(mutex);
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, SpinLock) {
+  using traits = LockTraits<SpinLock>;
+  static_assert(!traits::is_timed, "folly::SpinLock is not a timed lock");
+  static_assert(!traits::is_shared, "folly::SpinLock is not a shared lock");
+
+  SpinLock mutex;
+  traits::lock(mutex);
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, RWSpinLock) {
+  using traits = LockTraits<RWSpinLock>;
+  static_assert(!traits::is_timed, "folly::RWSpinLock is not a timed lock");
+  static_assert(traits::is_shared, "folly::RWSpinLock is a shared lock");
+
+  RWSpinLock mutex;
+  traits::lock(mutex);
+  traits::unlock(mutex);
+
+  traits::lock_shared(mutex);
+  traits::lock_shared(mutex);
+  traits::unlock_shared(mutex);
+  traits::unlock_shared(mutex);
+
+  lock_shared_or_unique(mutex);
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, boost_mutex) {
+  using traits = LockTraits<boost::mutex>;
+  static_assert(!traits::is_timed, "boost::mutex is not a timed lock");
+  static_assert(!traits::is_shared, "boost::mutex is not a shared lock");
+
+  boost::mutex mutex;
+  traits::lock(mutex);
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, boost_recursive_mutex) {
+  using traits = LockTraits<boost::recursive_mutex>;
+  static_assert(
+      !traits::is_timed, "boost::recursive_mutex is not a timed lock");
+  static_assert(
+      !traits::is_shared, "boost::recursive_mutex is not a shared lock");
+
+  boost::recursive_mutex mutex;
+  traits::lock(mutex);
+  traits::lock(mutex);
+  traits::unlock(mutex);
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  lock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
+TEST(LockTraits, timed_mutex) {
+  using traits = LockTraits<std::timed_mutex>;
+  static_assert(traits::is_timed, "std::timed_mutex is a timed lock");
+  static_assert(!traits::is_shared, "std::timed_mutex is not a shared lock");
+
+  std::timed_mutex mutex;
+  traits::lock(mutex);
+  bool gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "timed_mutex a second time";
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "timed_mutex a second time";
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, recursive_timed_mutex) {
+  using traits = LockTraits<std::recursive_timed_mutex>;
+  static_assert(traits::is_timed, "std::recursive_timed_mutex is a timed lock");
+  static_assert(
+      !traits::is_shared, "std::recursive_timed_mutex is not a shared lock");
+
+  std::recursive_timed_mutex mutex;
+  traits::lock(mutex);
+  auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(10));
+  EXPECT_TRUE(gotLock) << "should have been able to acquire the "
+                       << "recursive_timed_mutex a second time";
+  traits::unlock(mutex);
+  traits::unlock(mutex);
+
+  lock_shared_or_unique(mutex);
+  gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(10));
+  EXPECT_TRUE(gotLock) << "should have been able to acquire the "
+                       << "recursive_timed_mutex a second time";
+  unlock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+
+TEST(LockTraits, boost_shared_mutex) {
+  using traits = LockTraits<boost::shared_mutex>;
+  static_assert(traits::is_timed, "boost::shared_mutex is a timed lock");
+  static_assert(traits::is_shared, "boost::shared_mutex is a shared lock");
+
+  boost::shared_mutex mutex;
+  traits::lock(mutex);
+  auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "shared_mutex a second time";
+  gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "shared_mutex a second time";
+  traits::unlock(mutex);
+
+  traits::lock_shared(mutex);
+  gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "shared_mutex a second time";
+  gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(10));
+  EXPECT_TRUE(gotLock) << "should have been able to acquire the "
+                       << "shared_mutex a second time in shared mode";
+  traits::unlock_shared(mutex);
+  traits::unlock_shared(mutex);
+
+  lock_shared_or_unique(mutex);
+  gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1));
+  EXPECT_FALSE(gotLock) << "should not have been able to acquire the "
+                        << "shared_mutex a second time";
+  gotLock = try_lock_shared_or_unique_for(mutex, std::chrono::milliseconds(10));
+  EXPECT_TRUE(gotLock) << "should have been able to acquire the "
+                       << "shared_mutex a second time in shared mode";
+  unlock_shared_or_unique(mutex);
+  unlock_shared_or_unique(mutex);
+}
+#endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
index 1857b32..eb338ca 100644 (file)
@@ -149,6 +149,10 @@ synchronized_test_SOURCES = SynchronizedTest.cpp
 synchronized_test_LDADD = libfollytestmain.la
 TESTS += synchronized_test
 
+lock_traits_test_SOURCES = LockTraitsTest.cpp
+lock_traits_test_LDADD = libfollytestmain.la
+TESTS += lock_traits_test
+
 concurrent_skiplist_test_SOURCES = ConcurrentSkipListTest.cpp
 concurrent_skiplist_test_LDADD = libfollytestmain.la
 TESTS += concurrent_skiplist_test