Skip to content

Commit

Permalink
Move EventCount to fbcode/folly/synchronization
Browse files Browse the repository at this point in the history
Summary:
The following targets were moved to folly/synchronization:
```
//folly/experimental:event_count
//folly/experimental/test:event_count_test
```

`arc f` was applied

This is a codemod. It was automatically generated and will be landed once it is approved and tests are passing in sandcastle.
You have been added as a reviewer by Sentinel or Butterfly.

Reviewed By: Orvid

Differential Revision: D57282995

fbshipit-source-id: dfc797a37098b9a672606b817ba6b3b693bf31d9
  • Loading branch information
Gownta authored and facebook-github-bot committed May 16, 2024
1 parent b4e8467 commit fc45a44
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 184 deletions.
185 changes: 1 addition & 184 deletions third-party/folly/src/folly/experimental/EventCount.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,187 +14,4 @@
* limitations under the License.
*/

#pragma once

#include <atomic>
#include <climits>
#include <thread>

#include <glog/logging.h>

#include <folly/Likely.h>
#include <folly/detail/Futex.h>
#include <folly/lang/Bits.h>
#include <folly/portability/SysTime.h>
#include <folly/portability/Unistd.h>

namespace folly {

/**
* Event count: a condition variable for lock free algorithms.
*
* See http://www.1024cores.net/home/lock-free-algorithms/eventcounts for
* details.
*
* Event counts allow you to convert a non-blocking lock-free / wait-free
* algorithm into a blocking one, by isolating the blocking logic. You call
* prepareWait() before checking your condition and then either cancelWait()
* or wait() depending on whether the condition was true. When another
* thread makes the condition true, it must call notify() / notifyAll() just
* like a regular condition variable.
*
* If "<" denotes the happens-before relationship, consider 2 threads (T1 and
* T2) and 3 events:
* - E1: T1 returns from prepareWait
* - E2: T1 calls wait
* (obviously E1 < E2, intra-thread)
* - E3: T2 calls notifyAll
*
* If E1 < E3, then E2's wait will complete (and T1 will either wake up,
* or not block at all)
*
* This means that you can use an EventCount in the following manner:
*
* Waiter:
* if (!condition()) { // handle fast path first
* for (;;) {
* auto key = eventCount.prepareWait();
* if (condition()) {
* eventCount.cancelWait();
* break;
* } else {
* eventCount.wait(key);
* }
* }
* }
*
* (This pattern is encapsulated in await())
*
* Poster:
* make_condition_true();
* eventCount.notifyAll();
*
* Note that, just like with regular condition variables, the waiter needs to
* be tolerant of spurious wakeups and needs to recheck the condition after
* being woken up. Also, as there is no mutual exclusion implied, "checking"
* the condition likely means attempting an operation on an underlying
* data structure (push into a lock-free queue, etc) and returning true on
* success and false on failure.
*/
class EventCount {
public:
EventCount() noexcept : val_(0) {}

class Key {
friend class EventCount;
explicit Key(uint32_t e) noexcept : epoch_(e) {}
uint32_t epoch_;
};

void notify() noexcept;
void notifyAll() noexcept;
Key prepareWait() noexcept;
void cancelWait() noexcept;
void wait(Key key) noexcept;

/**
* Wait for condition() to become true. Will clean up appropriately if
* condition() throws, and then rethrow.
*/
template <class Condition>
void await(Condition condition);

private:
void doNotify(int n) noexcept;
EventCount(const EventCount&) = delete;
EventCount(EventCount&&) = delete;
EventCount& operator=(const EventCount&) = delete;
EventCount& operator=(EventCount&&) = delete;

// This requires 64-bit
static_assert(sizeof(int) == 4, "bad platform");
static_assert(sizeof(uint32_t) == 4, "bad platform");
static_assert(sizeof(uint64_t) == 8, "bad platform");
static_assert(sizeof(std::atomic<uint64_t>) == 8, "bad platform");
static_assert(sizeof(detail::Futex<std::atomic>) == 4, "bad platform");

static constexpr size_t kEpochOffset = kIsLittleEndian ? 1 : 0;

// val_ stores the epoch in the most significant 32 bits and the
// waiter count in the least significant 32 bits.
std::atomic<uint64_t> val_;

static constexpr uint64_t kAddWaiter = uint64_t(1);
static constexpr uint64_t kSubWaiter = uint64_t(-1);
static constexpr size_t kEpochShift = 32;
static constexpr uint64_t kAddEpoch = uint64_t(1) << kEpochShift;
static constexpr uint64_t kWaiterMask = kAddEpoch - 1;
};

inline void EventCount::notify() noexcept {
doNotify(1);
}

inline void EventCount::notifyAll() noexcept {
doNotify(INT_MAX);
}

inline void EventCount::doNotify(int n) noexcept {
uint64_t prev = val_.fetch_add(kAddEpoch, std::memory_order_acq_rel);
if (FOLLY_UNLIKELY(prev & kWaiterMask)) {
detail::futexWake(
reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset, n);
}
}

inline EventCount::Key EventCount::prepareWait() noexcept {
uint64_t prev = val_.fetch_add(kAddWaiter, std::memory_order_acq_rel);
return Key(prev >> kEpochShift);
}

inline void EventCount::cancelWait() noexcept {
// memory_order_relaxed would suffice for correctness, but the faster
// #waiters gets to 0, the less likely it is that we'll do spurious wakeups
// (and thus system calls).
uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
DCHECK_NE((prev & kWaiterMask), 0);
}

inline void EventCount::wait(Key key) noexcept {
while ((val_.load(std::memory_order_acquire) >> kEpochShift) == key.epoch_) {
detail::futexWait(
reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset,
key.epoch_);
}
// memory_order_relaxed would suffice for correctness, but the faster
// #waiters gets to 0, the less likely it is that we'll do spurious wakeups
// (and thus system calls)
uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
DCHECK_NE((prev & kWaiterMask), 0);
}

template <class Condition>
void EventCount::await(Condition condition) {
if (condition()) {
return; // fast path
}

// condition() is the only thing that may throw, everything else is
// noexcept, so we can hoist the try/catch block outside of the loop
try {
for (;;) {
auto key = prepareWait();
if (condition()) {
cancelWait();
break;
} else {
wait(key);
}
}
} catch (...) {
cancelWait();
throw;
}
}

} // namespace folly
#include <folly/synchronization/EventCount.h>
200 changes: 200 additions & 0 deletions third-party/folly/src/folly/synchronization/EventCount.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* 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.
*/

#pragma once

#include <atomic>
#include <climits>
#include <thread>

#include <glog/logging.h>

#include <folly/Likely.h>
#include <folly/detail/Futex.h>
#include <folly/lang/Bits.h>
#include <folly/portability/SysTime.h>
#include <folly/portability/Unistd.h>

namespace folly {

/**
* Event count: a condition variable for lock free algorithms.
*
* See http://www.1024cores.net/home/lock-free-algorithms/eventcounts for
* details.
*
* Event counts allow you to convert a non-blocking lock-free / wait-free
* algorithm into a blocking one, by isolating the blocking logic. You call
* prepareWait() before checking your condition and then either cancelWait()
* or wait() depending on whether the condition was true. When another
* thread makes the condition true, it must call notify() / notifyAll() just
* like a regular condition variable.
*
* If "<" denotes the happens-before relationship, consider 2 threads (T1 and
* T2) and 3 events:
* - E1: T1 returns from prepareWait
* - E2: T1 calls wait
* (obviously E1 < E2, intra-thread)
* - E3: T2 calls notifyAll
*
* If E1 < E3, then E2's wait will complete (and T1 will either wake up,
* or not block at all)
*
* This means that you can use an EventCount in the following manner:
*
* Waiter:
* if (!condition()) { // handle fast path first
* for (;;) {
* auto key = eventCount.prepareWait();
* if (condition()) {
* eventCount.cancelWait();
* break;
* } else {
* eventCount.wait(key);
* }
* }
* }
*
* (This pattern is encapsulated in await())
*
* Poster:
* make_condition_true();
* eventCount.notifyAll();
*
* Note that, just like with regular condition variables, the waiter needs to
* be tolerant of spurious wakeups and needs to recheck the condition after
* being woken up. Also, as there is no mutual exclusion implied, "checking"
* the condition likely means attempting an operation on an underlying
* data structure (push into a lock-free queue, etc) and returning true on
* success and false on failure.
*/
class EventCount {
public:
EventCount() noexcept : val_(0) {}

class Key {
friend class EventCount;
explicit Key(uint32_t e) noexcept : epoch_(e) {}
uint32_t epoch_;
};

void notify() noexcept;
void notifyAll() noexcept;
Key prepareWait() noexcept;
void cancelWait() noexcept;
void wait(Key key) noexcept;

/**
* Wait for condition() to become true. Will clean up appropriately if
* condition() throws, and then rethrow.
*/
template <class Condition>
void await(Condition condition);

private:
void doNotify(int n) noexcept;
EventCount(const EventCount&) = delete;
EventCount(EventCount&&) = delete;
EventCount& operator=(const EventCount&) = delete;
EventCount& operator=(EventCount&&) = delete;

// This requires 64-bit
static_assert(sizeof(int) == 4, "bad platform");
static_assert(sizeof(uint32_t) == 4, "bad platform");
static_assert(sizeof(uint64_t) == 8, "bad platform");
static_assert(sizeof(std::atomic<uint64_t>) == 8, "bad platform");
static_assert(sizeof(detail::Futex<std::atomic>) == 4, "bad platform");

static constexpr size_t kEpochOffset = kIsLittleEndian ? 1 : 0;

// val_ stores the epoch in the most significant 32 bits and the
// waiter count in the least significant 32 bits.
std::atomic<uint64_t> val_;

static constexpr uint64_t kAddWaiter = uint64_t(1);
static constexpr uint64_t kSubWaiter = uint64_t(-1);
static constexpr size_t kEpochShift = 32;
static constexpr uint64_t kAddEpoch = uint64_t(1) << kEpochShift;
static constexpr uint64_t kWaiterMask = kAddEpoch - 1;
};

inline void EventCount::notify() noexcept {
doNotify(1);
}

inline void EventCount::notifyAll() noexcept {
doNotify(INT_MAX);
}

inline void EventCount::doNotify(int n) noexcept {
uint64_t prev = val_.fetch_add(kAddEpoch, std::memory_order_acq_rel);
if (FOLLY_UNLIKELY(prev & kWaiterMask)) {
detail::futexWake(
reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset, n);
}
}

inline EventCount::Key EventCount::prepareWait() noexcept {
uint64_t prev = val_.fetch_add(kAddWaiter, std::memory_order_acq_rel);
return Key(prev >> kEpochShift);
}

inline void EventCount::cancelWait() noexcept {
// memory_order_relaxed would suffice for correctness, but the faster
// #waiters gets to 0, the less likely it is that we'll do spurious wakeups
// (and thus system calls).
uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
DCHECK_NE((prev & kWaiterMask), 0);
}

inline void EventCount::wait(Key key) noexcept {
while ((val_.load(std::memory_order_acquire) >> kEpochShift) == key.epoch_) {
detail::futexWait(
reinterpret_cast<detail::Futex<std::atomic>*>(&val_) + kEpochOffset,
key.epoch_);
}
// memory_order_relaxed would suffice for correctness, but the faster
// #waiters gets to 0, the less likely it is that we'll do spurious wakeups
// (and thus system calls)
uint64_t prev = val_.fetch_add(kSubWaiter, std::memory_order_seq_cst);
DCHECK_NE((prev & kWaiterMask), 0);
}

template <class Condition>
void EventCount::await(Condition condition) {
if (condition()) {
return; // fast path
}

// condition() is the only thing that may throw, everything else is
// noexcept, so we can hoist the try/catch block outside of the loop
try {
for (;;) {
auto key = prepareWait();
if (condition()) {
cancelWait();
break;
} else {
wait(key);
}
}
} catch (...) {
cancelWait();
throw;
}
}

} // namespace folly

0 comments on commit fc45a44

Please sign in to comment.