1 /** 2 * Copyright (c) 2023-2025 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_MANAGER_H 16 #define PANDA_RUNTIME_COROUTINES_COROUTINE_MANAGER_H 17 18 #include "runtime/coroutines/coroutine_context.h" 19 #include "runtime/thread_manager.h" 20 #include "runtime/include/runtime.h" 21 #include "runtime/coroutines/coroutine.h" 22 #include "runtime/coroutines/coroutine_events.h" 23 24 namespace ark { 25 26 /// @brief describes the set of adjustable parameters for CoroutineManager and its descendants initialization 27 struct CoroutineManagerConfig { 28 static constexpr uint32_t WORKERS_COUNT_AUTO = 0; 29 30 /// enable the experimental task execution interface 31 bool enableDrainQueueIface = false; 32 /// enable migration 33 bool enableMigration = false; 34 /// migrate coroutines that resumed from wait 35 bool migrateAwakenedCoros = false; 36 /// Number of coroutine workers for the N:M mode 37 uint32_t workersCount = WORKERS_COUNT_AUTO; 38 /// Limit on the number of exclusive coroutines workers 39 uint32_t exclusiveWorkersLimit = 0; 40 /// Collection of performance statistics 41 bool enablePerfStats = false; 42 }; 43 44 /// @brief defines the requested launch mode for a coroutine 45 enum class CoroutineLaunchMode { 46 /// no specific requests 47 DEFAULT, 48 /// schedule to the parent's worker only 49 SAME_WORKER, 50 /// schedule to the main worker only 51 MAIN_WORKER, 52 /// schedule exclusively, moving other coros off the target worker 53 EXCLUSIVE 54 }; 55 56 /// @brief defines the scheduling policy for a coroutine. Maybe in future we would like to add more types. 57 enum class CoroutineSchedulingPolicy { 58 /// choose the least busy worker 59 ANY_WORKER, 60 /** 61 * same as any_worker but exclude the main worker from available hosts on launch and 62 * disallow non_main -> main transitions on migration 63 */ 64 NON_MAIN_WORKER 65 }; 66 67 /// @brief defines the selection policy for a worker. 68 enum class WorkerSelectionPolicy { 69 /// choose the least busy worker 70 LEAST_LOADED, 71 /// choose the busiest worker 72 MOST_LOADED 73 }; 74 75 /** 76 * @brief The interface of all coroutine manager implementations. 77 * 78 * Manages (registers, unregisters, enumerates) and schedules coroutines for execution using the worker threads. 79 * Creates and destroys the main coroutine. 80 * Provides interfaces for coroutine synchronization. 81 */ 82 class CoroutineManager : public ThreadManager { 83 public: 84 /** 85 * @brief The coroutine factory interface. 86 * 87 * @param name the coroutine name (for debugging and logging purposes) 88 * 89 * @param ctx the instance of implementation-dependend native coroutine context. Usually is provided by the 90 * concrete CoroutineManager descendant. 91 * 92 * @param ep_info if provided (that means, ep_info != std::nullopt), defines the coroutine entry point to execute. 93 * It can be either (the bytecode method, its arguments and the completion event instance to hold the method return 94 * value) or (a native function and its parameter). See Coroutine::EntrypointInfo for details. If this parameter is 95 * std::nullopt (i.e. no entrypoint present) then the following rules apply: 96 * - the factory should only create the coroutine instance and expect that the bytecode for the coroutine will be 97 * invoked elsewhere within the context of the newly created coroutine 98 * - the coroutine should be destroyed manually by the runtime by calling the Destroy() method 99 * - the coroutine does not use the method/arguments passing interface and the completion event interface 100 * - no other coroutine will await this one (although it can await others). 101 * The "main" coroutine (the EP of the application) is the specific example of such "no entrypoint" coroutine 102 * If ep_info is provided then the newly created coroutine will execute the specified method and do all the 103 * initialization/finalization steps, including completion_event management and notification of waiters. 104 * 105 * @param type type of work, which the coroutine performs: whether it is a mutator, a schedule loop or some other 106 * thing 107 */ 108 using CoroutineFactory = Coroutine *(*)(Runtime *runtime, PandaVM *vm, PandaString name, CoroutineContext *ctx, 109 std::optional<Coroutine::EntrypointInfo> &&epInfo, Coroutine::Type type, 110 CoroutinePriority priority); 111 using NativeEntrypointFunc = Coroutine::NativeEntrypointInfo::NativeEntrypointFunc; 112 113 NO_COPY_SEMANTIC(CoroutineManager); 114 NO_MOVE_SEMANTIC(CoroutineManager); 115 116 /// Factory is used to create coroutines when needed. See CoroutineFactory for details. 117 explicit CoroutineManager(CoroutineFactory factory); 118 ~CoroutineManager() override = default; 119 120 /** 121 * @brief Should be called after CoroutineManager creation and before any other method calls. 122 * Initializes internal structures, creates the main coroutine. 123 * 124 * @param config describes the CoroutineManager operation mode 125 */ 126 virtual void Initialize(CoroutineManagerConfig config, Runtime *runtime, PandaVM *vm) = 0; 127 /// Should be called after all execution is finished. Destroys the main coroutine. 128 virtual void Finalize() = 0; 129 /// Add coroutine to registry (used for enumeration and tracking) and perform all the required actions 130 virtual void RegisterCoroutine(Coroutine *co) = 0; 131 /** 132 * @brief Remove coroutine from all internal structures, notify waiters about its completion, correctly 133 * delete coroutine and free its resources 134 * @return returnes true if coroutine has been deleted, false otherwise (e.g. in case of terminating main) 135 */ 136 virtual bool TerminateCoroutine(Coroutine *co) = 0; 137 /** 138 * @brief The public coroutine creation interface. 139 * 140 * @param completionEvent the event used for notification when coroutine completes (also used to pass the return 141 * value to the language-level entities) 142 * @param entrypoint the coroutine entrypoint method 143 * @param arguments array of coroutine's entrypoint arguments 144 * @param abortFlag if true, finishing with an exception will abort the program 145 */ 146 virtual bool Launch(CompletionEvent *completionEvent, Method *entrypoint, PandaVector<Value> &&arguments, 147 CoroutineLaunchMode mode, CoroutinePriority priority, bool abortFlag) = 0; 148 /** 149 * @brief The public coroutine creation and execution interface. Switching to the newly created coroutine occurs 150 * immediately. Coroutine launch mode should correspond to the use of parent's worker. 151 * 152 * @param completionEvent the event used for notification when coroutine completes (also used to pass the return 153 * value to the language-level entities) 154 * @param entrypoint the coroutine entrypoint method 155 * @param arguments array of coroutine's entrypoint arguments 156 */ 157 virtual bool LaunchImmediately(CompletionEvent *completionEvent, Method *entrypoint, PandaVector<Value> &&arguments, 158 CoroutineLaunchMode mode, CoroutinePriority priority, bool abortFlag) = 0; 159 160 /** 161 * @brief The public coroutine creation and execution interface with native entrypoint. 162 * 163 * @param epFunc the native function of coroutine entrypoint 164 * @param param the argument of coroutine entrypoint 165 * @param coroName the name of launching coroutine 166 * NOTE: native function can have Managed scopes 167 */ 168 virtual bool LaunchNative(NativeEntrypointFunc epFunc, void *param, PandaString coroName, CoroutineLaunchMode mode, 169 CoroutinePriority priority, bool abortFlag) = 0; 170 /// Suspend the current coroutine and schedule the next ready one for execution 171 virtual void Schedule() = 0; 172 /** 173 * @brief Move the current coroutine to the waiting state until awaitee happens and schedule the 174 * next ready coroutine for execution. 175 */ 176 virtual void Await(CoroutineEvent *awaitee) RELEASE(awaitee) = 0; 177 /** 178 * @brief Notify the waiting coroutines that an event has happened, so they can stop waiting and 179 * become ready for execution 180 * @param blocker the blocking event which transitioned from pending to happened state 181 * 182 * NB! this functions deletes @param blocker (assuming that it was allocated via InternalAllocator)! 183 */ 184 virtual void UnblockWaiters(CoroutineEvent *blocker) = 0; 185 186 /** 187 * The designated interface for creating the main coroutine instance. 188 * The program EP will be invoked within its context. 189 */ 190 Coroutine *CreateMainCoroutine(Runtime *runtime, PandaVM *vm); 191 /// Delete the main coroutine instance and free its resources 192 void DestroyMainCoroutine(); 193 194 /** 195 * Create a coroutine instance (including the context) for internal purposes (e.g. verifier) and 196 * set it as the current one. 197 * The created coroutine instance will not have any method to execute. All the control flow must be managed 198 * by the caller. 199 * 200 * @return nullptr if resource limit reached or something went wrong; ptr to the coroutine otherwise 201 */ 202 Coroutine *CreateEntrypointlessCoroutine(Runtime *runtime, PandaVM *vm, bool makeCurrent, PandaString name, 203 Coroutine::Type type, CoroutinePriority priority); 204 void DestroyEntrypointlessCoroutine(Coroutine *co); 205 206 /// Destroy a coroutine with an entrypoint 207 virtual void DestroyEntrypointfulCoroutine(Coroutine *co); 208 209 /// @return unique coroutine id 210 virtual uint32_t AllocateCoroutineId(); 211 /// mark the specified @param id as free 212 virtual void FreeCoroutineId(uint32_t id); 213 214 void SetSchedulingPolicy(CoroutineSchedulingPolicy policy); 215 CoroutineSchedulingPolicy GetSchedulingPolicy() const; 216 217 virtual bool IsMainWorker(Coroutine *coro) const = 0; 218 CreateExclusiveWorkerForThread(Runtime * runtime,PandaVM * vm)219 virtual Coroutine *CreateExclusiveWorkerForThread([[maybe_unused]] Runtime *runtime, [[maybe_unused]] PandaVM *vm) 220 { 221 return nullptr; 222 } 223 DestroyExclusiveWorker()224 virtual bool DestroyExclusiveWorker() 225 { 226 return false; 227 } 228 229 /** 230 * @brief This method creates the required number of worker threads 231 * @param howMany - number of common workers to be created 232 */ CreateWorkers(size_t howMany,Runtime * runtime,PandaVM * vm)233 virtual void CreateWorkers([[maybe_unused]] size_t howMany, [[maybe_unused]] Runtime *runtime, 234 [[maybe_unused]] PandaVM *vm) 235 { 236 } 237 238 /** 239 * @brief This method finalizes the required number of common worker threads 240 * @param howMany - number of common workers to be finalized 241 * NOTE: Make sure that howMany is less than the number of active workers 242 */ FinalizeWorkers(size_t howMany,Runtime * runtime,PandaVM * vm)243 virtual void FinalizeWorkers([[maybe_unused]] size_t howMany, [[maybe_unused]] Runtime *runtime, 244 [[maybe_unused]] PandaVM *vm) 245 { 246 } 247 IsExclusiveWorkersLimitReached()248 virtual bool IsExclusiveWorkersLimitReached() const 249 { 250 return false; 251 } 252 253 /* events */ 254 /// Should be called when a coro makes the non_active->active transition (see the state diagram in coroutine.h) OnCoroBecameActive(Coroutine * co)255 virtual void OnCoroBecameActive([[maybe_unused]] Coroutine *co) {}; 256 /** 257 * Should be called when a running coro is being blocked or terminated, i.e. makes 258 * the active->non_active transition (see the state diagram in coroutine.h) 259 */ OnCoroBecameNonActive(Coroutine * co)260 virtual void OnCoroBecameNonActive([[maybe_unused]] Coroutine *co) {}; 261 /// Should be called at the beginning of the VM native interface call OnNativeCallEnter(Coroutine * co)262 virtual void OnNativeCallEnter([[maybe_unused]] Coroutine *co) {}; 263 /// Should be called at the end of the VM native interface call OnNativeCallExit(Coroutine * co)264 virtual void OnNativeCallExit([[maybe_unused]] Coroutine *co) {}; 265 266 /* debugging tools */ 267 /** 268 * Disable coroutine switch for the current worker. 269 * If an attempt to switch the active coroutine is performed when coroutine switch is disabled, the exact actions 270 * are defined by the concrete CoroutineManager implementation (they could include no-op, program halt or something 271 * else). 272 * 273 * NOTE(konstanting): consider extending this interface to allow for disabling the individual coroutine 274 * operations, like CoroutineOperation::LAUNCH, AWAIT, SCHEDULE, ALL 275 */ 276 virtual void DisableCoroutineSwitch() = 0; 277 /// Enable coroutine switch for the current worker. 278 virtual void EnableCoroutineSwitch() = 0; 279 /// @return true if coroutine switch for the current worker is disabled 280 virtual bool IsCoroutineSwitchDisabled() = 0; 281 282 /* ZygoteFork operations */ 283 /// Called before Zygote fork to clean up and stop all worker threads. 284 virtual void PreZygoteFork() = 0; 285 /// Called after Zygote fork to reinitialize and restart worker threads. 286 virtual void PostZygoteFork() = 0; 287 288 protected: 289 using EntrypointInfo = Coroutine::EntrypointInfo; 290 /// Create native coroutine context instance (implementation dependent) 291 virtual CoroutineContext *CreateCoroutineContext(bool coroHasEntrypoint) = 0; 292 /// Delete native coroutine context instance (implementation dependent) 293 virtual void DeleteCoroutineContext(CoroutineContext *) = 0; 294 /** 295 * Create coroutine instance including the native context and link the context and the coroutine. 296 * The coroutine is created using the factory provided in the CoroutineManager constructor and the virtual 297 * context creation function, which should be defined in concrete CoroutineManager implementations. 298 * 299 * @return nullptr if resource limit reached or something went wrong; ptr to the coroutine otherwise 300 */ 301 Coroutine *CreateCoroutineInstance(EntrypointInfo &&epInfo, PandaString name, Coroutine::Type type, 302 CoroutinePriority priority); 303 /// Returns number of existing coroutines 304 virtual size_t GetCoroutineCount() = 0; 305 /** 306 * @brief returns number of coroutines that could be created till the resource limit is reached. 307 * The resource limit definition is specific to the exact coroutines/coroutine manager implementation. 308 */ 309 virtual size_t GetCoroutineCountLimit() = 0; 310 311 /// Can be used in descendants to create custom coroutines manually 312 CoroutineFactory GetCoroutineFactory(); 313 314 /// limit the number of IDs for performance reasons 315 static constexpr size_t MAX_COROUTINE_ID = std::min(0xffffU, Coroutine::MAX_COROUTINE_ID); 316 static constexpr size_t UNINITIALIZED_COROUTINE_ID = 0x0U; 317 318 private: 319 CoroutineFactory coFactory_ = nullptr; 320 321 mutable os::memory::Mutex policyLock_; 322 CoroutineSchedulingPolicy schedulingPolicy_ GUARDED_BY(policyLock_) = CoroutineSchedulingPolicy::NON_MAIN_WORKER; 323 324 // coroutine id management 325 os::memory::Mutex idsLock_; 326 std::bitset<MAX_COROUTINE_ID> coroutineIds_ GUARDED_BY(idsLock_); 327 uint32_t lastCoroutineId_ GUARDED_BY(idsLock_) = UNINITIALIZED_COROUTINE_ID; 328 }; 329 330 /// Disables coroutine switch on the current worker for some scope. Can be used recursively. 331 class ScopedDisableCoroutineSwitch { 332 public: ScopedDisableCoroutineSwitch(CoroutineManager * coroManager)333 explicit ScopedDisableCoroutineSwitch(CoroutineManager *coroManager) : coroManager_(coroManager) 334 { 335 ASSERT(coroManager_ != nullptr); 336 coroManager_->DisableCoroutineSwitch(); 337 } 338 ~ScopedDisableCoroutineSwitch()339 ~ScopedDisableCoroutineSwitch() 340 { 341 coroManager_->EnableCoroutineSwitch(); 342 } 343 344 private: 345 CoroutineManager *coroManager_; 346 347 NO_COPY_SEMANTIC(ScopedDisableCoroutineSwitch); 348 NO_MOVE_SEMANTIC(ScopedDisableCoroutineSwitch); 349 }; 350 351 } // namespace ark 352 353 #endif /* PANDA_RUNTIME_COROUTINES_COROUTINE_MANAGER_H */ 354