operations-barrier.h 3.48 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_TASKS_OPERATIONS_BARRIER_H_
#define V8_TASKS_OPERATIONS_BARRIER_H_

#include <cstdint>

#include "src/base/macros.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"

namespace v8 {
namespace internal {

// A thread-safe barrier to manage lifetime of muti-threaded operations.
//
// The barrier is used to determine if operations are allowed, and to keep track
// of how many are currently active. Users will call TryLock() before starting
// such operations. If the call succeeds the user can run the operation and the
// barrier will keep track of it until the user signals that the operation is
// completed. No operations are allowed after CancelAndWait() is called.
//
// There is no explicit way of telling the barrier when an operation is
// completed, instead for convenience TryLock() will return a RAII
// like object that will do so on destruction.
//
// For example:
//
// OperationsBarrier barrier_;
//
// void TearDown() {
//   barrier_.CancelAndWait();
// }
//
// void MaybeRunOperation() {
38
//   if (token = barrier_.TryLock()) Process();
39 40 41 42 43 44 45 46
// }
//
class V8_EXPORT_PRIVATE OperationsBarrier {
 public:
  // The owner of a Token which evaluates to true can safely perform an
  // operation while being certain it happens-before CancelAndWait(). Releasing
  // this Token relinquishes this right.
  //
47
  // This class is thread-safe.
48 49 50 51 52 53 54
  class Token {
   public:
    Token() = default;
    ~Token() {
      if (outer_) outer_->Release();
    }
    Token(const Token&) = delete;
55
    Token(Token&& other) V8_NOEXCEPT : outer_(other.outer_) {
56 57 58
      other.outer_ = nullptr;
    }

59 60 61 62 63 64 65 66 67
    Token& operator=(const Token&) = delete;
    Token& operator=(Token&& other) V8_NOEXCEPT {
      DCHECK_NE(this, &other);
      if (outer_) outer_->Release();
      outer_ = other.outer_;
      other.outer_ = nullptr;
      return *this;
    }

68 69 70 71
    operator bool() const { return !!outer_; }

   private:
    friend class OperationsBarrier;
72 73 74
    explicit Token(OperationsBarrier* outer) : outer_(outer) {
      DCHECK_NOT_NULL(outer_);
    }
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    OperationsBarrier* outer_ = nullptr;
  };

  OperationsBarrier() = default;

  // Users must call CancelAndWait() before destroying an instance of this
  // class.
  ~OperationsBarrier() { DCHECK(cancelled_); }

  OperationsBarrier(const OperationsBarrier&) = delete;
  OperationsBarrier& operator=(const OperationsBarrier&) = delete;

  // Returns a RAII like object that implicitly converts to true if operations
  // are allowed i.e. if this call happens-before CancelAndWait(), otherwise the
  // object will convert to false. On successful return, this OperationsBarrier
  // will keep track of the operation until the returned object goes out of
  // scope.
  Token TryLock();

  // Prevents further calls to TryLock() from succeeding and waits for
  // all the ongoing operations to complete.
  //
  // Attention: Can only be called once.
  void CancelAndWait();

  bool cancelled() const { return cancelled_; }

 private:
  void Release();

  // Mutex and condition variable enabling concurrent register and removing, as
  // well as waiting for background tasks on {CancelAndWait}.
  base::Mutex mutex_;
  base::ConditionVariable release_condition_;
  bool cancelled_ = false;
  size_t operations_count_{0};
};

}  // namespace internal
}  // namespace v8

#endif  // V8_TASKS_OPERATIONS_BARRIER_H_