Skip to content

Commit

Permalink
runtime: make signals parallelism-safe
Browse files Browse the repository at this point in the history
  • Loading branch information
aykevl committed Nov 30, 2024
1 parent 13b2572 commit 8d67ed4
Showing 1 changed file with 18 additions and 6 deletions.
24 changes: 18 additions & 6 deletions src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,12 @@ func signal_enable(s uint32) {
// receivedSignals into a uint32 array.
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

// It's easier to implement this function in C.
tinygo_signal_enable(s)
}
Expand Down Expand Up @@ -391,6 +396,9 @@ func signal_disable(s uint32) {
func signal_waitUntilIdle() {
// Wait until signal_recv has processed all signals.
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()
}
}
Expand Down Expand Up @@ -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
// signals.
var signalRecvWaiter *task.Task
var signalRecvWaiter *atomic.Pointer[task.Task]

//go:linkname signal_recv os/signal.signal_recv
func signal_recv() uint32 {
Expand All @@ -443,7 +451,10 @@ func signal_recv() uint32 {
val := receivedSignals.Load()
if val == 0 {
// 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()
continue
}
Expand Down Expand Up @@ -474,10 +485,11 @@ func signal_recv() uint32 {
// Return true if it was reactivated (and therefore the scheduler should run
// again), and false otherwise.
func checkSignals() bool {
if receivedSignals.Load() != 0 && signalRecvWaiter != nil {
scheduleTask(signalRecvWaiter)
signalRecvWaiter = nil
return true
if receivedSignals.Load() != 0 {
if waiter := signalRecvWaiter.Swap(nil); waiter != nil {
scheduleTask(waiter)
return true
}
}
return false
}
Expand Down

0 comments on commit 8d67ed4

Please sign in to comment.