mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
128 lines
4.1 KiB
Go
128 lines
4.1 KiB
Go
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License-AGPL.txt in the project root for license information.
|
|
|
|
package clock
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// This clock roughly implements a Hybrid Logical Clock (HLC) as described in
|
|
//
|
|
// Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases
|
|
// Kulkarni et al.
|
|
// https://cse.buffalo.edu/tech-reports/2014-04.pdf
|
|
//
|
|
// This implementation is very much geared towards single process use, yet still
|
|
// protects us from the funny bits of physical wall time, e.g. clock skew or time
|
|
// running backwards. Also, this implementation is naively synchronised using a simple mutex.
|
|
//
|
|
// In the future, if we ever make ws-manager properly distributed, we might want
|
|
// to take inspiration from cockroachDB's implementation:
|
|
//
|
|
// https://github.com/cockroachdb/cockroach/blob/master/pkg/util/hlc/hlc.go
|
|
type HLC struct {
|
|
physicalTime func() uint64
|
|
backwardsFlowReporter func(diff uint64)
|
|
|
|
mu struct {
|
|
sync.Mutex
|
|
|
|
lastPhysicalTime uint64
|
|
lc uint64
|
|
}
|
|
}
|
|
|
|
// System takes the wall time from the actual system time.
|
|
// Beware: when using the clock wall time, it's a good idea to wait out
|
|
//
|
|
// the maximum expected real wall time skew, e.g. as imposed by
|
|
// NTP. time.Sleep(2*time.Second) should be enough in most cases.
|
|
func System() *HLC {
|
|
return &HLC{
|
|
physicalTime: func() uint64 { return uint64(time.Now().Unix()) },
|
|
}
|
|
}
|
|
|
|
// LogicalOnly includes no system time and resorts to the logical clock only.
|
|
// NEVER USE THIS IN PRODUCTION!
|
|
func LogicalOnly() *HLC {
|
|
return &HLC{physicalTime: func() uint64 { return 1 }}
|
|
}
|
|
|
|
const (
|
|
physicalTimeMask = 0xFFFFFFFFFFFF
|
|
logicalTimeMask = 0xFFFF
|
|
)
|
|
|
|
// ReportBackwardsTime registers a reporter which gets called when the wall time
|
|
// does not increase monotonously.
|
|
// Beware: this function is not synchronised with Tick() and must not be called
|
|
//
|
|
// concurrently.
|
|
func (c *HLC) ReportBackwardsTime(r func(diff uint64)) {
|
|
c.backwardsFlowReporter = r
|
|
}
|
|
|
|
func (c *HLC) Tick() uint64 {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// We trade of physical and logical clock precision here.
|
|
// The logical clock only has to cover all events happening while
|
|
// time is runninig backwards (e.g. after/during an NTP re-sync).
|
|
t := c.physicalTime() & physicalTimeMask
|
|
|
|
if t == 0 {
|
|
panic("physical time must never be 0")
|
|
}
|
|
|
|
if c.mu.lastPhysicalTime >= t {
|
|
if c.mu.lc >= logicalTimeMask {
|
|
// If this happens the logical clock cannot compensate for the physical wall clock skew.
|
|
// We want to be loud about this because this should never happen.
|
|
// In reality, the panic will probably restart the service which will let the wall clock
|
|
// skew seep in and thereby destroy the order guarantees this clock is supposed to provide.
|
|
// However, if the wall clock has been off for more than 65535 events, something is probably
|
|
// wrong on that node to begin with.
|
|
//
|
|
// TODO(cw): If this happens too often, we'll want to introduce forward time jump detection for the
|
|
// wall clock. Maybe time isn't running backwards, but rather jumping forwards and then
|
|
// standing still.
|
|
panic("logical clock overflow")
|
|
}
|
|
|
|
// time is moving backwards. We might want to report this.
|
|
if c.backwardsFlowReporter != nil {
|
|
c.backwardsFlowReporter(c.mu.lastPhysicalTime - t)
|
|
}
|
|
|
|
// time has moved backwards - tick using logic clock instead
|
|
c.mu.lc++
|
|
t = c.mu.lastPhysicalTime
|
|
} else {
|
|
// time is flowing in the right direction - reset the logical clock
|
|
c.mu.lc = 0
|
|
c.mu.lastPhysicalTime = t
|
|
}
|
|
|
|
return (t << 16) | c.mu.lc&logicalTimeMask
|
|
}
|
|
|
|
// PrometheusWallTimeMonotonicityReporter reports the number of times Tick() was called
|
|
// while the monotonicity of the wall tiime was violated.
|
|
func PrometheusWallTimeMonotonicityReporter(reg prometheus.Registerer) func(uint64) {
|
|
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "wall_time_monotonicity_violation_total",
|
|
})
|
|
reg.MustRegister(gauge)
|
|
|
|
return func(_ uint64) {
|
|
gauge.Inc()
|
|
}
|
|
}
|