mirror of
https://github.com/golang/go
synced 2024-11-18 14:04:45 -07:00
b3932baba4
The SudoG used to sit on the stack, so it was cheap to allocated and didn't need to be cleaned up when finished. For the conversion to Go, we had to move sudog off the stack for a few reasons, so we added a cache of recently used sudogs to keep allocation cheap. But we didn't add any of the necessary cleanup before adding a SudoG to the new cache, and so the cached SudoGs had stale pointers inside them that have caused all sorts of awful, hard to debug problems. CL 155760043 made sure SudoG.elem is cleaned up. CL 150520043 made sure SudoG.selectdone is cleaned up. This CL makes sure SudoG.next, SudoG.prev, and SudoG.waitlink are cleaned up. I should have done this when I did the other two fields; instead I wasted a week tracking down a leak they caused. A dangling SudoG.waitlink can point into a sudogcache list that has been "forgotten" in order to let the GC collect it, but that dangling .waitlink keeps the list from being collected. And then the list holding the SudoG with the dangling waitlink can find itself in the same situation, and so on. We end up with lists of lists of unusable SudoGs that are still linked into the object graph and never collected (given the right mix of non-trivial selects and non-channel synchronization). More details in golang.org/issue/9110. Fixes #9110. LGTM=r R=r CC=dvyukov, golang-codereviews, iant, khr https://golang.org/cl/177870043
91 lines
1.7 KiB
Go
91 lines
1.7 KiB
Go
// run
|
|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Scenario that used to leak arbitrarily many SudoG structs.
|
|
// See golang.org/issue/9110.
|
|
|
|
package main
|
|
|
|
import (
|
|
"runtime"
|
|
"runtime/debug"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
debug.SetGCPercent(1000000) // only GC when we ask for GC
|
|
|
|
var stats, stats1, stats2 runtime.MemStats
|
|
|
|
release := func() {}
|
|
for i := 0; i < 20; i++ {
|
|
if i == 10 {
|
|
// Should be warmed up by now.
|
|
runtime.ReadMemStats(&stats1)
|
|
}
|
|
|
|
c := make(chan int)
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
select {
|
|
case <-c:
|
|
case <-c:
|
|
case <-c:
|
|
}
|
|
}()
|
|
}
|
|
time.Sleep(1 * time.Millisecond)
|
|
release()
|
|
|
|
close(c) // let select put its sudog's into the cache
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// pick up top sudog
|
|
var cond1 sync.Cond
|
|
var mu1 sync.Mutex
|
|
cond1.L = &mu1
|
|
go func() {
|
|
mu1.Lock()
|
|
cond1.Wait()
|
|
mu1.Unlock()
|
|
}()
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// pick up next sudog
|
|
var cond2 sync.Cond
|
|
var mu2 sync.Mutex
|
|
cond2.L = &mu2
|
|
go func() {
|
|
mu2.Lock()
|
|
cond2.Wait()
|
|
mu2.Unlock()
|
|
}()
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// put top sudog back
|
|
cond1.Broadcast()
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// drop cache on floor
|
|
runtime.GC()
|
|
|
|
// release cond2 after select has gotten to run
|
|
release = func() {
|
|
cond2.Broadcast()
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
runtime.GC()
|
|
|
|
runtime.ReadMemStats(&stats2)
|
|
|
|
if int(stats2.HeapObjects)-int(stats1.HeapObjects) > 20 { // normally at most 1 or 2; was 300 with leak
|
|
print("BUG: object leak: ", stats.HeapObjects, " -> ", stats1.HeapObjects, " -> ", stats2.HeapObjects, "\n")
|
|
}
|
|
}
|