summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorClaire Wang <claire@tailscale.com>2023-07-11 16:03:01 -0400
committerClaire Wang <claire@tailscale.com>2023-07-12 13:53:32 -0400
commit6ae633b6897e354523fce47ba6466ad531b0cbf3 (patch)
tree8b2b7b2e641a8378c701e97bb01cc6a9df5bdc0b
parent96d7af34694a859f4dd6c332ac0675203008114a (diff)
downloadtailscale-clairew/refactor-new-timer.tar.xz
tailscale-clairew/refactor-new-timer.zip
tstime: replace time.NewTimer with tstime.Clock timerclairew/refactor-new-timer
Updates #8587 Signed-off-by: Claire Wang <claire@tailscale.com>
-rw-r--r--cmd/tailscale/cli/cli.go2
-rw-r--r--cmd/tailscale/cli/debug.go2
-rw-r--r--cmd/tailscale/cli/netcheck.go4
-rw-r--r--cmd/tailscale/cli/up.go5
-rw-r--r--control/controlclient/auto.go15
-rw-r--r--control/controlclient/direct.go37
-rw-r--r--control/controlclient/noise.go2
-rw-r--r--control/controlhttp/client.go6
-rw-r--r--control/controlhttp/constants.go3
-rw-r--r--control/controlhttp/http_test.go3
-rw-r--r--derp/derp_test.go6
-rw-r--r--derp/derphttp/derphttp_client.go10
-rw-r--r--derp/derphttp/mesh_client.go4
-rw-r--r--ipn/ipnlocal/local.go7
-rw-r--r--logtail/backoff/backoff.go12
-rw-r--r--net/dns/resolver/forwarder.go7
-rw-r--r--net/dnscache/dnscache.go7
-rw-r--r--net/netcheck/netcheck.go16
-rw-r--r--net/netcheck/netcheck_test.go9
-rw-r--r--tsnet/tsnet.go9
-rw-r--r--types/logger/logger.go7
-rw-r--r--wgengine/magicsock/magicsock.go1
-rw-r--r--wgengine/watchdog.go8
23 files changed, 113 insertions, 69 deletions
diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go
index ce41e5965..e46757d16 100644
--- a/cmd/tailscale/cli/cli.go
+++ b/cmd/tailscale/cli/cli.go
@@ -23,11 +23,13 @@ import (
"tailscale.com/client/tailscale"
"tailscale.com/envknob"
"tailscale.com/paths"
+ "tailscale.com/tstime"
"tailscale.com/version/distro"
)
var Stderr io.Writer = os.Stderr
var Stdout io.Writer = os.Stdout
+var clock tstime.Clock = &tstime.StdClock{} // global tstime.clock variable for tailscale cli package.
func errf(format string, a ...any) {
fmt.Fprintf(Stderr, format, a...)
diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go
index 851ab5a93..70e2032ce 100644
--- a/cmd/tailscale/cli/debug.go
+++ b/cmd/tailscale/cli/debug.go
@@ -37,6 +37,7 @@ import (
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/must"
@@ -678,6 +679,7 @@ func runTS2021(ctx context.Context, args []string) error {
ProtocolVersion: uint16(ts2021Args.version),
Dialer: dialFunc,
Logf: logf,
+ Clock: &tstime.StdClock{},
}).Dial(ctx)
log.Printf("controlhttp.Dial = %p, %v", conn, err)
if err != nil {
diff --git a/cmd/tailscale/cli/netcheck.go b/cmd/tailscale/cli/netcheck.go
index bac5a0a48..6b9f7c0d0 100644
--- a/cmd/tailscale/cli/netcheck.go
+++ b/cmd/tailscale/cli/netcheck.go
@@ -22,6 +22,7 @@ import (
"tailscale.com/net/netmon"
"tailscale.com/net/portmapper"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
)
@@ -54,7 +55,8 @@ func runNetcheck(ctx context.Context, args []string) error {
c := &netcheck.Client{
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"),
PortMapper: portmapper.NewClient(logf, netMon, nil, nil),
- UseDNSCache: false, // always resolve, don't cache
+ UseDNSCache: false, // always resolve, don't cache,
+ Clock: &tstime.StdClock{},
}
if netcheckArgs.verbose {
c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index 06d9b5ff8..3f4096bf8 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -37,6 +37,7 @@ import (
"tailscale.com/net/tsaddr"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
"tailscale.com/util/dnsname"
@@ -692,9 +693,9 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
// shuts down. (Issue 2333)
var timeoutCh <-chan time.Time
if upArgs.timeout > 0 {
- timeoutTimer := time.NewTimer(upArgs.timeout)
+ var timeoutTimer tstime.TimerController
+ timeoutTimer, timeoutCh = clock.NewTimer(upArgs.timeout)
defer timeoutTimer.Stop()
- timeoutCh = timeoutTimer.C
}
select {
case <-running:
diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index dab37a510..7c143980d 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -15,6 +15,7 @@ import (
"tailscale.com/logtail/backoff"
"tailscale.com/net/sockstats"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/empty"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -48,7 +49,7 @@ var _ Client = (*Auto)(nil)
// It's a concrete implementation of the Client interface.
type Auto struct {
direct *Direct // our interface to the server APIs
- timeNow func() time.Time
+ clock tstime.Clock
logf logger.Logf
expiry *time.Time
closed bool
@@ -107,12 +108,12 @@ func NewNoStart(opts Options) (_ *Auto, err error) {
if opts.Logf == nil {
opts.Logf = func(fmt string, args ...any) {}
}
- if opts.TimeNow == nil {
- opts.TimeNow = time.Now
+ if opts.Clock == nil {
+ opts.Clock = &tstime.StdClock{}
}
c := &Auto{
direct: direct,
- timeNow: opts.TimeNow,
+ clock: opts.Clock,
logf: opts.Logf,
newMapCh: make(chan struct{}, 1),
quit: make(chan struct{}),
@@ -702,14 +703,14 @@ func (c *Auto) Logout(ctx context.Context) error {
c.mu.Unlock()
c.cancelAuth()
- timer := time.NewTimer(10 * time.Second)
+ timer, timerChannel := c.clock.NewTimer(10 * time.Second)
defer timer.Stop()
select {
case err := <-errc:
return err
case <-ctx.Done():
return ctx.Err()
- case <-timer.C:
+ case <-timerChannel:
return context.DeadlineExceeded
}
}
@@ -770,7 +771,7 @@ func (c *Auto) TestOnlySetAuthKey(authkey string) {
}
func (c *Auto) TestOnlyTimeNow() time.Time {
- return c.timeNow()
+ return c.clock.Now()
}
// SetDNS sends the SetDNSRequest request to the control plane server,
diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go
index 006f2614a..54d5ea895 100644
--- a/control/controlclient/direct.go
+++ b/control/controlclient/direct.go
@@ -45,6 +45,7 @@ import (
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tka"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
@@ -63,7 +64,7 @@ type Direct struct {
dialer *tsdial.Dialer
dnsCache *dnscache.Resolver
serverURL string // URL of the tailcontrol server
- timeNow func() time.Time
+ clock tstime.Clock
lastPrintMap time.Time
newDecompressor func() (Decompressor, error)
keepAlive bool
@@ -105,8 +106,8 @@ type Options struct {
GetMachinePrivateKey func() (key.MachinePrivate, error) // returns the machine key to use
ServerURL string // URL of the tailcontrol server
AuthKey string // optional node auth key for auto registration
- TimeNow func() time.Time // time.Now implementation used by Client
- Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
+ Clock tstime.Clock
+ Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
DiscoPublicKey key.DiscoPublic
NewDecompressor func() (Decompressor, error)
KeepAlive bool
@@ -191,8 +192,8 @@ func NewDirect(opts Options) (*Direct, error) {
if err != nil {
return nil, err
}
- if opts.TimeNow == nil {
- opts.TimeNow = time.Now
+ if opts.Clock == nil {
+ opts.Clock = &tstime.StdClock{}
}
if opts.Logf == nil {
// TODO(apenwarr): remove this default and fail instead.
@@ -235,7 +236,7 @@ func NewDirect(opts Options) (*Direct, error) {
httpc: httpc,
getMachinePrivKey: opts.GetMachinePrivateKey,
serverURL: opts.ServerURL,
- timeNow: opts.TimeNow,
+ clock: opts.Clock,
logf: opts.Logf,
newDecompressor: opts.NewDecompressor,
keepAlive: opts.KeepAlive,
@@ -432,7 +433,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
authKey, isWrapped, wrappedSig, wrappedKey := decodeWrappedAuthkey(c.authKey, c.logf)
hi := c.hostInfoLocked()
backendLogID := hi.BackendLogID
- expired := c.expiry != nil && !c.expiry.IsZero() && c.expiry.Before(c.timeNow())
+ expired := c.expiry != nil && !c.expiry.IsZero() && c.expiry.Before(c.clock.Now())
c.mu.Unlock()
machinePrivKey, err := c.getMachinePrivKey()
@@ -947,7 +948,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
return nil
}
- timeout := time.NewTimer(pollTimeout)
+ timeout, timeoutChannel := c.clock.NewTimer(pollTimeout)
timeoutReset := make(chan struct{})
pollDone := make(chan struct{})
defer close(pollDone)
@@ -957,14 +958,14 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
case <-pollDone:
vlogf("netmap: ending timeout goroutine")
return
- case <-timeout.C:
+ case <-timeoutChannel:
c.logf("map response long-poll timed out!")
cancel()
return
case <-timeoutReset:
if !timeout.Stop() {
select {
- case <-timeout.C:
+ case <-timeoutChannel:
case <-pollDone:
vlogf("netmap: ending timeout goroutine")
return
@@ -1089,7 +1090,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
go dumpGoroutinesToURL(c.httpc, resp.Debug.GoroutineDumpURL)
}
if sleep := time.Duration(resp.Debug.SleepSeconds * float64(time.Second)); sleep > 0 {
- if err := sleepAsRequested(ctx, c.logf, timeoutReset, sleep); err != nil {
+ if err := sleepAsRequested(ctx, c.logf, timeoutReset, sleep, c.clock); err != nil {
return err
}
}
@@ -1119,7 +1120,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
// This is handy for debugging, and our logs processing
// pipeline depends on it. (TODO: Remove this dependency.)
// Code elsewhere prints netmap diffs every time they are received.
- now := c.timeNow()
+ now := c.clock.Now()
if now.Sub(c.lastPrintMap) >= 5*time.Minute {
c.lastPrintMap = now
c.logf("[v1] new network map[%d]:\n%s", i, nm.VeryConcise())
@@ -1459,7 +1460,7 @@ func answerC2NPing(logf logger.Logf, c2nHandler http.Handler, c *http.Client, pr
}
}
-func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<- struct{}, d time.Duration) error {
+func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<- struct{}, d time.Duration, clock tstime.Clock) error {
const maxSleep = 5 * time.Minute
if d > maxSleep {
logf("sleeping for %v, capped from server-requested %v ...", maxSleep, d)
@@ -1468,20 +1469,20 @@ func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<-
logf("sleeping for server-requested %v ...", d)
}
- ticker := time.NewTicker(pollTimeout / 2)
+ ticker, tickerChannel := clock.NewTicker(pollTimeout / 2)
defer ticker.Stop()
- timer := time.NewTimer(d)
+ timer, timerChannel := clock.NewTimer(d)
defer timer.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
- case <-timer.C:
+ case <-timerChannel:
return nil
- case <-ticker.C:
+ case <-tickerChannel:
select {
case timeoutReset <- struct{}{}:
- case <-timer.C:
+ case <-timerChannel:
return nil
case <-ctx.Done():
return ctx.Err()
diff --git a/control/controlclient/noise.go b/control/controlclient/noise.go
index 4a8335133..e3a8a9b9e 100644
--- a/control/controlclient/noise.go
+++ b/control/controlclient/noise.go
@@ -23,6 +23,7 @@ import (
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/mak"
@@ -450,6 +451,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
DialPlan: dialPlan,
Logf: nc.logf,
NetMon: nc.netMon,
+ Clock: &tstime.StdClock{},
}).Dial(ctx)
if err != nil {
return nil, err
diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go
index b0d91bada..facd0f1b1 100644
--- a/control/controlhttp/client.go
+++ b/control/controlhttp/client.go
@@ -147,13 +147,13 @@ func (a *Dialer) dial(ctx context.Context) (*ClientConn, error) {
// before we do anything.
if c.DialStartDelaySec > 0 {
a.logf("[v2] controlhttp: waiting %.2f seconds before dialing %q @ %v", c.DialStartDelaySec, a.Hostname, c.IP)
- tmr := time.NewTimer(time.Duration(c.DialStartDelaySec * float64(time.Second)))
+ tmr, tmrChannel := a.Clock.NewTimer(time.Duration(c.DialStartDelaySec * float64(time.Second)))
defer tmr.Stop()
select {
case <-ctx.Done():
err = ctx.Err()
return
- case <-tmr.C:
+ case <-tmrChannel:
}
}
@@ -319,7 +319,7 @@ func (a *Dialer) dialHost(ctx context.Context, addr netip.Addr) (*ClientConn, er
// In case outbound port 80 blocked or MITM'ed poorly, start a backup timer
// to dial port 443 if port 80 doesn't either succeed or fail quickly.
- try443Timer := time.AfterFunc(a.httpsFallbackDelay(), func() { try(u443) })
+ try443Timer := a.Clock.AfterFunc(a.httpsFallbackDelay(), func() { try(u443) })
defer try443Timer.Stop()
var err80, err443 error
diff --git a/control/controlhttp/constants.go b/control/controlhttp/constants.go
index b838f84c4..76c76699f 100644
--- a/control/controlhttp/constants.go
+++ b/control/controlhttp/constants.go
@@ -11,6 +11,7 @@ import (
"tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
@@ -89,6 +90,8 @@ type Dialer struct {
drainFinished chan struct{}
omitCertErrorLogging bool
testFallbackDelay time.Duration
+
+ Clock tstime.Clock
}
func strDef(v1, v2 string) string {
diff --git a/control/controlhttp/http_test.go b/control/controlhttp/http_test.go
index 3cf790d8c..c5fcdf5c3 100644
--- a/control/controlhttp/http_test.go
+++ b/control/controlhttp/http_test.go
@@ -25,6 +25,7 @@ import (
"tailscale.com/net/socks5"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg"
+ "tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
@@ -204,6 +205,7 @@ func testControlHTTP(t *testing.T, param httpTestParam) {
Logf: t.Logf,
omitCertErrorLogging: true,
testFallbackDelay: 50 * time.Millisecond,
+ Clock: &tstest.Clock{},
}
if proxy != nil {
@@ -660,6 +662,7 @@ func TestDialPlan(t *testing.T) {
drainFinished: drained,
omitCertErrorLogging: true,
testFallbackDelay: 50 * time.Millisecond,
+ Clock: &tstest.Clock{},
}
conn, err := a.dial(ctx)
diff --git a/derp/derp_test.go b/derp/derp_test.go
index 62d4e0a00..6aa9e4a59 100644
--- a/derp/derp_test.go
+++ b/derp/derp_test.go
@@ -27,6 +27,7 @@ import (
"golang.org/x/time/rate"
"tailscale.com/disco"
"tailscale.com/net/memnet"
+ "tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
@@ -267,6 +268,7 @@ func TestSendRecv(t *testing.T) {
}
func TestSendFreeze(t *testing.T) {
+ clock := &tstest.Clock{}
serverPrivateKey := key.NewNode()
s := NewServer(serverPrivateKey, t.Logf)
defer s.Close()
@@ -398,14 +400,14 @@ func TestSendFreeze(t *testing.T) {
}
drain := func(t *testing.T, name string) bool {
t.Helper()
- timer := time.NewTimer(1 * time.Second)
+ timer, timerChannel := clock.NewTimer(1 * time.Second)
defer timer.Stop()
// Ensure ch has at least one element.
ch := chs(name)
select {
case <-ch:
- case <-timer.C:
+ case <-timerChannel:
t.Errorf("no packet received by %s", name)
return false
}
diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go
index 07317fcbf..89cad7111 100644
--- a/derp/derphttp/derphttp_client.go
+++ b/derp/derphttp/derphttp_client.go
@@ -38,6 +38,7 @@ import (
"tailscale.com/net/tshttpproxy"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/util/cmpx"
@@ -83,6 +84,7 @@ type Client struct {
serverPubKey key.NodePublic
tlsState *tls.ConnectionState
pingOut map[derp.PingMessage]chan<- bool // chan to send to on pong
+ clock tstime.Clock
}
func (c *Client) String() string {
@@ -101,6 +103,7 @@ func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmo
getRegion: getRegion,
ctx: ctx,
cancelCtx: cancel,
+ clock: &tstime.StdClock{},
}
return c
}
@@ -108,7 +111,7 @@ func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmo
// NewNetcheckClient returns a Client that's only able to have its DialRegionTLS method called.
// It's used by the netcheck package.
func NewNetcheckClient(logf logger.Logf) *Client {
- return &Client{logf: logf}
+ return &Client{logf: logf, clock: &tstime.StdClock{}}
}
// NewClient returns a new DERP-over-HTTP client. It connects lazily.
@@ -129,6 +132,7 @@ func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf) (
url: u,
ctx: ctx,
cancelCtx: cancel,
+ clock: &tstime.StdClock{},
}
return c, nil
}
@@ -644,14 +648,14 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
nwait++
go func() {
if proto == "tcp4" && c.preferIPv6() {
- t := time.NewTimer(200 * time.Millisecond)
+ t, tChannel := c.clock.NewTimer(200 * time.Millisecond)
select {
case <-ctx.Done():
// Either user canceled original context,
// it timed out, or the v6 dial succeeded.
t.Stop()
return
- case <-t.C:
+ case <-tChannel:
// Start v4 dial
}
}
diff --git a/derp/derphttp/mesh_client.go b/derp/derphttp/mesh_client.go
index 4454136ab..eb74a6fd9 100644
--- a/derp/derphttp/mesh_client.go
+++ b/derp/derphttp/mesh_client.go
@@ -91,11 +91,11 @@ func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key
}
sleep := func(d time.Duration) {
- t := time.NewTimer(d)
+ t, tChannel := c.clock.NewTimer(d)
select {
case <-ctx.Done():
t.Stop()
- case <-t.C:
+ case <-tChannel:
}
}
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 86a550a05..24904c7ea 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -60,6 +60,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/tka"
"tailscale.com/tsd"
+ "tailscale.com/tstime"
"tailscale.com/types/dnstype"
"tailscale.com/types/empty"
"tailscale.com/types/key"
@@ -259,6 +260,7 @@ type LocalBackend struct {
// tkaSyncLock MUST be taken before mu (or inversely, mu must not be held
// at the moment that tkaSyncLock is taken).
tkaSyncLock sync.Mutex
+ clock tstime.Clock
}
// clientGen is a func that creates a control plane client.
@@ -311,6 +313,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
em: newExpiryManager(logf),
gotPortPollRes: make(chan struct{}),
loginFlags: loginFlags,
+ clock: &tstime.StdClock{},
}
netMon := sys.NetMon.Get()
@@ -1380,12 +1383,12 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
// HTTP request (via doSetHostinfoFilterServices >
// cli.SetHostinfo). In practice this is very quick.
t0 := time.Now()
- timer := time.NewTimer(time.Second)
+ timer, timerChannel := b.clock.NewTimer(time.Second)
select {
case <-b.gotPortPollRes:
b.logf("[v1] got initial portlist info in %v", time.Since(t0).Round(time.Millisecond))
timer.Stop()
- case <-timer.C:
+ case <-timerChannel:
b.logf("timeout waiting for initial portlist")
}
})
diff --git a/logtail/backoff/backoff.go b/logtail/backoff/backoff.go
index ffec64ecd..951359a0f 100644
--- a/logtail/backoff/backoff.go
+++ b/logtail/backoff/backoff.go
@@ -9,6 +9,7 @@ import (
"math/rand"
"time"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
)
@@ -23,10 +24,7 @@ type Backoff struct {
// logf is the function used for log messages when backing off.
logf logger.Logf
- // NewTimer is the function that acts like time.NewTimer.
- // It's for use in unit tests.
- NewTimer func(time.Duration) *time.Timer
-
+ Clock tstime.Clock // use tstime.Clock for NewTimer
// LogLongerThan sets the minimum time of a single backoff interval
// before we mention it in the log.
LogLongerThan time.Duration
@@ -40,7 +38,7 @@ func NewBackoff(name string, logf logger.Logf, maxBackoff time.Duration) *Backof
name: name,
logf: logf,
maxBackoff: maxBackoff,
- NewTimer: time.NewTimer,
+ Clock: &tstime.StdClock{},
}
}
@@ -72,10 +70,10 @@ func (b *Backoff) BackOff(ctx context.Context, err error) {
if d >= b.LogLongerThan {
b.logf("%s: [v1] backoff: %d msec", b.name, d.Milliseconds())
}
- t := b.NewTimer(d)
+ t, tChannel := b.Clock.NewTimer(d)
select {
case <-ctx.Done():
t.Stop()
- case <-t.C:
+ case <-tChannel:
}
}
diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go
index 85670e1d6..49fc75784 100644
--- a/net/dns/resolver/forwarder.go
+++ b/net/dns/resolver/forwarder.go
@@ -29,6 +29,7 @@ import (
"tailscale.com/net/netns"
"tailscale.com/net/sockstats"
"tailscale.com/net/tsdial"
+ "tailscale.com/tstime"
"tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
@@ -200,6 +201,7 @@ type forwarder struct {
// /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub
// resolver lookup.
cloudHostFallback []resolverAndDelay
+ clock tstime.Clock
}
func init() {
@@ -212,6 +214,7 @@ func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkS
netMon: netMon,
linkSel: linkSel,
dialer: dialer,
+ clock: &tstime.StdClock{},
}
f.ctx, f.ctxCancel = context.WithCancel(context.Background())
return f
@@ -695,9 +698,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
for i := range resolvers {
go func(rr *resolverAndDelay) {
if rr.startDelay > 0 {
- timer := time.NewTimer(rr.startDelay)
+ timer, timerChannel := f.clock.NewTimer(rr.startDelay)
select {
- case <-timer.C:
+ case <-timerChannel:
case <-ctx.Done():
timer.Stop()
return
diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go
index f3fd50fb9..a183f8497 100644
--- a/net/dnscache/dnscache.go
+++ b/net/dnscache/dnscache.go
@@ -22,6 +22,7 @@ import (
"tailscale.com/envknob"
"tailscale.com/net/netmon"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
"tailscale.com/util/cloudenv"
"tailscale.com/util/singleflight"
@@ -397,6 +398,7 @@ func (d *dialer) DialContext(ctx context.Context, network, address string) (retC
address: address,
host: host,
port: port,
+ clock: &tstime.StdClock{},
}
defer func() {
// On failure, consider that our DNS might be wrong and ask the DNS fallback mechanism for
@@ -471,6 +473,7 @@ type dialCall struct {
mu sync.Mutex // lock ordering: dialer.mu, then dialCall.mu
fails map[netip.Addr]error // set of IPs that failed to dial thus far
+ clock tstime.Clock
}
// dnsWasTrustworthy reports whether we think the IP address(es) we
@@ -585,9 +588,9 @@ func (dc *dialCall) raceDial(ctx context.Context, ips []netip.Addr) (net.Conn, e
go func() {
for i, ip := range ips {
if i != 0 {
- timer := time.NewTimer(fallbackDelay)
+ timer, timerChannel := dc.clock.NewTimer(fallbackDelay)
select {
- case <-timer.C:
+ case <-timerChannel:
case <-failBoost:
timer.Stop()
case <-ctx.Done():
diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go
index b0a93454a..32cd00244 100644
--- a/net/netcheck/netcheck.go
+++ b/net/netcheck/netcheck.go
@@ -37,6 +37,7 @@ import (
"tailscale.com/net/stun"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
"tailscale.com/types/opt"
@@ -166,8 +167,7 @@ type Client struct {
// If nil, the interface will be looked up dynamically.
NetMon *netmon.Monitor
- // TimeNow, if non-nil, is used instead of time.Now.
- TimeNow func() time.Time
+ Clock tstime.Clock // use tstime.Clock.Now() instead of time.Now
// GetSTUNConn4 optionally provides a func to return the
// connection to use for sending & receiving IPv4 packets. If
@@ -1013,11 +1013,11 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
}(probeSet)
}
- stunTimer := time.NewTimer(stunProbeTimeout)
+ stunTimer, stunTimerChannel := c.Clock.NewTimer(stunProbeTimeout)
defer stunTimer.Stop()
select {
- case <-stunTimer.C:
+ case <-stunTimerChannel:
case <-ctx.Done():
case <-wg.DoneChan():
// All of our probes finished, so if we have >0 responses, we
@@ -1436,8 +1436,8 @@ func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
}
func (c *Client) timeNow() time.Time {
- if c.TimeNow != nil {
- return c.TimeNow()
+ if c.Clock != nil {
+ return c.Clock.Now()
}
return time.Now()
}
@@ -1531,9 +1531,9 @@ func (rs *reportState) runProbe(ctx context.Context, dm *tailcfg.DERPMap, probe
}
if probe.delay > 0 {
- delayTimer := time.NewTimer(probe.delay)
+ delayTimer, delayTimerChannel := c.Clock.NewTimer(probe.delay)
select {
- case <-delayTimer.C:
+ case <-delayTimerChannel:
case <-ctx.Done():
delayTimer.Stop()
return
diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go
index 1de3693ce..9d874fdd9 100644
--- a/net/netcheck/netcheck_test.go
+++ b/net/netcheck/netcheck_test.go
@@ -161,6 +161,7 @@ func TestBasic(t *testing.T) {
c := &Client{
Logf: t.Logf,
UDPBindAddr: "127.0.0.1:0",
+ Clock: &tstest.Clock{},
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
@@ -200,7 +201,8 @@ func TestWorksWhenUDPBlocked(t *testing.T) {
dm.Regions[1].Nodes[0].STUNOnly = true
c := &Client{
- Logf: t.Logf,
+ Logf: t.Logf,
+ Clock: &tstest.Clock{},
}
ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
defer cancel()
@@ -340,10 +342,10 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
fakeTime := time.Unix(123, 0)
c := &Client{
- TimeNow: func() time.Time { return fakeTime },
+ Clock: tstest.NewClock(tstest.ClockOpts{Start: fakeTime}),
}
for _, s := range tt.steps {
- fakeTime = fakeTime.Add(s.after)
+ c.Clock.(*tstest.Clock).Advance(s.after)
c.addReportHistoryAndSetPreferredDERP(s.r)
}
lastReport := tt.steps[len(tt.steps)-1].r
@@ -800,6 +802,7 @@ func TestNoCaptivePortalWhenUDP(t *testing.T) {
// Set the delay long enough that we have time to cancel it
// when our STUN probe succeeds.
testCaptivePortalDelay: 10 * time.Second,
+ Clock: &tstest.Clock{},
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go
index d819e5a79..b5587501d 100644
--- a/tsnet/tsnet.go
+++ b/tsnet/tsnet.go
@@ -49,6 +49,7 @@ import (
"tailscale.com/net/tsdial"
"tailscale.com/smallzstd"
"tailscale.com/tsd"
+ "tailscale.com/tstime"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/types/nettype"
@@ -1029,7 +1030,8 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro
keys: keys,
addr: addr,
- conn: make(chan net.Conn),
+ conn: make(chan net.Conn),
+ clock: &tstime.StdClock{},
}
s.mu.Lock()
for _, key := range keys {
@@ -1061,6 +1063,7 @@ type listener struct {
addr string
conn chan net.Conn
closed bool // guarded by s.mu
+ clock tstime.Clock
}
func (ln *listener) Accept() (net.Conn, error) {
@@ -1096,11 +1099,11 @@ func (ln *listener) closeLocked() error {
}
func (ln *listener) handle(c net.Conn) {
- t := time.NewTimer(time.Second)
+ t, tChannel := ln.clock.NewTimer(time.Second)
defer t.Stop()
select {
case ln.conn <- c:
- case <-t.C:
+ case <-tChannel:
// TODO(bradfitz): this isn't ideal. Think about how
// we how we want to do pushback.
c.Close()
diff --git a/types/logger/logger.go b/types/logger/logger.go
index 1df273b3e..488fbbf6e 100644
--- a/types/logger/logger.go
+++ b/types/logger/logger.go
@@ -21,6 +21,7 @@ import (
"context"
"tailscale.com/envknob"
+ "tailscale.com/tstime"
)
// Logf is the basic Tailscale logger type: a printf-like func.
@@ -47,6 +48,8 @@ var jencPool = &sync.Pool{New: func() any {
return je
}}
+var clock tstime.Clock = &tstime.StdClock{}
+
// JSON marshals v as JSON and writes it to logf formatted with the annotation to make logtail
// treat it as a structured log.
//
@@ -259,10 +262,10 @@ func SlowLoggerWithClock(ctx context.Context, logf Logf, f time.Duration, burst
// Otherwise, sleep for 2x the duration so that we don't
// immediately sleep again on the next call.
- tmr := time.NewTimer(2 * f)
+ tmr, tmrChannel := clock.NewTimer(2 * f)
defer tmr.Stop()
select {
- case curr := <-tmr.C:
+ case curr := <-tmrChannel:
tb.AdvanceTo(curr)
case <-ctx.Done():
return
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index 2136bdb1d..f480f8287 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -673,6 +673,7 @@ func NewConn(opts Options) (*Conn, error) {
SkipExternalNetwork: inTest(),
PortMapper: c.portMapper,
UseDNSCache: true,
+ Clock: &tstime.StdClock{},
}
c.ignoreSTUNPackets()
diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go
index 19505be89..476ec4dd5 100644
--- a/wgengine/watchdog.go
+++ b/wgengine/watchdog.go
@@ -18,6 +18,7 @@ import (
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/dns"
"tailscale.com/tailcfg"
+ "tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/capture"
@@ -40,6 +41,7 @@ func NewWatchdog(e Engine) Engine {
fatalf: log.Fatalf,
maxWait: 45 * time.Second,
inFlight: make(map[inFlightKey]time.Time),
+ clock: &tstime.StdClock{},
}
}
@@ -58,6 +60,8 @@ type watchdogEngine struct {
inFlightMu sync.Mutex
inFlight map[inFlightKey]time.Time
inFlightCtr uint64
+
+ clock tstime.Clock
}
func (e *watchdogEngine) watchdogErr(name string, fn func() error) error {
@@ -82,12 +86,12 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error {
go func() {
errCh <- fn()
}()
- t := time.NewTimer(e.maxWait)
+ t, tChannel := e.clock.NewTimer(e.maxWait)
select {
case err := <-errCh:
t.Stop()
return err
- case <-t.C:
+ case <-tChannel:
buf := new(strings.Builder)
pprof.Lookup("goroutine").WriteTo(buf, 1)
e.logf("wgengine watchdog stacks:\n%s", buf.String())