summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2013-08-16 22:43:05 -0400
committerRuss Cox <rsc@golang.org>2013-08-16 22:43:05 -0400
commitab44fd165fa8aa8f44103a035726527b0fdeb025 (patch)
tree1cb4432071883e3e07fc9aafbca3f1409bac8566
parentaa613443d38b1cc8a06f1a90a8171fd472b9bb14 (diff)
downloadgo-ab44fd165fa8aa8f44103a035726527b0fdeb025.tar.gz
net: limit number of concurrent cgo calls
The limit is 500. There is no way to change it. This primarily affects name resolution. If a million goroutines try to resolve DNS names, only 500 will get to execute cgo calls at a time. But in return the operating system will not crash. Fixes issue 5625. R=golang-dev, dan.kortschak, r, dvyukov CC=bradfitz, golang-dev https://codereview.appspot.com/13038043
-rw-r--r--src/pkg/net/cgo_unix.go6
-rw-r--r--src/pkg/net/dialgoogle_test.go24
-rw-r--r--src/pkg/net/lookup_windows.go25
-rw-r--r--src/pkg/net/net.go16
4 files changed, 71 insertions, 0 deletions
diff --git a/src/pkg/net/cgo_unix.go b/src/pkg/net/cgo_unix.go
index c39ada6ae..ade84162f 100644
--- a/src/pkg/net/cgo_unix.go
+++ b/src/pkg/net/cgo_unix.go
@@ -32,6 +32,9 @@ func cgoLookupHost(name string) (addrs []string, err error, completed bool) {
}
func cgoLookupPort(net, service string) (port int, err error, completed bool) {
+ acquireThread()
+ defer releaseThread()
+
var res *C.struct_addrinfo
var hints C.struct_addrinfo
@@ -79,6 +82,9 @@ func cgoLookupPort(net, service string) (port int, err error, completed bool) {
}
func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool) {
+ acquireThread()
+ defer releaseThread()
+
var res *C.struct_addrinfo
var hints C.struct_addrinfo
diff --git a/src/pkg/net/dialgoogle_test.go b/src/pkg/net/dialgoogle_test.go
index 73a94f5bf..0a0f7eef2 100644
--- a/src/pkg/net/dialgoogle_test.go
+++ b/src/pkg/net/dialgoogle_test.go
@@ -54,6 +54,30 @@ var googleaddrsipv4 = []string{
"[0:0:0:0:0:ffff::%d.%d.%d.%d]:80",
}
+func TestDNSThreadLimit(t *testing.T) {
+ if testing.Short() || !*testExternal {
+ t.Skip("skipping test to avoid external network")
+ }
+
+ const N = 10000
+ c := make(chan int, N)
+ for i := 0; i < N; i++ {
+ go func() {
+ LookupIP(fmt.Sprintf("%d.net-test.golang.org", i))
+ c <- 1
+ }()
+ }
+ // Don't bother waiting for the stragglers; stop at 0.9 N.
+ for i := 0; i < N*9/10; i++ {
+ if i%100 == 0 {
+ //println("TestDNSThreadLimit:", i)
+ }
+ <-c
+ }
+
+ // If we're still here, it worked.
+}
+
func TestDialGoogleIPv4(t *testing.T) {
if testing.Short() || !*testExternal {
t.Skip("skipping test to avoid external network")
diff --git a/src/pkg/net/lookup_windows.go b/src/pkg/net/lookup_windows.go
index 3b29724f2..6d20b7976 100644
--- a/src/pkg/net/lookup_windows.go
+++ b/src/pkg/net/lookup_windows.go
@@ -34,6 +34,8 @@ func lookupProtocol(name string) (proto int, err error) {
}
ch := make(chan result)
go func() {
+ acquireThread()
+ defer releaseThread()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
proto, err := getprotobyname(name)
@@ -56,6 +58,7 @@ func lookupHost(name string) (addrs []string, err error) {
}
func gethostbyname(name string) (addrs []IP, err error) {
+ // caller already acquired thread
h, err := syscall.GetHostByName(name)
if err != nil {
return nil, os.NewSyscallError("GetHostByName", err)
@@ -83,6 +86,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
}
ch := make(chan result)
go func() {
+ acquireThread()
+ defer releaseThread()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
addrs, err := gethostbyname(name)
@@ -93,6 +98,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
}
func newLookupIP(name string) (addrs []IP, err error) {
+ acquireThread()
+ defer releaseThread()
hints := syscall.AddrinfoW{
Family: syscall.AF_UNSPEC,
Socktype: syscall.SOCK_STREAM,
@@ -122,6 +129,8 @@ func newLookupIP(name string) (addrs []IP, err error) {
}
func getservbyname(network, service string) (port int, err error) {
+ acquireThread()
+ defer releaseThread()
switch network {
case "tcp4", "tcp6":
network = "tcp"
@@ -144,6 +153,8 @@ func oldLookupPort(network, service string) (port int, err error) {
}
ch := make(chan result)
go func() {
+ acquireThread()
+ defer releaseThread()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
port, err := getservbyname(network, service)
@@ -154,6 +165,8 @@ func oldLookupPort(network, service string) (port int, err error) {
}
func newLookupPort(network, service string) (port int, err error) {
+ acquireThread()
+ defer releaseThread()
var stype int32
switch network {
case "tcp4", "tcp6":
@@ -188,6 +201,8 @@ func newLookupPort(network, service string) (port int, err error) {
}
func lookupCNAME(name string) (cname string, err error) {
+ acquireThread()
+ defer releaseThread()
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil)
if e != nil {
@@ -202,6 +217,8 @@ func lookupCNAME(name string) (cname string, err error) {
}
func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
+ acquireThread()
+ defer releaseThread()
var target string
if service == "" && proto == "" {
target = name
@@ -224,6 +241,8 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
}
func lookupMX(name string) (mx []*MX, err error) {
+ acquireThread()
+ defer releaseThread()
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil)
if e != nil {
@@ -240,6 +259,8 @@ func lookupMX(name string) (mx []*MX, err error) {
}
func lookupNS(name string) (ns []*NS, err error) {
+ acquireThread()
+ defer releaseThread()
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &r, nil)
if e != nil {
@@ -255,6 +276,8 @@ func lookupNS(name string) (ns []*NS, err error) {
}
func lookupTXT(name string) (txt []string, err error) {
+ acquireThread()
+ defer releaseThread()
var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)
if e != nil {
@@ -273,6 +296,8 @@ func lookupTXT(name string) (txt []string, err error) {
}
func lookupAddr(addr string) (name []string, err error) {
+ acquireThread()
+ defer releaseThread()
arpa, err := reverseaddr(addr)
if err != nil {
return nil, err
diff --git a/src/pkg/net/net.go b/src/pkg/net/net.go
index 2cbd5d854..c918e96b4 100644
--- a/src/pkg/net/net.go
+++ b/src/pkg/net/net.go
@@ -433,3 +433,19 @@ func (d *deadline) setTime(t time.Time) {
d.set(t.UnixNano())
}
}
+
+// Limit the number of concurrent cgo-using goroutines, because
+// each will block an entire operating system thread. The usual culprit
+// is resolving many DNS names in separate goroutines but the DNS
+// server is not responding. Then the many lookups each use a different
+// thread, and the system or the program runs out of threads.
+
+var threadLimit = make(chan struct{}, 500)
+
+func acquireThread() {
+ threadLimit <- struct{}{}
+}
+
+func releaseThread() {
+ <-threadLimit
+}