proxygen
SaturatingSemaphore.h
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <folly/Likely.h>
20 #include <folly/detail/Futex.h>
22 #include <folly/portability/Asm.h>
25 
26 #include <glog/logging.h>
27 
28 #include <atomic>
29 
30 namespace folly {
31 
118 
119 template <bool MayBlock, template <typename> class Atom = std::atomic>
122 
123  enum State : uint32_t {
124  NOTREADY = 0,
125  READY = 1,
126  BLOCKED = 2,
127  };
128 
129  public:
131  return {};
132  }
133 
135  constexpr SaturatingSemaphore() noexcept : state_(NOTREADY) {}
136 
139 
142  return state_.load(std::memory_order_acquire) == READY;
143  }
144 
146  void reset() noexcept {
147  state_.store(NOTREADY, std::memory_order_relaxed);
148  }
149 
152  if (!MayBlock) {
153  state_.store(READY, std::memory_order_release);
154  } else {
156  }
157  }
158 
161  void wait(const WaitOptions& opt = wait_options()) noexcept {
163  }
164 
167  return ready();
168  }
169 
171  template <typename Clock, typename Duration>
173  const std::chrono::time_point<Clock, Duration>& deadline,
174  const WaitOptions& opt = wait_options()) noexcept {
175  if (LIKELY(try_wait())) {
176  return true;
177  }
178  return tryWaitSlow(deadline, opt);
179  }
180 
182  template <class Rep, class Period>
184  const std::chrono::duration<Rep, Period>& duration,
185  const WaitOptions& opt = wait_options()) noexcept {
186  if (LIKELY(try_wait())) {
187  return true;
188  }
189  auto deadline = std::chrono::steady_clock::now() + duration;
190  return tryWaitSlow(deadline, opt);
191  }
192 
193  private:
195  uint32_t before = NOTREADY;
196  if (LIKELY(state_.compare_exchange_strong(
197  before,
198  READY,
199  std::memory_order_release,
200  std::memory_order_relaxed))) {
201  return;
202  }
203  postSlowWaiterMayBlock(before);
204  }
205 
206  void postSlowWaiterMayBlock(uint32_t before) noexcept; // defined below
207 
208  template <typename Clock, typename Duration>
209  bool tryWaitSlow(
210  const std::chrono::time_point<Clock, Duration>& deadline,
211  const WaitOptions& opt) noexcept; // defined below
212 };
213 
217 
219 template <bool MayBlock, template <typename> class Atom>
221  uint32_t before) noexcept {
222  while (true) {
223  if (before == NOTREADY) {
224  if (state_.compare_exchange_strong(
225  before,
226  READY,
227  std::memory_order_release,
228  std::memory_order_relaxed)) {
229  return;
230  }
231  }
232  if (before == READY) { // Only if multiple posters
233  // The reason for not simply returning (without the following
234  // steps) is to prevent the following case:
235  //
236  // T1: T2: T3:
237  // local1.post(); local2.post(); global.wait();
238  // global.post(); global.post(); global.reset();
239  // seq_cst fence
240  // local1.try_wait() == true;
241  // local2.try_wait() == false;
242  //
243  // This following steps correspond to T2's global.post(), where
244  // global is already posted by T1.
245  //
246  // The following fence and load guarantee that T3 does not miss
247  // T2's prior stores, i.e., local2.post() in this example.
248  //
249  // The following case is prevented:
250  //
251  // Starting with local2 == NOTREADY and global == READY
252  //
253  // T2: T3:
254  // store READY to local2 // post store NOTREADY to global // reset
255  // seq_cst fenc seq_cst fence
256  // load READY from global // post load NOTREADY from local2 // try_wait
257  //
258  std::atomic_thread_fence(std::memory_order_seq_cst);
259  before = state_.load(std::memory_order_relaxed);
260  if (before == READY) {
261  return;
262  }
263  continue;
264  }
265  DCHECK_EQ(before, BLOCKED);
266  if (state_.compare_exchange_strong(
267  before,
268  READY,
269  std::memory_order_release,
270  std::memory_order_relaxed)) {
271  detail::futexWake(&state_);
272  return;
273  }
274  }
275 }
276 
278 template <bool MayBlock, template <typename> class Atom>
279 template <typename Clock, typename Duration>
281  const std::chrono::time_point<Clock, Duration>& deadline,
282  const WaitOptions& opt) noexcept {
283  switch (detail::spin_pause_until(deadline, opt, [=] { return ready(); })) {
285  return true;
287  return false;
289  break;
290  }
291 
292  if (!MayBlock) {
293  switch (detail::spin_yield_until(deadline, [=] { return ready(); })) {
295  return true;
297  return false;
299  break;
300  }
301  }
302 
303  auto before = state_.load(std::memory_order_relaxed);
304  while (before == NOTREADY &&
305  !state_.compare_exchange_strong(
306  before,
307  BLOCKED,
308  std::memory_order_relaxed,
309  std::memory_order_relaxed)) {
310  if (before == READY) {
311  // TODO: move the acquire to the compare_exchange failure load after C++17
312  std::atomic_thread_fence(std::memory_order_acquire);
313  return true;
314  }
315  }
316 
317  while (true) {
318  auto rv = detail::MemoryIdler::futexWaitUntil(state_, BLOCKED, deadline);
319  if (rv == detail::FutexResult::TIMEDOUT) {
320  assert(deadline != (std::chrono::time_point<Clock, Duration>::max()));
321  return false;
322  }
323 
324  if (ready()) {
325  return true;
326  }
327  }
328 }
329 
330 } // namespace folly
FOLLY_ALWAYS_INLINE bool ready() const noexcept
static FutexResult futexWaitUntil(Futex &fut, uint32_t expected, Deadline const &deadline, uint32_t waitMask=-1, IdleTime const &idleTimeout=defaultIdleTimeout.load(std::memory_order_acquire), size_t stackToRetain=kDefaultStackToRetain, float timeoutVariationFrac=0.5)
Definition: MemoryIdler.h:139
FOLLY_ALWAYS_INLINE void wait(const WaitOptions &opt=wait_options()) noexcept
spin_result spin_yield_until(std::chrono::time_point< Clock, Duration > const &deadline, F f)
Definition: Spin.h:70
#define FOLLY_ALWAYS_INLINE
Definition: CPortability.h:151
LogLevel max
Definition: LogLevel.cpp:31
std::chrono::steady_clock::time_point now()
Atom< std::uint32_t > Futex
Definition: Futex.h:51
#define LIKELY(x)
Definition: Likely.h:47
FOLLY_ALWAYS_INLINE void postFastWaiterMayBlock() noexcept
detail::Futex< Atom > state_
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires E e noexcept(noexcept(s.error(std::move(e))))
bool tryWaitSlow(const std::chrono::time_point< Clock, Duration > &deadline, const WaitOptions &opt) noexcept
static FOLLY_ALWAYS_INLINE WaitOptions wait_options()
FOLLY_ALWAYS_INLINE void post() noexcept
#define FOLLY_NOINLINE
Definition: CPortability.h:142
FOLLY_ALWAYS_INLINE bool try_wait_until(const std::chrono::time_point< Clock, Duration > &deadline, const WaitOptions &opt=wait_options()) noexcept
#define Atom
constexpr SaturatingSemaphore() noexcept
const
Definition: upload.py:398
FOLLY_ALWAYS_INLINE bool try_wait_for(const std::chrono::duration< Rep, Period > &duration, const WaitOptions &opt=wait_options()) noexcept
FOLLY_ALWAYS_INLINE bool try_wait() noexcept
void postSlowWaiterMayBlock(uint32_t before) noexcept
spin_result spin_pause_until(std::chrono::time_point< Clock, Duration > const &deadline, WaitOptions const &opt, F f)
Definition: Spin.h:36
int futexWake(const Futex *futex, int count, uint32_t wakeMask)
Definition: Futex-inl.h:107