blob: f1949e31fb9e99d78f3b68a85e0efdfa695e655b [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_
#define BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_
#include <atomic>
#include <cstdint>
#include "base/base_export.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/synchronization/waitable_event.h"
namespace base {
namespace internal {
// A lock-free thread-safe controller to manage critical multi-threaded
// operations without locks.
//
// The controller is used to determine if operations are allowed, and to keep
// track of how many are currently active. Users will call TryBeginOperation()
// before starting such operations. If the call succeeds the user can run the
// operation and the controller will keep track of it until the user signals
// that the operation is completed. No operations are allowed before
// StartAcceptingOperations() is called, or after
// ShutdownAndWaitForZeroOperations() is called.
//
// There is no explicit way of telling the controller when an operation is
// completed, instead for convenience TryBeginOperation() will return a RAII
// like object that will do so on destruction.
//
// For example:
//
// OperationsController controller_;
//
// void SetUp() {
// controller_.StartAcceptingOperations();
// }
//
// void TearDown() {
// controller_.ShutdownAndWaitForZeroOperations();
// }
//
// void MaybeRunOperation() {
// auto operation_token = controller_.TryBeginOperation();
// if (operation_token) {
// Process();
// }
// }
//
// This class is thread-safe.
// But note that StartAcceptingOperations can never be called after
// ShutdownAndWaitForZeroOperations.
class BASE_EXPORT OperationsController {
public:
// The owner of an OperationToken which evaluates to true can safely perform
// an operation while being certain it happens-after
// StartAcceptingOperations() and happens-before
// ShutdownAndWaitForZeroOperations(). Releasing this OperationToken
// relinquishes this right.
//
// This class is thread-safe
class OperationToken {
public:
~OperationToken() {
if (outer_)
outer_->DecrementBy(1);
}
OperationToken(const OperationToken&) = delete;
OperationToken(OperationToken&& other) {
this->outer_ = other.outer_;
other.outer_ = nullptr;
}
operator bool() const { return !!outer_; }
private:
friend class OperationsController;
explicit OperationToken(OperationsController* outer) : outer_(outer) {}
// `outer_` is not a raw_ptr<...> for performance reasons (based on analysis
// of sampling profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION OperationsController* outer_;
};
OperationsController();
// Users must call ShutdownAndWaitForZeroOperations() before destroying an
// instance of this class if StartAcceptingOperations() was called.
~OperationsController();
OperationsController(const OperationsController&) = delete;
OperationsController& operator=(const OperationsController&) = delete;
// Starts to accept operations (before this point TryBeginOperation() returns
// an invalid token). Returns true if an attempt to perform an operation was
// made and denied before StartAcceptingOperations() was called. Can be called
// at most once, never after ShutdownAndWaitForZeroOperations().
bool StartAcceptingOperations();
// Returns a RAII like object that implicitly converts to true if operations
// are allowed i.e. if this call happens-after StartAcceptingOperations() and
// happens-before Shutdown(), otherwise the object will convert to false. On
// successful return, this OperationsController will keep track of the
// operation until the returned object goes out of scope.
OperationToken TryBeginOperation();
// Prevents further calls to TryBeginOperation() from succeeding and waits for
// all the ongoing operations to complete.
//
// Attention: Can only be called once.
void ShutdownAndWaitForZeroOperations();
private:
// Atomic representation of the state of this class. We use the upper 2 bits
// to keep track of flag like values and the remainder bits are used as a
// counter. The 2 flags are used to represent 3 different states:
//
// State | AcceptOperations Bit | ShuttingDown Bit
// --------------------------------------------------------------
// kRejectingOperations | 0 | 0
// kAcceptingOperations | 1 | 0
// kShuttingDown | * | 1
//
// The counter keeps track of the rejected operations when we are in
// the kRejectingOperations state, the number of inflight operations
// otherwise. If the count reaches zero and we are in the shutting down state
// |shutdown_complete_| will be signaled.
static constexpr uint32_t kShuttingDownBitMask = uint32_t{1} << 31;
static constexpr uint32_t kAcceptingOperationsBitMask = uint32_t{1} << 30;
static constexpr uint32_t kFlagsBitMask =
(kShuttingDownBitMask | kAcceptingOperationsBitMask);
static constexpr uint32_t kCountBitMask = ~kFlagsBitMask;
enum class State {
kRejectingOperations,
kAcceptingOperations,
kShuttingDown,
};
// Helper methods for the bit fiddling. Pass a |state_and_count_| value to
// extract state or count out of it.
static uint32_t ExtractCount(uint32_t value) { return value & kCountBitMask; }
static State ExtractState(uint32_t value);
// Decrements the counter by |n| and signals |shutdown_complete_| if needed.
void DecrementBy(uint32_t n);
std::atomic<uint32_t> state_and_count_{0};
WaitableEvent shutdown_complete_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_