| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a MIT |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "os" |
| "runtime" |
| "runtime/pprof" |
| "sync" |
| "time" |
| ) |
| |
| // This test case is a reproduction of grpc/3017. |
| // |
| // It is a goroutine leak that also simultaneously engages many GC assists. |
| // Testing runtime behaviour when pivoting between regular and goroutine leak detection modes. |
| |
| func init() { |
| register("Grpc3017", Grpc3017) |
| } |
| |
| type Address_grpc3017 int |
| type SubConn_grpc3017 int |
| |
| type subConnCacheEntry_grpc3017 struct { |
| sc SubConn_grpc3017 |
| cancel func() |
| abortDeleting bool |
| } |
| |
| type lbCacheClientConn_grpc3017 struct { |
| mu sync.Mutex // L1 |
| timeout time.Duration |
| subConnCache map[Address_grpc3017]*subConnCacheEntry_grpc3017 |
| subConnToAddr map[SubConn_grpc3017]Address_grpc3017 |
| } |
| |
| func (ccc *lbCacheClientConn_grpc3017) NewSubConn(addrs []Address_grpc3017) SubConn_grpc3017 { |
| if len(addrs) != 1 { |
| return SubConn_grpc3017(1) |
| } |
| addrWithoutMD := addrs[0] |
| ccc.mu.Lock() // L1 |
| defer ccc.mu.Unlock() |
| if entry, ok := ccc.subConnCache[addrWithoutMD]; ok { |
| entry.cancel() |
| delete(ccc.subConnCache, addrWithoutMD) |
| return entry.sc |
| } |
| scNew := SubConn_grpc3017(1) |
| ccc.subConnToAddr[scNew] = addrWithoutMD |
| return scNew |
| } |
| |
| func (ccc *lbCacheClientConn_grpc3017) RemoveSubConn(sc SubConn_grpc3017) { |
| ccc.mu.Lock() // L1 |
| defer ccc.mu.Unlock() |
| addr, ok := ccc.subConnToAddr[sc] |
| if !ok { |
| return |
| } |
| |
| if entry, ok := ccc.subConnCache[addr]; ok { |
| if entry.sc != sc { |
| delete(ccc.subConnToAddr, sc) |
| } |
| return |
| } |
| |
| entry := &subConnCacheEntry_grpc3017{ |
| sc: sc, |
| } |
| ccc.subConnCache[addr] = entry |
| |
| timer := time.AfterFunc(ccc.timeout, func() { // G3 |
| runtime.Gosched() |
| ccc.mu.Lock() // L1 |
| if entry.abortDeleting { |
| return // Missing unlock |
| } |
| delete(ccc.subConnToAddr, sc) |
| delete(ccc.subConnCache, addr) |
| ccc.mu.Unlock() |
| }) |
| |
| entry.cancel = func() { |
| if !timer.Stop() { |
| entry.abortDeleting = true |
| } |
| } |
| } |
| |
| func Grpc3017() { |
| prof := pprof.Lookup("goroutineleak") |
| defer func() { |
| time.Sleep(100 * time.Millisecond) |
| prof.WriteTo(os.Stdout, 2) |
| }() |
| |
| for i := 0; i < 100; i++ { |
| go func() { //G1 |
| done := make(chan struct{}) |
| |
| ccc := &lbCacheClientConn_grpc3017{ |
| timeout: time.Nanosecond, |
| subConnCache: make(map[Address_grpc3017]*subConnCacheEntry_grpc3017), |
| subConnToAddr: make(map[SubConn_grpc3017]Address_grpc3017), |
| } |
| |
| sc := ccc.NewSubConn([]Address_grpc3017{Address_grpc3017(1)}) |
| go func() { // G2 |
| for i := 0; i < 10000; i++ { |
| ccc.RemoveSubConn(sc) |
| sc = ccc.NewSubConn([]Address_grpc3017{Address_grpc3017(1)}) |
| } |
| close(done) |
| }() |
| <-done |
| }() |
| } |
| } |