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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
base / task / common / operations_controller.h [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/stack_allocated.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 {
STACK_ALLOCATED();
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) {}
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_