mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os/signal: avoid race between Stop and receiving on channel
When Stop is called on a channel, wait until all signals have been delivered to the channel before returning. Use atomic operations in sigqueue to communicate more reliably between the os/signal goroutine and the signal handler. Fixes #14571 Change-Id: I6c5a9eea1cff85e37a34dffe96f4bb2699e12c6e Reviewed-on: https://go-review.googlesource.com/46003 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
parent
3785457c76
commit
8ec7a39fec
4 changed files with 221 additions and 9 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue