device: test that we do not leak goroutines
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
This commit is contained in:
parent
8a374a35a0
commit
fd63a233c9
|
@ -12,6 +12,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -163,6 +165,7 @@ NextAttempt:
|
||||||
// If there's something permanent wrong,
|
// If there's something permanent wrong,
|
||||||
// we'll see that when we run out of attempts.
|
// we'll see that when we run out of attempts.
|
||||||
tb.Logf("failed to configure device %d: %v", i, err)
|
tb.Logf("failed to configure device %d: %v", i, err)
|
||||||
|
p.dev.Close()
|
||||||
continue NextAttempt
|
continue NextAttempt
|
||||||
}
|
}
|
||||||
// The device might still not be up, e.g. due to an error
|
// The device might still not be up, e.g. due to an error
|
||||||
|
@ -170,6 +173,7 @@ NextAttempt:
|
||||||
// Assume it's due to a transient error (port in use), and retry.
|
// Assume it's due to a transient error (port in use), and retry.
|
||||||
if !p.dev.isUp.Get() {
|
if !p.dev.isUp.Get() {
|
||||||
tb.Logf("device %d did not come up, trying again", i)
|
tb.Logf("device %d did not come up, trying again", i)
|
||||||
|
p.dev.Close()
|
||||||
continue NextAttempt
|
continue NextAttempt
|
||||||
}
|
}
|
||||||
// The device is up. Close it when the test completes.
|
// The device is up. Close it when the test completes.
|
||||||
|
@ -183,6 +187,7 @@ NextAttempt:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTwoDevicePing(t *testing.T) {
|
func TestTwoDevicePing(t *testing.T) {
|
||||||
|
goroutineLeakCheck(t)
|
||||||
pair := genTestPair(t)
|
pair := genTestPair(t)
|
||||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||||
pair.Send(t, Ping, nil)
|
pair.Send(t, Ping, nil)
|
||||||
|
@ -352,3 +357,29 @@ func BenchmarkUAPIGet(b *testing.B) {
|
||||||
pair[0].dev.IpcGetOperation(ioutil.Discard)
|
pair[0].dev.IpcGetOperation(ioutil.Discard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func goroutineLeakCheck(t *testing.T) {
|
||||||
|
goroutines := func() (int, []byte) {
|
||||||
|
p := pprof.Lookup("goroutine")
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
p.WriteTo(b, 1)
|
||||||
|
return p.Count(), b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
startGoroutines, startStacks := goroutines()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if t.Failed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Give goroutines time to exit, if they need it.
|
||||||
|
for i := 0; i < 1000 && startGoroutines < runtime.NumGoroutine(); i++ {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if got := runtime.NumGoroutine(); startGoroutines < got {
|
||||||
|
_, endStacks := goroutines()
|
||||||
|
t.Logf("starting stacks:\n%s\n", startStacks)
|
||||||
|
t.Logf("ending stacks:\n%s\n", endStacks)
|
||||||
|
t.Fatalf("expected %d goroutines, got %d, leak?", startGoroutines, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue