Repository: grpc/grpc-go
File: internal/leakcheck/leakcheck.go (Go)

Code before:
	tf.mu.Lock()
	defer tf.mu.Unlock()
	delete(tf.allocatedTimers, timer)
}

func (tf *timerFactory) pendingTimers() []string {
	tf.mu.Lock()
	defer tf.mu.Unlock()
	leaked := []string{}
	for _, stack := range tf.allocatedTimers {
		leaked = append(leaked, fmt.Sprintf("Allocated timer never cancelled:\n%s", traceToString(stack)))
	}
	return leaked
}

type trackingTimer struct {
	internal.Timer
	parent *timerFactory
}

func (t *trackingTimer) Stop() bool {
	t.parent.remove(t.Timer)
	return t.Timer.Stop()
}

// TrackTimers replaces internal.TimerAfterFunc  with one that tracks timer
// creations, stoppages and expirations. CheckTimers should then be invoked at
// the end of the test to validate that all timers created have either executed
// or are cancelled.
func TrackTimers() {
	globalTimerTracker = &timerFactory{
		allocatedTimers: make(map[internal.Timer][]uintptr),
	}
	internal.TimeAfterFunc = globalTimerTracker.timeAfterFunc
}

// CheckTimers undoes the effects of TrackTimers, and fails unit tests if not
// all timers were cancelled or executed. It is invalid to invoke this function
// without previously having invoked TrackTimers.
func CheckTimers(ctx context.Context, logger Logger) {
	tt := globalTimerTracker

	// Loop, waiting for timers to be cancelled.
	// Wait up to timeout, but finish as quickly as possible.
	var leaked []string
	for ctx.Err() == nil {
		if leaked = tt.pendingTimers(); len(leaked) == 0 {
			return
		}
		time.Sleep(50 * time.Millisecond)
	}

Review comment (suggestion):
```suggestion
// TrackTimers replaces internal.TimerAfterFunc with one that tracks timer
```

(This one is a typo :stuck_out_tongue: )

Code after:
	tf.mu.Lock()
	defer tf.mu.Unlock()
	delete(tf.allocatedTimers, timer)
}

func (tf *timerFactory) pendingTimers() []string {
	tf.mu.Lock()
	defer tf.mu.Unlock()
	leaked := []string{}
	for _, stack := range tf.allocatedTimers {
		leaked = append(leaked, fmt.Sprintf("Allocated timer never cancelled:\n%s", traceToString(stack)))
	}
	return leaked
}

type trackingTimer struct {
	internal.Timer
	parent *timerFactory
}

func (t *trackingTimer) Stop() bool {
	t.parent.remove(t.Timer)
	return t.Timer.Stop()
}

// TrackTimers replaces internal.TimerAfterFunc with one that tracks timer
// creations, stoppages and expirations. CheckTimers should then be invoked at
// the end of the test to validate that all timers created have either executed
// or are cancelled.
func TrackTimers() {
	globalTimerTracker = &timerFactory{
		allocatedTimers: make(map[internal.Timer][]uintptr),
	}
	internal.TimeAfterFunc = globalTimerTracker.timeAfterFunc
}

// CheckTimers undoes the effects of TrackTimers, and fails unit tests if not
// all timers were cancelled or executed. It is invalid to invoke this function
// without previously having invoked TrackTimers.
func CheckTimers(ctx context.Context, logger Logger) {
	tt := globalTimerTracker

	// Loop, waiting for timers to be cancelled.
	// Wait up to timeout, but finish as quickly as possible.
	var leaked []string
	for ctx.Err() == nil {
		if leaked = tt.pendingTimers(); len(leaked) == 0 {
			return
		}
		time.Sleep(50 * time.Millisecond)
	}