update reuseport for the check
Juan Batiz-Benet committed
Jan 20, 2015 at 19:34 UTC
ee4c727c83e41e67788e897108e820735a7dba11
5 files changed
+119
-3
Godeps/Godeps.json
+1
-1
@@ -160,7 +160,7 @@
160
},
161
{
162
"ImportPath": "github.com/jbenet/go-reuseport",
163
- "Rev": "1e1968c4744fef51234e83f015aa0187b4bd796b"
163
+ "Rev": "a2e454f12a99b8898c41f9dcebae6c35dc3efa3a"
164
},
165
{
166
"ImportPath": "github.com/jbenet/go-sockaddr/net",
Godeps/_workspace/src/github.com/jbenet/go-reuseport/.travis.yml
+1
-2
@@ -1,11 +1,10 @@
1
language: go
2
3
go:
4
- - 1.2
4
- 1.3
5
- 1.4
6
- release
7
- tip
8
9
script:
11
- - go test -v ./...
10
+ - go test -race -cpu=5 -v ./...
Godeps/_workspace/src/github.com/jbenet/go-reuseport/available_unix.go
new
+90
@@ -0,0 +1,90 @@
1
+// +build darwin freebsd dragonfly netbsd openbsd linux
2
+package reuseport
3
+
4
+import (
5
+ "sync"
6
+ "sync/atomic"
7
+ "syscall"
8
+ "time"
9
+)
10
+
11
+// checker is a struct to gather the availability check fields + funcs.
12
+// we use atomic ints because this is potentially a really hot function call.
13
+type checkerT struct {
14
+ avail int32 // atomic int managed by set/isAvailable()
15
+ check int32 // atomic int managed by has/checked()
16
+ mu sync.Mutex // synchonizes the actual check
17
+}
18
+
19
+// the static location of the vars.
20
+var checker checkerT
21
+
22
+func (c *checkerT) isAvailable() bool {
23
+ return atomic.LoadInt32(&c.avail) != 0
24
+}
25
+
26
+func (c *checkerT) setIsAvailable(b bool) {
27
+ if b {
28
+ atomic.StoreInt32(&c.avail, 1)
29
+ } else {
30
+ atomic.StoreInt32(&c.avail, 0)
31
+ }
32
+}
33
+
34
+func (c *checkerT) hasChecked() bool {
35
+ return atomic.LoadInt32(&c.check) != 0
36
+}
37
+
38
+func (c *checkerT) setHasChecked(b bool) {
39
+ if b {
40
+ atomic.StoreInt32(&c.check, 1)
41
+ } else {
42
+ atomic.StoreInt32(&c.check, 0)
43
+ }
44
+}
45
+
46
+// Available returns whether or not SO_REUSEPORT is available in the OS.
47
+// It does so by attepting to open a tcp listener, setting the option, and
48
+// checking ENOPROTOOPT on error. After checking, the decision is cached
49
+// for the rest of the process run.
50
+func available() bool {
51
+ if checker.hasChecked() {
52
+ return checker.isAvailable()
53
+ }
54
+
55
+ // synchronize, only one should check
56
+ checker.mu.Lock()
57
+ defer checker.mu.Unlock()
58
+
59
+ // we blocked. someone may have been gotten this.
60
+ if checker.hasChecked() {
61
+ return checker.isAvailable()
62
+ }
63
+
64
+ // there may be fluke reasons to fail to add a listener.
65
+ // so we give it 5 shots. if not, give up and call it not avail.
66
+ for i := 0; i < 5; i++ {
67
+ // try to listen at tcp port 0.
68
+ l, err := listenStream("tcp", "127.0.0.1:0")
69
+ if err == nil {
70
+ // no error? available.
71
+ checker.setIsAvailable(true)
72
+ checker.setHasChecked(true)
73
+ l.Close() // Go back to the Shadow!
74
+ return true
75
+ }
76
+
77
+ if errno, ok := err.(syscall.Errno); ok {
78
+ if errno == syscall.ENOPROTOOPT {
79
+ break // :( that's all folks.
80
+ }
81
+ }
82
+
83
+ // not an errno? or not ENOPROTOOPT? retry.
84
+ <-time.After(20 * time.Millisecond) // wait a bit
85
+ }
86
+
87
+ checker.setIsAvailable(false)
88
+ checker.setHasChecked(true)
89
+ return false
90
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_windows.go
+7
@@ -13,3 +13,10 @@ func listen(network, address string) (net.Listener, error) {
13
func dial(dialer net.Dialer, network, address string) (net.Conn, error) {
14
return dialer.Dial(network, address)
15
}
16
+
17
+// on windows, we just use the regular functions. sources
18
+// vary on this-- some claim port reuse behavior is on by default
19
+// on some windows systems. So we try. may the force be with you.
20
+func available() bool {
21
+ return true
22
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/interface.go
+20
@@ -20,9 +20,18 @@ package reuseport
20
import (
21
"errors"
22
"net"
23
+ "syscall"
24
"time"
25
)
26
27
+// Available returns whether or not SO_REUSEPORT is available in the OS.
28
+// It does so by attepting to open a tcp listener, setting the option, and
29
+// checking ENOPROTOOPT on error. After checking, the decision is cached
30
+// for the rest of the process run.
31
+func Available() bool {
32
+ return available()
33
+}
34
+
35
// ErrUnsuportedProtocol signals that the protocol is not currently
36
// supported by this package. This package currently only supports TCP.
37
var ErrUnsupportedProtocol = errors.New("protocol not yet supported")
@@ -34,6 +43,10 @@ var ErrReuseFailed = errors.New("reuse failed")
43
// Returns a net.Listener created from a file discriptor for a socket
44
// with SO_REUSEPORT and SO_REUSEADDR option set.
45
func Listen(network, address string) (net.Listener, error) {
46
+ if !available() {
47
+ return nil, syscall.Errno(syscall.ENOPROTOOPT)
48
+ }
49
+
50
return listenStream(network, address)
51
}
52
@@ -41,6 +54,10 @@ func Listen(network, address string) (net.Listener, error) {
54
// Returns a net.Listener created from a file discriptor for a socket
55
// with SO_REUSEPORT and SO_REUSEADDR option set.
56
func ListenPacket(network, address string) (net.PacketConn, error) {
57
+ if !available() {
58
+ return nil, syscall.Errno(syscall.ENOPROTOOPT)
59
+ }
60
+
61
return listenPacket(network, address)
62
}
63
@@ -48,6 +65,9 @@ func ListenPacket(network, address string) (net.PacketConn, error) {
65
// Returns a net.Conn created from a file discriptor for a socket
66
// with SO_REUSEPORT and SO_REUSEADDR option set.
67
func Dial(network, laddr, raddr string) (net.Conn, error) {
68
+ if !available() {
69
+ return nil, syscall.Errno(syscall.ENOPROTOOPT)
70
+ }
71
72
var d Dialer
73
if laddr != "" {