Skip to content

Commit 9ebc347

Browse files
committed
change time sync algorithm and also clarify that time is no longer used for anything important
1 parent 449d1a4 commit 9ebc347

File tree

1 file changed

+26
-10
lines changed

1 file changed

+26
-10
lines changed

src/packages/nats/time.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
/*
22
Time sync -- relies on a hub running a time sync server.
33
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+
410
To use this, call the default export, which is a sync
511
function that returns the current sync'd time (in ms since epoch), or
612
throws an error if the first time sync hasn't succeeded.
@@ -33,13 +39,15 @@ Type ".help" for more information.
3339

3440
import { timeClient } from "@cocalc/nats/service/time";
3541
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
36-
import { once } from "@cocalc/util/async-utils";
3742
import { getClient } from "@cocalc/nats/client";
3843
import { delay } from "awaiting";
44+
import { waitUntilConnected } from "./util";
3945

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;
4351

4452
export function init() {
4553
syncLoop();
@@ -57,18 +65,24 @@ async function syncLoop() {
5765
}
5866
syncLoopStarted = true;
5967
const client = getClient();
68+
let d = INTERVAL_START;
6069
while (state != "closed" && client.state != "closed") {
6170
try {
71+
const lastSkew = skew ?? 0;
6272
await getSkew();
6373
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);
6979
}
80+
await delay(d);
81+
} catch (err) {
7082
console.log(`WARNING: failed to sync clock -- ${err}`);
71-
await delay(INTERVAL_BAD);
83+
// reset delay
84+
d = INTERVAL_START;
85+
await delay(d);
7286
}
7387
}
7488
}
@@ -81,13 +95,15 @@ export const getSkew = reuseInFlight(async (): Promise<number> => {
8195
skew = 0;
8296
return skew;
8397
}
98+
await waitUntilConnected();
8499
const start = Date.now();
85100
const client = getClient();
86101
const tc = timeClient(client);
87102
const serverTime = await tc.time();
88103
const end = Date.now();
89104
rtt = end - start;
90105
skew = start + rtt / 2 - serverTime;
106+
console.log("getSkew", { skew });
91107
return skew;
92108
});
93109

0 commit comments

Comments
 (0)