diff options
Diffstat (limited to 'src/runtime')
-rw-r--r-- | src/runtime/sigqueue.go | 63 | ||||
-rw-r--r-- | src/runtime/sigqueue_plan9.go | 20 |
2 files changed, 76 insertions, 7 deletions
diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go index 0162ffa04f..236bb29929 100644 --- a/src/runtime/sigqueue.go +++ b/src/runtime/sigqueue.go @@ -33,6 +33,17 @@ import ( _ "unsafe" // for go:linkname ) +// sig handles communication between the signal handler and os/signal. +// Other than the inuse and recv fields, the fields are accessed atomically. +// +// The wanted and ignored fields are only written by one goroutine at +// a time; access is controlled by the handlers Mutex in os/signal. +// The fields are only read by that one goroutine and by the signal handler. +// We access them atomically to minimize the race between setting them +// in the goroutine calling os/signal and the signal handler, +// which may be running in a different thread. That race is unavoidable, +// as there is no connection between handling a signal and receiving one, +// but atomic instructions should minimize it. var sig struct { note note mask [(_NSIG + 31) / 32]uint32 @@ -53,7 +64,11 @@ const ( // Reports whether the signal was sent. If not, the caller typically crashes the program. func sigsend(s uint32) bool { bit := uint32(1) << uint(s&31) - if !sig.inuse || s >= uint32(32*len(sig.wanted)) || sig.wanted[s/32]&bit == 0 { + if !sig.inuse || s >= uint32(32*len(sig.wanted)) { + return false + } + + if w := atomic.Load(&sig.wanted[s/32]); w&bit == 0 { return false } @@ -131,6 +146,23 @@ func signal_recv() uint32 { } } +// signalWaitUntilIdle waits until the signal delivery mechanism is idle. +// This is used to ensure that we do not drop a signal notification due +// to a race between disabling a signal and receiving a signal. +// This assumes that signal delivery has already been disabled for +// the signal(s) in question, and here we are just waiting to make sure +// that all the signals have been delivered to the user channels +// by the os/signal package. +//go:linkname signalWaitUntilIdle os/signal.signalWaitUntilIdle +func signalWaitUntilIdle() { + // Although WaitUntilIdle seems like the right name for this + // function, the state we are looking for is sigReceiving, not + // sigIdle. The sigIdle state is really more like sigProcessing. + for atomic.Load(&sig.state) != sigReceiving { + Gosched() + } +} + // Must only be called from a single goroutine at a time. //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { @@ -146,8 +178,15 @@ func signal_enable(s uint32) { if s >= uint32(len(sig.wanted)*32) { return } - sig.wanted[s/32] |= 1 << (s & 31) - sig.ignored[s/32] &^= 1 << (s & 31) + + w := sig.wanted[s/32] + w |= 1 << (s & 31) + atomic.Store(&sig.wanted[s/32], w) + + i := sig.ignored[s/32] + i &^= 1 << (s & 31) + atomic.Store(&sig.ignored[s/32], i) + sigenable(s) } @@ -157,8 +196,11 @@ func signal_disable(s uint32) { if s >= uint32(len(sig.wanted)*32) { return } - sig.wanted[s/32] &^= 1 << (s & 31) sigdisable(s) + + w := sig.wanted[s/32] + w &^= 1 << (s & 31) + atomic.Store(&sig.wanted[s/32], w) } // Must only be called from a single goroutine at a time. @@ -167,12 +209,19 @@ func signal_ignore(s uint32) { if s >= uint32(len(sig.wanted)*32) { return } - sig.wanted[s/32] &^= 1 << (s & 31) - sig.ignored[s/32] |= 1 << (s & 31) sigignore(s) + + w := sig.wanted[s/32] + w &^= 1 << (s & 31) + atomic.Store(&sig.wanted[s/32], w) + + i := sig.ignored[s/32] + i |= 1 << (s & 31) + atomic.Store(&sig.ignored[s/32], i) } // Checked by signal handlers. func signal_ignored(s uint32) bool { - return sig.ignored[s/32]&(1<<(s&31)) != 0 + i := atomic.Load(&sig.ignored[s/32]) + return i&(1<<(s&31)) != 0 } diff --git a/src/runtime/sigqueue_plan9.go b/src/runtime/sigqueue_plan9.go index 575d26afb4..76668045a8 100644 --- a/src/runtime/sigqueue_plan9.go +++ b/src/runtime/sigqueue_plan9.go @@ -110,6 +110,26 @@ func signal_recv() string { } } +// signalWaitUntilIdle waits until the signal delivery mechanism is idle. +// This is used to ensure that we do not drop a signal notification due +// to a race between disabling a signal and receiving a signal. +// This assumes that signal delivery has already been disabled for +// the signal(s) in question, and here we are just waiting to make sure +// that all the signals have been delivered to the user channels +// by the os/signal package. +//go:linkname signalWaitUntilIdle os/signal.signalWaitUntilIdle +func signalWaitUntilIdle() { + for { + lock(&sig.lock) + sleeping := sig.sleeping + unlock(&sig.lock) + if sleeping { + return + } + Gosched() + } +} + // Must only be called from a single goroutine at a time. //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { |