Action Engine
Loading...
Searching...
No Matches
cases.h
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef THREAD_FIBER_CASES_H_
16#define THREAD_FIBER_CASES_H_
17
18#include "thread/boost_primitives.h"
19
20namespace thread {
21namespace internal {
22template <typename T>
23concept IsPointer = std::is_pointer_v<T>;
24
25template <typename T>
26concept IsNonConstPointer =
27 IsPointer<T> && !std::is_const_v<std::remove_pointer_t<T>>;
28
29template <typename T>
30concept IsConstPointer =
31 IsPointer<T> && std::is_const_v<std::remove_pointer_t<T>>;
32
33struct Selector {
34 static constexpr int kNonePicked = -1;
35
36 bool TryPick(int case_index) ABSL_EXCLUSIVE_LOCKS_REQUIRED
37
38 (mu) {
39 if (picked_case_index != kNonePicked) {
40 return false; // Already picked.
41 }
42
43 picked_case_index = case_index;
44 cv.Signal();
45 return true;
46 }
47
48 // Returns true iff a case was picked before the deadline.
49 bool WaitForPickUntil(absl::Time deadline) ABSL_SHARED_LOCKS_REQUIRED
50
51 (mu) {
52 while (picked_case_index == internal::Selector::kNonePicked) {
53 if (cv.WaitWithDeadline(&mu, deadline) &&
54 picked_case_index == internal::Selector::kNonePicked) {
55 return false;
56 }
57 }
58 return true;
59 }
60
61 // kNonePicked until a case is picked, or the index of picked case
62 int picked_case_index{kNonePicked};
63
64 act::concurrency::impl::Mutex mu;
65 act::concurrency::impl::CondVar cv ABSL_GUARDED_BY(mu);
66};
67
68class Selectable;
69} // namespace internal
70
80struct [[nodiscard]] Case {
81 internal::Selectable* absl_nonnull selectable;
82 absl::InlinedVector<void * absl_nullable, 2> arguments;
83
84 Case(internal::Selectable* absl_nullable s = nullptr) : selectable(s) {}
85
86 Case(internal::Selectable* absl_nullable s, internal::IsPointer auto... args)
87 : selectable(s) {
88 AddArgs(args...);
89 }
90
91 Case(const Case&) = default;
92 Case& operator=(const Case&) = default;
93
94 // // Disallow casting from bool
95 // // ReSharper disable once CppNonExplicitConvertingConstructor
96 template <typename... Args>
97 Case(bool, Args... args) = delete;
98
99 void AddArgs(internal::IsNonConstPointer auto arg) {
100 arguments.push_back(static_cast<void*>(arg));
101 }
102
103 void AddArgs(internal::IsConstPointer auto arg) {
104 arguments.push_back(const_cast<void*>(static_cast<const void*>(arg)));
105 }
106
107 void AddArgs(internal::IsPointer auto... args) { (AddArgs(args), ...); }
108
109 [[nodiscard]] void* absl_nonnull GetArgPtr(int index) const {
110 if (index < 0 || index >= arguments.size()) {
111 LOG(FATAL) << "Case::GetArgOrDie: index out of bounds: " << index
112 << ", arguments.size() = " << arguments.size();
113 ABSL_ASSUME(false);
114 }
115 return arguments[index];
116 }
117
118 template <typename T>
119 [[nodiscard]] T* absl_nonnull GetArgPtr(int index) const {
120 return static_cast<T*>(GetArgPtr(index));
121 }
122
123 [[nodiscard]] size_t GetNumArgs() const { return arguments.size(); }
124};
125
126// An array of cases; the type supplied to Select. Must be initializer list
127// compatible.
128typedef absl::InlinedVector<Case, 4> CaseArray;
129
130namespace internal {
131// A PerSelectCaseState represents the per-Select call information kept for a Case.
132// This separation from Case allows a particular Case to be safely passed to
133// multiple Select calls concurrently.
134//
135// PerSelectCaseStates contain an intrusive, circular, doubly-linked list, to be used by
136// selectables for enqueuing cases that are waiting on some condition. See the
137// notes on Selectable::Handle below. Once enqueued, the Selectable is
138// responsible for synchronizing any modifications.
139struct CaseInSelectClause {
140 const Case* absl_nonnull case_ptr; // Initialized by Select()
141 int index; // Provided by Select(): index in parameter list.
142 internal::Selector* absl_nonnull
143 selector; // Provided by Select(): owning selector.
144 CaseInSelectClause* absl_nullable
145 prev; // Initialized by Select(), nullptr -> not on list.
146 CaseInSelectClause* absl_nullable next;
147
148 [[nodiscard]] const Case* absl_nonnull GetCase() const { return case_ptr; }
149
150 // Attempt to cause the owning Selector to choose this case. Returns true if
151 // and only if this case will be the one chosen, because no other case has
152 // already become ready and been chosen.
153 //
154 // After the caller releases selector->mu, this object is no longer guaranteed to
155 // continue to exist.
156 bool TryPick() ABSL_EXCLUSIVE_LOCKS_REQUIRED(selector->mu) {
157 return selector->TryPick(index);
158 }
159
160 bool Handle(bool enqueue);
161 void Unregister();
162};
163
164using CaseStateArray = absl::InlinedVector<CaseInSelectClause, 4>;
165
166// The interface implemented by objects that can be used with Select(). Note
167// that a single Selectable may be enqueued against multiple Select statements,
168// this indirection is represented using Case tokens above. Case tokens allow
169// for passing arguments to a single selectable used in multiple different ways.
170class Selectable {
171 public:
172 virtual ~Selectable() = default;
173
174 // If this selectable is ready to be picked up by c's Select, call c->TryPick()
175 // (which may or may not pick this selectable), and return true.
176 // If not ready to be picked: enqueue the case (if enqueue is true) and return
177 // false.
178 //
179 // The selectable should implement the following algorithm:
180 // if (currently ready) {
181 // c->selector->mu.Lock();
182 // if (c->TryPick()) {
183 // ... perform any side effects of being picked ...
184 // }
185 // c->selector->mu.Unlock();
186 // return true;
187 // } else {
188 // if (enqueue) {
189 // ... enqueue the PerSelectCaseState ...
190 // }
191 // return false;
192 // }
193 //
194 // If the PerSelectCaseState is enqueued and the selectable later becomes ready before
195 // Unregister is called, it should again lock c->selector->mu, call c->TryPick(),
196 // and perform side effects iff picked, while c->selector->mu is still held.
197 virtual bool Handle(CaseInSelectClause* absl_nonnull case_state,
198 bool enqueue) = 0;
199
200 // Unregister a case against future transitions for this Selectable.
201 //
202 // Called for all cases c1 where a previous call Handle(c1, true) returned
203 // false and another case c2 was successfully selected (c2->TryPick() returned
204 // true), or a timeout occurred.
205 virtual void Unregister(CaseInSelectClause* absl_nonnull case_state) = 0;
206};
207
208inline bool CaseInSelectClause::Handle(bool enqueue) {
209 return GetCase()->selectable->Handle(this, enqueue);
210}
211
212inline void CaseInSelectClause::Unregister() {
213 GetCase()->selectable->Unregister(this);
214}
215
216// Shared linked list code. Implements a doubly-linked list where the list head
217// is a pointer to the oldest element added to the list.
218inline void PushBack(CaseInSelectClause* absl_nonnull* absl_nonnull head,
219 CaseInSelectClause* absl_nonnull element) {
220 if (*head == nullptr) {
221 // Queue is empty; make singleton queue.
222 element->next = element;
223 element->prev = element;
224 *head = element;
225 } else {
226 // Add just before the oldest element (*head).
227 element->next = *head;
228 element->prev = element->next->prev;
229 element->prev->next = element;
230 element->next->prev = element;
231 }
232}
233
234inline void UnlinkFromList(CaseInSelectClause* absl_nonnull* absl_nonnull head,
235 CaseInSelectClause* absl_nonnull element) {
236 if (element->next == element) {
237 // Single entry; clear list
238 *head = nullptr;
239 } else {
240 // Remove from list
241 element->next->prev = element->prev;
242 element->prev->next = element->next;
243 if (*head == element) {
244 *head = element->next;
245 }
246 }
247 // Maintaining this state in "prev" allows the safe removal of the current
248 // element while iterating forwards.
249 element->prev = nullptr;
250}
251} // namespace internal
252} // namespace thread
253
254#endif // THREAD_FIBER_CASES_H_
A Case represents a selectable case in a Select statement.
Definition cases.h:80