tstime/mono: fix Time.Unmarshal (#8480)

Calling both mono.Now() and time.Now() is slow and
leads to unnecessary precision errors.
Instead, directly compute mono.Time relative to baseMono and baseWall.
This is the opposite calculation as mono.Time.WallTime.

Updates tailscale/corp#8427

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
Joe Tsai 2023-06-28 15:16:52 -07:00 committed by GitHub
parent 075abd8ec1
commit e42be5a060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 20 additions and 3 deletions

View File

@ -104,7 +104,8 @@ func (t Time) WallTime() time.Time {
// MarshalJSON formats t for JSON as if it were a time.Time.
// We format Time this way for backwards-compatibility.
// This is best-effort only. Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged.
// Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged
// across different invocations of the Go process. This is best-effort only.
// Since t is a monotonic time, it can vary from the actual wall clock by arbitrary amounts.
// Even in the best of circumstances, it may vary by a few milliseconds.
func (t Time) MarshalJSON() ([]byte, error) {
@ -113,7 +114,8 @@ func (t Time) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON sets t according to data.
// This is best-effort only. Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged.
// Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged
// across different invocations of the Go process. This is best-effort only.
func (t *Time) UnmarshalJSON(data []byte) error {
var tt time.Time
err := tt.UnmarshalJSON(data)
@ -124,6 +126,6 @@ func (t *Time) UnmarshalJSON(data []byte) error {
*t = 0
return nil
}
*t = Now().Add(-time.Since(tt))
*t = baseMono.Add(tt.Sub(baseWall))
return nil
}

View File

@ -33,6 +33,21 @@ func TestUnmarshalZero(t *testing.T) {
}
}
func TestJSONRoundtrip(t *testing.T) {
want := Now()
b, err := want.MarshalJSON()
if err != nil {
t.Errorf("MarshalJSON error: %v", err)
}
var got Time
if err := got.UnmarshalJSON(b); err != nil {
t.Errorf("UnmarshalJSON error: %v", err)
}
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func BenchmarkMonoNow(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {