From: Damien Neil Date: Wed, 12 Mar 2025 23:00:26 +0000 (-0700) Subject: runtime, time: don't use monotonic clock inside synctest bubbles X-Git-Tag: go1.25rc1~679 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=5916bc5b5796e8fd15f3b4e95c945f5f4933e1d5;p=gostls13.git runtime, time: don't use monotonic clock inside synctest bubbles Don't include a monotonic time in time.Times created inside a bubble, to avoid the confusion of different Times using different monotonic clock epochs. For #67434 goos: darwin goarch: arm64 pkg: time cpu: Apple M1 Pro │ /tmp/bench.0 │ /tmp/bench.1 │ │ sec/op │ sec/op vs base │ Since-10 18.42n ± 2% 18.68n ± 1% ~ (p=0.101 n=10) Until-10 18.28n ± 2% 18.46n ± 2% +0.98% (p=0.009 n=10) geomean 18.35n 18.57n +1.20% Change-Id: Iaf1b80d0a4df52139c5b80d4bde4410ef8a49f2f Reviewed-on: https://go-review.googlesource.com/c/go/+/657415 LUCI-TryBot-Result: Go LUCI Auto-Submit: Damien Neil Reviewed-by: Michael Pratt --- diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go index 450d5f5416..62acb42359 100644 --- a/src/internal/synctest/synctest_test.go +++ b/src/internal/synctest/synctest_test.go @@ -37,6 +37,27 @@ func TestNow(t *testing.T) { }) } +// TestMonotonicClock exercises comparing times from within a bubble +// with ones from outside the bubble. +func TestMonotonicClock(t *testing.T) { + start := time.Now() + synctest.Run(func() { + time.Sleep(time.Until(start.Round(0))) + if got, want := time.Now().In(time.UTC), start.In(time.UTC); !got.Equal(want) { + t.Fatalf("time.Now() = %v, want %v", got, want) + } + + wait := 1 * time.Second + time.Sleep(wait) + if got := time.Since(start); got != wait { + t.Fatalf("time.Since(start) = %v, want %v", got, wait) + } + if got := time.Now().Sub(start); got != wait { + t.Fatalf("time.Now().Sub(start) = %v, want %v", got, wait) + } + }) +} + func TestRunEmpty(t *testing.T) { synctest.Run(func() { }) diff --git a/src/runtime/time.go b/src/runtime/time.go index c22d39c089..3ece161cf4 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -18,7 +18,13 @@ func time_runtimeNow() (sec int64, nsec int32, mono int64) { if sg := getg().syncGroup; sg != nil { sec = sg.now / (1000 * 1000 * 1000) nsec = int32(sg.now % (1000 * 1000 * 1000)) - return sec, nsec, sg.now + // Don't return a monotonic time inside a synctest bubble. + // If we return a monotonic time based on the fake clock, + // arithmetic on times created inside/outside bubbles is confusing. + // If we return a monotonic time based on the real monotonic clock, + // arithmetic on times created in the same bubble is confusing. + // Simplest is to omit the monotonic time within a bubble. + return sec, nsec, 0 } return time_now() } @@ -32,6 +38,11 @@ func time_runtimeNano() int64 { return nanotime() } +//go:linkname time_runtimeIsBubbled time.runtimeIsBubbled +func time_runtimeIsBubbled() bool { + return getg().syncGroup != nil +} + // A timer is a potentially repeating trigger for calling t.f(t.arg, t.seq). // Timers are allocated by client code, often as part of other data structures. // Each P has a heap of pointers to timers that it manages. diff --git a/src/time/time.go b/src/time/time.go index 14e79672ca..bcaeee407e 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -1221,7 +1221,7 @@ func subMono(t, u int64) Duration { // Since returns the time elapsed since t. // It is shorthand for time.Now().Sub(t). func Since(t Time) Duration { - if t.wall&hasMonotonic != 0 { + if t.wall&hasMonotonic != 0 && !runtimeIsBubbled() { // Common case optimization: if t has monotonic time, then Sub will use only it. return subMono(runtimeNano()-startNano, t.ext) } @@ -1231,7 +1231,7 @@ func Since(t Time) Duration { // Until returns the duration until t. // It is shorthand for t.Sub(time.Now()). func Until(t Time) Duration { - if t.wall&hasMonotonic != 0 { + if t.wall&hasMonotonic != 0 && !runtimeIsBubbled() { // Common case optimization: if t has monotonic time, then Sub will use only it. return subMono(t.ext, runtimeNano()-startNano) } @@ -1325,6 +1325,9 @@ func runtimeNow() (sec int64, nsec int32, mono int64) //go:linkname runtimeNano func runtimeNano() int64 +//go:linkname runtimeIsBubbled +func runtimeIsBubbled() bool + // Monotonic times are reported as offsets from startNano. // We initialize startNano to runtimeNano() - 1 so that on systems where // monotonic time resolution is fairly low (e.g. Windows 2008 diff --git a/src/time/time_test.go b/src/time/time_test.go index ff253be46b..dcb477b658 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -1497,6 +1497,20 @@ func BenchmarkNowUnixMicro(b *testing.B) { } } +func BenchmarkSince(b *testing.B) { + start := Now() + for b.Loop() { + u = int64(Since(start)) + } +} + +func BenchmarkUntil(b *testing.B) { + end := Now().Add(1 * Hour) + for b.Loop() { + u = int64(Until(end)) + } +} + func BenchmarkFormat(b *testing.B) { t := Unix(1265346057, 0) for i := 0; i < b.N; i++ {