]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: don't block signals that will kill the program
authorIan Lance Taylor <iant@golang.org>
Thu, 23 Nov 2017 03:12:12 +0000 (19:12 -0800)
committerIan Lance Taylor <iant@golang.org>
Thu, 30 Nov 2017 23:29:30 +0000 (23:29 +0000)
Otherwise we may delay the delivery of these signals for an arbitrary
length of time. We are already careful to not block signals that the
program has asked to see.

Also make sure that we don't miss a signal delivery if a thread
decides to stop for a while while executing the signal handler.

Also clean up the TestAtomicStop output a little bit.

Fixes #21433

Change-Id: Ic0c1a4eaf7eba80d1abc1e9537570bf4687c2434
Reviewed-on: https://go-review.googlesource.com/79581
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
src/os/signal/signal_test.go
src/runtime/runtime2.go
src/runtime/signal_unix.go
src/runtime/sigqueue.go

index dea2add0abb1dcffd5328b3075dbfcf712e1d8c0..e4df8af816e0c2c8b1dc02bb92ed14e0d22ed94e 100644 (file)
@@ -321,7 +321,9 @@ func TestAtomicStop(t *testing.T) {
                cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1")
                out, err := cmd.CombinedOutput()
                if err == nil {
-                       t.Logf("iteration %d: output %s", i, out)
+                       if len(out) > 0 {
+                               t.Logf("iteration %d: output %s", i, out)
+                       }
                } else {
                        t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
                }
@@ -378,7 +380,7 @@ func atomicStopTestProgram() {
                case <-cs:
                case <-time.After(1 * time.Second):
                        if !printed {
-                               fmt.Print("lost signal on iterations:")
+                               fmt.Print("lost signal on tries:")
                                printed = true
                        }
                        fmt.Printf(" %d", i)
index 0e7ef2fda2df5c99282996a9919d854b9609906c..c75f0b1b7aca199267f48f4f928caab1ebb84acc 100644 (file)
@@ -622,7 +622,7 @@ const (
        _SigDefault              // if the signal isn't explicitly requested, don't monitor it
        _SigGoExit               // cause all runtime procs to exit (only used on Plan 9).
        _SigSetStack             // add SA_ONSTACK to libc handler
-       _SigUnblock              // unblocked in minit
+       _SigUnblock              // always unblock; see blockableSig
        _SigIgn                  // _SIG_DFL action is to ignore the signal
 )
 
index 9dae058f2cd6d25373b18fc5e6b5b4221f7fd9a2..e1ba2dbc78b08b82514569d07ab7876a68b916a2 100644 (file)
@@ -526,7 +526,7 @@ func ensureSigM() {
                // mask accordingly.
                sigBlocked := sigset_all
                for i := range sigtable {
-                       if sigtable[i].flags&_SigUnblock != 0 {
+                       if !blockableSig(uint32(i)) {
                                sigdelset(&sigBlocked, i)
                        }
                }
@@ -538,7 +538,7 @@ func ensureSigM() {
                                        sigdelset(&sigBlocked, int(sig))
                                }
                        case sig := <-disableSigChan:
-                               if sig > 0 {
+                               if sig > 0 && blockableSig(sig) {
                                        sigaddset(&sigBlocked, int(sig))
                                }
                        }
@@ -736,7 +736,7 @@ func minitSignalStack() {
 func minitSignalMask() {
        nmask := getg().m.sigmask
        for i := range sigtable {
-               if sigtable[i].flags&_SigUnblock != 0 {
+               if !blockableSig(uint32(i)) {
                        sigdelset(&nmask, i)
                }
        }
@@ -757,6 +757,25 @@ func unminitSignals() {
        }
 }
 
+// blockableSig returns whether sig may be blocked by the signal mask.
+// We never want to block the signals marked _SigUnblock;
+// these are the synchronous signals that turn into a Go panic.
+// In a Go program--not a c-archive/c-shared--we never want to block
+// the signals marked _SigKill or _SigThrow, as otherwise it's possible
+// for all running threads to block them and delay their delivery until
+// we start a new thread. When linked into a C program we let the C code
+// decide on the disposition of those signals.
+func blockableSig(sig uint32) bool {
+       flags := sigtable[sig].flags
+       if flags&_SigUnblock != 0 {
+               return false
+       }
+       if isarchive || islibrary {
+               return true
+       }
+       return flags&(_SigKill|_SigThrow) == 0
+}
+
 // gsignalStack saves the fields of the gsignal stack changed by
 // setGsignalStack.
 type gsignalStack struct {
index 236bb29929672fabf79b2b23b2dbd849a8cd0b22..94e2b69d92c41b40e2790bbbdfafec57ab9db75c 100644 (file)
@@ -45,13 +45,14 @@ import (
 // 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
-       wanted  [(_NSIG + 31) / 32]uint32
-       ignored [(_NSIG + 31) / 32]uint32
-       recv    [(_NSIG + 31) / 32]uint32
-       state   uint32
-       inuse   bool
+       note       note
+       mask       [(_NSIG + 31) / 32]uint32
+       wanted     [(_NSIG + 31) / 32]uint32
+       ignored    [(_NSIG + 31) / 32]uint32
+       recv       [(_NSIG + 31) / 32]uint32
+       state      uint32
+       delivering uint32
+       inuse      bool
 }
 
 const (
@@ -68,7 +69,11 @@ func sigsend(s uint32) bool {
                return false
        }
 
+       atomic.Xadd(&sig.delivering, 1)
+       // We are running in the signal handler; defer is not available.
+
        if w := atomic.Load(&sig.wanted[s/32]); w&bit == 0 {
+               atomic.Xadd(&sig.delivering, -1)
                return false
        }
 
@@ -76,6 +81,7 @@ func sigsend(s uint32) bool {
        for {
                mask := sig.mask[s/32]
                if mask&bit != 0 {
+                       atomic.Xadd(&sig.delivering, -1)
                        return true // signal already in queue
                }
                if atomic.Cas(&sig.mask[s/32], mask, mask|bit) {
@@ -104,6 +110,7 @@ Send:
                }
        }
 
+       atomic.Xadd(&sig.delivering, -1)
        return true
 }
 
@@ -155,6 +162,15 @@ func signal_recv() uint32 {
 // by the os/signal package.
 //go:linkname signalWaitUntilIdle os/signal.signalWaitUntilIdle
 func signalWaitUntilIdle() {
+       // Although the signals we care about have been removed from
+       // sig.wanted, it is possible that another thread has received
+       // a signal, has read from sig.wanted, is now updating sig.mask,
+       // and has not yet woken up the processor thread. We need to wait
+       // until all current signal deliveries have completed.
+       for atomic.Load(&sig.delivering) != 0 {
+               Gosched()
+       }
+
        // 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.