Skip to content

Commit 8d67ed4

Browse files
committed
runtime: make signals parallelism-safe
1 parent 13b2572 commit 8d67ed4

File tree

1 file changed

+18
-6
lines changed

1 file changed

+18
-6
lines changed

src/runtime/runtime_unix.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,12 @@ func signal_enable(s uint32) {
362362
// receivedSignals into a uint32 array.
363363
runtimePanicAt(returnAddress(0), "unsupported signal number")
364364
}
365+
366+
// This is intentonally a non-atomic store. This is safe, since hasSignals
367+
// is only used in waitForEvents which is only called when there's a
368+
// scheduler (and therefore there is no parallelism).
365369
hasSignals = true
370+
366371
// It's easier to implement this function in C.
367372
tinygo_signal_enable(s)
368373
}
@@ -391,6 +396,9 @@ func signal_disable(s uint32) {
391396
func signal_waitUntilIdle() {
392397
// Wait until signal_recv has processed all signals.
393398
for receivedSignals.Load() != 0 {
399+
// TODO: this becomes a busy loop when using threads.
400+
// We might want to pause until signal_recv has no more incoming signals
401+
// to process.
394402
Gosched()
395403
}
396404
}
@@ -434,7 +442,7 @@ func tinygo_signal_handler(s int32) {
434442

435443
// Task waiting for a signal to arrive, or nil if it is running or there are no
436444
// signals.
437-
var signalRecvWaiter *task.Task
445+
var signalRecvWaiter *atomic.Pointer[task.Task]
438446

439447
//go:linkname signal_recv os/signal.signal_recv
440448
func signal_recv() uint32 {
@@ -443,7 +451,10 @@ func signal_recv() uint32 {
443451
val := receivedSignals.Load()
444452
if val == 0 {
445453
// There are no signals to receive. Sleep until there are.
446-
signalRecvWaiter = task.Current()
454+
if signalRecvWaiter.Swap(task.Current()) != nil {
455+
// We expect only a single goroutine to call signal_recv.
456+
runtimePanic("signal_recv called concurrently")
457+
}
447458
task.Pause()
448459
continue
449460
}
@@ -474,10 +485,11 @@ func signal_recv() uint32 {
474485
// Return true if it was reactivated (and therefore the scheduler should run
475486
// again), and false otherwise.
476487
func checkSignals() bool {
477-
if receivedSignals.Load() != 0 && signalRecvWaiter != nil {
478-
scheduleTask(signalRecvWaiter)
479-
signalRecvWaiter = nil
480-
return true
488+
if receivedSignals.Load() != 0 {
489+
if waiter := signalRecvWaiter.Swap(nil); waiter != nil {
490+
scheduleTask(waiter)
491+
return true
492+
}
481493
}
482494
return false
483495
}

0 commit comments

Comments
 (0)