1 /** 2 * Copyright (c) 2023 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 16 #ifndef PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_WORKER_H 17 #define PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_WORKER_H 18 19 #include "runtime/coroutines/coroutine.h" 20 #include "runtime/coroutines/coroutine_events.h" 21 22 namespace panda { 23 24 class StackfulCoroutineContext; 25 class StackfulCoroutineManager; 26 27 /** 28 * Represents a worker thread for stackful coroutines. 29 * Contains local part of the scheduling machinery (local coroutine queues, methods) 30 */ 31 class StackfulCoroutineWorker { 32 public: 33 enum class ScheduleLoopType { THREAD, FIBER }; 34 35 NO_COPY_SEMANTIC(StackfulCoroutineWorker); 36 NO_MOVE_SEMANTIC(StackfulCoroutineWorker); 37 38 /** 39 * @brief The worker constructor. Create the worker and its schedule loop. 40 * 41 * Notable parameters: 42 * @param type defines the schedule loop type for this worker: a separate thread or a coroutine ("FIBER") 43 */ 44 StackfulCoroutineWorker(Runtime *runtime, PandaVM *vm, StackfulCoroutineManager *coroManager, ScheduleLoopType type, 45 PandaString name); 46 ~StackfulCoroutineWorker() = default; 47 48 /// @return false if the worker is stopped and does not schedule anything, otherwise true IsActive()49 bool IsActive() const 50 { 51 os::memory::LockHolder lock(runnablesLock_); 52 return active_; 53 } 54 55 /// enable or disable the worker SetActive(bool val)56 void SetActive(bool val) 57 { 58 os::memory::LockHolder lock(runnablesLock_); 59 active_ = val; 60 runnablesCv_.Signal(); 61 } 62 63 /// @return the moving average number of runnable coroutines in the queue GetLoadFactor()64 double GetLoadFactor() const 65 { 66 return loadFactor_; 67 } 68 GetName()69 PandaString GetName() const 70 { 71 return name_; 72 } 73 SetName(PandaString name)74 void SetName(PandaString name) 75 { 76 name_ = std::move(name); 77 } 78 79 /** 80 * @brief Adds a coroutine to the runnables queue 81 * @param new_coro coroutine to add 82 * @param prioritize if true, add to the beginning of the queue (otherwise to the end) 83 */ 84 void AddRunnableCoroutine(Coroutine *newCoro, bool prioritize = false); 85 86 /** 87 * @brief Block current coroutine till an event happens and switch context to the next ready one 88 * @param awaitee the event to wait 89 * @return false if the event is apready happened, true after the waiting is completed 90 */ 91 bool WaitForEvent(CoroutineEvent *awaitee) RELEASE(awaitee); 92 93 /** 94 * @brief Signal that an event has happened and unblock all the coroutines in the current worker that are waiting 95 * for this event 96 * @param blocker the event that has happened 97 */ 98 void UnblockWaiters(CoroutineEvent *blocker); 99 100 /** 101 * @brief Add a coroutine to the finalization queue for future destruction and schedule the next ready one for 102 * execution. Used by a coroutine being terminated for safe self-destruction. 103 * @param finalizee coroutine to finalize (the caller) 104 */ 105 void RequestFinalization(Coroutine *finalizee); 106 107 /// @brief schedule the next ready coroutine from the runnables queue for execution 108 void RequestSchedule(); 109 110 /// @brief call to delete the fake "schedule loop" coroutine 111 void FinalizeFiberScheduleLoop(); 112 113 /* debugging tools */ 114 // See CoroutineManager/StackfulCoroutineManager for details 115 void DisableCoroutineSwitch(); 116 void EnableCoroutineSwitch(); 117 bool IsCoroutineSwitchDisabled(); 118 119 #ifndef NDEBUG 120 void PrintRunnables(const PandaString &requester); 121 #endif 122 123 private: 124 /* schedule loop management */ 125 /// the EP for threaded schedule loops 126 void ThreadProc(); 127 /// the EP for fiber schedule loops 128 void ScheduleLoop(); 129 /// the schedule loop itself 130 void ScheduleLoopBody(); 131 /// the helper proxy function for the fiber schedule loop 132 static void ScheduleLoopProxy(void *worker); 133 134 /* runnables queue management */ 135 void PushToRunnableQueue(Coroutine *co, bool pushFront = false); 136 Coroutine *PopFromRunnableQueue(); 137 bool RunnableCoroutinesExist() const; 138 void WaitForRunnables() REQUIRES(runnablesLock_); 139 140 /* scheduling machinery from high level functions to elementary helpers */ 141 /** 142 * Schedule the next ready coroutine from the runnables queue for execution if present, otherwise wait till those 143 * appear, then pick the best suitable and schedule it. After that eventually the current coroutine will be 144 * scheduled for execution too. Upon that, the control flow will get back to this function and it will return. 145 */ 146 void RequestScheduleImpl(); 147 void BlockCurrentCoroAndScheduleNext() RELEASE(runnablesLock_) RELEASE(waitersLock_); 148 void SuspendCurrentCoroAndScheduleNext() RELEASE(runnablesLock_); 149 template <bool SUSPEND_AS_BLOCKED> 150 void SuspendCurrentCoroGeneric(); 151 void BlockCurrentCoro(); 152 void SuspendCurrentCoro(); 153 /* "the lesser evil": keep thread safety annotations but duplicate the function body */ 154 void ScheduleNextCoroUnlockRunnablesWaiters() RELEASE(runnablesLock_) RELEASE(waitersLock_); 155 void ScheduleNextCoroUnlockRunnables() RELEASE(runnablesLock_); 156 void ScheduleNextCoroUnlockNone(); 157 StackfulCoroutineContext *GetCurrentContext() const; 158 StackfulCoroutineContext *PrepareNextRunnableContextForSwitch(); 159 void SwitchCoroutineContext(StackfulCoroutineContext *from, StackfulCoroutineContext *to); 160 161 /* various helper functions */ 162 /// parse the finalization queue and destroy all coroutines from it 163 void FinalizeTerminatedCoros(); 164 /// recalculate the load factor 165 void UpdateLoadFactor() REQUIRES(runnablesLock_); 166 /** 167 * This checker is called on a coroutine switch attempt. Issues fatal error in case when coroutine switch is 168 * disabled. 169 */ 170 void EnsureCoroutineSwitchEnabled(); 171 172 Runtime *runtime_ = nullptr; 173 PandaVM *vm_ = nullptr; 174 StackfulCoroutineManager *coroManager_; 175 Coroutine *scheduleLoopCtx_ = nullptr; 176 bool active_ GUARDED_BY(runnablesLock_) = true; 177 os::thread::ThreadId id_; 178 179 // runnable coroutines-related members 180 mutable os::memory::RecursiveMutex runnablesLock_; 181 os::memory::ConditionVariable runnablesCv_; 182 PandaDeque<Coroutine *> runnables_ GUARDED_BY(runnablesLock_); 183 // blocked coros-related members: Coroutine AWAITS CoroutineEvent 184 mutable os::memory::Mutex waitersLock_; 185 PandaMap<CoroutineEvent *, Coroutine *> waiters_ GUARDED_BY(waitersLock_); 186 // terminated coros (waiting for deletion) 187 PandaQueue<Coroutine *> finalizationQueue_; 188 189 /// the moving average number of coroutines in the runnable queue 190 std::atomic<double> loadFactor_ = 0; 191 192 /** 193 * This counter is incremented on DisableCoroutineSwitch calls and decremented on EnableCoroutineSwitch calls. 194 * The value 0 means that coroutine switch is ENABLED. 195 */ 196 uint32_t disableCoroSwitchCounter_ = 0; 197 198 PandaString name_; 199 }; 200 201 } // namespace panda 202 203 #endif /* PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_WORKER_H */ 204