Skip to content

Commit 9c7eb68

Browse files
[release-branch.go1.15] runtime: stop preemption during syscall.Exec on Darwin
On current macOS versions a program that receives a signal during an execve can fail with a SIGILL signal. This appears to be a macOS kernel bug. It has been reported to Apple. This CL partially works around the problem by using execLock to not send preemption signals during execve. Of course some other stray signal could occur, but at least we can avoid exacerbating the problem. We can't simply disable signals, as that would mean that the exec'ed process would start with all signals blocked, which it likely does not expect. For #41702 Fixes #41704 Change-Id: I91b0add967b315671ddcf73269c4d30136e579b4 Reviewed-on: https://go-review.googlesource.com/c/go/+/262438 Trust: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]> (cherry picked from commit 64fb6ae) Reviewed-on: https://go-review.googlesource.com/c/go/+/262717
1 parent 1984ee0 commit 9c7eb68

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

src/runtime/signal_unix.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,13 @@ func preemptM(mp *m) {
357357
// required).
358358
return
359359
}
360+
361+
// On Darwin, don't try to preempt threads during exec.
362+
// Issue #41702.
363+
if GOOS == "darwin" {
364+
execLock.rlock()
365+
}
366+
360367
if atomic.Cas(&mp.signalPending, 0, 1) {
361368
// If multiple threads are preempting the same M, it may send many
362369
// signals to the same M such that it hardly make progress, causing
@@ -365,6 +372,10 @@ func preemptM(mp *m) {
365372
// Only send a signal if there isn't already one pending.
366373
signalM(mp, sigPreempt)
367374
}
375+
376+
if GOOS == "darwin" {
377+
execLock.runlock()
378+
}
368379
}
369380

370381
// sigFetchG fetches the value of G safely when running in a signal handler.

src/syscall/exec_unix_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ package syscall_test
99
import (
1010
"internal/testenv"
1111
"io"
12+
"math/rand"
1213
"os"
1314
"os/exec"
1415
"os/signal"
16+
"runtime"
1517
"syscall"
1618
"testing"
19+
"time"
1720
"unsafe"
1821
)
1922

@@ -241,3 +244,45 @@ func TestInvalidExec(t *testing.T) {
241244
}
242245
})
243246
}
247+
248+
// TestExec is for issue #41702.
249+
func TestExec(t *testing.T) {
250+
cmd := exec.Command(os.Args[0], "-test.run=TestExecHelper")
251+
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=2")
252+
o, err := cmd.CombinedOutput()
253+
if err != nil {
254+
t.Errorf("%s\n%v", o, err)
255+
}
256+
}
257+
258+
// TestExecHelper is used by TestExec. It does nothing by itself.
259+
// In testing on macOS 10.14, this used to fail with
260+
// "signal: illegal instruction" more than half the time.
261+
func TestExecHelper(t *testing.T) {
262+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "2" {
263+
return
264+
}
265+
266+
// We don't have to worry about restoring these values.
267+
// We are in a child process that only runs this test,
268+
// and we are going to call syscall.Exec anyhow.
269+
runtime.GOMAXPROCS(50)
270+
os.Setenv("GO_WANT_HELPER_PROCESS", "3")
271+
272+
stop := time.Now().Add(time.Second)
273+
for i := 0; i < 100; i++ {
274+
go func(i int) {
275+
r := rand.New(rand.NewSource(int64(i)))
276+
for time.Now().Before(stop) {
277+
r.Uint64()
278+
}
279+
}(i)
280+
}
281+
282+
time.Sleep(10 * time.Millisecond)
283+
284+
argv := []string{os.Args[0], "-test.run=TestExecHelper"}
285+
syscall.Exec(os.Args[0], argv, os.Environ())
286+
287+
t.Error("syscall.Exec returned")
288+
}

0 commit comments

Comments
 (0)