Skip to content

Commit 8c1db77

Browse files
internal/poll, os: loop on EINTR
Historically we've assumed that we can install all signal handlers with the SA_RESTART flag set, and let the system restart slow functions if a signal is received. Therefore, we don't have to worry about EINTR. This is only partially true, and we've added EINTR checks already for connect, and open/read on Darwin, and sendfile on Solaris. Other cases have turned up in #36644, #38033, and #38836. Also, #20400 points out that when Go code is included in a C program, the C program may install its own signal handlers without SA_RESTART. In that case, Go code will see EINTR no matter what it does. So, go ahead and check for EINTR. We don't check in the syscall package; people using syscalls directly may want to check for EINTR themselves. But we do check for EINTR in the higher level APIs in os and net, and retry the system call if we see it. This change looks safe, but of course we may be missing some cases where we need to check for EINTR. As such cases turn up, we can add tests to runtime/testdata/testprogcgo/eintr.go, and fix the code. If there are any such cases, their handling after this change will be no worse than it is today. For #22838 Fixes #20400 Fixes #36644 Fixes #38033 Fixes #38836 Change-Id: I7e46ca8cafed0429c7a2386cc9edc9d9d47a6896 Reviewed-on: https://go-review.googlesource.com/c/go/+/232862 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 910fee4 commit 8c1db77

14 files changed

+359
-30
lines changed

src/internal/poll/copy_file_range_linux.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ func copyFileRange(dst, src *FD, max int) (written int64, err error) {
8888
return 0, err
8989
}
9090
defer src.readUnlock()
91-
n, err := unix.CopyFileRange(src.Sysfd, nil, dst.Sysfd, nil, max, 0)
91+
var n int
92+
for {
93+
n, err = unix.CopyFileRange(src.Sysfd, nil, dst.Sysfd, nil, max, 0)
94+
if err != syscall.EINTR {
95+
break
96+
}
97+
}
9298
return int64(n), err
9399
}

src/internal/poll/fd_unix.go

+47-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package poll
88

99
import (
1010
"io"
11-
"runtime"
1211
"sync/atomic"
1312
"syscall"
1413
)
@@ -153,20 +152,14 @@ func (fd *FD) Read(p []byte) (int, error) {
153152
p = p[:maxRW]
154153
}
155154
for {
156-
n, err := syscall.Read(fd.Sysfd, p)
155+
n, err := ignoringEINTR(syscall.Read, fd.Sysfd, p)
157156
if err != nil {
158157
n = 0
159158
if err == syscall.EAGAIN && fd.pd.pollable() {
160159
if err = fd.pd.waitRead(fd.isFile); err == nil {
161160
continue
162161
}
163162
}
164-
165-
// On MacOS we can see EINTR here if the user
166-
// pressed ^Z. See issue #22838.
167-
if runtime.GOOS == "darwin" && err == syscall.EINTR {
168-
continue
169-
}
170163
}
171164
err = fd.eofError(n, err)
172165
return n, err
@@ -184,7 +177,16 @@ func (fd *FD) Pread(p []byte, off int64) (int, error) {
184177
if fd.IsStream && len(p) > maxRW {
185178
p = p[:maxRW]
186179
}
187-
n, err := syscall.Pread(fd.Sysfd, p, off)
180+
var (
181+
n int
182+
err error
183+
)
184+
for {
185+
n, err = syscall.Pread(fd.Sysfd, p, off)
186+
if err != syscall.EINTR {
187+
break
188+
}
189+
}
188190
if err != nil {
189191
n = 0
190192
}
@@ -205,6 +207,9 @@ func (fd *FD) ReadFrom(p []byte) (int, syscall.Sockaddr, error) {
205207
for {
206208
n, sa, err := syscall.Recvfrom(fd.Sysfd, p, 0)
207209
if err != nil {
210+
if err == syscall.EINTR {
211+
continue
212+
}
208213
n = 0
209214
if err == syscall.EAGAIN && fd.pd.pollable() {
210215
if err = fd.pd.waitRead(fd.isFile); err == nil {
@@ -229,6 +234,9 @@ func (fd *FD) ReadMsg(p []byte, oob []byte) (int, int, int, syscall.Sockaddr, er
229234
for {
230235
n, oobn, flags, sa, err := syscall.Recvmsg(fd.Sysfd, p, oob, 0)
231236
if err != nil {
237+
if err == syscall.EINTR {
238+
continue
239+
}
232240
// TODO(dfc) should n and oobn be set to 0
233241
if err == syscall.EAGAIN && fd.pd.pollable() {
234242
if err = fd.pd.waitRead(fd.isFile); err == nil {
@@ -256,7 +264,7 @@ func (fd *FD) Write(p []byte) (int, error) {
256264
if fd.IsStream && max-nn > maxRW {
257265
max = nn + maxRW
258266
}
259-
n, err := syscall.Write(fd.Sysfd, p[nn:max])
267+
n, err := ignoringEINTR(syscall.Write, fd.Sysfd, p[nn:max])
260268
if n > 0 {
261269
nn += n
262270
}
@@ -293,6 +301,9 @@ func (fd *FD) Pwrite(p []byte, off int64) (int, error) {
293301
max = nn + maxRW
294302
}
295303
n, err := syscall.Pwrite(fd.Sysfd, p[nn:max], off+int64(nn))
304+
if err == syscall.EINTR {
305+
continue
306+
}
296307
if n > 0 {
297308
nn += n
298309
}
@@ -319,6 +330,9 @@ func (fd *FD) WriteTo(p []byte, sa syscall.Sockaddr) (int, error) {
319330
}
320331
for {
321332
err := syscall.Sendto(fd.Sysfd, p, 0, sa)
333+
if err == syscall.EINTR {
334+
continue
335+
}
322336
if err == syscall.EAGAIN && fd.pd.pollable() {
323337
if err = fd.pd.waitWrite(fd.isFile); err == nil {
324338
continue
@@ -342,6 +356,9 @@ func (fd *FD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (int, int, err
342356
}
343357
for {
344358
n, err := syscall.SendmsgN(fd.Sysfd, p, oob, sa, 0)
359+
if err == syscall.EINTR {
360+
continue
361+
}
345362
if err == syscall.EAGAIN && fd.pd.pollable() {
346363
if err = fd.pd.waitWrite(fd.isFile); err == nil {
347364
continue
@@ -370,6 +387,8 @@ func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
370387
return s, rsa, "", err
371388
}
372389
switch err {
390+
case syscall.EINTR:
391+
continue
373392
case syscall.EAGAIN:
374393
if fd.pd.pollable() {
375394
if err = fd.pd.waitRead(fd.isFile); err == nil {
@@ -404,7 +423,7 @@ func (fd *FD) ReadDirent(buf []byte) (int, error) {
404423
}
405424
defer fd.decref()
406425
for {
407-
n, err := syscall.ReadDirent(fd.Sysfd, buf)
426+
n, err := ignoringEINTR(syscall.ReadDirent, fd.Sysfd, buf)
408427
if err != nil {
409428
n = 0
410429
if err == syscall.EAGAIN && fd.pd.pollable() {
@@ -495,7 +514,7 @@ func (fd *FD) WriteOnce(p []byte) (int, error) {
495514
return 0, err
496515
}
497516
defer fd.writeUnlock()
498-
return syscall.Write(fd.Sysfd, p)
517+
return ignoringEINTR(syscall.Write, fd.Sysfd, p)
499518
}
500519

501520
// RawRead invokes the user-defined function f for a read operation.
@@ -535,3 +554,19 @@ func (fd *FD) RawWrite(f func(uintptr) bool) error {
535554
}
536555
}
537556
}
557+
558+
// ignoringEINTR makes a function call and repeats it if it returns
559+
// an EINTR error. This appears to be required even though we install
560+
// all signal handlers with SA_RESTART: see #22838, #38033, #38836.
561+
// Also #20400 and #36644 are issues in which a signal handler is
562+
// installed without setting SA_RESTART. None of these are the common case,
563+
// but there are enough of them that it seems that we can't avoid
564+
// an EINTR loop.
565+
func ignoringEINTR(fn func(fd int, p []byte) (int, error), fd int, p []byte) (int, error) {
566+
for {
567+
n, err := fn(fd, p)
568+
if err != syscall.EINTR {
569+
return n, err
570+
}
571+
}
572+
}

src/internal/poll/fd_writev_unix.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ import (
1212
)
1313

1414
func writev(fd int, iovecs []syscall.Iovec) (uintptr, error) {
15-
r, _, e := syscall.Syscall(syscall.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(len(iovecs)))
15+
var (
16+
r uintptr
17+
e syscall.Errno
18+
)
19+
for {
20+
r, _, e = syscall.Syscall(syscall.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(len(iovecs)))
21+
if e != syscall.EINTR {
22+
break
23+
}
24+
}
1625
if e != 0 {
17-
return r, syscall.Errno(e)
26+
return r, e
1827
}
1928
return r, nil
2029
}

src/internal/poll/sendfile_bsd.go

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func SendFile(dstFD *FD, src int, pos, remain int64) (int64, error) {
3535
} else if n == 0 && err1 == nil {
3636
break
3737
}
38+
if err1 == syscall.EINTR {
39+
continue
40+
}
3841
if err1 == syscall.EAGAIN {
3942
if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
4043
continue

src/internal/poll/sendfile_linux.go

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ func SendFile(dstFD *FD, src int, remain int64) (int64, error) {
3232
} else if n == 0 && err1 == nil {
3333
break
3434
}
35+
if err1 == syscall.EINTR {
36+
continue
37+
}
3538
if err1 == syscall.EAGAIN {
3639
if err1 = dstFD.pd.waitWrite(dstFD.isFile); err1 == nil {
3740
continue

src/internal/poll/splice_linux.go

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ func spliceDrain(pipefd int, sock *FD, max int) (int, error) {
8787
}
8888
for {
8989
n, err := splice(pipefd, sock.Sysfd, max, spliceNonblock)
90+
if err == syscall.EINTR {
91+
continue
92+
}
9093
if err != syscall.EAGAIN {
9194
return n, err
9295
}

src/internal/poll/writev.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ func (fd *FD) Writev(v *[][]byte) (int64, error) {
6868
iovecs[i] = syscall.Iovec{}
6969
}
7070
if err != nil {
71-
if err.(syscall.Errno) == syscall.EAGAIN {
71+
if err == syscall.EINTR {
72+
continue
73+
}
74+
if err == syscall.EAGAIN {
7275
if err = fd.pd.waitWrite(fd.isFile); err == nil {
7376
continue
7477
}

src/os/exec_unix.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,18 @@ func (p *Process) wait() (ps *ProcessState, err error) {
3333
p.sigMu.Unlock()
3434
}
3535

36-
var status syscall.WaitStatus
37-
var rusage syscall.Rusage
38-
pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage)
36+
var (
37+
status syscall.WaitStatus
38+
rusage syscall.Rusage
39+
pid1 int
40+
e error
41+
)
42+
for {
43+
pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage)
44+
if e != syscall.EINTR {
45+
break
46+
}
47+
}
3948
if e != nil {
4049
return nil, NewSyscallError("wait", e)
4150
}

src/os/wait_wait6.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ const _P_PID = 0
1818
// It does not actually call p.Wait.
1919
func (p *Process) blockUntilWaitable() (bool, error) {
2020
var errno syscall.Errno
21-
// The arguments on 32-bit FreeBSD look like the following:
22-
// - freebsd32_wait6_args{ idtype, id1, id2, status, options, wrusage, info } or
23-
// - freebsd32_wait6_args{ idtype, pad, id1, id2, status, options, wrusage, info } when PAD64_REQUIRED=1 on ARM, MIPS or PowerPC
24-
if runtime.GOARCH == "386" {
25-
_, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0, 0)
26-
} else if runtime.GOARCH == "arm" {
27-
_, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, 0, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0)
28-
} else {
29-
_, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0)
21+
for {
22+
// The arguments on 32-bit FreeBSD look like the following:
23+
// - freebsd32_wait6_args{ idtype, id1, id2, status, options, wrusage, info } or
24+
// - freebsd32_wait6_args{ idtype, pad, id1, id2, status, options, wrusage, info } when PAD64_REQUIRED=1 on ARM, MIPS or PowerPC
25+
if runtime.GOARCH == "386" {
26+
_, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0, 0)
27+
} else if runtime.GOARCH == "arm" {
28+
_, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, 0, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0)
29+
} else {
30+
_, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0)
31+
}
32+
if errno != syscall.EINTR {
33+
break
34+
}
3035
}
3136
runtime.KeepAlive(p)
3237
if errno != 0 {

src/os/wait_waitid.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ func (p *Process) blockUntilWaitable() (bool, error) {
2727
// We don't care about the values it returns.
2828
var siginfo [16]uint64
2929
psig := &siginfo[0]
30-
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
30+
var e syscall.Errno
31+
for {
32+
_, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
33+
if e != syscall.EINTR {
34+
break
35+
}
36+
}
3137
runtime.KeepAlive(p)
3238
if e != 0 {
3339
// waitid has been available since Linux 2.6.9, but

src/runtime/crash_cgo_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,30 @@ func TestSegv(t *testing.T) {
573573
})
574574
}
575575
}
576+
577+
// TestEINTR tests that we handle EINTR correctly.
578+
// See issue #20400 and friends.
579+
func TestEINTR(t *testing.T) {
580+
switch runtime.GOOS {
581+
case "plan9", "windows":
582+
t.Skipf("no EINTR on %s", runtime.GOOS)
583+
case "linux":
584+
if runtime.GOARCH == "386" {
585+
// On linux-386 the Go signal handler sets
586+
// a restorer function that is not preserved
587+
// by the C sigaction call in the test,
588+
// causing the signal handler to crash when
589+
// returning the normal code. The test is not
590+
// architecture-specific, so just skip on 386
591+
// rather than doing a complicated workaround.
592+
t.Skip("skipping on linux-386; C sigaction does not preserve Go restorer")
593+
}
594+
}
595+
596+
t.Parallel()
597+
output := runTestProg(t, "testprogcgo", "EINTR")
598+
want := "OK\n"
599+
if output != want {
600+
t.Fatalf("want %s, got %s\n", want, output)
601+
}
602+
}

0 commit comments

Comments
 (0)