Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: make signals parallelism-safe #4641

Merged
merged 1 commit into from
Dec 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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