Skip to content

Commit 37f4822

Browse files
panjf2000gopherbot
authored andcommitted
net: separate the Solaris fast/slow path of setting SOCK_* from others
Along with the removal of the slow path from Linux and *BSD. For #59359 Change-Id: I6c79594252e5e5f1c1c57c11e09458fcae3793d2 Reviewed-on: https://go-review.googlesource.com/c/go/+/577175 Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Andy Pan <[email protected]>
1 parent 16df533 commit 37f4822

9 files changed

+263
-54
lines changed

src/internal/poll/sock_cloexec.go

+2-29
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// This file implements accept for platforms that provide a fast path for
66
// setting SetNonblock and CloseOnExec.
77

8-
//go:build dragonfly || freebsd || (linux && !arm) || netbsd || openbsd || solaris
8+
//go:build dragonfly || freebsd || (linux && !arm) || netbsd || openbsd
99

1010
package poll
1111

@@ -15,35 +15,8 @@ import "syscall"
1515
// descriptor as nonblocking and close-on-exec.
1616
func accept(s int) (int, syscall.Sockaddr, string, error) {
1717
ns, sa, err := Accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
18-
// TODO: We can remove the fallback on Linux and *BSD,
19-
// as currently supported versions all support accept4
20-
// with SOCK_CLOEXEC, but Solaris does not. See issue #59359.
21-
switch err {
22-
case nil:
23-
return ns, sa, "", nil
24-
default: // errors other than the ones listed
25-
return -1, sa, "accept4", err
26-
case syscall.ENOSYS: // syscall missing
27-
case syscall.EINVAL: // some Linux use this instead of ENOSYS
28-
case syscall.EACCES: // some Linux use this instead of ENOSYS
29-
case syscall.EFAULT: // some Linux use this instead of ENOSYS
30-
}
31-
32-
// See ../syscall/exec_unix.go for description of ForkLock.
33-
// It is probably okay to hold the lock across syscall.Accept
34-
// because we have put fd.sysfd into non-blocking mode.
35-
// However, a call to the File method will put it back into
36-
// blocking mode. We can't take that risk, so no use of ForkLock here.
37-
ns, sa, err = AcceptFunc(s)
38-
if err == nil {
39-
syscall.CloseOnExec(ns)
40-
}
4118
if err != nil {
42-
return -1, nil, "accept", err
43-
}
44-
if err = syscall.SetNonblock(ns, true); err != nil {
45-
CloseFunc(ns)
46-
return -1, nil, "setnonblock", err
19+
return -1, nil, "accept4", err
4720
}
4821
return ns, sa, "", nil
4922
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file implements accept for platforms that provide a fast path for
6+
// setting SetNonblock and CloseOnExec, but don't necessarily have accept4.
7+
// The accept4(3c) function was added to Oracle Solaris in the Solaris 11.4.0
8+
// release. Thus, on releases prior to 11.4, we fall back to the combination
9+
// of accept(3c) and fcntl(2).
10+
11+
package poll
12+
13+
import (
14+
"internal/syscall/unix"
15+
"syscall"
16+
)
17+
18+
// Wrapper around the accept system call that marks the returned file
19+
// descriptor as nonblocking and close-on-exec.
20+
func accept(s int) (int, syscall.Sockaddr, string, error) {
21+
// Perform a cheap test and try the fast path first.
22+
if unix.SupportAccept4() {
23+
ns, sa, err := Accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
24+
if err != nil {
25+
return -1, nil, "accept4", err
26+
}
27+
return ns, sa, "", nil
28+
}
29+
30+
// See ../syscall/exec_unix.go for description of ForkLock.
31+
// It is probably okay to hold the lock across syscall.Accept
32+
// because we have put fd.sysfd into non-blocking mode.
33+
// However, a call to the File method will put it back into
34+
// blocking mode. We can't take that risk, so no use of ForkLock here.
35+
ns, sa, err := AcceptFunc(s)
36+
if err == nil {
37+
syscall.CloseOnExec(ns)
38+
}
39+
if err != nil {
40+
return -1, nil, "accept", err
41+
}
42+
if err = syscall.SetNonblock(ns, true); err != nil {
43+
CloseFunc(ns)
44+
return -1, nil, "setnonblock", err
45+
}
46+
return ns, sa, "", nil
47+
}

src/internal/syscall/unix/asm_solaris.s

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88

99
TEXT ·syscall6(SB),NOSPLIT,$0-88
1010
JMP syscall·sysvicall6(SB)
11+
12+
TEXT ·rawSyscall6(SB),NOSPLIT,$0-88
13+
JMP syscall·rawSysvicall6(SB)

src/internal/syscall/unix/at_solaris.go

+4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import "syscall"
99
// Implemented as sysvicall6 in runtime/syscall_solaris.go.
1010
func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
1111

12+
// Implemented as rawsysvicall6 in runtime/syscall_solaris.go.
13+
func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
14+
1215
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
1316
//go:cgo_import_dynamic libc_openat openat "libc.so"
1417
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
18+
//go:cgo_import_dynamic libc_uname uname "libc.so"
1519

1620
const (
1721
AT_REMOVEDIR = 0x1

src/internal/syscall/unix/kernel_version_other.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build !linux
5+
//go:build !linux && !solaris
66

77
package unix
88

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unix
6+
7+
import (
8+
"runtime"
9+
"sync"
10+
"syscall"
11+
"unsafe"
12+
)
13+
14+
//go:linkname procUname libc_uname
15+
16+
var procUname uintptr
17+
18+
// utsname represents the fields of a struct utsname defined in <sys/utsname.h>.
19+
type utsname struct {
20+
Sysname [257]byte
21+
Nodename [257]byte
22+
Release [257]byte
23+
Version [257]byte
24+
Machine [257]byte
25+
}
26+
27+
// KernelVersion returns major and minor kernel version numbers
28+
// parsed from the syscall.Uname's Version field, or (0, 0) if the
29+
// version can't be obtained or parsed.
30+
func KernelVersion() (major int, minor int) {
31+
var un utsname
32+
_, _, errno := rawSyscall6(uintptr(unsafe.Pointer(&procUname)), 1, uintptr(unsafe.Pointer(&un)), 0, 0, 0, 0, 0)
33+
if errno != 0 {
34+
return 0, 0
35+
}
36+
37+
// The version string is in the form "<version>.<update>.<sru>.<build>.<reserved>"
38+
// on Solaris: https://blogs.oracle.com/solaris/post/whats-in-a-uname-
39+
// Therefore, we use the Version field on Solaris when available.
40+
ver := un.Version[:]
41+
if runtime.GOOS == "illumos" {
42+
// Illumos distributions use different formats without a parsable
43+
// and unified pattern for the Version field while Release level
44+
// string is guaranteed to be in x.y or x.y.z format regardless of
45+
// whether the kernel is Solaris or illumos.
46+
ver = un.Release[:]
47+
}
48+
49+
parseNext := func() (n int) {
50+
for i, c := range ver {
51+
if c == '.' {
52+
ver = ver[i+1:]
53+
return
54+
}
55+
if '0' <= c && c <= '9' {
56+
n = n*10 + int(c-'0')
57+
}
58+
}
59+
ver = nil
60+
return
61+
}
62+
63+
major = parseNext()
64+
minor = parseNext()
65+
66+
return
67+
}
68+
69+
// SupportSockNonblockCloexec tests if SOCK_NONBLOCK and SOCK_CLOEXEC are supported
70+
// for socket() system call, returns true if affirmative.
71+
var SupportSockNonblockCloexec = sync.OnceValue(func() bool {
72+
// First test if socket() supports SOCK_NONBLOCK and SOCK_CLOEXEC directly.
73+
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
74+
if err == nil {
75+
syscall.Close(s)
76+
return true
77+
}
78+
if err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL {
79+
// Something wrong with socket(), fall back to checking the kernel version.
80+
major, minor := KernelVersion()
81+
if runtime.GOOS == "illumos" {
82+
return major > 5 || (major == 5 && minor >= 11) // minimal requirement is SunOS 5.11
83+
}
84+
return major > 11 || (major == 11 && minor >= 4)
85+
}
86+
return false
87+
})
88+
89+
// SupportAccept4 tests whether accept4 system call is available.
90+
var SupportAccept4 = sync.OnceValue(func() bool {
91+
for {
92+
// Test if the accept4() is available.
93+
_, _, err := syscall.Accept4(0, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
94+
if err == syscall.EINTR {
95+
continue
96+
}
97+
return err != syscall.ENOSYS
98+
}
99+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build solaris
6+
7+
package unix_test
8+
9+
import (
10+
"internal/syscall/unix"
11+
"runtime"
12+
"syscall"
13+
"testing"
14+
)
15+
16+
func TestSupportSockNonblockCloexec(t *testing.T) {
17+
// Test that SupportSockNonblockCloexec returns true if socket succeeds with SOCK_NONBLOCK and SOCK_CLOEXEC.
18+
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
19+
if err == nil {
20+
syscall.Close(s)
21+
}
22+
wantSock := err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL
23+
gotSock := unix.SupportSockNonblockCloexec()
24+
if wantSock != gotSock {
25+
t.Fatalf("SupportSockNonblockCloexec, got %t; want %t", gotSock, wantSock)
26+
}
27+
28+
// Test that SupportAccept4 returns true if accept4 is available.
29+
for {
30+
_, _, err = syscall.Accept4(0, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
31+
if err != syscall.EINTR {
32+
break
33+
}
34+
}
35+
wantAccept4 := err != syscall.ENOSYS
36+
gotAccept4 := unix.SupportAccept4()
37+
if wantAccept4 != gotAccept4 {
38+
t.Fatalf("SupportAccept4, got %t; want %t", gotAccept4, wantAccept4)
39+
}
40+
41+
// Test that the version returned by KernelVersion matches expectations.
42+
major, minor := unix.KernelVersion()
43+
t.Logf("Kernel version: %d.%d", major, minor)
44+
if runtime.GOOS == "illumos" {
45+
if gotSock && gotAccept4 && (major < 5 || (major == 5 && minor < 11)) {
46+
t.Fatalf("SupportSockNonblockCloexec and SupportAccept4 are true, but kernel version is older than 5.11, SunOS version: %d.%d", major, minor)
47+
}
48+
if !gotSock && !gotAccept4 && (major > 5 || (major == 5 && minor >= 11)) {
49+
t.Errorf("SupportSockNonblockCloexec and SupportAccept4 are false, but kernel version is 5.11 or newer, SunOS version: %d.%d", major, minor)
50+
}
51+
} else { // Solaris
52+
if gotSock && gotAccept4 && (major < 11 || (major == 11 && minor < 4)) {
53+
t.Fatalf("SupportSockNonblockCloexec and SupportAccept4 are true, but kernel version is older than 11.4, Solaris version: %d.%d", major, minor)
54+
}
55+
if !gotSock && !gotAccept4 && (major > 11 || (major == 11 && minor >= 4)) {
56+
t.Errorf("SupportSockNonblockCloexec and SupportAccept4 are false, but kernel version is 11.4 or newer, Solaris version: %d.%d", major, minor)
57+
}
58+
}
59+
}

src/net/sock_cloexec.go

+1-24
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
// This file implements sysSocket for platforms that provide a fast path for
66
// setting SetNonblock and CloseOnExec.
77

8-
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
8+
//go:build dragonfly || freebsd || linux || netbsd || openbsd
99

1010
package net
1111

1212
import (
13-
"internal/poll"
1413
"os"
1514
"syscall"
1615
)
@@ -19,30 +18,8 @@ import (
1918
// descriptor as nonblocking and close-on-exec.
2019
func sysSocket(family, sotype, proto int) (int, error) {
2120
s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
22-
// TODO: We can remove the fallback on Linux and *BSD,
23-
// as currently supported versions all support accept4
24-
// with SOCK_CLOEXEC, but Solaris does not. See issue #59359.
25-
switch err {
26-
case nil:
27-
return s, nil
28-
default:
29-
return -1, os.NewSyscallError("socket", err)
30-
case syscall.EPROTONOSUPPORT, syscall.EINVAL:
31-
}
32-
33-
// See ../syscall/exec_unix.go for description of ForkLock.
34-
syscall.ForkLock.RLock()
35-
s, err = socketFunc(family, sotype, proto)
36-
if err == nil {
37-
syscall.CloseOnExec(s)
38-
}
39-
syscall.ForkLock.RUnlock()
4021
if err != nil {
4122
return -1, os.NewSyscallError("socket", err)
4223
}
43-
if err = syscall.SetNonblock(s, true); err != nil {
44-
poll.CloseFunc(s)
45-
return -1, os.NewSyscallError("setnonblock", err)
46-
}
4724
return s, nil
4825
}

src/net/sock_cloexec_solaris.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This file implements sysSocket for platforms that provide a fast path for
6+
// setting SetNonblock and CloseOnExec, but don't necessarily support it.
7+
// Support for SOCK_* flags as part of the type parameter was added to Oracle
8+
// Solaris in the 11.4 release. Thus, on releases prior to 11.4, we fall back
9+
// to the combination of socket(3c) and fcntl(2).
10+
11+
package net
12+
13+
import (
14+
"internal/poll"
15+
"internal/syscall/unix"
16+
"os"
17+
"syscall"
18+
)
19+
20+
// Wrapper around the socket system call that marks the returned file
21+
// descriptor as nonblocking and close-on-exec.
22+
func sysSocket(family, sotype, proto int) (int, error) {
23+
// Perform a cheap test and try the fast path first.
24+
if unix.SupportSockNonblockCloexec() {
25+
s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
26+
if err != nil {
27+
return -1, os.NewSyscallError("socket", err)
28+
}
29+
return s, nil
30+
}
31+
32+
// See ../syscall/exec_unix.go for description of ForkLock.
33+
syscall.ForkLock.RLock()
34+
s, err := socketFunc(family, sotype, proto)
35+
if err == nil {
36+
syscall.CloseOnExec(s)
37+
}
38+
syscall.ForkLock.RUnlock()
39+
if err != nil {
40+
return -1, os.NewSyscallError("socket", err)
41+
}
42+
if err = syscall.SetNonblock(s, true); err != nil {
43+
poll.CloseFunc(s)
44+
return -1, os.NewSyscallError("setnonblock", err)
45+
}
46+
return s, nil
47+
}

0 commit comments

Comments
 (0)