Skip to content

Commit 1d3efd6

Browse files
committed
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
1 parent 665feee commit 1d3efd6

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

src/pkg/net/cgo_unix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ func cgoLookupHost(name string) (addrs []string, err error, completed bool) {
3232
}
3333

3434
func cgoLookupPort(net, service string) (port int, err error, completed bool) {
35+
acquireThread()
36+
defer releaseThread()
37+
3538
var res *C.struct_addrinfo
3639
var hints C.struct_addrinfo
3740

@@ -79,6 +82,9 @@ func cgoLookupPort(net, service string) (port int, err error, completed bool) {
7982
}
8083

8184
func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool) {
85+
acquireThread()
86+
defer releaseThread()
87+
8288
var res *C.struct_addrinfo
8389
var hints C.struct_addrinfo
8490

src/pkg/net/dialgoogle_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ var googleaddrsipv4 = []string{
5454
"[0:0:0:0:0:ffff::%d.%d.%d.%d]:80",
5555
}
5656

57+
func TestDNSThreadLimit(t *testing.T) {
58+
if testing.Short() || !*testExternal {
59+
t.Skip("skipping test to avoid external network")
60+
}
61+
62+
const N = 10000
63+
c := make(chan int, N)
64+
for i := 0; i < N; i++ {
65+
go func() {
66+
LookupIP(fmt.Sprintf("%d.net-test.golang.org", i))
67+
c <- 1
68+
}()
69+
}
70+
// Don't bother waiting for the stragglers; stop at 0.9 N.
71+
for i := 0; i < N*9/10; i++ {
72+
if i%100 == 0 {
73+
//println("TestDNSThreadLimit:", i)
74+
}
75+
<-c
76+
}
77+
78+
// If we're still here, it worked.
79+
}
80+
5781
func TestDialGoogleIPv4(t *testing.T) {
5882
if testing.Short() || !*testExternal {
5983
t.Skip("skipping test to avoid external network")

src/pkg/net/lookup_windows.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func lookupProtocol(name string) (proto int, err error) {
3434
}
3535
ch := make(chan result)
3636
go func() {
37+
acquireThread()
38+
defer releaseThread()
3739
runtime.LockOSThread()
3840
defer runtime.UnlockOSThread()
3941
proto, err := getprotobyname(name)
@@ -56,6 +58,7 @@ func lookupHost(name string) (addrs []string, err error) {
5658
}
5759

5860
func gethostbyname(name string) (addrs []IP, err error) {
61+
// caller already acquired thread
5962
h, err := syscall.GetHostByName(name)
6063
if err != nil {
6164
return nil, os.NewSyscallError("GetHostByName", err)
@@ -83,6 +86,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
8386
}
8487
ch := make(chan result)
8588
go func() {
89+
acquireThread()
90+
defer releaseThread()
8691
runtime.LockOSThread()
8792
defer runtime.UnlockOSThread()
8893
addrs, err := gethostbyname(name)
@@ -93,6 +98,8 @@ func oldLookupIP(name string) (addrs []IP, err error) {
9398
}
9499

95100
func newLookupIP(name string) (addrs []IP, err error) {
101+
acquireThread()
102+
defer releaseThread()
96103
hints := syscall.AddrinfoW{
97104
Family: syscall.AF_UNSPEC,
98105
Socktype: syscall.SOCK_STREAM,
@@ -122,6 +129,8 @@ func newLookupIP(name string) (addrs []IP, err error) {
122129
}
123130

124131
func getservbyname(network, service string) (port int, err error) {
132+
acquireThread()
133+
defer releaseThread()
125134
switch network {
126135
case "tcp4", "tcp6":
127136
network = "tcp"
@@ -144,6 +153,8 @@ func oldLookupPort(network, service string) (port int, err error) {
144153
}
145154
ch := make(chan result)
146155
go func() {
156+
acquireThread()
157+
defer releaseThread()
147158
runtime.LockOSThread()
148159
defer runtime.UnlockOSThread()
149160
port, err := getservbyname(network, service)
@@ -154,6 +165,8 @@ func oldLookupPort(network, service string) (port int, err error) {
154165
}
155166

156167
func newLookupPort(network, service string) (port int, err error) {
168+
acquireThread()
169+
defer releaseThread()
157170
var stype int32
158171
switch network {
159172
case "tcp4", "tcp6":
@@ -188,6 +201,8 @@ func newLookupPort(network, service string) (port int, err error) {
188201
}
189202

190203
func lookupCNAME(name string) (cname string, err error) {
204+
acquireThread()
205+
defer releaseThread()
191206
var r *syscall.DNSRecord
192207
e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &r, nil)
193208
if e != nil {
@@ -202,6 +217,8 @@ func lookupCNAME(name string) (cname string, err error) {
202217
}
203218

204219
func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
220+
acquireThread()
221+
defer releaseThread()
205222
var target string
206223
if service == "" && proto == "" {
207224
target = name
@@ -224,6 +241,8 @@ func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err err
224241
}
225242

226243
func lookupMX(name string) (mx []*MX, err error) {
244+
acquireThread()
245+
defer releaseThread()
227246
var r *syscall.DNSRecord
228247
e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &r, nil)
229248
if e != nil {
@@ -240,6 +259,8 @@ func lookupMX(name string) (mx []*MX, err error) {
240259
}
241260

242261
func lookupNS(name string) (ns []*NS, err error) {
262+
acquireThread()
263+
defer releaseThread()
243264
var r *syscall.DNSRecord
244265
e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &r, nil)
245266
if e != nil {
@@ -255,6 +276,8 @@ func lookupNS(name string) (ns []*NS, err error) {
255276
}
256277

257278
func lookupTXT(name string) (txt []string, err error) {
279+
acquireThread()
280+
defer releaseThread()
258281
var r *syscall.DNSRecord
259282
e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &r, nil)
260283
if e != nil {
@@ -273,6 +296,8 @@ func lookupTXT(name string) (txt []string, err error) {
273296
}
274297

275298
func lookupAddr(addr string) (name []string, err error) {
299+
acquireThread()
300+
defer releaseThread()
276301
arpa, err := reverseaddr(addr)
277302
if err != nil {
278303
return nil, err

src/pkg/net/net.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,19 @@ func (d *deadline) setTime(t time.Time) {
433433
d.set(t.UnixNano())
434434
}
435435
}
436+
437+
// Limit the number of concurrent cgo-using goroutines, because
438+
// each will block an entire operating system thread. The usual culprit
439+
// is resolving many DNS names in separate goroutines but the DNS
440+
// server is not responding. Then the many lookups each use a different
441+
// thread, and the system or the program runs out of threads.
442+
443+
var threadLimit = make(chan struct{}, 500)
444+
445+
func acquireThread() {
446+
threadLimit <- struct{}{}
447+
}
448+
449+
func releaseThread() {
450+
<-threadLimit
451+
}

0 commit comments

Comments
 (0)