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