Commit 43ea7bf4 authored by Andrii Grynenko's avatar Andrii Grynenko Committed by Facebook Github Bot

Fix a race in fibers::Semaphore

Summary: We should never end up doing compare_exchange_weak with oldVal==0, because that may result in increment/decrement from 0.

Reviewed By: yfeldblum

Differential Revision: D13514210

fbshipit-source-id: 0ccffe5d9525389ba02208f3bf37ce14acb9f28e
parent 7d487470
...@@ -44,10 +44,11 @@ bool Semaphore::signalSlow() { ...@@ -44,10 +44,11 @@ bool Semaphore::signalSlow() {
void Semaphore::signal() { void Semaphore::signal() {
auto oldVal = tokens_.load(std::memory_order_acquire); auto oldVal = tokens_.load(std::memory_order_acquire);
do { do {
if (oldVal == 0) { while (oldVal == 0) {
if (signalSlow()) { if (signalSlow()) {
break; return;
} }
oldVal = tokens_.load(std::memory_order_acquire);
} }
} while (!tokens_.compare_exchange_weak( } while (!tokens_.compare_exchange_weak(
oldVal, oldVal,
...@@ -80,12 +81,13 @@ bool Semaphore::waitSlow() { ...@@ -80,12 +81,13 @@ bool Semaphore::waitSlow() {
void Semaphore::wait() { void Semaphore::wait() {
auto oldVal = tokens_.load(std::memory_order_acquire); auto oldVal = tokens_.load(std::memory_order_acquire);
do { do {
if (oldVal == 0) { while (oldVal == 0) {
// If waitSlow fails it is because the token is non-zero by the time // If waitSlow fails it is because the token is non-zero by the time
// the lock is taken, so we can just continue round the loop // the lock is taken, so we can just continue round the loop
if (waitSlow()) { if (waitSlow()) {
break; return;
} }
oldVal = tokens_.load(std::memory_order_acquire);
} }
} while (!tokens_.compare_exchange_weak( } while (!tokens_.compare_exchange_weak(
oldVal, oldVal,
......
...@@ -1555,12 +1555,14 @@ TEST(FiberManager, semaphore) { ...@@ -1555,12 +1555,14 @@ TEST(FiberManager, semaphore) {
static constexpr size_t kTasks = 10; static constexpr size_t kTasks = 10;
static constexpr size_t kIterations = 10000; static constexpr size_t kIterations = 10000;
static constexpr size_t kNumTokens = 10; static constexpr size_t kNumTokens = 10;
static constexpr size_t kNumThreads = 16;
Semaphore sem(kNumTokens); Semaphore sem(kNumTokens);
int counterA = 0;
int counterB = 0;
auto task = [&sem](int& counter, folly::fibers::Baton& baton) { struct Worker {
explicit Worker(Semaphore& s) : sem(s), t([&] { run(); }) {}
void run() {
FiberManager manager(std::make_unique<EventBaseLoopController>()); FiberManager manager(std::make_unique<EventBaseLoopController>());
folly::EventBase evb; folly::EventBase evb;
dynamic_cast<EventBaseLoopController&>(manager.loopController()) dynamic_cast<EventBaseLoopController&>(manager.loopController())
...@@ -1583,26 +1585,28 @@ TEST(FiberManager, semaphore) { ...@@ -1583,26 +1585,28 @@ TEST(FiberManager, semaphore) {
} }
}); });
} }
baton.wait();
} }
evb.loopForever(); evb.loopForever();
}
Semaphore& sem;
int counter{0};
std::thread t;
}; };
folly::fibers::Baton batonA; std::vector<Worker> workers;
folly::fibers::Baton batonB; workers.reserve(kNumThreads);
std::thread threadA([&] { task(counterA, batonA); }); for (size_t i = 0; i < kNumThreads; ++i) {
std::thread threadB([&] { task(counterB, batonB); }); workers.emplace_back(sem);
}
batonA.post(); for (auto& worker : workers) {
batonB.post(); worker.t.join();
threadA.join(); }
threadB.join();
EXPECT_LT(counterA, kNumTokens); for (auto& worker : workers) {
EXPECT_LT(counterB, kNumTokens); EXPECT_EQ(0, worker.counter);
EXPECT_GE(counterA, 0); }
EXPECT_GE(counterB, 0);
} }
template <typename ExecutorT> template <typename ExecutorT>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment