runtime: impose thread count limit
Actually working to stay within the limit could cause subtle deadlocks.
Crashing avoids the subtlety.
Fixes #4056.
R=golang-dev, r, dvyukov
CC=golang-dev
https://golang.org/cl/13037043
diff --git a/src/pkg/runtime/crash_test.go b/src/pkg/runtime/crash_test.go
index 7ea1b6b..e07810b 100644
--- a/src/pkg/runtime/crash_test.go
+++ b/src/pkg/runtime/crash_test.go
@@ -125,6 +125,14 @@
}
}
+func TestThreadExhaustion(t *testing.T) {
+ output := executeTest(t, threadExhaustionSource, nil)
+ want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
const crashSource = `
package main
@@ -243,3 +251,25 @@
return x[0] + f(buf[:])
}
`
+
+const threadExhaustionSource = `
+package main
+
+import (
+ "runtime"
+ "runtime/debug"
+)
+
+func main() {
+ debug.SetMaxThreads(10)
+ c := make(chan int)
+ for i := 0; i < 100; i++ {
+ go func() {
+ runtime.LockOSThread()
+ c <- 0
+ select{}
+ }()
+ <-c
+ }
+}
+`
diff --git a/src/pkg/runtime/debug/garbage.go b/src/pkg/runtime/debug/garbage.go
index 3658fea..8337d5d 100644
--- a/src/pkg/runtime/debug/garbage.go
+++ b/src/pkg/runtime/debug/garbage.go
@@ -25,6 +25,7 @@
func setGCPercent(int) int
func freeOSMemory()
func setMaxStack(int) int
+func setMaxThreads(int) int
// ReadGCStats reads statistics about garbage collection into stats.
// The number of entries in the pause history is system-dependent;
@@ -114,3 +115,21 @@
func SetMaxStack(bytes int) int {
return setMaxStack(bytes)
}
+
+// SetMaxThreads sets the maximum number of operating system
+// threads that the Go program can use. If it attempts to use more than
+// this many, the program crashes.
+// SetMaxThreads returns the previous setting.
+// The initial setting is 10,000 threads.
+//
+// The limit controls the number of operating system threads, not the number
+// of goroutines. A Go program creates a new thread only when a goroutine
+// is ready to run but all the existing threads are blocked in system calls, cgo calls,
+// or are locked to other goroutines due to use of runtime.LockOSThread.
+//
+// SetMaxThreads is useful mainly for limiting the damage done by
+// programs that create an unbounded number of threads. The idea is
+// to take down the program before it takes down the operating system.
+func SetMaxThreads(threads int) int {
+ return setMaxThreads(threads)
+}
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index 6950f4b..dab62ad 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -32,6 +32,7 @@
int32 nmidle; // number of idle m's waiting for work
int32 nmidlelocked; // number of locked m's waiting for work
int32 mcount; // number of m's that have been created
+ int32 maxmcount; // maximum number of m's allowed (or die)
P* pidle; // idle P's
uint32 npidle;
@@ -126,6 +127,8 @@
int32 n, procs;
byte *p;
+ runtime·sched.maxmcount = 10000;
+
m->nomemprof++;
runtime·mprofinit();
runtime·mallocinit();
@@ -284,6 +287,16 @@
}
static void
+checkmcount(void)
+{
+ // sched lock is held
+ if(runtime·sched.mcount > runtime·sched.maxmcount) {
+ runtime·printf("runtime: program exceeds %d-thread limit\n", runtime·sched.maxmcount);
+ runtime·throw("thread exhaustion");
+ }
+}
+
+static void
mcommoninit(M *mp)
{
// If there is no mcache runtime·callers() will crash,
@@ -295,7 +308,7 @@
runtime·lock(&runtime·sched);
mp->id = runtime·sched.mcount++;
-
+ checkmcount();
runtime·mpreinit(mp);
// Add to runtime·allm so garbage collector doesn't free m
@@ -2821,3 +2834,14 @@
f->entry == (uintptr)runtime·lessstack ||
f->entry == (uintptr)_rt0_go;
}
+
+void
+runtimeādebug·setMaxThreads(intgo in, intgo out)
+{
+ runtime·lock(&runtime·sched);
+ out = runtime·sched.maxmcount;
+ runtime·sched.maxmcount = in;
+ checkmcount();
+ runtime·unlock(&runtime·sched);
+ FLUSH(&out);
+}