Commit 1d3efd65 authored by Russ Cox's avatar Russ Cox

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 #5625.

R=golang-dev, dan.kortschak, r, dvyukov
CC=bradfitz, golang-dev
https://golang.org/cl/13038043
parent 665feeed
...@@ -32,6 +32,9 @@ func cgoLookupHost(name string) (addrs []string, err error, completed bool) { ...@@ -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) { func cgoLookupPort(net, service string) (port int, err error, completed bool) {
acquireThread()
defer releaseThread()
var res *C.struct_addrinfo var res *C.struct_addrinfo
var hints C.struct_addrinfo var hints C.struct_addrinfo
...@@ -79,6 +82,9 @@ func cgoLookupPort(net, service string) (port int, err error, completed bool) { ...@@ -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) { func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool) {
acquireThread()
defer releaseThread()
var res *C.struct_addrinfo var res *C.struct_addrinfo
var hints C.struct_addrinfo var hints C.struct_addrinfo
......
...@@ -54,6 +54,30 @@ var googleaddrsipv4 = []string{ ...@@ -54,6 +54,30 @@ var googleaddrsipv4 = []string{
"[0:0:0:0:0:ffff::%d.%d.%d.%d]:80", "[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) { func TestDialGoogleIPv4(t *testing.T) {
if testing.Short() || !*testExternal { if testing.Short() || !*testExternal {
t.Skip("skipping test to avoid external network") t.Skip("skipping test to avoid external network")
......
...@@ -34,6 +34,8 @@ func lookupProtocol(name string) (proto int, err error) { ...@@ -34,6 +34,8 @@ func lookupProtocol(name string) (proto int, err error) {
} }
ch := make(chan result) ch := make(chan result)
go func() { go func() {
acquireThread()
defer releaseThread()
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
proto, err := getprotobyname(name) proto, err := getprotobyname(name)
...@@ -56,6 +58,7 @@ func lookupHost(name string) (addrs []string, err error) { ...@@ -56,6 +58,7 @@ func lookupHost(name string) (addrs []string, err error) {
} }
func gethostbyname(name string) (addrs []IP, err error) { func gethostbyname(name string) (addrs []IP, err error) {
// caller already acquired thread
h, err := syscall.GetHostByName(name) h, err := syscall.GetHostByName(name)
if err != nil { if err != nil {
return nil, os.NewSyscallError("GetHostByName", err) return nil, os.NewSyscallError("GetHostByName", err)
...@@ -83,6 +86,8 @@ func oldLookupIP(name string) (addrs []IP, err error) { ...@@ -83,6 +86,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
} }
ch := make(chan result) ch := make(chan result)
go func() { go func() {
acquireThread()
defer releaseThread()
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
addrs, err := gethostbyname(name) addrs, err := gethostbyname(name)
...@@ -93,6 +98,8 @@ func oldLookupIP(name string) (addrs []IP, err error) { ...@@ -93,6 +98,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
} }
func newLookupIP(name string) (addrs []IP, err error) { func newLookupIP(name string) (addrs []IP, err error) {
acquireThread()
defer releaseThread()
hints := syscall.AddrinfoW{ hints := syscall.AddrinfoW{
Family: syscall.AF_UNSPEC, Family: syscall.AF_UNSPEC,
Socktype: syscall.SOCK_STREAM, Socktype: syscall.SOCK_STREAM,
...@@ -122,6 +129,8 @@ func newLookupIP(name string) (addrs []IP, err error) { ...@@ -122,6 +129,8 @@ func newLookupIP(name string) (addrs []IP, err error) {
} }
func getservbyname(network, service string) (port int, err error) { func getservbyname(network, service string) (port int, err error) {
acquireThread()
defer releaseThread()
switch network { switch network {
case "tcp4", "tcp6": case "tcp4", "tcp6":
network = "tcp" network = "tcp"
...@@ -144,6 +153,8 @@ func oldLookupPort(network, service string) (port int, err error) { ...@@ -144,6 +153,8 @@ func oldLookupPort(network, service string) (port int, err error) {
} }
ch := make(chan result) ch := make(chan result)
go func() { go func() {
acquireThread()
defer releaseThread()
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
port, err := getservbyname(network, service) port, err := getservbyname(network, service)
...@@ -154,6 +165,8 @@ func oldLookupPort(network, service string) (port int, err error) { ...@@ -154,6 +165,8 @@ func oldLookupPort(network, service string) (port int, err error) {
} }
func newLookupPort(network, service string) (port int, err error) { func newLookupPort(network, service string) (port int, err error) {
acquireThread()
defer releaseThread()
var stype int32 var stype int32
switch network { switch network {
case "tcp4", "tcp6": case "tcp4", "tcp6":
...@@ -188,6 +201,8 @@ func newLookupPort(network, service string) (port int, err error) { ...@@ -188,6 +201,8 @@ func newLookupPort(network, service string) (port int, err error) {
} }
func lookupCNAME(name string) (cname string, err error) { func lookupCNAME(name string) (cname string, err error) {
acquireThread()
defer releaseThread()
var r *syscall.DNSRecord var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil) e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil)
if e != nil { if e != nil {
...@@ -202,6 +217,8 @@ func lookupCNAME(name string) (cname string, err error) { ...@@ -202,6 +217,8 @@ func lookupCNAME(name string) (cname string, err error) {
} }
func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
acquireThread()
defer releaseThread()
var target string var target string
if service == "" && proto == "" { if service == "" && proto == "" {
target = name target = name
...@@ -224,6 +241,8 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err ...@@ -224,6 +241,8 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
} }
func lookupMX(name string) (mx []*MX, err error) { func lookupMX(name string) (mx []*MX, err error) {
acquireThread()
defer releaseThread()
var r *syscall.DNSRecord var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil) e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil)
if e != nil { if e != nil {
...@@ -240,6 +259,8 @@ func lookupMX(name string) (mx []*MX, err error) { ...@@ -240,6 +259,8 @@ func lookupMX(name string) (mx []*MX, err error) {
} }
func lookupNS(name string) (ns []*NS, err error) { func lookupNS(name string) (ns []*NS, err error) {
acquireThread()
defer releaseThread()
var r *syscall.DNSRecord var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &r, nil) e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &r, nil)
if e != nil { if e != nil {
...@@ -255,6 +276,8 @@ func lookupNS(name string) (ns []*NS, err error) { ...@@ -255,6 +276,8 @@ func lookupNS(name string) (ns []*NS, err error) {
} }
func lookupTXT(name string) (txt []string, err error) { func lookupTXT(name string) (txt []string, err error) {
acquireThread()
defer releaseThread()
var r *syscall.DNSRecord var r *syscall.DNSRecord
e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil) e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)
if e != nil { if e != nil {
...@@ -273,6 +296,8 @@ func lookupTXT(name string) (txt []string, err error) { ...@@ -273,6 +296,8 @@ func lookupTXT(name string) (txt []string, err error) {
} }
func lookupAddr(addr string) (name []string, err error) { func lookupAddr(addr string) (name []string, err error) {
acquireThread()
defer releaseThread()
arpa, err := reverseaddr(addr) arpa, err := reverseaddr(addr)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -433,3 +433,19 @@ func (d *deadline) setTime(t time.Time) { ...@@ -433,3 +433,19 @@ func (d *deadline) setTime(t time.Time) {
d.set(t.UnixNano()) 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
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment