1 /** 2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd. 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 PANDA_RUNTIME_COROUTINES_COROUTINE_H 16 #define PANDA_RUNTIME_COROUTINES_COROUTINE_H 17 18 #include <atomic> 19 #include <optional> 20 #include "runtime/coroutines/coroutine_events.h" 21 #include "runtime/include/runtime.h" 22 #include "runtime/include/managed_thread.h" 23 #include "runtime/include/callback_queue.h" 24 25 namespace ark { 26 27 class CoroutineContext; 28 class CompletionEvent; 29 /** 30 * @brief The base class for all coroutines. Holds the managed part of the coroutine context. 31 * 32 * The coroutine context is splitted into managed and native parts. 33 * The managed part is store in this class and its descendants. For the native part see the 34 * CoroutineContext class and its descendants. 35 */ 36 class Coroutine : public ManagedThread { 37 public: 38 NO_COPY_SEMANTIC(Coroutine); 39 NO_MOVE_SEMANTIC(Coroutine); 40 41 /** 42 * Status transitions: 43 * 44 * +---------+ +----------+ 45 * | CREATED | -------------------------------> | | <-------------+ 46 * +---------+ | RUNNABLE | | 47 * +--- | | <--+ | 48 * | +----------+ | | 49 * | | | 50 * | +----------+ | +----------+ 51 * +--> | | ---+ | | 52 * +------------+ +-------------+ | RUNNING | | BLOCKED | 53 * | AWAIT_LOOP | <--- | TERMINATING | <------- | | -------> | | 54 * +------------+ +-------------+ +----------+ +----------+ 55 * 56 * 57 * Main coroutine gets AWAIT_LOOP status once it starts the final waiting loop. After all 58 * other coroutines are completed, the main coroutine exits. 59 */ 60 enum class Status { CREATED, RUNNABLE, RUNNING, BLOCKED, TERMINATING, AWAIT_LOOP }; 61 62 /// Needed for object locking 63 static constexpr ThreadId MAX_COROUTINE_ID = MarkWord::LIGHT_LOCK_THREADID_MAX_COUNT; 64 65 /// A helper struct that aggregates all EP related data for a coroutine with a managed EP 66 struct ManagedEntrypointInfo { 67 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 68 CompletionEvent *completionEvent; ///< not owned by this structure, just passed! 69 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 70 Method *entrypoint; 71 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 72 PandaVector<Value> &&arguments; 73 74 /** 75 * @param event an instance of CompletionEvent to be used on coroutine completion to pass the 76 * return value to the point where it is needed. Also is used to unblock the coroutines that are waiting for 77 * the one being created to complete. 78 * 79 * @param entry managed method to execute in the context of coroutine. 80 * 81 * @param args the array of EP method arguments 82 */ ManagedEntrypointInfoManagedEntrypointInfo83 explicit ManagedEntrypointInfo(CompletionEvent *event, Method *entry, PandaVector<Value> &&args) 84 : completionEvent(event), entrypoint(entry), arguments(std::move(args)) 85 { 86 ASSERT(event != nullptr); 87 ASSERT(entry != nullptr); 88 } 89 }; 90 91 /// a helper struct that aggregates all EP related data for a coroutine with a native EP 92 struct NativeEntrypointInfo { 93 using NativeEntrypointFunc = void (*)(void *); 94 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 95 NativeEntrypointFunc entrypoint; 96 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 97 void *param; 98 99 /** 100 * @param entry native function to execute in the context of coroutine 101 * 102 * @param arguments a parameter which will be passed to the entrypoint (usually some object pointer) 103 */ NativeEntrypointInfoNativeEntrypointInfo104 explicit NativeEntrypointInfo(NativeEntrypointFunc entry, void *data) : entrypoint(entry), param(data) 105 { 106 ASSERT(data != nullptr); 107 ASSERT(entry != nullptr); 108 } 109 }; 110 111 using EntrypointInfo = std::variant<ManagedEntrypointInfo, NativeEntrypointInfo>; 112 113 /** 114 * The coroutine factory: creates and initializes a coroutine instance. The preferred way to create a 115 * coroutine. For details see CoroutineManager::CoroutineFactory 116 */ 117 static Coroutine *Create(Runtime *runtime, PandaVM *vm, PandaString name, CoroutineContext *context, 118 std::optional<EntrypointInfo> &&epInfo = std::nullopt); 119 ~Coroutine() override; 120 121 /// Should be called after creation in order to create native context and do other things 122 virtual void Initialize(); 123 /** 124 * Coroutine reinitialization: semantically equivalent to Initialize(), 125 * but is called to prepare a cached Coroutine instance for reuse when it is needed. 126 * Implies that the CleanUp() method was called before caching. 127 */ 128 void ReInitialize(PandaString name, CoroutineContext *context, std::optional<EntrypointInfo> &&epInfo); 129 /** 130 * Manual destruction, applicable only to the main coro. Other ones get deleted by the coroutine manager once they 131 * finish execution of their entrypoint method. 132 */ 133 void Destroy(); 134 135 void CleanUp() override; 136 137 bool RetrieveStackInfo(void *&stackAddr, size_t &stackSize, size_t &guardSize) override; 138 ThreadIsCoroutine(Thread * thread)139 static bool ThreadIsCoroutine(Thread *thread) 140 { 141 ASSERT(thread != nullptr); 142 // NOTE(konstanting, #I67QXC): THREAD_TYPE_TASK -> THREAD_TYPE_COROUTINE and 143 // remove the runtime/scheduler directory contents 144 return thread->GetThreadType() == Thread::ThreadType::THREAD_TYPE_TASK; 145 } 146 CastFromThread(Thread * thread)147 static Coroutine *CastFromThread(Thread *thread) 148 { 149 ASSERT(thread != nullptr); 150 ASSERT(ThreadIsCoroutine(thread)); 151 return static_cast<Coroutine *>(thread); 152 } 153 GetCurrent()154 static Coroutine *GetCurrent() 155 { 156 Thread *thread = Thread::GetCurrent(); 157 ASSERT(thread != nullptr); 158 if (ThreadIsCoroutine(thread)) { 159 return CastFromThread(thread); 160 } 161 return nullptr; 162 } 163 164 /// Get coroutine status. It is independent from ThreadStatus. 165 Status GetCoroutineStatus() const; 166 /// Get coroutine name. 167 PandaString GetName() const; 168 /// Get unique coroutine ID GetCoroutineId()169 uint32_t GetCoroutineId() const 170 { 171 return coroutineId_; 172 } 173 174 /** 175 * Suspend a coroutine, so its status becomes either Status::RUNNABLE or Status::BLOCKED, depending on the suspend 176 * reason. 177 */ 178 virtual void RequestSuspend(bool getsBlocked); 179 /// Resume the suspended coroutine, so its status becomes Status::RUNNING. 180 virtual void RequestResume(); 181 /// Unblock the blocked coroutine, setting its status to Status::RUNNABLE 182 virtual void RequestUnblock(); 183 /** 184 * @brief Indicate that coroutine entrypoint execution is finished. Propagates the coroutine 185 * return value to language level objects. 186 */ 187 virtual void RequestCompletion(Value returnValue); 188 189 /// Get the CompletionEvent instance GetCompletionEvent()190 CompletionEvent *GetCompletionEvent() 191 { 192 return std::get<ManagedEntrypointData>(entrypoint_).completionEvent; 193 } 194 HasManagedEntrypoint()195 bool HasManagedEntrypoint() const 196 { 197 return std::holds_alternative<ManagedEntrypointData>(entrypoint_); 198 } 199 200 /// Get coroutine's managed entrypoint method. GetManagedEntrypoint()201 Method *GetManagedEntrypoint() const 202 { 203 ASSERT(HasManagedEntrypoint()); 204 return std::get<ManagedEntrypointData>(entrypoint_).entrypoint; 205 } 206 207 /// Get coroutine's managed entrypoint args if any. GetManagedEntrypointArguments()208 PandaVector<Value> &GetManagedEntrypointArguments() 209 { 210 ASSERT(HasManagedEntrypoint()); 211 return std::get<ManagedEntrypointData>(entrypoint_).arguments; 212 } 213 GetManagedEntrypointArguments()214 const PandaVector<Value> &GetManagedEntrypointArguments() const 215 { 216 ASSERT(HasManagedEntrypoint()); 217 return std::get<ManagedEntrypointData>(entrypoint_).arguments; 218 } 219 HasNativeEntrypoint()220 bool HasNativeEntrypoint() const 221 { 222 return std::holds_alternative<NativeEntrypointData>(entrypoint_); 223 } 224 225 /// Get coroutine's native entry function (if this is a "native" coroutine) GetNativeEntrypoint()226 NativeEntrypointInfo::NativeEntrypointFunc GetNativeEntrypoint() const 227 { 228 ASSERT(HasNativeEntrypoint()); 229 return std::get<NativeEntrypointData>(entrypoint_).entrypoint; 230 } 231 232 /// Get coroutine's native entry function parameter (if this is a "native" coroutine) GetNativeEntrypointParam()233 void *GetNativeEntrypointParam() const 234 { 235 ASSERT(HasNativeEntrypoint()); 236 return std::get<NativeEntrypointData>(entrypoint_).param; 237 } 238 239 template <class T> GetContext()240 T *GetContext() const 241 { 242 return static_cast<T *>(context_); 243 } 244 IsSuspendOnStartup()245 bool IsSuspendOnStartup() const 246 { 247 return startSuspended_; 248 } 249 250 /** 251 * Add callback to the callback queue. 252 * This method should be called for the current coroutine. 253 * Otherwise, see the method AcceptAnnouncedCallbacks. 254 */ AddCallback(PandaUniquePtr<Callback> callback)255 void AddCallback(PandaUniquePtr<Callback> callback) 256 { 257 ASSERT(this == Coroutine::GetCurrent()); 258 ASSERT(callbackQueue_ != nullptr); 259 callbackQueue_->Post(std::move(callback)); 260 } 261 262 /// Process callbacks of the callback queue ProcessPresentCallbacks()263 void ProcessPresentCallbacks() 264 { 265 ASSERT(callbackQueue_ != nullptr); 266 callbackQueue_->Process(); 267 } 268 269 /// Announce an event that the callback will be added to the queue in the future AnnounceCallbackAddition()270 void AnnounceCallbackAddition() 271 { 272 ASSERT(this == Coroutine::GetCurrent()); 273 // Atomic with relaxed order reason: data race with counter with no synchronization or ordering 274 // constraints 275 preparingCallbacks_.fetch_add(1, std::memory_order_relaxed); 276 } 277 278 /// Accept announced callbacks and unblock the blocked coroutine 279 void AcceptAnnouncedCallbacks(PandaList<PandaUniquePtr<Callback>> callbacks); 280 281 /// Process the current callbacks and wait until the announced callbacks are ready 282 void ProcessPresentAndAnnouncedCallbacks(); 283 284 /// Destroy the current callback queue and set a new one SetCallbackQueue(CallbackQueue * queue)285 void SetCallbackQueue(CallbackQueue *queue) 286 { 287 if (callbackQueue_ != nullptr) { 288 callbackQueue_->Destroy(); 289 } 290 callbackQueue_ = queue; 291 } 292 293 /** 294 * Check size of callback queue, return false and don't set awaitee event in case callback queue is not empty. 295 * Otherwise set awaitee event if some of callbacks are not ready yet. 296 */ 297 bool TryEnterAwaitModeAndLockAwaitee(CoroutineEvent *event) TRY_ACQUIRE(true, event); 298 299 /// Set awaitee event to nullptr. This method should be called from the current coroutine LeaveAwaitMode()300 void LeaveAwaitMode() 301 { 302 ASSERT(this == Coroutine::GetCurrent()); 303 awaiteeEvent_ = nullptr; 304 } 305 306 protected: 307 // We would like everyone to use the factory to create a Coroutine, thus ctor is protected 308 explicit Coroutine(ThreadId id, mem::InternalAllocatorPtr allocator, PandaVM *vm, 309 ark::panda_file::SourceLang threadLang, PandaString name, CoroutineContext *context, 310 CallbackQueue *queue, std::optional<EntrypointInfo> &&epInfo); 311 312 void SetCoroutineStatus(Status newStatus); 313 314 private: 315 /// a converter function that stores the data from EntrypointInfo in the member variables 316 void SetEntrypointData(std::optional<EntrypointInfo> &&epInfo); 317 318 void WaitTillAnnouncedCallbacksAreDelivered(); 319 320 PandaString name_; 321 uint32_t coroutineId_ = 0; 322 323 /// contains managed entrypoint parameters if the coroutine an EP and is "managed" 324 struct ManagedEntrypointData { 325 NO_COPY_SEMANTIC(ManagedEntrypointData); 326 NO_MOVE_SEMANTIC(ManagedEntrypointData); 327 328 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 329 CompletionEvent *completionEvent = nullptr; ///< is owned by this structure 330 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 331 Method *entrypoint = nullptr; 332 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 333 PandaVector<Value> arguments; 334 ManagedEntrypointDataManagedEntrypointData335 explicit ManagedEntrypointData(CompletionEvent *event, Method *entry, PandaVector<Value> &&args) 336 : completionEvent(event), entrypoint(entry), arguments(std::move(args)) 337 { 338 ASSERT(event != nullptr); 339 ASSERT(entry != nullptr); 340 } 341 342 ~ManagedEntrypointData(); 343 }; 344 /// contains native entrypoint parameters if the coroutine has an EP and is "native" 345 struct NativeEntrypointData { 346 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 347 NativeEntrypointInfo::NativeEntrypointFunc entrypoint; 348 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) 349 void *param; 350 NativeEntrypointDataNativeEntrypointData351 explicit NativeEntrypointData(NativeEntrypointInfo::NativeEntrypointFunc entry, void *data) 352 : entrypoint(entry), param(data) 353 { 354 ASSERT(data != nullptr); 355 ASSERT(entry != nullptr); 356 } 357 }; 358 std::variant<std::monostate, ManagedEntrypointData, NativeEntrypointData> entrypoint_; 359 360 CoroutineContext *context_ = nullptr; 361 // NOTE(konstanting, #I67QXC): check if we still need this functionality 362 bool startSuspended_ = false; 363 364 CallbackQueue *callbackQueue_ = nullptr; 365 os::memory::Mutex preparingCallbacksLock_; 366 GenericEvent callbacksEvent_; 367 std::atomic<uint32_t> preparingCallbacks_ = 0; 368 std::atomic<CoroutineEvent *> awaiteeEvent_ = nullptr; 369 370 // Allocator calls our protected ctor 371 friend class mem::Allocator; 372 friend class CoroutineContext; 373 }; 374 375 std::ostream &operator<<(std::ostream &os, Coroutine::Status status); 376 377 } // namespace ark 378 379 #endif // PANDA_RUNTIME_COROUTINES_COROUTINE_H 380