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