Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 33 additions & 81 deletions src/runtime/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ import (

const schedulerDebug = false

// Queues used by the scheduler.
var (
runqueue task.Queue
sleepQueue *task.Task
sleepQueueBaseTime timeUnit
)
// runqueue is a queue of tasks that are ready to run.
var runqueue task.Queue

// Simple logging, for debugging.
func scheduleLog(msg string) {
Expand Down Expand Up @@ -74,95 +70,51 @@ func runqueuePushBack(t *task.Task) {
runqueue.Push(t)
}

// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t *task.Task, duration int64) {
if schedulerDebug {
println(" set sleep:", t, uint(duration/tickMicros))
if t.Next != nil {
panic("runtime: addSleepTask: expected next task to be nil")
}
}
t.Data = uint(duration / tickMicros) // TODO: longer durations
now := ticks()
if sleepQueue == nil {
scheduleLog(" -> sleep new queue")
var schedulerDone bool

// set new base time
sleepQueueBaseTime = now
}

// Add to sleep queue.
q := &sleepQueue
for ; *q != nil; q = &(*q).Next {
if t.Data < (*q).Data {
// this will finish earlier than the next - insert here
break
} else {
// this will finish later - adjust delay
t.Data -= (*q).Data
}
}
if *q != nil {
// cut delay time between this sleep task and the next
(*q).Data -= t.Data
}
t.Next = *q
*q = t
}
const pollInterval = 256

// Run the scheduler until all tasks have finished.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe this sentence is true anymore with this PR: the scheduler runs until the main task has finished.

func scheduler() {
// Main scheduler loop.
var now timeUnit
for {
scheduleLog("")
scheduleLog(" schedule")
if sleepQueue != nil {
now = ticks()
}

// Add tasks that are done sleeping to the end of the runqueue so they
// will be executed soon.
if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) {
t := sleepQueue
scheduleLogTask(" awake:", t)
sleepQueueBaseTime += timeUnit(t.Data)
sleepQueue = t.Next
t.Next = nil
runqueue.Push(t)
}

var n uint
for !schedulerDone {
// Get the next available task.
t := runqueue.Pop()
if t == nil {
if sleepQueue == nil {
// No more tasks to execute.
// It would be nice if we could detect deadlocks here, because
// there might still be functions waiting on each other in a
// deadlock.
scheduleLog(" no tasks left!")
return
}
timeLeft := timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
for t := sleepQueue; t != nil; t = t.Next {
println(" task sleeping:", t, timeUnit(t.Data))
}
scheduleLog(" runqueue empty")

// Check for any available tasks.
if poll() {
// A task was found and pushed onto the runqueue.
continue
}
sleepTicks(timeLeft)

// Sleep until another task is available.
wait()
if asyncScheduler {
// The sleepTicks function above only sets a timeout at which
// point the scheduler will be called again. It does not really
// sleep.
break
// This platform (WebAssembly) requires us to return control to the host while waiting.
// The host will eventually re-invoke the scheduler when there is work available.
return
}

// Try again.
continue
}

// Run the given task.
scheduleLogTask(" run:", t)
// Run task.
scheduleLogTask("resuming:", t)
t.Resume()

// Periodically poll for additional events.
if !asyncScheduler && n > pollInterval {
poll()
n = 0
} else {
n++
}
}

scheduleLog(" program complete!")
}

func Gosched() {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/scheduler_any.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func run() {
initAll()
postinit()
callMain()
schedulerDone = true
}()
scheduler()
}
100 changes: 100 additions & 0 deletions src/runtime/scheduler_legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package runtime

import "internal/task"

var (
sleepQueue *task.Task
sleepQueueBaseTime timeUnit
)

// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t *task.Task, duration int64) {
if schedulerDebug {
println(" set sleep:", t, uint(duration/tickMicros))
if t.Next != nil {
panic("runtime: addSleepTask: expected next task to be nil")
}
}
t.Data = uint(duration / tickMicros) // TODO: longer durations
now := ticks()
if sleepQueue == nil {
scheduleLog(" -> sleep new queue")

// set new base time
sleepQueueBaseTime = now
}

// Add to sleep queue.
q := &sleepQueue
for ; *q != nil; q = &(*q).Next {
if t.Data < (*q).Data {
// this will finish earlier than the next - insert here
break
} else {
// this will finish later - adjust delay
t.Data -= (*q).Data
}
}
if *q != nil {
// cut delay time between this sleep task and the next
(*q).Data -= t.Data
}
t.Next = *q
*q = t
}

// pollSleepQueue checks the sleep queue to see if any tasks are now ready to run.
func pollSleepQueue(now timeUnit) bool {
// Add tasks that are done sleeping to the end of the runqueue so they
// will be executed soon.
var awoke bool
for sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) {
t := sleepQueue
scheduleLogTask(" awake:", t)
sleepQueueBaseTime += timeUnit(t.Data)
sleepQueue = t.Next
t.Next = nil
runqueue.Push(t)
awoke = true
}

return awoke
}

var curTime timeUnit

// poll checks for any events that are ready and pushes them onto the runqueue.
// It returns true if any tasks were ready.
func poll() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this function is rather broad. Perhaps something like pollForEvents or checkForNewTasks? Or fillRunqueue?

scheduleLog(" polling for events")
if sleepQueue == nil {
return false
}
curTime = ticks()
return pollSleepQueue(curTime)
}

// wait sleeps until any tasks are awoken by external events.
func wait() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this function. I think something like waitForEvents would be clearer.
(I'm assuming this will wait for the next interrupt soon, with wfe?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, #1040 adds an implementation for atsamd21 which directly uses wfe.

scheduleLog(" waiting for events")
if sleepQueue == nil {
// There are no timers, so timer wakeup is impossible.
if asyncScheduler {
// On WASM, sometimes callbacks are used to process wakeups from JS events.
// Therefore, we do not actually know if we deadlocked or not.
scheduleLog(" no tasks left!")
return
}
runtimePanic("deadlock")
}

// Sleep until the next timer hits.
timeLeft := timeUnit(sleepQueue.Data) - (curTime - sleepQueueBaseTime)
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
for t := sleepQueue; t != nil; t = t.Next {
println(" task sleeping:", t, timeUnit(t.Data))
}
}
sleepTicks(timeLeft)
}