runtime: make Goexit exit host threads

The threads scheduler handled runtime.Goexit using the generic deadlock
path, which parked the current pthread forever. Add a scheduler-specific
goexit hook so it can remove the current task and exit the pthread while
ordinary blocking deadlocks still block.

Track main goroutine Goexit so the last remaining goroutine reports the
expected deadlock instead of letting the process exit successfully.
Expand the crash coverage with the Goexit cases covered by Big Go's
runtime tests.
This commit is contained in:
Jake Bailey
2026-07-03 16:48:11 -07:00
committed by Ron Evans
parent 16eefc59eb
commit 432ebe13ed
14 changed files with 225 additions and 36 deletions
+42
View File
@@ -0,0 +1,42 @@
//go:build scheduler.tasks || scheduler.asyncify || scheduler.cores
package task
import "sync/atomic"
var (
mainTask *Task
liveTasks uint32
mainExitedByGoexit uint32
)
func addLiveTask(t *Task) {
if mainTask == nil {
mainTask = t
}
atomic.AddUint32(&liveTasks, 1)
}
// Exit exits the current task because runtime.Goexit was called.
func Exit() {
exit(true)
}
func exit(goexit bool) {
t := Current()
remaining := atomic.AddUint32(&liveTasks, ^uint32(0))
if t == mainTask {
if goexit {
if remaining == 0 {
runtimePanic("all goroutines are asleep - deadlock!")
}
atomic.StoreUint32(&mainExitedByGoexit, 1)
}
} else if atomic.LoadUint32(&mainExitedByGoexit) != 0 && remaining == 0 {
runtimePanic("all goroutines are asleep - deadlock!")
}
// TODO: explicitly free the stack after switching back to the scheduler.
Pause()
runtimePanic("unreachable")
}