1 // Copyright 2021 gRPC authors.
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 GRPC_SRC_CORE_LIB_PROMISE_POLL_H
16 #define GRPC_SRC_CORE_LIB_PROMISE_POLL_H
17
18 #include <grpc/support/port_platform.h>
19
20 #include <string>
21 #include <utility>
22
23 #include "absl/log/check.h"
24 #include "absl/strings/str_format.h"
25 #include "absl/types/optional.h"
26 #include "src/core/util/construct_destruct.h"
27
28 namespace grpc_core {
29
30 // A type that signals a Promise is still pending and not yet completed.
31 // Allows writing 'return Pending{}' and with automatic conversions gets
32 // upgraded to a Poll<> object.
33 struct Pending {};
34 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline bool operator==(const Pending&,
35 const Pending&) {
36 return true;
37 }
38
39 // A type that contains no value. Useful for simulating 'void' in promises that
40 // always need to return some kind of value.
41 struct Empty {};
42 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline bool operator==(const Empty&,
43 const Empty&) {
44 return true;
45 }
46 template <typename Sink>
AbslStringify(Sink & sink,Empty)47 void AbslStringify(Sink& sink, Empty) {
48 sink.Append("{}");
49 }
50
51 // The result of polling a Promise once.
52 //
53 // Can be either pending - the Promise has not yet completed, or ready -
54 // indicating that the Promise has completed AND should not be polled again.
55 template <typename T>
56 class Poll {
57 public:
58 // NOLINTNEXTLINE(google-explicit-constructor)
Poll(Pending)59 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(Pending) : ready_(false) {}
Poll()60 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll() : ready_(false) {}
Poll(const Poll & other)61 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(const Poll& other)
62 : ready_(other.ready_) {
63 if (ready_) Construct(&value_, other.value_);
64 }
Poll(Poll && other)65 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(Poll&& other) noexcept
66 : ready_(other.ready_) {
67 if (ready_) Construct(&value_, std::move(other.value_));
68 }
69 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll& operator=(const Poll& other) {
70 if (ready_) {
71 if (other.ready_) {
72 value_ = other.value_;
73 } else {
74 Destruct(&value_);
75 ready_ = false;
76 }
77 } else if (other.ready_) {
78 Construct(&value_, other.value_);
79 ready_ = true;
80 }
81 return *this;
82 }
83 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll& operator=(Poll&& other) noexcept {
84 if (ready_) {
85 if (other.ready_) {
86 value_ = std::move(other.value_);
87 } else {
88 Destruct(&value_);
89 ready_ = false;
90 }
91 } else if (other.ready_) {
92 Construct(&value_, std::move(other.value_));
93 ready_ = true;
94 }
95 return *this;
96 }
97 template <typename U>
98 // NOLINTNEXTLINE(google-explicit-constructor)
Poll(U value)99 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(U value) : ready_(true) {
100 Construct(&value_, std::move(value));
101 }
102 // NOLINTNEXTLINE(google-explicit-constructor)
Poll(T && value)103 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(T&& value) : ready_(true) {
104 Construct(&value_, std::forward<T>(value));
105 }
~Poll()106 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION ~Poll() {
107 if (ready_) Destruct(&value_);
108 }
109
pending()110 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION bool pending() const { return !ready_; }
ready()111 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION bool ready() const { return ready_; }
112
value()113 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION T& value() {
114 DCHECK(ready());
115 return value_;
116 }
117
value()118 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION const T& value() const {
119 DCHECK(ready());
120 return value_;
121 }
122
value_if_ready()123 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION T* value_if_ready() {
124 if (ready()) return &value_;
125 return nullptr;
126 }
127
value_if_ready()128 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION const T* value_if_ready() const {
129 if (ready()) return &value_;
130 return nullptr;
131 }
132
133 private:
134 // Flag indicating readiness, followed by an optional value.
135 //
136 // Why not optional<T>?
137 //
138 // We have cases where we want to return absl::nullopt{} from a promise, and
139 // have that upgraded to a Poll<absl::nullopt_t> prior to a cast to some
140 // Poll<optional<T>>.
141 //
142 // Since optional<nullopt_t> is not allowed, we'd not be allowed to make
143 // Poll<nullopt_t> and so we'd need to pollute all poll handling code with
144 // some edge case handling template magic - the complexity would explode and
145 // grow over time - versus hand coding the pieces we need here and containing
146 // that quirk to one place.
147 bool ready_;
148 // We do a single element union so we can choose when to construct/destruct
149 // this value.
150 union {
151 T value_;
152 };
153 };
154
155 template <>
156 class Poll<Empty> {
157 public:
158 // NOLINTNEXTLINE(google-explicit-constructor)
Poll(Pending)159 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(Pending) : ready_(false) {}
Poll()160 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll() : ready_(false) {}
161 Poll(const Poll& other) = default;
162 Poll(Poll&& other) noexcept = default;
163 Poll& operator=(const Poll& other) = default;
164 Poll& operator=(Poll&& other) = default;
165 // NOLINTNEXTLINE(google-explicit-constructor)
Poll(Empty)166 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll(Empty) : ready_(true) {}
167 ~Poll() = default;
168
pending()169 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION bool pending() const { return !ready_; }
ready()170 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION bool ready() const { return ready_; }
171
value()172 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Empty value() const {
173 DCHECK(ready());
174 return Empty{};
175 }
176
value_if_ready()177 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Empty* value_if_ready() {
178 static Empty value;
179 if (ready()) return &value;
180 return nullptr;
181 }
182
value_if_ready()183 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION const Empty* value_if_ready() const {
184 static Empty value;
185 if (ready()) return &value;
186 return nullptr;
187 }
188
189 private:
190 // Flag indicating readiness.
191 bool ready_;
192 };
193
194 // Ensure degenerate cases are not defined:
195
196 // Can't poll for a Pending
197 template <>
198 class Poll<Pending>;
199
200 // Can't poll for a poll
201 template <class T>
202 class Poll<Poll<T>>;
203
204 // PollTraits tells us whether a type is Poll<> or some other type, and is
205 // leveraged in the PromiseLike/PromiseFactory machinery to select the
206 // appropriate implementation of those concepts based upon the return type of a
207 // lambda, for example (via enable_if).
208 template <typename T>
209 struct PollTraits {
210 using Type = T;
is_pollPollTraits211 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static constexpr bool is_poll() {
212 return false;
213 }
214 };
215
216 template <typename T>
217 struct PollTraits<Poll<T>> {
218 using Type = T;
219 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static constexpr bool is_poll() {
220 return true;
221 }
222 };
223
224 template <typename T>
225 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline bool operator==(const Poll<T>& a,
226 const Poll<T>& b) {
227 if (a.pending() && b.pending()) return true;
228 if (a.ready() && b.ready()) return a.value() == b.value();
229 return false;
230 }
231
232 template <typename T, typename U, typename SfinaeVoid = void>
233 struct PollCastImpl;
234
235 template <typename T, typename U>
236 struct PollCastImpl<T, Poll<U>> {
237 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static Poll<T> Cast(Poll<U>&& poll) {
238 if (poll.pending()) return Pending{};
239 return static_cast<T>(std::move(poll.value()));
240 }
241 };
242
243 template <typename T, typename U>
244 struct PollCastImpl<T, U, std::enable_if<!PollTraits<U>::is_poll()>> {
245 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static Poll<T> Cast(U&& poll) {
246 return Poll<T>(T(std::move(poll)));
247 }
248 };
249
250 template <typename T>
251 struct PollCastImpl<T, T> {
252 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static Poll<T> Cast(T&& poll) {
253 return Poll<T>(std::move(poll));
254 }
255 };
256
257 template <typename T>
258 struct PollCastImpl<T, Poll<T>> {
259 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static Poll<T> Cast(Poll<T>&& poll) {
260 return std::move(poll);
261 }
262 };
263
264 template <typename T, typename U>
265 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline Poll<T> poll_cast(U poll) {
266 return PollCastImpl<T, U>::Cast(std::move(poll));
267 }
268
269 // Convert a poll to a string
270 template <typename T, typename F>
271 std::string PollToString(
272 const Poll<T>& poll,
273 F t_to_string = [](const T& t) { return t.ToString(); }) {
274 if (poll.pending()) {
275 return "<<pending>>";
276 }
277 return t_to_string(poll.value());
278 }
279
280 template <typename Sink, typename T>
281 void AbslStringify(Sink& sink, const Poll<T>& poll) {
282 if (poll.pending()) {
283 absl::Format(&sink, "<<pending>>");
284 return;
285 }
286 absl::Format(&sink, "%v", poll.value());
287 }
288
289 template <typename Sink, typename T>
290 void AbslStringify(Sink& sink, const Poll<absl::StatusOr<T>>& poll) {
291 if (poll.pending()) {
292 absl::Format(&sink, "<<pending>>");
293 return;
294 }
295 if (poll.value().ok()) {
296 absl::Format(&sink, "%v", *poll.value());
297 } else {
298 absl::Format(&sink, "%v", poll.value().status());
299 }
300 }
301
302 template <typename Sink, typename T>
303 void AbslStringify(Sink& sink, const Poll<absl::optional<T>>& poll) {
304 if (poll.pending()) {
305 absl::Format(&sink, "<<pending>>");
306 return;
307 }
308 const auto& value = poll.value();
309 if (value.has_value()) {
310 absl::Format(&sink, "%v", value);
311 } else {
312 sink.append("nullopt");
313 }
314 }
315
316 // Hack to get metadata printing
317 template <typename Sink, typename T, typename Deleter>
318 void AbslStringify(
319 Sink& sink, const Poll<absl::optional<std::unique_ptr<T, Deleter>>>& poll) {
320 if (poll.pending()) {
321 absl::Format(&sink, "<<pending>>");
322 return;
323 }
324 const auto& value = poll.value();
325 if (value.has_value()) {
326 absl::Format(&sink, "%v", *value);
327 } else {
328 sink.Append("nullopt");
329 }
330 }
331
332 } // namespace grpc_core
333
334 #endif // GRPC_SRC_CORE_LIB_PROMISE_POLL_H
335