• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_LOOP_H
16 #define GRPC_SRC_CORE_LIB_PROMISE_LOOP_H
17 
18 #include <grpc/support/port_platform.h>
19 
20 #include <utility>
21 
22 #include "absl/status/status.h"
23 #include "absl/status/statusor.h"
24 #include "absl/types/variant.h"
25 #include "src/core/lib/debug/trace.h"
26 #include "src/core/lib/promise/detail/promise_factory.h"
27 #include "src/core/lib/promise/poll.h"
28 #include "src/core/util/construct_destruct.h"
29 
30 namespace grpc_core {
31 
32 // Special type - signals to loop to take another iteration, instead of
33 // finishing
34 struct Continue {};
35 
36 // Result of polling a loop promise - either Continue looping, or return a value
37 // T
38 template <typename T>
39 using LoopCtl = absl::variant<Continue, T>;
40 
41 namespace promise_detail {
42 
43 template <typename T>
44 struct LoopTraits;
45 
46 template <typename T>
47 struct LoopTraits<LoopCtl<T>> {
48   using Result = T;
49   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static LoopCtl<T> ToLoopCtl(
50       LoopCtl<T> value) {
51     return value;
52   }
53 };
54 
55 template <typename T>
56 struct LoopTraits<absl::StatusOr<LoopCtl<T>>> {
57   using Result = absl::StatusOr<T>;
58   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static LoopCtl<Result> ToLoopCtl(
59       absl::StatusOr<LoopCtl<T>> value) {
60     if (!value.ok()) return value.status();
61     auto& inner = *value;
62     if (absl::holds_alternative<Continue>(inner)) return Continue{};
63     return absl::get<T>(std::move(inner));
64   }
65 };
66 
67 template <>
68 struct LoopTraits<absl::StatusOr<LoopCtl<absl::Status>>> {
69   using Result = absl::Status;
70   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static LoopCtl<Result> ToLoopCtl(
71       absl::StatusOr<LoopCtl<absl::Status>> value) {
72     if (!value.ok()) return value.status();
73     const auto& inner = *value;
74     if (absl::holds_alternative<Continue>(inner)) return Continue{};
75     return absl::get<absl::Status>(inner);
76   }
77 };
78 
79 template <typename F>
80 class Loop {
81  private:
82   using Factory = promise_detail::RepeatedPromiseFactory<void, F>;
83   using PromiseType = decltype(std::declval<Factory>().Make());
84   using PromiseResult = typename PromiseType::Result;
85 
86  public:
87   using Result = typename LoopTraits<PromiseResult>::Result;
88 
89   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION explicit Loop(F f)
90       : factory_(std::move(f)) {}
91   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION ~Loop() {
92     if (started_) Destruct(&promise_);
93   }
94 
95   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Loop(Loop&& loop) noexcept
96       : factory_(std::move(loop.factory_)), started_(loop.started_) {
97     if (started_) Construct(&promise_, std::move(loop.promise_));
98   }
99 
100   Loop(const Loop& loop) = delete;
101   Loop& operator=(const Loop& loop) = delete;
102 
103   GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION Poll<Result> operator()() {
104     GRPC_TRACE_LOG(promise_primitives, INFO)
105         << "loop[" << this << "] begin poll started=" << started_;
106     if (!started_) {
107       started_ = true;
108       Construct(&promise_, factory_.Make());
109     }
110     while (true) {
111       // Poll the inner promise.
112       auto promise_result = promise_();
113       // If it returns a value:
114       if (auto* p = promise_result.value_if_ready()) {
115         //  - then if it's Continue, destroy the promise and recreate a new one
116         //  from our factory.
117         auto lc = LoopTraits<PromiseResult>::ToLoopCtl(std::move(*p));
118         if (absl::holds_alternative<Continue>(lc)) {
119           GRPC_TRACE_LOG(promise_primitives, INFO)
120               << "loop[" << this << "] iteration complete, continue";
121           Destruct(&promise_);
122           Construct(&promise_, factory_.Make());
123           continue;
124         }
125         GRPC_TRACE_LOG(promise_primitives, INFO)
126             << "loop[" << this << "] iteration complete, return";
127         //  - otherwise there's our result... return it out.
128         return absl::get<Result>(std::move(lc));
129       } else {
130         // Otherwise the inner promise was pending, so we are pending.
131         GRPC_TRACE_LOG(promise_primitives, INFO)
132             << "loop[" << this << "] pending";
133         return Pending();
134       }
135     }
136   }
137 
138  private:
139   GPR_NO_UNIQUE_ADDRESS Factory factory_;
140   GPR_NO_UNIQUE_ADDRESS union {
141     GPR_NO_UNIQUE_ADDRESS PromiseType promise_;
142   };
143   bool started_ = false;
144 };
145 
146 }  // namespace promise_detail
147 
148 // Looping combinator.
149 // Expects F returns LoopCtl<T> - if it's Continue, then run the loop again -
150 // otherwise yield the returned value as the result of the loop.
151 template <typename F>
152 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION inline promise_detail::Loop<F> Loop(F f) {
153   return promise_detail::Loop<F>(std::move(f));
154 }
155 
156 }  // namespace grpc_core
157 
158 #endif  // GRPC_SRC_CORE_LIB_PROMISE_LOOP_H
159