mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 03:27:48 +00:00
e569dcfe38
* runtime: fix ticker not stopping when Stop races with its callback On the threads and cores schedulers, timer callbacks run concurrently with user goroutines. If Stop (or Reset) was called in the window after the node was popped but before its callback re-added it, removeTimer would not find the timer in the queue, so it would be re-added to timer anyway. Track timers whose callback is currently running in a firing list, and have removeTimer mark a firing timer as stopped so its callback does not re-add it. Also make the timers test drain robust: allow a possible in-flight tick delivered concurrently with Stop to settle before draining the channel. Signed-off-by: deadprogram <ron@hybridgroup.com> * runtime: fix Stop/Reset semantics when racing a firing timer callback Address review feedback on the ticker Stop-race fix. On the threads and cores schedulers a timer callback runs concurrently with user goroutines, which left several problems: - removeTimer reported a firing timer as successfully removed, so Stop/Reset could return true even though the callback had already started (wrong semantics, notably for AfterFunc). firingTimerStop now returns a bool and removeTimer no longer hands the still-firing node back to resetTimer. - The periodic advance (when += period) ran in timerCallback outside the scheduler timer lock. Move it into each scheduler's reAddTimer, under the lock and after the stopped check, so a concurrent Reset can't have its freshly-queued deadline corrupted. - resetTimer now sets when/period after removeTimer for the same reason. Add testdata/timer_stop_reset_race.go and TestTimerStopResetRace, which reproduce the stop-while-firing and reset-while-firing races via the runtime timer linkname hooks. Signed-off-by: deadprogram <ron@hybridgroup.com> * runtime: fix timer Stop/Reset race with firing periodic timers Signed-off-by: deadprogram <ron@hybridgroup.com> --------- Signed-off-by: deadprogram <ron@hybridgroup.com>
99 lines
2.0 KiB
Go
99 lines
2.0 KiB
Go
//go:build scheduler.none
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"internal/task"
|
|
"runtime/interrupt"
|
|
)
|
|
|
|
const hasScheduler = false
|
|
|
|
// No goroutines are allowed, so there's no parallelism anywhere.
|
|
const hasParallelism = false
|
|
|
|
// Set to true after main.main returns.
|
|
var mainExited bool
|
|
|
|
// dummy flag, not used without scheduler
|
|
var schedulerExit bool
|
|
|
|
// run is called by the program entry point to execute the go program.
|
|
// With the "none" scheduler, init and the main function are invoked directly.
|
|
func run() {
|
|
initRand()
|
|
initHeap()
|
|
initAll()
|
|
callMain()
|
|
mainExited = true
|
|
}
|
|
|
|
//go:linkname sleep time.Sleep
|
|
func sleep(duration int64) {
|
|
if duration <= 0 {
|
|
return
|
|
}
|
|
|
|
sleepTicks(nanosecondsToTicks(duration))
|
|
}
|
|
|
|
func deadlock() {
|
|
// The only goroutine available is deadlocked.
|
|
runtimePanic("all goroutines are asleep - deadlock!")
|
|
}
|
|
|
|
func scheduleTask(t *task.Task) {
|
|
// Pause() will panic, so this should not be reachable.
|
|
}
|
|
|
|
func Gosched() {
|
|
// There are no other goroutines, so there's nothing to schedule.
|
|
}
|
|
|
|
// NumCPU returns the number of logical CPUs usable by the current process.
|
|
func NumCPU() int {
|
|
return 1
|
|
}
|
|
|
|
func addTimer(tim *timerNode) {
|
|
runtimePanic("timers not supported without a scheduler")
|
|
}
|
|
|
|
func reAddTimer(tn *timerNode) {
|
|
runtimePanic("timers not supported without a scheduler")
|
|
}
|
|
|
|
func removeTimer(tim *timer) *timerNode {
|
|
runtimePanic("timers not supported without a scheduler")
|
|
return nil
|
|
}
|
|
|
|
func schedulerRunQueue() *task.Queue {
|
|
// This function is not actually used, it is only called when hasScheduler
|
|
// is true.
|
|
runtimePanic("unreachable: no runqueue without a scheduler")
|
|
return nil
|
|
}
|
|
|
|
func scheduler(returnAtDeadlock bool) {
|
|
// The scheduler should never be run when using -scheduler=none. Meaning,
|
|
// this code should be unreachable.
|
|
runtimePanic("unreachable: scheduler must not be called with the 'none' scheduler")
|
|
}
|
|
|
|
func lockAtomics() interrupt.State {
|
|
return interrupt.Disable()
|
|
}
|
|
|
|
func unlockAtomics(mask interrupt.State) {
|
|
interrupt.Restore(mask)
|
|
}
|
|
|
|
func printlock() {
|
|
// nothing to do
|
|
}
|
|
|
|
func printunlock() {
|
|
// nothing to do
|
|
}
|