1
0
mirror of https://github.com/golang/go synced 2024-11-25 03:47:57 -07:00

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
This commit is contained in:
Russ Cox 2013-08-16 22:43:05 -04:00
parent 665feeedcb
commit 1d3efd6533
4 changed files with 71 additions and 0 deletions

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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
}