runtime: make signals parallelism-safe

This commit is contained in:
Ayke van Laethem
2024-11-30 09:24:22 +01:00
committed by Ayke
parent aed555d858
commit 09a22ac4b4
+18 -6
View File
@@ -362,7 +362,12 @@ func signal_enable(s uint32) {
// receivedSignals into a uint32 array. // receivedSignals into a uint32 array.
runtimePanicAt(returnAddress(0), "unsupported signal number") runtimePanicAt(returnAddress(0), "unsupported signal number")
} }
// This is intentonally a non-atomic store. This is safe, since hasSignals
// is only used in waitForEvents which is only called when there's a
// scheduler (and therefore there is no parallelism).
hasSignals = true hasSignals = true
// It's easier to implement this function in C. // It's easier to implement this function in C.
tinygo_signal_enable(s) tinygo_signal_enable(s)
} }
@@ -391,6 +396,9 @@ func signal_disable(s uint32) {
func signal_waitUntilIdle() { func signal_waitUntilIdle() {
// Wait until signal_recv has processed all signals. // Wait until signal_recv has processed all signals.
for receivedSignals.Load() != 0 { for receivedSignals.Load() != 0 {
// TODO: this becomes a busy loop when using threads.
// We might want to pause until signal_recv has no more incoming signals
// to process.
Gosched() Gosched()
} }
} }
@@ -434,7 +442,7 @@ func tinygo_signal_handler(s int32) {
// Task waiting for a signal to arrive, or nil if it is running or there are no // Task waiting for a signal to arrive, or nil if it is running or there are no
// signals. // signals.
var signalRecvWaiter *task.Task var signalRecvWaiter atomic.Pointer[task.Task]
//go:linkname signal_recv os/signal.signal_recv //go:linkname signal_recv os/signal.signal_recv
func signal_recv() uint32 { func signal_recv() uint32 {
@@ -443,7 +451,10 @@ func signal_recv() uint32 {
val := receivedSignals.Load() val := receivedSignals.Load()
if val == 0 { if val == 0 {
// There are no signals to receive. Sleep until there are. // There are no signals to receive. Sleep until there are.
signalRecvWaiter = task.Current() if signalRecvWaiter.Swap(task.Current()) != nil {
// We expect only a single goroutine to call signal_recv.
runtimePanic("signal_recv called concurrently")
}
task.Pause() task.Pause()
continue continue
} }
@@ -474,10 +485,11 @@ func signal_recv() uint32 {
// Return true if it was reactivated (and therefore the scheduler should run // Return true if it was reactivated (and therefore the scheduler should run
// again), and false otherwise. // again), and false otherwise.
func checkSignals() bool { func checkSignals() bool {
if receivedSignals.Load() != 0 && signalRecvWaiter != nil { if receivedSignals.Load() != 0 {
scheduleTask(signalRecvWaiter) if waiter := signalRecvWaiter.Swap(nil); waiter != nil {
signalRecvWaiter = nil scheduleTask(waiter)
return true return true
}
} }
return false return false
} }