folly/folly/test/SynchronizedTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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.
 */

// Test bed for folly/Synchronized.h

#include <folly/Synchronized.h>

#include <folly/Function.h>
#include <folly/Portability.h>
#include <folly/ScopeGuard.h>
#include <folly/SharedMutex.h>
#include <folly/SpinLock.h>
#include <folly/portability/GTest.h>
#include <folly/synchronization/DistributedMutex.h>
#include <folly/synchronization/RWSpinLock.h>
#include <folly/test/SynchronizedTestLib.h>

FOLLY_GNU_DISABLE_WARNING("-Wdeprecated-declarations")

using namespace folly::sync_tests;

namespace folly {

template <class Mutex>
class SynchronizedTest : public testing::Test {};

using SynchronizedTestTypes = testing::Types<
    folly::DistributedMutex,
    folly::SharedMutexReadPriority,
    folly::SharedMutexWritePriority,
    std::mutex,
    std::recursive_mutex,
#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
    std::timed_mutex,
    std::recursive_timed_mutex,
#endif
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
    folly::RWTicketSpinLock32,
    folly::RWTicketSpinLock64,
#endif
    folly::SpinLock>;
TYPED_TEST_SUITE(SynchronizedTest, SynchronizedTestTypes);

TYPED_TEST(SynchronizedTest, Basic) {
  testBasic<TypeParam>();
}

TYPED_TEST(SynchronizedTest, WithLock) {
  testWithLock<TypeParam>();
}

TYPED_TEST(SynchronizedTest, Unlock) {
  testUnlock<TypeParam>();
}

TYPED_TEST(SynchronizedTest, Deprecated) {
  testDeprecated<TypeParam>();
}

TYPED_TEST(SynchronizedTest, Concurrency) {
  testConcurrency<TypeParam>();
}

TYPED_TEST(SynchronizedTest, AcquireLocked) {
  testAcquireLocked<TypeParam>();
}

TYPED_TEST(SynchronizedTest, AcquireLockedWithConst) {
  testAcquireLockedWithConst<TypeParam>();
}

TYPED_TEST(SynchronizedTest, DualLocking) {
  testDualLocking<TypeParam>();
}

TYPED_TEST(SynchronizedTest, DualLockingWithConst) {
  testDualLockingWithConst<TypeParam>();
}

TYPED_TEST(SynchronizedTest, ConstCopy) {
  testConstCopy<TypeParam>();
}

TYPED_TEST(SynchronizedTest, InPlaceConstruction) {
  testInPlaceConstruction<TypeParam>();
}

TYPED_TEST(SynchronizedTest, Exchange) {
  testExchange<TypeParam>();
}

template <class Mutex>
class SynchronizedTimedTest : public testing::Test {};

using SynchronizedTimedTestTypes = testing::Types<
#if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
    std::timed_mutex,
    std::recursive_timed_mutex,
#endif
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
    folly::RWTicketSpinLock32,
    folly::RWTicketSpinLock64,
#endif
    folly::SharedMutexReadPriority,
    folly::SharedMutexWritePriority>;
TYPED_TEST_SUITE(SynchronizedTimedTest, SynchronizedTimedTestTypes);

TYPED_TEST(SynchronizedTimedTest, Timed) {
  testTimed<TypeParam>();
}

template <class Mutex>
class SynchronizedTimedWithConstTest : public testing::Test {};

using SynchronizedTimedWithConstTestTypes = testing::Types<
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
    folly::RWTicketSpinLock32,
    folly::RWTicketSpinLock64,
#endif
    folly::SharedMutexReadPriority,
    folly::SharedMutexWritePriority>;
TYPED_TEST_SUITE(
    SynchronizedTimedWithConstTest, SynchronizedTimedWithConstTestTypes);

TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) {
  testTimedShared<TypeParam>();
}

using CountPair = std::pair<int, int>;
// This class is specialized only to be uesed in SynchronizedLockTest
class FakeMutex {
 public:
  void lock() { ++lockCount_; }

  void unlock() { ++unlockCount_; }

  static CountPair getLockUnlockCount() {
    return CountPair{lockCount_, unlockCount_};
  }

  static void resetLockUnlockCount() {
    lockCount_ = 0;
    unlockCount_ = 0;
  }

 private:
  // Keep these two static for test access
  // Keep them thread_local in case of tests are run in parallel within one
  // process
  static thread_local int lockCount_;
  static thread_local int unlockCount_;
};
thread_local int FakeMutex::lockCount_{0};
thread_local int FakeMutex::unlockCount_{0};

// SynchronizedLockTest is used to verify the correct lock unlock behavior
// happens per design
class SynchronizedLockTest : public testing::Test {
 public:
  void SetUp() override { FakeMutex::resetLockUnlockCount(); }
};

/**
 * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp
 */
class FakeAllPowerfulAssertingMutexInternal {
 public:
  enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE };

  void lock() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
    this->lock_state = CurrentLockState::UNIQUE;
  }
  void unlock() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
    this->lock_state = CurrentLockState::UNLOCKED;
  }
  void lock_shared() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
    this->lock_state = CurrentLockState::SHARED;
  }
  void unlock_shared() {
    EXPECT_EQ(this->lock_state, CurrentLockState::SHARED);
    this->lock_state = CurrentLockState::UNLOCKED;
  }
  void lock_upgrade() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
    this->lock_state = CurrentLockState::UPGRADE;
  }
  void unlock_upgrade() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
    this->lock_state = CurrentLockState::UNLOCKED;
  }

  void unlock_upgrade_and_lock() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
    this->lock_state = CurrentLockState::UNIQUE;
  }
  void unlock_and_lock_upgrade() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
    this->lock_state = CurrentLockState::UPGRADE;
  }
  void unlock_and_lock_shared() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
    this->lock_state = CurrentLockState::SHARED;
  }
  void unlock_upgrade_and_lock_shared() {
    EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
    this->lock_state = CurrentLockState::SHARED;
  }

  template <class Rep, class Period>
  bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
    this->lock_state = CurrentLockState::UNIQUE;
    return true;
  }

  template <class Rep, class Period>
  bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) {
    EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
    this->lock_state = CurrentLockState::UPGRADE;
    return true;
  }

  template <class Rep, class Period>
  bool try_unlock_upgrade_and_lock_for(
      const std::chrono::duration<Rep, Period>&) {
    EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
    this->lock_state = CurrentLockState::UNIQUE;
    return true;
  }

  /*
   * Initialize the FakeMutex with an unlocked state
   */
  CurrentLockState lock_state{CurrentLockState::UNLOCKED};
};

/**
 * The following works around the internal mutex for synchronized being
 * private
 *
 * This is horridly thread unsafe.
 */
static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex;

class FakeAllPowerfulAssertingMutex {
 public:
  void lock() { globalAllPowerfulAssertingMutex.lock(); }
  void unlock() { globalAllPowerfulAssertingMutex.unlock(); }
  void lock_shared() { globalAllPowerfulAssertingMutex.lock_shared(); }
  void unlock_shared() { globalAllPowerfulAssertingMutex.unlock_shared(); }
  void lock_upgrade() { globalAllPowerfulAssertingMutex.lock_upgrade(); }
  void unlock_upgrade() { globalAllPowerfulAssertingMutex.unlock_upgrade(); }

  void unlock_upgrade_and_lock() {
    globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock();
  }
  void unlock_and_lock_upgrade() {
    globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade();
  }
  void unlock_and_lock_shared() {
    globalAllPowerfulAssertingMutex.unlock_and_lock_shared();
  }
  void unlock_upgrade_and_lock_shared() {
    globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared();
  }

  template <class Rep, class Period>
  bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) {
    return globalAllPowerfulAssertingMutex.try_lock_for(arg);
  }

  template <class Rep, class Period>
  bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) {
    return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg);
  }

  template <class Rep, class Period>
  bool try_unlock_upgrade_and_lock_for(
      const std::chrono::duration<Rep, Period>& arg) {
    return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg);
  }

  // reset state on destruction
  ~FakeAllPowerfulAssertingMutex() {
    globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{};
  }
};

class NonDefaultConstructibleMutex {
 public:
  explicit NonDefaultConstructibleMutex(int valueIn) { value = valueIn; }
  NonDefaultConstructibleMutex() = delete;
  NonDefaultConstructibleMutex(const NonDefaultConstructibleMutex&) = delete;
  NonDefaultConstructibleMutex(NonDefaultConstructibleMutex&&) = delete;
  NonDefaultConstructibleMutex& operator=(const NonDefaultConstructibleMutex&) =
      delete;
  NonDefaultConstructibleMutex& operator=(NonDefaultConstructibleMutex&&) =
      delete;

  static int value;

  void lock() {}
  void unlock() {}
};
int NonDefaultConstructibleMutex::value{0};

TEST_F(SynchronizedLockTest, TestCopyConstructibleValues) {
  struct NonCopyConstructible {
    NonCopyConstructible(const NonCopyConstructible&) = delete;
    NonCopyConstructible& operator=(const NonCopyConstructible&) = delete;
  };
  struct CopyConstructible {};
  EXPECT_FALSE(std::is_copy_constructible<
               folly::Synchronized<NonCopyConstructible>>::value);
  EXPECT_FALSE(std::is_copy_assignable<
               folly::Synchronized<NonCopyConstructible>>::value);
  EXPECT_TRUE(std::is_copy_constructible<
              folly::Synchronized<CopyConstructible>>::value);
  EXPECT_TRUE(
      std::is_copy_assignable<folly::Synchronized<CopyConstructible>>::value);
}

namespace {
class Dummy {
 public:
  void foo() {}
};
} // namespace

TEST_F(SynchronizedLockTest, ReadLockAsNonConstUnsafe) {
  {
    folly::Synchronized<Dummy> sync;
    auto rlock = sync.rlock();
    rlock.asNonConstUnsafe().foo();
  }
  {
    folly::Synchronized<Dummy> sync;
    auto rlock = sync.rlock(std::chrono::seconds{1});
    rlock.asNonConstUnsafe().foo();
  }
}

TEST_F(SynchronizedLockTest, UpgradeLocking) {
  folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;

  // sanity assert
  static_assert(
      std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
      "The ulock function was not well configured, blame [email protected]");

  {
    auto ulock = sync.ulock();
    EXPECT_TRUE((std::is_same<decltype(*ulock), const int&>::value));
    EXPECT_TRUE(
        (std::is_same<decltype(ulock.asNonConstUnsafe()), int&>::value));
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
  }

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test going from upgrade to exclusive
  {
    auto ulock = sync.ulock();
    auto wlock = ulock.moveFromUpgradeToWrite();
    EXPECT_TRUE((std::is_same<decltype(*wlock), int&>::value));
    EXPECT_EQ(static_cast<bool>(ulock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
  }

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test going from upgrade to shared
  {
    auto ulock = sync.ulock();
    auto slock = ulock.moveFromUpgradeToRead();
    EXPECT_EQ(static_cast<bool>(ulock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
  }

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test going from exclusive to upgrade
  {
    auto wlock = sync.wlock();
    auto ulock = wlock.moveFromWriteToUpgrade();
    EXPECT_EQ(static_cast<bool>(wlock), false);
    EXPECT_TRUE((std::is_same<decltype(*ulock), const int&>::value));
    EXPECT_TRUE(
        (std::is_same<decltype(ulock.asNonConstUnsafe()), int&>::value));
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
  }

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test going from exclusive to shared
  {
    auto wlock = sync.wlock();
    auto slock = wlock.moveFromWriteToRead();
    EXPECT_EQ(static_cast<bool>(wlock), false);
    EXPECT_TRUE((std::is_same<decltype(*slock), const int&>::value));
    EXPECT_TRUE(
        (std::is_same<decltype(slock.asNonConstUnsafe()), int&>::value));
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
  }

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
}

TEST_F(SynchronizedLockTest, UpgradeLockingWithULock) {
  folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;

  // sanity assert
  static_assert(
      std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
      "The ulock function was not well configured, blame [email protected]");

  // test from upgrade to write
  sync.withULockPtr([](auto ulock) {
    EXPECT_EQ(static_cast<bool>(ulock), true);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);

    auto wlock = ulock.moveFromUpgradeToWrite();
    EXPECT_EQ(static_cast<bool>(ulock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
  });

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test from write to upgrade
  sync.withWLockPtr([](auto wlock) {
    EXPECT_EQ(static_cast<bool>(wlock), true);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);

    auto ulock = wlock.moveFromWriteToUpgrade();
    EXPECT_EQ(static_cast<bool>(wlock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
  });

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test from upgrade to shared
  sync.withULockPtr([](auto ulock) {
    EXPECT_EQ(static_cast<bool>(ulock), true);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);

    auto slock = ulock.moveFromUpgradeToRead();
    EXPECT_EQ(static_cast<bool>(ulock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
  });

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);

  // test from write to shared
  sync.withWLockPtr([](auto wlock) {
    EXPECT_EQ(static_cast<bool>(wlock), true);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);

    auto slock = wlock.moveFromWriteToRead();
    EXPECT_EQ(static_cast<bool>(wlock), false);
    EXPECT_EQ(
        globalAllPowerfulAssertingMutex.lock_state,
        FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
  });

  // should be unlocked here
  EXPECT_EQ(
      globalAllPowerfulAssertingMutex.lock_state,
      FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
}

TEST_F(SynchronizedLockTest, TestPieceWiseConstruct) {
  auto&& synchronized = folly::Synchronized<int, NonDefaultConstructibleMutex>{
      std::piecewise_construct,
      std::forward_as_tuple(3),
      std::forward_as_tuple(1)};

  EXPECT_EQ(*synchronized.lock(), 3);
  EXPECT_EQ(NonDefaultConstructibleMutex::value, 1);
}

TEST_F(SynchronizedLockTest, TestConstConversion) {
  folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync{};
  EXPECT_EQ(0, sync.copy());

  {
    using ct = decltype(std::as_const(sync).rlock());
    ct l = sync.rlock(); // const-converting constructor
    EXPECT_EQ(0, *l);
    l.unlock();
    l = sync.rlock(); // const-converting assignment operator
    EXPECT_EQ(0, *l);
  }
}

namespace {
constexpr auto kLockable = 1;
constexpr auto kWLockable = 2;
constexpr auto kRLockable = 4;
constexpr auto kULockable = 8;

template <int kLockableType>
class TryLockable {
 public:
  explicit TryLockable(
      bool shouldSucceed,
      folly::Function<void()> onLockIn,
      folly::Function<void()> onUnlockIn)
      : kShouldSucceed{shouldSucceed},
        onLock{std::move(onLockIn)},
        onUnlock{std::move(onUnlockIn)} {}

  void lock() { EXPECT_TRUE(false); }
  template <
      int LockableType = kLockableType,
      std::enable_if_t<LockableType != kLockable>* = nullptr>
  void lock_shared() {
    EXPECT_TRUE(false);
  }
  template <
      int LockableType = kLockableType,
      std::enable_if_t<LockableType == kULockable>* = nullptr>
  void lock_upgrade() {
    EXPECT_TRUE(false);
  }

  bool tryLockImpl(int lockableMask) {
    // if the lockable type of this instance is one of the possible options as
    // expressed in the mask go through the usual test code
    if (kLockableType | lockableMask) {
      if (kShouldSucceed) {
        onLock();
        return true;
      } else {
        return false;
      }
    }

    // else fail the test
    EXPECT_TRUE(false);
    return false;
  }
  void unlockImpl(int lockableMask) {
    if (kLockableType | lockableMask) {
      onUnlock();
      return;
    }

    EXPECT_TRUE(false);
  }

  bool try_lock() { return tryLockImpl(kLockable | kWLockable); }
  bool try_lock_shared() { return tryLockImpl(kRLockable); }
  bool try_lock_upgrade() { return tryLockImpl(kULockable); }

  void unlock() { unlockImpl(kLockable | kWLockable); }
  void unlock_shared() { unlockImpl(kLockable | kRLockable); }
  void unlock_upgrade() { unlockImpl(kLockable | kULockable); }

  const bool kShouldSucceed;
  folly::Function<void()> onLock;
  folly::Function<void()> onUnlock;
};

struct TestSharedMutex {
 public:
  void lock() { onLock_(); }
  void unlock() { onUnlock_(); }
  void lock_shared() { onLockShared_(); }
  void unlock_shared() { onUnlockShared_(); }

  bool try_lock() {
    onLock_();
    return true;
  }
  bool try_lock_shared() {
    onLockShared_();
    return true;
  }

  std::function<void()> onLock_;
  std::function<void()> onUnlock_;
  std::function<void()> onLockShared_;
  std::function<void()> onUnlockShared_;
};

struct TestMutex {
 public:
  void lock() {
    onLock();
    ++numTimesLocked;
  }
  bool try_lock() {
    if (shouldTryLockSucceed) {
      lock();
      return true;
    }
    return false;
  }
  void unlock() {
    onUnlock();
    ++numTimesUnlocked;
  }

  int numTimesLocked{0};
  int numTimesUnlocked{0};
  bool shouldTryLockSucceed{true};
  std::function<void()> onLock{[] {}};
  std::function<void()> onUnlock{[] {}};
};

template <int kLockable, typename Func>
void testTryLock(Func func) {
  {
    auto locked = 0;
    auto unlocked = 0;
    folly::Synchronized<int, TryLockable<kLockable>> synchronized{
        std::piecewise_construct,
        std::make_tuple(),
        std::make_tuple(true, [&] { ++locked; }, [&] { ++unlocked; })};

    {
      auto lock = func(synchronized);
      EXPECT_TRUE(lock);
      EXPECT_EQ(locked, 1);
    }
    EXPECT_EQ(locked, 1);
    EXPECT_EQ(unlocked, 1);
  }
  {
    auto locked = 0;
    auto unlocked = 0;
    folly::Synchronized<int, TryLockable<kLockable>> synchronized{
        std::piecewise_construct,
        std::make_tuple(),
        std::make_tuple(false, [&] { ++locked; }, [&] { ++unlocked; })};

    {
      auto lock = func(synchronized);
      EXPECT_FALSE(lock);
      EXPECT_EQ(locked, 0);
    }
    EXPECT_EQ(locked, 0);
    EXPECT_EQ(unlocked, 0);
  }
}

class MutexTrack {
 public:
  static int gOrder;

  void lock_shared() {}
  void unlock_shared() {}
  void lock() { order = MutexTrack::gOrder++; }
  void unlock() {
    order = -1;
    --gOrder;
  }

  int order{-1};
};
int MutexTrack::gOrder{0};
} // namespace

TEST_F(SynchronizedLockTest, TestTryLock) {
  testTryLock<kLockable>(
      [](auto& synchronized) { return synchronized.tryLock(); });
}

TEST_F(SynchronizedLockTest, TestTryWLock) {
  testTryLock<kWLockable>(
      [](auto& synchronized) { return synchronized.tryWLock(); });
}

TEST_F(SynchronizedLockTest, TestTryRLock) {
  testTryLock<kRLockable>(
      [](auto& synchronized) { return synchronized.tryRLock(); });
}

TEST_F(SynchronizedLockTest, TestTryULock) {
  testTryLock<kULockable>(
      [](auto& synchronized) { return synchronized.tryULock(); });
}

template <typename LockPolicy>
using LPtr = LockedPtr<Synchronized<int>, LockPolicy>;

namespace {
template <template <typename...> class Trait>
void testLockedPtrCompatibilityExclusive() {
  EXPECT_TRUE((Trait<
               LPtr<detail::SynchronizedLockPolicyExclusive>,
               LPtr<detail::SynchronizedLockPolicyTryExclusive>&&>::value));

  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyExclusive>&,
                LPtr<detail::SynchronizedLockPolicyShared>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyExclusive>,
                LPtr<detail::SynchronizedLockPolicyTryShared>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyExclusive>,
                LPtr<detail::SynchronizedLockPolicyUpgrade>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyExclusive>,
                LPtr<detail::SynchronizedLockPolicyTryUpgrade>&&>::value));
}

template <template <typename...> class Trait>
void testLockedPtrCompatibilityShared() {
  EXPECT_TRUE((Trait<
               LPtr<detail::SynchronizedLockPolicyShared>,
               LPtr<detail::SynchronizedLockPolicyTryShared>&&>::value));

  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyShared>,
                LPtr<detail::SynchronizedLockPolicyExclusive>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyShared>,
                LPtr<detail::SynchronizedLockPolicyTryExclusive>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyShared>,
                LPtr<detail::SynchronizedLockPolicyUpgrade>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyShared>,
                LPtr<detail::SynchronizedLockPolicyTryUpgrade>&&>::value));
}

template <template <typename...> class Trait>
void testLockedPtrCompatibilityUpgrade() {
  EXPECT_TRUE((Trait<
               LPtr<detail::SynchronizedLockPolicyUpgrade>,
               LPtr<detail::SynchronizedLockPolicyTryUpgrade>&&>::value));

  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyUpgrade>,
                LPtr<detail::SynchronizedLockPolicyExclusive>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyUpgrade>,
                LPtr<detail::SynchronizedLockPolicyTryExclusive>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyUpgrade>,
                LPtr<detail::SynchronizedLockPolicyShared>&&>::value));
  EXPECT_FALSE((Trait<
                LPtr<detail::SynchronizedLockPolicyUpgrade>,
                LPtr<detail::SynchronizedLockPolicyTryShared>&&>::value));
}
} // namespace

TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityExclusive) {
  testLockedPtrCompatibilityExclusive<std::is_assignable>();
  testLockedPtrCompatibilityExclusive<std::is_constructible>();
}

TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityShared) {
  testLockedPtrCompatibilityShared<std::is_assignable>();
  testLockedPtrCompatibilityShared<std::is_constructible>();
}

TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityUpgrade) {
  testLockedPtrCompatibilityUpgrade<std::is_assignable>();
  testLockedPtrCompatibilityUpgrade<std::is_constructible>();
}

TEST_F(SynchronizedLockTest, TestConvertTryLockToLock) {
  auto synchronized = folly::Synchronized<int>{0};
  auto wlock = synchronized.wlock();
  wlock.unlock();

  auto ulock = synchronized.ulock();
  wlock = ulock.moveFromUpgradeToWrite();
  wlock.unlock();

  auto value = synchronized.withWLock([](auto& integer) { return integer; });
  EXPECT_EQ(value, 0);
}

TEST(FollyLockTest, TestVariadicLockWithSynchronized) {
  {
    auto syncs = std::array<folly::Synchronized<int>, 3>{};
    auto& one = syncs[0];
    auto const& two = syncs[1];
    auto& three = syncs[2];
    auto locks =
        lock(folly::wlock(one), folly::rlock(two), folly::wlock(three));
    EXPECT_TRUE(std::get<0>(locks));
    EXPECT_TRUE(std::get<1>(locks));
    EXPECT_TRUE(std::get<2>(locks));
  }
  {
    auto syncs = std::array<folly::Synchronized<int, std::mutex>, 2>{};
    auto locks = lock(folly::lock(syncs[0]), folly::lock(syncs[1]));
    EXPECT_TRUE(std::get<0>(locks));
    EXPECT_TRUE(std::get<1>(locks));
  }
}

TEST(FollyLockTest, TestVariadicLockWithArbitraryLockables) {
  auto&& one = std::mutex{};
  auto&& two = std::mutex{};

  auto lckOne = std::unique_lock<std::mutex>{one, std::defer_lock};
  auto lckTwo = std::unique_lock<std::mutex>{two, std::defer_lock};
  folly::lock(lckOne, lckTwo);
  EXPECT_TRUE(lckOne);
  EXPECT_TRUE(lckTwo);
}

TEST(FollyLockTest, TestVariadicLockSmartAndPoliteAlgorithm) {
  auto one = TestMutex{};
  auto two = TestMutex{};
  auto three = TestMutex{};
  auto makeReset = [&] {
    return folly::makeGuard([&] {
      one = TestMutex{};
      two = TestMutex{};
      three = TestMutex{};
    });
  };

  {
    auto reset = makeReset();
    folly::lock(one, two, three);
    EXPECT_EQ(one.numTimesLocked, 1);
    EXPECT_EQ(one.numTimesUnlocked, 0);
    EXPECT_EQ(two.numTimesLocked, 1);
    EXPECT_EQ(two.numTimesUnlocked, 0);
    EXPECT_EQ(three.numTimesLocked, 1);
    EXPECT_EQ(three.numTimesUnlocked, 0);
  }

  {
    auto reset = makeReset();
    two.shouldTryLockSucceed = false;
    folly::lock(one, two, three);
    EXPECT_EQ(one.numTimesLocked, 2);
    EXPECT_EQ(one.numTimesUnlocked, 1);
    EXPECT_EQ(two.numTimesLocked, 1);
    EXPECT_EQ(two.numTimesUnlocked, 0);
    EXPECT_EQ(three.numTimesLocked, 1);
    EXPECT_EQ(three.numTimesUnlocked, 0);
  }

  {
    auto reset = makeReset();
    three.shouldTryLockSucceed = false;
    folly::lock(one, two, three);
    EXPECT_EQ(one.numTimesLocked, 2);
    EXPECT_EQ(one.numTimesUnlocked, 1);
    EXPECT_EQ(two.numTimesLocked, 2);
    EXPECT_EQ(two.numTimesUnlocked, 1);
    EXPECT_EQ(three.numTimesLocked, 1);
    EXPECT_EQ(three.numTimesUnlocked, 0);
  }

  {
    auto reset = makeReset();
    three.shouldTryLockSucceed = false;

    three.onLock = [&] {
      // when three gets locked make one fail
      one.shouldTryLockSucceed = false;
      // then when one gets locked make three succeed to finish the test
      one.onLock = [&] { three.shouldTryLockSucceed = true; };
    };

    folly::lock(one, two, three);
    EXPECT_EQ(one.numTimesLocked, 2);
    EXPECT_EQ(one.numTimesUnlocked, 1);
    EXPECT_EQ(two.numTimesLocked, 2);
    EXPECT_EQ(two.numTimesUnlocked, 1);
    EXPECT_EQ(three.numTimesLocked, 2);
    EXPECT_EQ(three.numTimesUnlocked, 1);
  }
}

TEST(SynchronizedAlgorithmTest, Basic) {
  auto sync = Synchronized<int>{0};
  auto value = synchronized([](auto s) { return *s; }, wlock(sync));
  EXPECT_EQ(value, 0);
}

TEST(SynchronizedAlgorithmTest, BasicNonShareableMutex) {
  auto sync = Synchronized<int, std::mutex>{0};
  auto value = synchronized([](auto s) { return *s; }, lock(sync));
  EXPECT_EQ(value, 0);
}

TEST(Synchronized, SynchronizedFunctionNonConst) {
  auto locked = 0;
  auto unlocked = 0;
  auto sync = Synchronized<int, TestSharedMutex>{
      std::piecewise_construct,
      std::make_tuple(0),
      std::make_tuple([&] { ++locked; }, [&] { ++unlocked; }, [] {}, [] {})};

  synchronized([](auto) {}, wlock(sync));
  EXPECT_EQ(locked, 1);
  EXPECT_EQ(unlocked, 1);
}

TEST(Synchronized, SynchronizedFunctionConst) {
  auto locked = 0;
  auto unlocked = 0;
  auto sync = Synchronized<int, TestSharedMutex>{
      std::piecewise_construct,
      std::make_tuple(0),
      std::make_tuple([] {}, [] {}, [&] { ++locked; }, [&] { ++unlocked; })};

  synchronized([](auto) {}, rlock(sync));
  EXPECT_EQ(locked, 1);
  EXPECT_EQ(unlocked, 1);
}

TEST(Synchronized, SynchronizedFunctionManyObjects) {
  auto fail = [] { EXPECT_TRUE(false); };
  auto pass = [] {};

  auto one = Synchronized<int, TestSharedMutex>{
      std::piecewise_construct,
      std::make_tuple(0),
      std::make_tuple(pass, pass, fail, fail)};
  auto two = Synchronized<std::string, TestSharedMutex>{
      std::piecewise_construct,
      std::make_tuple(),
      std::make_tuple(fail, fail, pass, pass)};

  synchronized([](auto, auto) {}, wlock(one), rlock(two));
}

namespace {
class TestStruct {
 public:
  constexpr TestStruct() = default;
  explicit constexpr TestStruct(int a) : a_{a} {}
  TestStruct(int a, int b) : a_{a}, b_{b} {}

 private:
  int a_{0};
  int b_{0};
};
} // namespace

TEST(Synchronized, ConstexprConstructor) {
  // Make sure the folly::Synchronized constructor can be constexpr
  static FOLLY_CONSTINIT folly::Synchronized<int> i{std::in_place, 5};
  static FOLLY_CONSTINIT folly::Synchronized<TestStruct> ts1;
  static FOLLY_CONSTINIT folly::Synchronized<TestStruct> ts2{std::in_place, 1};

  // Not constinit, since the int value will be uninitialized
  static folly::Synchronized<int> i2;

  // Not constinit, since this TestStruct constructor is not constexpr
  static folly::Synchronized<TestStruct> ts3{std::in_place, 1, 2};
}

} // namespace folly