proxygen
folly::detail::LifoSemBase< Handoff, Atom > Struct Template Reference

#include <LifoSem.h>

Public Member Functions

constexpr LifoSemBase (uint32_t initialValue=0)
 Constructor. More...
 
 LifoSemBase (LifoSemBase const &)=delete
 
LifoSemBaseoperator= (LifoSemBase const &)=delete
 
bool post ()
 Silently saturates if value is already 2^32-1. More...
 
void post (uint32_t n)
 
bool isShutdown () const
 Returns true iff shutdown() has been called. More...
 
void shutdown ()
 
bool tryWait ()
 Returns true iff value was decremented. More...
 
uint32_t tryWait (uint32_t n)
 
void wait ()
 
template<typename Rep , typename Period >
bool try_wait_for (const std::chrono::duration< Rep, Period > &timeout)
 
template<typename Clock , typename Duration >
bool try_wait_until (const std::chrono::time_point< Clock, Duration > &deadline)
 
uint32_t valueGuess () const
 

Protected Types

enum  WaitResult { WaitResult::PUSH, WaitResult::DECR, WaitResult::SHUTDOWN }
 
typedef std::unique_ptr< LifoSemNode< Handoff, Atom >, LifoSemNodeRecycler< Handoff, Atom > > UniquePtr
 

Protected Member Functions

template<typename... Args>
UniquePtr allocateNode (Args &&...args)
 Returns a node that can be passed to decrOrLink. More...
 
WaitResult tryWaitOrPush (LifoSemNode< Handoff, Atom > &waiterNode)
 

Private Member Functions

bool tryRemoveNode (const LifoSemNode< Handoff, Atom > &removenode)
 
uint32_t incrOrPop (uint32_t n)
 
WaitResult decrOrPush (uint32_t &n, uint32_t idx)
 

Static Private Member Functions

static LifoSemNode< Handoff, Atom > & idxToNode (uint32_t idx)
 
static uint32_t nodeToIdx (const LifoSemNode< Handoff, Atom > &node)
 

Private Attributes

CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
 

Detailed Description

template<typename Handoff, template< typename > class Atom = std::atomic>
struct folly::detail::LifoSemBase< Handoff, Atom >

LifoSemBase is the engine for several different types of LIFO semaphore. LifoSemBase handles storage of positive semaphore values and wait nodes, but the actual waiting and notification mechanism is up to the client.

The Handoff type is responsible for arranging one wakeup notification. See LifoSemNode for more information on how to make your own.

Definition at line 352 of file LifoSem.h.

Member Typedef Documentation

template<typename Handoff, template< typename > class Atom = std::atomic>
typedef std:: unique_ptr<LifoSemNode<Handoff, Atom>, LifoSemNodeRecycler<Handoff, Atom> > folly::detail::LifoSemBase< Handoff, Atom >::UniquePtr
protected

The type of a std::unique_ptr that will automatically return a LifoSemNode to the appropriate IndexedMemPool

Definition at line 547 of file LifoSem.h.

Member Enumeration Documentation

template<typename Handoff, template< typename > class Atom = std::atomic>
enum folly::detail::LifoSemBase::WaitResult
strongprotected
Enumerator
PUSH 
DECR 
SHUTDOWN 

Definition at line 537 of file LifoSem.h.

537  {
538  PUSH,
539  DECR,
540  SHUTDOWN,
541  };

Constructor & Destructor Documentation

template<typename Handoff, template< typename > class Atom = std::atomic>
constexpr folly::detail::LifoSemBase< Handoff, Atom >::LifoSemBase ( uint32_t  initialValue = 0)
inlineexplicit

Constructor.

Definition at line 354 of file LifoSem.h.

355  : head_(LifoSemHead::fresh(initialValue)) {}
static constexpr LifoSemHead fresh(uint32_t value)
Definition: LifoSem.h:267
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
folly::detail::LifoSemBase< Handoff, Atom >::LifoSemBase ( LifoSemBase< Handoff, Atom > const &  )
delete

Member Function Documentation

template<typename Handoff, template< typename > class Atom = std::atomic>
template<typename... Args>
UniquePtr folly::detail::LifoSemBase< Handoff, Atom >::allocateNode ( Args &&...  args)
inlineprotected

Returns a node that can be passed to decrOrLink.

Definition at line 551 of file LifoSem.h.

551  {
552  auto idx = LifoSemRawNode<Atom>::pool().allocIndex();
553  if (idx != 0) {
554  auto& node = idxToNode(idx);
555  node.clearShutdownNotice();
556  try {
557  node.init(std::forward<Args>(args)...);
558  } catch (...) {
560  throw;
561  }
562  return UniquePtr(&node);
563  } else {
564  return UniquePtr();
565  }
566  }
std::unique_ptr< LifoSemNode< Handoff, Atom >, LifoSemNodeRecycler< Handoff, Atom > > UniquePtr
Definition: LifoSem.h:547
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
uint32_t allocIndex(Args &&...args)
void recycleIndex(uint32_t idx)
Gives up ownership previously granted by alloc()
static Pool & pool()
Storage for all of the waiter nodes for LifoSem-s that use Atom.
template<typename Handoff, template< typename > class Atom = std::atomic>
WaitResult folly::detail::LifoSemBase< Handoff, Atom >::decrOrPush ( uint32_t n,
uint32_t  idx 
)
inlineprivate

Returns DECR if some amount was decremented, with that amount subtracted from n. If n is 1 and this function returns DECR then n must be 0 afterward. Returns PUSH if no value could be decremented and idx was pushed, or if idx was zero and no push was performed but a push would have been performed with a valid node. Returns SHUTDOWN if the caller should have blocked but isShutdown(). If idx == 0, may return PUSH even after isShutdown() or may return SHUTDOWN

Definition at line 678 of file LifoSem.h.

678  {
679  assert(n > 0);
680 
681  while (true) {
682  auto head = head_->load(std::memory_order_acquire);
683 
684  if (head.isLocked()) {
686  continue;
687  }
688 
689  if (!head.isNodeIdx() && head.value() > 0) {
690  // decr
691  auto delta = std::min(n, head.value());
692  if (head_->compare_exchange_strong(head, head.withValueDecr(delta))) {
693  n -= delta;
694  return WaitResult::DECR;
695  }
696  } else {
697  // push
698  if (idx == 0) {
699  return WaitResult::PUSH;
700  }
701 
702  if (UNLIKELY(head.isShutdown())) {
703  return WaitResult::SHUTDOWN;
704  }
705 
706  auto& node = idxToNode(idx);
707  node.next = head.isNodeIdx() ? head.idx() : 0;
708  if (head_->compare_exchange_strong(head, head.withPush(idx))) {
709  // push succeeded
710  return WaitResult::PUSH;
711  }
712  }
713  }
714  // retry
715  }
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
LogLevel min
Definition: LogLevel.cpp:30
#define UNLIKELY(x)
Definition: Likely.h:48
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
static LifoSemNode<Handoff, Atom>& folly::detail::LifoSemBase< Handoff, Atom >::idxToNode ( uint32_t  idx)
inlinestaticprivate

Definition at line 582 of file LifoSem.h.

582  {
583  auto raw = &LifoSemRawNode<Atom>::pool()[idx];
584  return *static_cast<LifoSemNode<Handoff, Atom>*>(raw);
585  }
static Pool & pool()
Storage for all of the waiter nodes for LifoSem-s that use Atom.
template<typename Handoff, template< typename > class Atom = std::atomic>
uint32_t folly::detail::LifoSemBase< Handoff, Atom >::incrOrPop ( uint32_t  n)
inlineprivate

Either increments by n and returns 0, or pops a node and returns it. If n + the stripe's value overflows, then the stripe's value saturates silently at 2^32-1

Definition at line 645 of file LifoSem.h.

645  {
646  while (true) {
647  assert(n > 0);
648 
649  auto head = head_->load(std::memory_order_acquire);
650  if (head.isLocked()) {
652  continue;
653  }
654  if (head.isNodeIdx()) {
655  auto& node = idxToNode(head.idx());
656  if (head_->compare_exchange_strong(head, head.withPop(node.next))) {
657  // successful pop
658  return head.idx();
659  }
660  } else {
661  auto after = head.withValueIncr(n);
662  if (head_->compare_exchange_strong(head, after)) {
663  // successful incr
664  return 0;
665  }
666  }
667  // retry
668  }
669  }
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
bool folly::detail::LifoSemBase< Handoff, Atom >::isShutdown ( ) const
inline

Returns true iff shutdown() has been called.

Definition at line 389 of file LifoSem.h.

Referenced by TEST_F().

389  {
390  return UNLIKELY(head_->load(std::memory_order_acquire).isShutdown());
391  }
#define UNLIKELY(x)
Definition: Likely.h:48
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
static uint32_t folly::detail::LifoSemBase< Handoff, Atom >::nodeToIdx ( const LifoSemNode< Handoff, Atom > &  node)
inlinestaticprivate

Definition at line 587 of file LifoSem.h.

587  {
588  return LifoSemRawNode<Atom>::pool().locateElem(&node);
589  }
uint32_t locateElem(const T *elem) const
static Pool & pool()
Storage for all of the waiter nodes for LifoSem-s that use Atom.
template<typename Handoff, template< typename > class Atom = std::atomic>
LifoSemBase& folly::detail::LifoSemBase< Handoff, Atom >::operator= ( LifoSemBase< Handoff, Atom > const &  )
delete
template<typename Handoff, template< typename > class Atom = std::atomic>
bool folly::detail::LifoSemBase< Handoff, Atom >::post ( )
inline

Silently saturates if value is already 2^32-1.

Definition at line 361 of file LifoSem.h.

Referenced by folly::UnboundedBlockingQueue< T >::add(), folly::LifoSemMPMCQueue< T, kBehavior >::add(), folly::ManualExecutor::add(), folly::PriorityLifoSemMPMCQueue< T, kBehavior >::addWithPriority(), BENCHMARK(), folly::ManualExecutor::scheduleAt(), TEST(), and TEST_F().

361  {
362  auto idx = incrOrPop(1);
363  if (idx != 0) {
364  idxToNode(idx).handoff().post();
365  return true;
366  }
367  return false;
368  }
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
uint32_t incrOrPop(uint32_t n)
Definition: LifoSem.h:645
template<typename Handoff, template< typename > class Atom = std::atomic>
void folly::detail::LifoSemBase< Handoff, Atom >::post ( uint32_t  n)
inline

Equivalent to n calls to post(), except may be much more efficient. At any point in time at which the semaphore's value would exceed 2^32-1 if tracked with infinite precision, it may be silently truncated to 2^32-1. This saturation is not guaranteed to be exact, although it is guaranteed that overflow won't result in wrap-around. There would be a substantial performance and complexity cost in guaranteeing exact saturation (similar to the cost of maintaining linearizability near the zero value, but without as much of a benefit).

Definition at line 379 of file LifoSem.h.

379  {
380  uint32_t idx;
381  while (n > 0 && (idx = incrOrPop(n)) != 0) {
382  // pop accounts for only 1
383  idxToNode(idx).handoff().post();
384  --n;
385  }
386  }
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
uint32_t incrOrPop(uint32_t n)
Definition: LifoSem.h:645
template<typename Handoff, template< typename > class Atom = std::atomic>
void folly::detail::LifoSemBase< Handoff, Atom >::shutdown ( )
inline

Prevents blocking on this semaphore, causing all blocking wait() calls to throw ShutdownSemError. Both currently blocked wait() and future calls to wait() for which tryWait() would return false will cause an exception. Calls to wait() for which the matching post() has already occurred will proceed normally.

Definition at line 398 of file LifoSem.h.

Referenced by TEST_F().

398  {
399  // first set the shutdown bit
400  auto h = head_->load(std::memory_order_acquire);
401  while (!h.isShutdown()) {
402  if (head_->compare_exchange_strong(h, h.withShutdown())) {
403  // success
404  h = h.withShutdown();
405  break;
406  }
407  // compare_exchange_strong rereads h, retry
408  }
409 
410  // now wake up any waiters
411  while (h.isNodeIdx()) {
412  if (h.isLocked()) {
414  h = head_->load(std::memory_order_acquire);
415  continue;
416  }
417  auto& node = idxToNode(h.idx());
418  auto repl = h.withPop(node.next);
419  if (head_->compare_exchange_strong(h, repl)) {
420  // successful pop, wake up the waiter and move on. The next
421  // field is used to convey that this wakeup didn't consume a value
422  node.setShutdownNotice();
423  node.handoff().post();
424  h = repl;
425  }
426  }
427  }
*than *hazptr_holder h
Definition: Hazptr.h:116
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
template<typename Rep , typename Period >
bool folly::detail::LifoSemBase< Handoff, Atom >::try_wait_for ( const std::chrono::duration< Rep, Period > &  timeout)
inline

Definition at line 472 of file LifoSem.h.

Referenced by TEST_F(), folly::UnboundedBlockingQueue< T >::try_take_for(), folly::LifoSemMPMCQueue< T, kBehavior >::try_take_for(), and folly::PriorityLifoSemMPMCQueue< T, kBehavior >::try_take_for().

472  {
474  }
std::chrono::steady_clock::time_point now()
bool try_wait_until(const std::chrono::time_point< Clock, Duration > &deadline)
Definition: LifoSem.h:477
template<typename Handoff, template< typename > class Atom = std::atomic>
template<typename Clock , typename Duration >
bool folly::detail::LifoSemBase< Handoff, Atom >::try_wait_until ( const std::chrono::time_point< Clock, Duration > &  deadline)
inline

Definition at line 477 of file LifoSem.h.

478  {
479  // early check isn't required for correctness, but is an important
480  // perf win if we can avoid allocating and deallocating a node
481  if (tryWait()) {
482  return true;
483  }
484 
485  // allocateNode() won't compile unless Handoff has a default
486  // constructor
487  UniquePtr node = allocateNode();
488 
489  auto rv = tryWaitOrPush(*node);
490  if (UNLIKELY(rv == WaitResult::SHUTDOWN)) {
491  assert(isShutdown());
492  throw ShutdownSemError("wait() would block but semaphore is shut down");
493  }
494 
495  if (rv == WaitResult::PUSH) {
496  if (!node->handoff().try_wait_until(deadline)) {
497  if (tryRemoveNode(*node)) {
498  return false;
499  } else {
500  // We could not remove our node. Return to waiting.
501  //
502  // This only happens if we lose a removal race with post(),
503  // so we are not likely to wait long. This is only
504  // necessary to ensure we don't return node's memory back to
505  // IndexedMemPool before post() has had a chance to post to
506  // handoff(). In a stronger memory reclamation scheme, such
507  // as hazptr or rcu, this would not be necessary.
508  node->handoff().wait();
509  }
510  }
511  if (UNLIKELY(node->isShutdownNotice())) {
512  // this wait() didn't consume a value, it was triggered by shutdown
513  throw ShutdownSemError(
514  "blocking wait() interrupted by semaphore shutdown");
515  }
516 
517  // node->handoff().wait() can't return until after the node has
518  // been popped and post()ed, so it is okay for the UniquePtr to
519  // recycle the node now
520  }
521  // else node wasn't pushed, so it is safe to recycle
522  return true;
523  }
std::unique_ptr< LifoSemNode< Handoff, Atom >, LifoSemNodeRecycler< Handoff, Atom > > UniquePtr
Definition: LifoSem.h:547
bool tryRemoveNode(const LifoSemNode< Handoff, Atom > &removenode)
Definition: LifoSem.h:594
UniquePtr allocateNode(Args &&...args)
Returns a node that can be passed to decrOrLink.
Definition: LifoSem.h:551
bool tryWait()
Returns true iff value was decremented.
Definition: LifoSem.h:430
bool isShutdown() const
Returns true iff shutdown() has been called.
Definition: LifoSem.h:389
#define UNLIKELY(x)
Definition: Likely.h:48
WaitResult tryWaitOrPush(LifoSemNode< Handoff, Atom > &waiterNode)
Definition: LifoSem.h:574
template<typename Handoff, template< typename > class Atom = std::atomic>
bool folly::detail::LifoSemBase< Handoff, Atom >::tryRemoveNode ( const LifoSemNode< Handoff, Atom > &  removenode)
inlineprivate

Definition at line 594 of file LifoSem.h.

594  {
595  auto removeidx = nodeToIdx(removenode);
596  auto head = head_->load(std::memory_order_acquire);
597  // Try to lock the head.
598  while (true) {
599  if (head.isLocked()) {
601  head = head_->load(std::memory_order_acquire);
602  continue;
603  }
604  if (!head.isNodeIdx()) {
605  return false;
606  }
607  if (head_->compare_exchange_weak(
608  head,
609  head.withLock(),
610  std::memory_order_acquire,
611  std::memory_order_relaxed)) {
612  break;
613  }
614  }
615  // Update local var to what head_ is, for better assert() checking.
616  head = head.withLock();
617  bool result = false;
618  auto idx = head.idx();
619  if (idx == removeidx) {
620  // pop from head. Head seqno is updated.
621  head_->store(
622  head.withoutLock(removenode.next), std::memory_order_release);
623  return true;
624  }
625  auto node = &idxToNode(idx);
626  idx = node->next;
627  while (idx) {
628  if (idx == removeidx) {
629  // Pop from mid-list.
630  node->next = removenode.next;
631  result = true;
632  break;
633  }
634  node = &idxToNode(idx);
635  idx = node->next;
636  }
637  // Unlock and return result
638  head_->store(head.withoutLock(head.idx()), std::memory_order_release);
639  return result;
640  }
static uint32_t nodeToIdx(const LifoSemNode< Handoff, Atom > &node)
Definition: LifoSem.h:587
static LifoSemNode< Handoff, Atom > & idxToNode(uint32_t idx)
Definition: LifoSem.h:582
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
bool folly::detail::LifoSemBase< Handoff, Atom >::tryWait ( )
inline

Returns true iff value was decremented.

Definition at line 430 of file LifoSem.h.

Referenced by BENCHMARK(), folly::ManualExecutor::run(), TEST(), and TEST_F().

430  {
431  uint32_t n = 1;
432  auto rv = decrOrPush(n, 0);
433  assert(
434  (rv == WaitResult::DECR && n == 0) ||
435  (rv != WaitResult::DECR && n == 1));
436  // SHUTDOWN is okay here, since we don't actually wait
437  return rv == WaitResult::DECR;
438  }
WaitResult decrOrPush(uint32_t &n, uint32_t idx)
Definition: LifoSem.h:678
template<typename Handoff, template< typename > class Atom = std::atomic>
uint32_t folly::detail::LifoSemBase< Handoff, Atom >::tryWait ( uint32_t  n)
inline

Equivalent to (but may be much more efficient than) n calls to tryWait(). Returns the total amount by which the semaphore's value was decreased

Definition at line 443 of file LifoSem.h.

443  {
444  auto const orig = n;
445  while (n > 0) {
446 #ifndef NDEBUG
447  auto prev = n;
448 #endif
449  auto rv = decrOrPush(n, 0);
450  assert(
451  (rv == WaitResult::DECR && n < prev) ||
452  (rv != WaitResult::DECR && n == prev));
453  if (rv != WaitResult::DECR) {
454  break;
455  }
456  }
457  return orig - n;
458  }
WaitResult decrOrPush(uint32_t &n, uint32_t idx)
Definition: LifoSem.h:678
template<typename Handoff, template< typename > class Atom = std::atomic>
WaitResult folly::detail::LifoSemBase< Handoff, Atom >::tryWaitOrPush ( LifoSemNode< Handoff, Atom > &  waiterNode)
inlineprotected

Returns DECR if the semaphore value was decremented (and waiterNode was untouched), PUSH if a reference to the wait node was pushed, or SHUTDOWN if decrement was not possible and push wasn't allowed because isShutdown(). Ownership of the wait node remains the responsibility of the caller, who must not release it until after the node's Handoff has been posted.

Definition at line 574 of file LifoSem.h.

574  {
575  uint32_t n = 1;
576  return decrOrPush(n, nodeToIdx(waiterNode));
577  }
static uint32_t nodeToIdx(const LifoSemNode< Handoff, Atom > &node)
Definition: LifoSem.h:587
WaitResult decrOrPush(uint32_t &n, uint32_t idx)
Definition: LifoSem.h:678
template<typename Handoff, template< typename > class Atom = std::atomic>
uint32_t folly::detail::LifoSemBase< Handoff, Atom >::valueGuess ( ) const
inline

Returns a guess at the current value, designed for debugging. If there are no concurrent posters or waiters then this will be correct

Definition at line 528 of file LifoSem.h.

Referenced by TEST_F().

528  {
529  // this is actually linearizable, but we don't promise that because
530  // we may want to add striping in the future to help under heavy
531  // contention
532  auto h = head_->load(std::memory_order_acquire);
533  return h.isNodeIdx() ? 0 : h.value();
534  }
*than *hazptr_holder h
Definition: Hazptr.h:116
CachelinePadded< folly::AtomicStruct< LifoSemHead, Atom > > head_
Definition: LifoSem.h:580
template<typename Handoff, template< typename > class Atom = std::atomic>
void folly::detail::LifoSemBase< Handoff, Atom >::wait ( )
inline

Blocks the current thread until there is a matching post or the semaphore is shut down. Throws ShutdownSemError if the semaphore has been shut down and this method would otherwise be blocking. Note that wait() doesn't throw during shutdown if tryWait() would return true

Definition at line 465 of file LifoSem.h.

Referenced by BENCHMARK(), folly::UnboundedBlockingQueue< T >::take(), folly::LifoSemMPMCQueue< T, kBehavior >::take(), folly::PriorityLifoSemMPMCQueue< T, kBehavior >::take(), TEST(), TEST_F(), and folly::ManualExecutor::wait().

465  {
466  auto const deadline = std::chrono::steady_clock::time_point::max();
467  auto res = try_wait_until(deadline);
468  FOLLY_SAFE_DCHECK(res, "infinity time has passed");
469  }
LogLevel max
Definition: LogLevel.cpp:31
bool try_wait_until(const std::chrono::time_point< Clock, Duration > &deadline)
Definition: LifoSem.h:477
#define FOLLY_SAFE_DCHECK(expr, msg)
Definition: SafeAssert.h:42

Member Data Documentation

template<typename Handoff, template< typename > class Atom = std::atomic>
CachelinePadded<folly::AtomicStruct<LifoSemHead, Atom> > folly::detail::LifoSemBase< Handoff, Atom >::head_
private

Definition at line 580 of file LifoSem.h.


The documentation for this struct was generated from the following file: