github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/scheduler.go (about) 1 package runtime 2 3 // This file implements the TinyGo scheduler. This scheduler is a very simple 4 // cooperative round robin scheduler, with a runqueue that contains a linked 5 // list of goroutines (tasks) that should be run next, in order of when they 6 // were added to the queue (first-in, first-out). It also contains a sleep queue 7 // with sleeping goroutines in order of when they should be re-activated. 8 // 9 // The scheduler is used both for the asyncify based scheduler and for the task 10 // based scheduler. In both cases, the 'internal/task.Task' type is used to represent one 11 // goroutine. 12 13 import ( 14 "internal/task" 15 "runtime/interrupt" 16 ) 17 18 const schedulerDebug = false 19 20 // On JavaScript, we can't do a blocking sleep. Instead we have to return and 21 // queue a new scheduler invocation using setTimeout. 22 const asyncScheduler = GOOS == "js" 23 24 var schedulerDone bool 25 26 // Queues used by the scheduler. 27 var ( 28 runqueue task.Queue 29 sleepQueue *task.Task 30 sleepQueueBaseTime timeUnit 31 timerQueue *timerNode 32 ) 33 34 // Simple logging, for debugging. 35 func scheduleLog(msg string) { 36 if schedulerDebug { 37 println("---", msg) 38 } 39 } 40 41 // Simple logging with a task pointer, for debugging. 42 func scheduleLogTask(msg string, t *task.Task) { 43 if schedulerDebug { 44 println("---", msg, t) 45 } 46 } 47 48 // Simple logging with a channel and task pointer. 49 func scheduleLogChan(msg string, ch *channel, t *task.Task) { 50 if schedulerDebug { 51 println("---", msg, ch, t) 52 } 53 } 54 55 // deadlock is called when a goroutine cannot proceed any more, but is in theory 56 // not exited (so deferred calls won't run). This can happen for example in code 57 // like this, that blocks forever: 58 // 59 // select{} 60 // 61 //go:noinline 62 func deadlock() { 63 // call yield without requesting a wakeup 64 task.Pause() 65 panic("unreachable") 66 } 67 68 // Goexit terminates the currently running goroutine. No other goroutines are affected. 69 // 70 // Unlike the main Go implementation, no deffered calls will be run. 71 // 72 //go:inline 73 func Goexit() { 74 // its really just a deadlock 75 deadlock() 76 } 77 78 // Add this task to the end of the run queue. 79 func runqueuePushBack(t *task.Task) { 80 runqueue.Push(t) 81 } 82 83 // Add this task to the sleep queue, assuming its state is set to sleeping. 84 func addSleepTask(t *task.Task, duration timeUnit) { 85 if schedulerDebug { 86 println(" set sleep:", t, duration) 87 if t.Next != nil { 88 panic("runtime: addSleepTask: expected next task to be nil") 89 } 90 } 91 t.Data = uint64(duration) 92 now := ticks() 93 if sleepQueue == nil { 94 scheduleLog(" -> sleep new queue") 95 96 // set new base time 97 sleepQueueBaseTime = now 98 } 99 100 // Add to sleep queue. 101 q := &sleepQueue 102 for ; *q != nil; q = &(*q).Next { 103 if t.Data < (*q).Data { 104 // this will finish earlier than the next - insert here 105 break 106 } else { 107 // this will finish later - adjust delay 108 t.Data -= (*q).Data 109 } 110 } 111 if *q != nil { 112 // cut delay time between this sleep task and the next 113 (*q).Data -= t.Data 114 } 115 t.Next = *q 116 *q = t 117 } 118 119 // addTimer adds the given timer node to the timer queue. It must not be in the 120 // queue already. 121 // This function is very similar to addSleepTask but for timerQueue instead of 122 // sleepQueue. 123 func addTimer(tim *timerNode) { 124 mask := interrupt.Disable() 125 126 // Add to timer queue. 127 q := &timerQueue 128 for ; *q != nil; q = &(*q).next { 129 if tim.whenTicks() < (*q).whenTicks() { 130 // this will finish earlier than the next - insert here 131 break 132 } 133 } 134 tim.next = *q 135 *q = tim 136 interrupt.Restore(mask) 137 } 138 139 // removeTimer is the implementation of time.stopTimer. It removes a timer from 140 // the timer queue, returning true if the timer is present in the timer queue. 141 func removeTimer(tim *timer) bool { 142 removedTimer := false 143 mask := interrupt.Disable() 144 for t := &timerQueue; *t != nil; t = &(*t).next { 145 if (*t).timer == tim { 146 scheduleLog("removed timer") 147 *t = (*t).next 148 removedTimer = true 149 break 150 } 151 } 152 if !removedTimer { 153 scheduleLog("did not remove timer") 154 } 155 interrupt.Restore(mask) 156 return removedTimer 157 } 158 159 // Run the scheduler until all tasks have finished. 160 func scheduler() { 161 // Main scheduler loop. 162 var now timeUnit 163 for !schedulerDone { 164 scheduleLog("") 165 scheduleLog(" schedule") 166 if sleepQueue != nil || timerQueue != nil { 167 now = ticks() 168 } 169 170 // Add tasks that are done sleeping to the end of the runqueue so they 171 // will be executed soon. 172 if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { 173 t := sleepQueue 174 scheduleLogTask(" awake:", t) 175 sleepQueueBaseTime += timeUnit(t.Data) 176 sleepQueue = t.Next 177 t.Next = nil 178 runqueue.Push(t) 179 } 180 181 // Check for expired timers to trigger. 182 if timerQueue != nil && now >= timerQueue.whenTicks() { 183 scheduleLog("--- timer awoke") 184 // Pop timer from queue. 185 tn := timerQueue 186 timerQueue = tn.next 187 tn.next = nil 188 // Run the callback stored in this timer node. 189 tn.callback(tn) 190 } 191 192 t := runqueue.Pop() 193 if t == nil { 194 if sleepQueue == nil && timerQueue == nil { 195 if asyncScheduler { 196 // JavaScript is treated specially, see below. 197 return 198 } 199 waitForEvents() 200 continue 201 } 202 203 var timeLeft timeUnit 204 if sleepQueue != nil { 205 timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) 206 } 207 if timerQueue != nil { 208 timeLeftForTimer := timerQueue.whenTicks() - now 209 if sleepQueue == nil || timeLeftForTimer < timeLeft { 210 timeLeft = timeLeftForTimer 211 } 212 } 213 214 if schedulerDebug { 215 println(" sleeping...", sleepQueue, uint(timeLeft)) 216 for t := sleepQueue; t != nil; t = t.Next { 217 println(" task sleeping:", t, timeUnit(t.Data)) 218 } 219 for tim := timerQueue; tim != nil; tim = tim.next { 220 println("--- timer waiting:", tim, tim.whenTicks()) 221 } 222 } 223 sleepTicks(timeLeft) 224 if asyncScheduler { 225 // The sleepTicks function above only sets a timeout at which 226 // point the scheduler will be called again. It does not really 227 // sleep. So instead of sleeping, we return and expect to be 228 // called again. 229 break 230 } 231 continue 232 } 233 234 // Run the given task. 235 scheduleLogTask(" run:", t) 236 t.Resume() 237 } 238 } 239 240 // This horrible hack exists to make WASM work properly. 241 // When a WASM program calls into JS which calls back into WASM, the event with which we called back in needs to be handled before returning. 242 // Thus there are two copies of the scheduler running at once. 243 // This is a reduced version of the scheduler which does not deal with the timer queue (that is a problem for the outer scheduler). 244 func minSched() { 245 scheduleLog("start nested scheduler") 246 for !schedulerDone { 247 t := runqueue.Pop() 248 if t == nil { 249 break 250 } 251 252 scheduleLogTask(" run:", t) 253 t.Resume() 254 } 255 scheduleLog("stop nested scheduler") 256 } 257 258 func Gosched() { 259 runqueue.Push(task.Current()) 260 task.Pause() 261 }