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,54 +1555,58 @@ TEST(FiberManager, semaphore) { ...@@ -1555,54 +1555,58 @@ 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) {
FiberManager manager(std::make_unique<EventBaseLoopController>());
folly::EventBase evb;
dynamic_cast<EventBaseLoopController&>(manager.loopController())
.attachEventBase(evb);
{
std::shared_ptr<folly::EventBase> completionCounter(
&evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); });
for (size_t i = 0; i < kTasks; ++i) {
manager.addTask([&, completionCounter]() {
for (size_t j = 0; j < kIterations; ++j) {
sem.wait();
++counter;
sem.signal();
--counter;
EXPECT_LT(counter, kNumTokens);
EXPECT_GE(counter, 0);
}
});
}
baton.wait(); struct Worker {
explicit Worker(Semaphore& s) : sem(s), t([&] { run(); }) {}
void run() {
FiberManager manager(std::make_unique<EventBaseLoopController>());
folly::EventBase evb;
dynamic_cast<EventBaseLoopController&>(manager.loopController())
.attachEventBase(evb);
{
std::shared_ptr<folly::EventBase> completionCounter(
&evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); });
for (size_t i = 0; i < kTasks; ++i) {
manager.addTask([&, completionCounter]() {
for (size_t j = 0; j < kIterations; ++j) {
sem.wait();
++counter;
sem.signal();
--counter;
EXPECT_LT(counter, kNumTokens);
EXPECT_GE(counter, 0);
}
});
}
}
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