package main import ( "context" "fmt" "strings" "sync" "testing" "time" ) // A take-in that waits is said once for each reason it waits for, and tried again every doneKeyRetry until it is taken // in: never left for a key write that may not come (issue 1945, live on 2026-10-25: mesh-host's take-in at 26bd waited, // said nothing, and nothing woke it until another write). func TestAWaitingTakeInIsSaidOnceAndTriedAgain(t *testing.T) { was, wasRetry, wasSay := doneKeyTakeOnce, doneKeyRetry, doneKeysSay t.Cleanup(func() { doneKeyTakeOnce, doneKeyRetry, doneKeysSay = was, wasRetry, wasSay }) doneKeyRetry = 5 * time.Millisecond var mu sync.Mutex var said []string doneKeysSay = func(s string) { mu.Lock(); said = append(said, s); mu.Unlock() } calls := 0 doneKeyTakeOnce = func(context.Context, *stores, string) error { calls++ switch { case calls <= 2: return fmt.Errorf("%w: the artifact store could not hold mesh-host's artifact", errKeyWaits) case calls == 3: return fmt.Errorf("%w: mesh-host's key cannot be read", errKeyWaits) } return nil } done := make(chan struct{}) go func() { takeDoneKey(context.Background(), nil, "mesh-host"); close(done) }() select { case <-done: case <-time.After(5 * time.Second): t.Fatal("a waiting take-in was not tried again until it was taken in") } if calls != 4 { t.Fatalf("tried %d times, want 4", calls) } mu.Lock() defer mu.Unlock() waits := 0 for _, s := range said { if strings.Contains(s, "mesh-host waits") { waits++ } } if waits != 2 { t.Fatalf("said %q: want each of the two reasons once", said) } } // A wake while a take-in of the module already follows it adds no second one: the follower looks again itself. func TestAWakeWhileATakeInFollowsAddsNone(t *testing.T) { was, wasRetry, wasSay := doneKeyTakeOnce, doneKeyRetry, doneKeysSay t.Cleanup(func() { doneKeyTakeOnce, doneKeyRetry, doneKeysSay = was, wasRetry, wasSay }) doneKeyRetry = 20 * time.Millisecond doneKeysSay = func(string) {} var mu sync.Mutex calls := 0 release := make(chan struct{}) doneKeyTakeOnce = func(context.Context, *stores, string) error { mu.Lock() calls++ mu.Unlock() select { case <-release: return nil default: return fmt.Errorf("%w: the key says building", errKeyWaits) } } go takeDoneKey(context.Background(), nil, "board") time.Sleep(5 * time.Millisecond) returned := make(chan struct{}) go func() { takeDoneKey(context.Background(), nil, "board"); close(returned) }() select { case <-returned: case <-time.After(time.Second): t.Fatal("a second wake waited for the follower") } close(release) }