Files
kopia/internal/clock/now_testing.go
Jarek Kowalski 0d0f48a7ee clock: discard monotonic clock component in clock.Now() (#1437)
The dual time measurement is described in
https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md

The fix is to discard hidden monotonic time component of time.Time
by converting to unix time and back.

Reviewed usage of clock.Now() and replaced with timetrack.StartTimer()
when measuring time.

The problem in #1402 was that passage of time was measured using
the monotonic time and not wall clock time. When the computer goes
to sleep, monotonic time is still monotonic while wall clock time makes
a leap when the computer wakes up. This is the behavior that
epoch manager (and most other compontents in Kopia) rely upon.

Fixes #1402

Co-authored-by: Julio Lopez <julio+gh@kasten.io>
2021-10-22 15:35:09 -07:00

79 lines
2.0 KiB
Go

//go:build testing
// +build testing
package clock
import (
"encoding/json"
"log"
"net/http"
"os"
"sync"
"time"
)
const refreshServerTimeEvery = 3 * time.Second
// Now is overridable function that returns current wall clock time.
var Now = func() time.Time {
return discardMonotonicTime(time.Now()) // nolint:forbidigo
}
func init() {
fakeTimeServer := os.Getenv("KOPIA_FAKE_CLOCK_ENDPOINT")
if fakeTimeServer == "" {
return
}
Now = getTimeFromServer(fakeTimeServer)
}
// getTimeFromServer returns a function that will return timestamp as returned by the server
// increasing it client-side by certain inteval until maximum is reached, at which point
// it will ask the server again for new timestamp.
//
// The server endpoint must be HTTP and be set using KOPIA_FAKE_CLOCK_ENDPOINT environment
// variable.
func getTimeFromServer(endpoint string) func() time.Time {
var mu sync.Mutex
var timeInfo struct {
Time time.Time `json:"time"`
ValidFor time.Duration `json:"validFor"`
}
var (
nextRefreshRealTime time.Time // nolint:forbidigo
localTimeOffset time.Duration // offset to be added to time.Now() to produce server time
)
return func() time.Time {
mu.Lock()
defer mu.Unlock()
localTime := time.Now() // nolint:forbidigo
if localTime.After(nextRefreshRealTime) {
resp, err := http.Get(endpoint) //nolint:gosec,noctx
if err != nil {
log.Fatalf("unable to get fake time from server: %v", err)
}
defer resp.Body.Close() //nolint:errcheck
if resp.StatusCode != http.StatusOK {
log.Fatalf("unable to get fake time from server: %v", resp.Status)
}
if err := json.NewDecoder(resp.Body).Decode(&timeInfo); err != nil {
log.Fatalf("invalid time received from fake time server: %v", err)
}
nextRefreshRealTime = localTime.Add(timeInfo.ValidFor) // nolint:forbidigo
// compute offset such that localTime + localTimeOffset == serverTime
localTimeOffset = timeInfo.Time.Sub(localTime)
}
return discardMonotonicTime(localTime.Add(localTimeOffset))
}
}