• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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