/*
* 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.
*/
#include <folly/synchronization/ParkingLot.h>
#include <thread>
#include <folly/portability/GTest.h>
#include <folly/synchronization/Baton.h>
using namespace folly;
TEST(ParkingLot, multilot) {
using SmallLot = ParkingLot<bool>;
using LargeLot = ParkingLot<uint64_t>;
SmallLot smalllot;
LargeLot largelot;
folly::Baton<> sb;
folly::Baton<> lb;
std::thread small([&]() {
smalllot.park(0, false, [] { return true; }, [&]() { sb.post(); });
});
std::thread large([&]() {
largelot.park(0, true, [] { return true; }, [&]() { lb.post(); });
});
sb.wait();
lb.wait();
int count = 0;
smalllot.unpark(0, [&](bool data) {
count++;
EXPECT_EQ(data, false);
return UnparkControl::RemoveContinue;
});
EXPECT_EQ(count, 1);
count = 0;
largelot.unpark(0, [&](bool data) {
count++;
EXPECT_EQ(data, true);
return UnparkControl::RemoveContinue;
});
EXPECT_EQ(count, 1);
small.join();
large.join();
}
TEST(ParkingLot, StressTestPingPong) {
auto lot = ParkingLot<std::uint32_t>{};
auto one = std::atomic<std::uint64_t>{0};
auto two = std::atomic<std::uint64_t>{0};
auto testDone = std::atomic<bool>{false};
auto threadOneDone = std::atomic<bool>{false};
auto threadOne = std::thread{[&]() {
auto local = std::uint64_t{0};
while (!testDone.load(std::memory_order_relaxed)) {
// wait while the atomic is still equal to c, the other thread unblocks us
// because it signals before spinning itself
lot.park(&one, -1, [&]() { return one.load() == local; }, []() {});
local = one.load(std::memory_order_acquire);
two.store(local, std::memory_order_release);
}
threadOneDone.store(true, std::memory_order_release);
}};
auto threadTwo = std::thread{[&]() {
for (auto i = std::uint64_t{1}; true; ++i) {
auto local = two.load(std::memory_order_acquire);
assert(local < i);
// unblock the other thread
one.store(i, std::memory_order_release);
lot.unpark(&one, [&](auto&&) { return UnparkControl::RemoveBreak; });
// spinning (vs sleeping with ParkingLot::park) happens to expose the bug
// more frequently in practice
while (two.load(std::memory_order_acquire) == local) {
if (threadOneDone.load(std::memory_order_acquire)) {
return;
}
}
}
}};
/* sleep override */
std::this_thread::sleep_for(std::chrono::seconds{10});
testDone.store(true);
threadOne.join();
threadTwo.join();
}
// This is not possible to implement with Futex, because futex
// and the native linux syscall are 32-bit only.
TEST(ParkingLot, LargeWord) {
ParkingLot<uint64_t> lot;
std::atomic<uint64_t> w{0};
lot.park(0, false, [&]() { return w == 1; }, []() {});
// Validate should return false, will hang otherwise.
}
class WaitableMutex : public std::mutex {
using Lot = ParkingLot<std::function<bool(void)>>;
static Lot lot;
public:
void unlock() {
bool unparked = false;
lot.unpark(uint64_t(this), [&](std::function<bool(void)> wfunc) {
if (wfunc()) {
unparked = true;
return UnparkControl::RemoveBreak;
} else {
return UnparkControl::RemoveContinue;
}
});
if (!unparked) {
std::mutex::unlock();
}
// Otherwise, we pass mutex directly to waiter without needing to unlock.
}
template <typename Wait>
void wait(Wait wfunc) {
lot.park(
uint64_t(this),
wfunc,
[&]() { return !wfunc(); },
[&]() { std::mutex::unlock(); });
}
};
WaitableMutex::Lot WaitableMutex::lot;
TEST(ParkingLot, WaitableMutexTest) {
if (kIsSanitizeThread) {
return;
}
std::atomic<bool> go{false};
WaitableMutex mu;
std::thread t([&]() {
std::lock_guard<WaitableMutex> g(mu);
mu.wait([&]() { return go == true; });
});
sleep(1);
{
std::lock_guard<WaitableMutex> g(mu);
go = true;
}
t.join();
}