tailscale/net/dns/direct_linux_test.go
Alex Chan db0b9a361c net/dns: don't timeout if inotify sends multiple events
This fixes a flaky test which has been occasionally timing out in CI.

In particular, this test times out if `watchFile` receives multiple
notifications from inotify before we cancel the test context. We block
processing the second notification, because we've stopped listening to
the `callbackDone` channel.

This patch changes the test so we only send on the first notification.

Testing this locally with `stress` confirms that the test is no longer
flaky.

Fixes #17172
Updates #14699

Signed-off-by: Alex Chan <alexc@tailscale.com>
2025-09-17 14:51:17 +01:00

62 lines
1.3 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"context"
"errors"
"fmt"
"os"
"sync/atomic"
"testing"
"time"
"golang.org/x/sync/errgroup"
)
func TestWatchFile(t *testing.T) {
dir := t.TempDir()
filepath := dir + "/test.txt"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var callbackCalled atomic.Bool
callbackDone := make(chan bool)
callback := func() {
// We only send to the channel once to avoid blocking if the
// callback is called multiple times -- this happens occasionally
// if inotify sends multiple events before we cancel the context.
if !callbackCalled.Load() {
callbackDone <- true
callbackCalled.Store(true)
}
}
var eg errgroup.Group
eg.Go(func() error { return watchFile(ctx, dir, filepath, callback) })
// Keep writing until we get a callback.
func() {
for i := range 10000 {
if err := os.WriteFile(filepath, []byte(fmt.Sprintf("write%d", i)), 0644); err != nil {
t.Fatal(err)
}
select {
case <-callbackDone:
return
case <-time.After(10 * time.Millisecond):
}
}
}()
cancel()
if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
t.Error(err)
}
if !callbackCalled.Load() {
t.Error("callback was not called")
}
}