runtime: add support for time.NewTimer and time.NewTicker

This commit adds support for time.NewTimer and time.NewTicker. It also
adds support for the Stop() method on time.Timer, but doesn't (yet) add
support for the Reset() method.

The implementation has been carefully written so that programs that
don't use these timers will normally not see an increase in RAM or
binary size. None of the examples in the drivers repo change as a result
of this commit. This comes at the cost of slightly more complex code and
possibly slower execution of the timers when they are used.
This commit is contained in:
Kenneth Bell
2022-07-02 17:19:36 +01:00
committed by Ron Evans
parent 80c17c0f32
commit 24b45555bd
7 changed files with 258 additions and 3 deletions
+70 -3
View File
@@ -12,6 +12,7 @@ package runtime
import (
"internal/task"
"runtime/interrupt"
)
const schedulerDebug = false
@@ -27,6 +28,7 @@ var (
runqueue task.Queue
sleepQueue *task.Task
sleepQueueBaseTime timeUnit
timerQueue *timerNode
)
// Simple logging, for debugging.
@@ -114,6 +116,46 @@ func addSleepTask(t *task.Task, duration timeUnit) {
*q = t
}
// addTimer adds the given timer node to the timer queue. It must not be in the
// queue already.
// This function is very similar to addSleepTask but for timerQueue instead of
// sleepQueue.
func addTimer(tim *timerNode) {
mask := interrupt.Disable()
// Add to timer queue.
q := &timerQueue
for ; *q != nil; q = &(*q).next {
if tim.whenTicks() < (*q).whenTicks() {
// this will finish earlier than the next - insert here
break
}
}
tim.next = *q
*q = tim
interrupt.Restore(mask)
}
// removeTimer is the implementation of time.stopTimer. It removes a timer from
// the timer queue, returning true if the timer is present in the timer queue.
func removeTimer(tim *timer) bool {
removedTimer := false
mask := interrupt.Disable()
for t := &timerQueue; *t != nil; t = &(*t).next {
if (*t).timer == tim {
scheduleLog("removed timer")
*t = (*t).next
removedTimer = true
break
}
}
if !removedTimer {
scheduleLog("did not remove timer")
}
interrupt.Restore(mask)
return removedTimer
}
// Run the scheduler until all tasks have finished.
func scheduler() {
// Main scheduler loop.
@@ -121,7 +163,7 @@ func scheduler() {
for !schedulerDone {
scheduleLog("")
scheduleLog(" schedule")
if sleepQueue != nil {
if sleepQueue != nil || timerQueue != nil {
now = ticks()
}
@@ -136,9 +178,20 @@ func scheduler() {
runqueue.Push(t)
}
// Check for expired timers to trigger.
if timerQueue != nil && now >= timerQueue.whenTicks() {
scheduleLog("--- timer awoke")
// Pop timer from queue.
tn := timerQueue
timerQueue = tn.next
tn.next = nil
// Run the callback stored in this timer node.
tn.callback(tn)
}
t := runqueue.Pop()
if t == nil {
if sleepQueue == nil {
if sleepQueue == nil && timerQueue == nil {
if asyncScheduler {
// JavaScript is treated specially, see below.
return
@@ -146,12 +199,26 @@ func scheduler() {
waitForEvents()
continue
}
timeLeft := timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime)
var timeLeft timeUnit
if sleepQueue != nil {
timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime)
}
if timerQueue != nil {
timeLeftForTimer := timerQueue.whenTicks() - now
if sleepQueue == nil || timeLeftForTimer < timeLeft {
timeLeft = timeLeftForTimer
}
}
if schedulerDebug {
println(" sleeping...", sleepQueue, uint(timeLeft))
for t := sleepQueue; t != nil; t = t.Next {
println(" task sleeping:", t, timeUnit(t.Data))
}
for tim := timerQueue; tim != nil; tim = tim.next {
println("--- timer waiting:", tim, tim.whenTicks())
}
}
sleepTicks(timeLeft)
if asyncScheduler {