1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef INCLUDE_PERFETTO_EXT_BASE_THREADING_POLL_H_ 18 #define INCLUDE_PERFETTO_EXT_BASE_THREADING_POLL_H_ 19 20 #include <optional> 21 22 #include <variant> 23 #include "perfetto/base/flat_set.h" 24 #include "perfetto/base/platform_handle.h" 25 26 namespace perfetto { 27 namespace base { 28 29 // Forward declarations. 30 class PollContext; 31 32 // "Void" type for futures: this type can be used when a Future/Stream wants 33 // to return no value. We cannot use void directly because it causes all sorts 34 // of subtle issues with templates. 35 struct FVoid {}; 36 37 // Indicates that the Future is not ready to produce data at the moment but 38 // will do so at a later date. 39 struct PendingPollResult {}; 40 41 // Return value of Future<T>::Poll. 42 // 43 // Essentially a wrapper around std::variant<T, PendingPollResult> but with 44 // higher level API. 45 template <typename T> 46 class FuturePollResult { 47 public: 48 using PollT = T; 49 50 // Intentionally implicit to allow idiomatic returns. FuturePollResult(const PendingPollResult &)51 FuturePollResult(const PendingPollResult&) : inner_(PendingPollResult()) {} FuturePollResult(T item)52 FuturePollResult(T item) noexcept : inner_(std::move(item)) {} 53 54 // Returns whether the Future is still pending. IsPending()55 bool IsPending() const { 56 return std::holds_alternative<PendingPollResult>(inner_); 57 } 58 59 // The real value inside this result: requires !IsPending(). item()60 T& item() { 61 PERFETTO_DCHECK(!IsPending()); 62 return std::get<T>(inner_); 63 } item()64 const T& item() const { 65 PERFETTO_DCHECK(!IsPending()); 66 return std::get<T>(inner_); 67 } 68 69 // The real value inside this result: requires !IsPending(). 70 T* operator->() { return &item(); } 71 const T* operator->() const { return &item(); } 72 73 private: 74 std::variant<PendingPollResult, T> inner_; 75 }; 76 77 // Interface for implementing the Future<T>::Poll function. 78 // 79 // This is essentially a variant of the common PIMPL (pointer to impl) pattern 80 // used in C++ to allow having different implementations for Future<T>::Poll. 81 // 82 // We are using this instead of having an abstract function in Future to avoid 83 // having to wrap Future in unique_ptr everywhere it's used. 84 // 85 // We could have used std::function<Result(PollContext*)> but not all 86 // implementations of FuturePollable are copyable. If we had C++23, we could use 87 // std::move_only_function but we are some years from being able to do that. 88 template <typename T> 89 class FuturePollable { 90 public: 91 using PollT = T; 92 93 virtual ~FuturePollable() = default; 94 95 // Implementation of the Poll function of a Future: see Future documentation 96 // for how this should be implemented. 97 virtual FuturePollResult<T> Poll(PollContext*) = 0; 98 }; 99 100 // Indicates that the Stream has been exhausted and no more values will be 101 // returned. 102 struct DonePollResult {}; 103 104 // Return value of Stream<T>::Poll. 105 // 106 // Essentially a wrapper around std::variant<T, PendingPollResult, 107 // DonePollResult> but with higher level API. 108 template <typename T> 109 class StreamPollResult { 110 public: 111 using PollT = T; 112 113 // Intentionally implicit to allow idiomatic returns. StreamPollResult(const PendingPollResult &)114 StreamPollResult(const PendingPollResult&) : inner_(PendingPollResult()) {} StreamPollResult(const DonePollResult &)115 StreamPollResult(const DonePollResult&) : inner_(DonePollResult()) {} StreamPollResult(T item)116 StreamPollResult(T item) : inner_(std::move(item)) {} 117 118 // Returns whether the Stream is still pending. IsPending()119 bool IsPending() const { 120 return std::holds_alternative<PendingPollResult>(inner_); 121 } 122 123 // Returns whether the Stream is done. IsDone()124 bool IsDone() const { return std::holds_alternative<DonePollResult>(inner_); } 125 126 // The real value inside this result: requires !IsPending() and !IsDone(). item()127 T& item() { 128 PERFETTO_DCHECK(!IsPending()); 129 PERFETTO_DCHECK(!IsDone()); 130 return std::get<T>(inner_); 131 } item()132 const T& item() const { 133 PERFETTO_DCHECK(!IsPending()); 134 PERFETTO_DCHECK(!IsDone()); 135 return std::get<T>(inner_); 136 } 137 138 // The real value inside this result: requires !IsPending() and !IsDone(). 139 T* operator->() { return &item(); } 140 const T* operator->() const { return &item(); } 141 142 private: 143 std::variant<PendingPollResult, DonePollResult, T> inner_; 144 }; 145 146 // Interface for implementing the Stream<T>::Poll function. 147 // 148 // This is essentially analagous to FuturePollable<T> for streams: check the 149 // documentation of that class for why this exists. 150 template <typename T> 151 class StreamPollable { 152 public: 153 using PollT = T; 154 155 virtual ~StreamPollable() = default; 156 157 // Implementation of the Poll function of a Stream: see Stream documentation 158 // for how this should be implemented. 159 virtual StreamPollResult<T> PollNext(PollContext*) = 0; 160 }; 161 162 // Context class passed to Pollable classes. 163 // 164 // Implementations of Pollable which simply wrap another Pollable will use 165 // this as an opaque parameter to pass on. 166 // 167 // "Source" pollables (i.e. Pollables dealing directly with FDs) should call 168 // |RegisterInterested| when the FD returns EAGAIN/EWOULDBLOCK with the 169 // PollContext passed in. 170 class PollContext { 171 public: PollContext(FlatSet<PlatformHandle> * interested_handles,const FlatSet<PlatformHandle> * ready_handles)172 explicit PollContext(FlatSet<PlatformHandle>* interested_handles, 173 const FlatSet<PlatformHandle>* ready_handles) 174 : interested_handles_(interested_handles), 175 ready_handles_(ready_handles) {} 176 177 PollContext(PollContext&&) = default; 178 PollContext& operator=(PollContext&&) = default; 179 180 // Called by implementations of Future<T> to indicate that Poll should be 181 // called again when |handle(s)| are ready for reading (or have been closed). RegisterInterested(PlatformHandle handle)182 void RegisterInterested(PlatformHandle handle) { 183 interested_handles_->insert(handle); 184 } RegisterAllInterested(const FlatSet<PlatformHandle> & handles)185 void RegisterAllInterested(const FlatSet<PlatformHandle>& handles) { 186 for (PlatformHandle handle : handles) { 187 RegisterInterested(handle); 188 } 189 } 190 191 // Returns a set of all the fds which were marked as "ready" by the operating 192 // system (i.e. POLLIN/POLLHUP on Linux). ready_handles()193 const FlatSet<PlatformHandle>& ready_handles() const { 194 return *ready_handles_; 195 } 196 197 private: 198 PollContext(const PollContext&) = delete; 199 PollContext& operator=(const PollContext&) = delete; 200 201 FlatSet<PlatformHandle>* interested_handles_ = nullptr; 202 const FlatSet<PlatformHandle>* ready_handles_ = nullptr; 203 }; 204 205 // Evaluates |expr|, which should return a FuturePollResult. If IsPending is 206 // true, returns base::PendingPollResult(). 207 // 208 // Example usage: 209 // 210 // Future<int> MyIntReturningFutureFn(); 211 // 212 // FuturePollResult<std::string> Poll(PollContext* ctx) { 213 // // res will be of type "int" 214 // ASSIGN_OR_RETURN_IF_PENDING_FUTURE(res, MyIntReturningFutureFn()); 215 // return std::to_string(*foo); 216 // } 217 #define ASSIGN_OR_RETURN_IF_PENDING_FUTURE(var, expr) \ 218 auto assign_and_return_if_poll_##var = (expr); \ 219 if (assign_and_return_if_poll_##var.IsPending()) \ 220 return base::PendingPollResult(); \ 221 auto var = std::move(assign_and_return_if_poll_##var.item()) 222 223 // Evaluates |expr|, which should return a PollResult. If IsPending is 224 // true, returns base::PendingPollResult(). 225 // 226 // Example usage: 227 // 228 // Strean<int> MyIntReturningStreamFn(); 229 // 230 // StreamPollResult<std::string> Poll(PollContext* ctx) { 231 // ASSIGN_OR_RETURN_IF_PENDING_STREAM(res, MyIntReturningStreamFn()); 232 // if (res.IsDone()) { 233 // return DonePollResult(); 234 // } 235 // return std::to_string(*foo); 236 // } 237 #define ASSIGN_OR_RETURN_IF_PENDING_STREAM(var, expr) \ 238 auto var = (expr); \ 239 if (var.IsPending()) \ 240 return base::PendingPollResult() 241 242 } // namespace base 243 } // namespace perfetto 244 245 #endif // INCLUDE_PERFETTO_EXT_BASE_THREADING_POLL_H_ 246