Skip to content

Commit

Permalink
signals: improve waitForEvents
Browse files Browse the repository at this point in the history
Signed-off-by: leongross <[email protected]>
  • Loading branch information
leongross committed Oct 8, 2024
1 parent 4946cc5 commit 501cda3
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 8 deletions.
43 changes: 41 additions & 2 deletions src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func usleep(usec uint) int
//export pause
func pause() int32

//export sigsuspend
func sigsuspend(sigset_t unsafe.Pointer) int32

// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
// Note: off_t is defined as int64 because:
// - musl (used on Linux) always defines it as int64
Expand Down Expand Up @@ -341,6 +344,12 @@ var hasSignals uint32
// signals into this value.
var receivedSignals uint32

// Bitmap keeping track of enabled signals.
var activeSignals uint32

// Bitmap keeping track of masked/disabled signals.
var maskedSignals uint32

//go:linkname signal_enable os/signal.signal_enable
func signal_enable(s uint32) {
if s >= 32 {
Expand All @@ -349,6 +358,10 @@ func signal_enable(s uint32) {
runtimePanicAt(returnAddress(0), "unsupported signal number")
}
atomic.StoreUint32(&hasSignals, 1)

// update the enabled signals bitmap
val := atomic.LoadUint32(&activeSignals)
atomic.CompareAndSwapUint32(&receivedSignals, val, val|(1<<s))
// It's easier to implement this function in C.
tinygo_signal_enable(s)
}
Expand Down Expand Up @@ -396,6 +409,11 @@ func tinygo_signal_ignore(s uint32)
//export tinygo_signal_disable
func tinygo_signal_disable(s uint32)

//export tinygo_mask_signals
func tinygo_mask_signals(mask uint32)

//export tinygo_unmask_active_signalsWY

// void tinygo_signal_handler(int sig);
//
//export tinygo_signal_handler
Expand Down Expand Up @@ -424,7 +442,7 @@ func signal_recv() uint32 {
return val
}

// Atomically find a signal that previously occured and send it into the
// Atomically find a signal that previously occurred and send it into the
// signalChan channel. Return true if at least one signal was delivered this
// way, false otherwise.
func checkSignals() bool {
Expand Down Expand Up @@ -467,6 +485,19 @@ func checkSignals() bool {
}
}

// mask a set of signals defined by the activeSignals bitmap (32bit)
// returns the C style sigset_t pointer.
func maskActiveSignals() unsafe.Pointer {
signals := atomic.LoadUint32(&activeSignals)
return tinygo_mask_signals(signals)
}

// unmask a set of signals defined by the activeSignals bitmap (32bit)
func unmaskActiveSignals() {
signals := atomic.LoadUint32(&activeSignals)
tinygo_unmask_active_signals(signals)
}

func waitForEvents() {
if atomic.LoadUint32(&hasSignals) != 0 {
// TODO: there is a race condition here. If a signal arrives between
Expand All @@ -479,9 +510,17 @@ func waitForEvents() {
// - unmask all active signals
// For a longer explanation of the problem, see:
// https://www.cipht.net/2023/11/30/perils-of-pause.html

// When a signal arrives while masked by the process, it remains pending
// until the process unmasks it.
sigset := maskActiveSignals()

checkSignals()
pause()
sigsuspend(sigset)

checkSignals()
unmaskActiveSignals()

} else {
// The program doesn't use signals, so this is a deadlock.
runtimePanic("deadlocked: no event source")
Expand Down
46 changes: 40 additions & 6 deletions src/runtime/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,54 @@
void tinygo_signal_handler(int sig);

// Enable a signal from the runtime.
void tinygo_signal_enable(uint32_t sig) {
struct sigaction act = { 0 };
void tinygo_signal_enable(uint32_t sig)
{
struct sigaction act = {0};
act.sa_handler = &tinygo_signal_handler;
sigaction(sig, &act, NULL);
}

void tinygo_signal_ignore(uint32_t sig) {
struct sigaction act = { 0 };
void tinygo_signal_ignore(uint32_t sig)
{
struct sigaction act = {0};
act.sa_handler = SIG_IGN;
sigaction(sig, &act, NULL);
}

void tinygo_signal_disable(uint32_t sig) {
struct sigaction act = { 0 };
void tinygo_signal_disable(uint32_t sig)
{
struct sigaction act = {0};
act.sa_handler = SIG_DFL;
sigaction(sig, &act, NULL);
}

// https://www.cipht.net/2023/11/30/perils-of-pause.html#text-3
void *tinygo_mask_signals(uint32_t mask)
{
sigset_t set, prev;
sigemptyset(&set);
for (int i = 0; i < 32; i++)
{
if (mask & (1 << i))
{
sigaddset(&set, i);
}
}
sigprocmask(SIG_BLOCK, &set, &prev);
return set;
}

void *tinygo_unmask_signals(uint32_t mask)
{
sigset_t set, prev;
sigemptyset(&set);
for (int i = 0; i < 32; i++)
{
if (mask & (1 << i))
{
sigaddset(&set, i);
}
}
sigprocmask(SIG_UNBLOCK, &set, &prev);
return prev;
}

0 comments on commit 501cda3

Please sign in to comment.