1
1
/*
2
2
Time sync -- relies on a hub running a time sync server.
3
3
4
+ IMPORTANT: Our realtime sync algorithm doesn't depend on an accurate clock anymore.
5
+ We may use time to compute logical timestamps for convenience, but they will always be
6
+ increasing and fall back to a non-time sequence for a while in case a clock is out of sync.
7
+ We do use the time for displaying edit times to users, which is one reason why syncing
8
+ the clock is useful.
9
+
4
10
To use this, call the default export, which is a sync
5
11
function that returns the current sync'd time (in ms since epoch), or
6
12
throws an error if the first time sync hasn't succeeded.
@@ -33,13 +39,15 @@ Type ".help" for more information.
33
39
34
40
import { timeClient } from "@cocalc/nats/service/time" ;
35
41
import { reuseInFlight } from "@cocalc/util/reuse-in-flight" ;
36
- import { once } from "@cocalc/util/async-utils" ;
37
42
import { getClient } from "@cocalc/nats/client" ;
38
43
import { delay } from "awaiting" ;
44
+ import { waitUntilConnected } from "./util" ;
39
45
40
- // sync clock this frequently once it has sync'd once
41
- const INTERVAL_GOOD = 1000 * 60 ;
42
- const INTERVAL_BAD = 5 * 1000 ;
46
+ // we use exponential backoff starting with a short interval
47
+ // then making it longer
48
+ const INTERVAL_START = 5 * 1000 ;
49
+ const INTERVAL_GOOD = 1000 * 120 ;
50
+ const TOLERANCE = 3000 ;
43
51
44
52
export function init ( ) {
45
53
syncLoop ( ) ;
@@ -57,18 +65,24 @@ async function syncLoop() {
57
65
}
58
66
syncLoopStarted = true ;
59
67
const client = getClient ( ) ;
68
+ let d = INTERVAL_START ;
60
69
while ( state != "closed" && client . state != "closed" ) {
61
70
try {
71
+ const lastSkew = skew ?? 0 ;
62
72
await getSkew ( ) ;
63
73
if ( state == "closed" ) return ;
64
- await delay ( INTERVAL_GOOD ) ;
65
- } catch ( err ) {
66
- if ( client . state != "connected" ) {
67
- await once ( client , "connected" ) ;
68
- continue ;
74
+ if ( Math . abs ( ( skew ?? 0 ) - lastSkew ) >= TOLERANCE ) {
75
+ // changing a lot so check again soon
76
+ d = INTERVAL_START ;
77
+ } else {
78
+ d = Math . min ( INTERVAL_GOOD , d * 2 ) ;
69
79
}
80
+ await delay ( d ) ;
81
+ } catch ( err ) {
70
82
console . log ( `WARNING: failed to sync clock -- ${ err } ` ) ;
71
- await delay ( INTERVAL_BAD ) ;
83
+ // reset delay
84
+ d = INTERVAL_START ;
85
+ await delay ( d ) ;
72
86
}
73
87
}
74
88
}
@@ -81,13 +95,15 @@ export const getSkew = reuseInFlight(async (): Promise<number> => {
81
95
skew = 0 ;
82
96
return skew ;
83
97
}
98
+ await waitUntilConnected ( ) ;
84
99
const start = Date . now ( ) ;
85
100
const client = getClient ( ) ;
86
101
const tc = timeClient ( client ) ;
87
102
const serverTime = await tc . time ( ) ;
88
103
const end = Date . now ( ) ;
89
104
rtt = end - start ;
90
105
skew = start + rtt / 2 - serverTime ;
106
+ console . log ( "getSkew" , { skew } ) ;
91
107
return skew ;
92
108
} ) ;
93
109
0 commit comments