types/lazy: fix flaky TestDeferAfterDo

This test verifies, among other things, that init functions cannot be deferred after (*DeferredFuncs).Do
has already been called and that all subsequent calls to (*DeferredFuncs).Defer return false.

However, the initial implementation of this check was racy: by the time (*DeferredFuncs).Do returned,
not all goroutines that successfully deferred an init function may have incremented the atomic variable
tracking the number of deferred functions. As a result, the variable's value could differ immediately
after (*DeferredFuncs).Do returned and after all goroutines had completed execution (i.e., after wg.Wait()).

In this PR, we replace the original racy check with a different one. Although this new check is also racy,
it can only produce false negatives. This means that if the test fails, it indicates an actual bug rather than
a flaky test.

Fixes #14039

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2025-01-14 19:36:27 -06:00
committed by Nick Khyl
parent 1b303ee5ba
commit f023c8603a
2 changed files with 34 additions and 7 deletions
+8 -1
View File
@@ -22,7 +22,14 @@ type DeferredInit struct {
// until the owner's [DeferredInit.Do] method is called
// for the first time.
//
// DeferredFuncs is safe for concurrent use.
// DeferredFuncs is safe for concurrent use. The execution
// order of functions deferred by different goroutines is
// unspecified and must not be relied upon.
// However, functions deferred by the same goroutine are
// executed in the same relative order they were deferred.
// Warning: this is the opposite of the behavior of Go's
// defer statement, which executes deferred functions in
// reverse order.
type DeferredFuncs struct {
m sync.Mutex
funcs []func() error