socket-function 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SetProcessVariables.ts.cache +2 -2
- package/dist/SocketFunction.ts.cache +2 -2
- package/hot/dist/HotReloadController.ts.cache +2 -2
- package/package.json +1 -1
- package/require/dist/RequireController.ts.cache +2 -2
- package/require/dist/compileFlags.ts.cache +2 -2
- package/require/dist/extMapper.ts.cache +2 -2
- package/require/dist/require.ts.cache +2 -2
- package/src/JSONLACKS/dist/JSONLACKS.ts.cache +2 -2
- package/src/dist/CallFactory.ts.cache +2 -2
- package/src/dist/Zip.ts.cache +2 -2
- package/src/dist/bits.ts.cache +2 -2
- package/src/dist/buffers.ts.cache +2 -2
- package/src/dist/caching.ts.cache +2 -2
- package/src/dist/callHTTPHandler.ts.cache +2 -2
- package/src/dist/callManager.ts.cache +2 -2
- package/src/dist/certStore.ts.cache +2 -2
- package/src/dist/fixLargeNetworkCalls.ts.cache +2 -2
- package/src/dist/forwardPort.ts.cache +2 -2
- package/src/dist/https.ts.cache +2 -2
- package/src/dist/nodeCache.ts.cache +2 -2
- package/src/dist/nodeProxy.ts.cache +2 -2
- package/src/dist/protocolNegotiation.ts.cache +2 -2
- package/src/dist/tlsParsing.ts.cache +2 -2
- package/src/dist/webSocketServer.ts.cache +2 -2
- package/src/dist/websocketFactory.ts.cache +2 -2
- package/src/lz4/dist/LZ4.ts.cache +2 -2
- package/time/dist/trueTimeShim.ts.cache +86 -35
- package/time/trueTimeShim.ts +78 -27
package/time/trueTimeShim.ts
CHANGED
|
@@ -18,6 +18,11 @@ const FETCH_RETRY_MAX_DELAY = 1000 * 30;
|
|
|
18
18
|
const FETCH_MAX_FAILURES = 8;
|
|
19
19
|
const NTP_TIMEOUT = 1000 * 10;
|
|
20
20
|
|
|
21
|
+
// The on-disk LMDB database can become corrupt (ex, a process killed mid-write). After this many
|
|
22
|
+
// consecutive failed operations we stop just reopening the handle and delete the files to start
|
|
23
|
+
// fresh - the data is only a cached time offset, so wiping it merely forces a re-measurement.
|
|
24
|
+
const LMDB_MAX_CORRUPTION_FAILURES = 3;
|
|
25
|
+
|
|
21
26
|
// Time can never go backwards, but we can run at a slower rate until the output time allows
|
|
22
27
|
// the real time to catch up with it.
|
|
23
28
|
const MINIMUM_TIME_RATE = 0.5;
|
|
@@ -283,67 +288,113 @@ type TimeOffsetStore = {
|
|
|
283
288
|
putInitial(data: TimeOffsetData): Promise<boolean>;
|
|
284
289
|
};
|
|
285
290
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (timeOffsetDb) return timeOffsetDb;
|
|
290
|
-
if (!isNode()) return undefined;
|
|
291
|
+
type TimeOffsetDb = import("lmdb").RootDatabase<TimeOffsetData, string>;
|
|
292
|
+
let timeOffsetDb: TimeOffsetDb | undefined = undefined;
|
|
293
|
+
let timeOffsetDbPath: string | undefined = undefined;
|
|
291
294
|
|
|
295
|
+
async function openTimeOffsetDb(): Promise<TimeOffsetDb | undefined> {
|
|
292
296
|
try {
|
|
293
297
|
const lmdb = await import("lmdb");
|
|
294
298
|
const path = await import("path");
|
|
295
299
|
const os = await import("os");
|
|
296
300
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
path:
|
|
301
|
+
timeOffsetDbPath = path.join(os.tmpdir(), "socket-function-time-offset-2");
|
|
302
|
+
return lmdb.open<TimeOffsetData, string>({
|
|
303
|
+
path: timeOffsetDbPath,
|
|
300
304
|
// Enable versioning for conditional writes
|
|
301
305
|
useVersions: true,
|
|
302
306
|
});
|
|
303
|
-
return timeOffsetDb;
|
|
304
307
|
} catch (e) {
|
|
305
|
-
console.error("Error opening LMDB database:", e);
|
|
308
|
+
console.error("Error opening LMDB database:", (e as Error).stack ?? e);
|
|
306
309
|
return undefined;
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
function
|
|
313
|
+
async function getTimeOffsetDb() {
|
|
314
|
+
if (!USE_LMDB_PROCESS_SYNC) return undefined;
|
|
315
|
+
if (!isNode()) return undefined;
|
|
316
|
+
if (timeOffsetDb) return timeOffsetDb;
|
|
317
|
+
timeOffsetDb = await openTimeOffsetDb();
|
|
318
|
+
return timeOffsetDb;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Closes the current handle (so the next op reopens a fresh one) and, when wipe is set, deletes the
|
|
322
|
+
// on-disk files so a corrupt database is replaced with an empty one. lmdb writes the main file at
|
|
323
|
+
// the path plus a sibling lock file, so we clear the path and its lock variants.
|
|
324
|
+
async function recreateTimeOffsetDb(config: { wipe: boolean }) {
|
|
325
|
+
let db = timeOffsetDb;
|
|
326
|
+
timeOffsetDb = undefined;
|
|
327
|
+
if (db) {
|
|
328
|
+
try {
|
|
329
|
+
await db.close();
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.error("Error closing LMDB database:", (e as Error).stack ?? e);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (config.wipe && timeOffsetDbPath) {
|
|
335
|
+
try {
|
|
336
|
+
const fs = await import("fs");
|
|
337
|
+
for (let suffix of ["", "-lock", ".lock"]) {
|
|
338
|
+
await fs.promises.rm(timeOffsetDbPath + suffix, { recursive: true, force: true });
|
|
339
|
+
}
|
|
340
|
+
console.log(yellow(`Wiped corrupt time-offset LMDB database at ${timeOffsetDbPath}`));
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error("Error wiping corrupt LMDB database:", (e as Error).stack ?? e);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// A corrupt database throws on every read and on the read-back inside every write, and we can't
|
|
348
|
+
// repair it. So we run each op through here: on success we reset the failure count, and on error
|
|
349
|
+
// we reopen the handle (wiping the files once we've hit the threshold) and return the fallback so
|
|
350
|
+
// callers degrade to running unsynced instead of throwing. Ops are serialized by updatingOffset,
|
|
351
|
+
// so there is no concurrent access to worry about.
|
|
352
|
+
let lmdbCorruptionFailures = 0;
|
|
353
|
+
async function runLmdbOp<T>(name: string, op: (db: TimeOffsetDb) => Promise<T>, fallback: T): Promise<T> {
|
|
354
|
+
const db = await getTimeOffsetDb();
|
|
355
|
+
if (!db) return fallback;
|
|
356
|
+
try {
|
|
357
|
+
const result = await op(db);
|
|
358
|
+
lmdbCorruptionFailures = 0;
|
|
359
|
+
return result;
|
|
360
|
+
} catch (e) {
|
|
361
|
+
lmdbCorruptionFailures++;
|
|
362
|
+
console.error(`Error during LMDB ${name} (failure ${lmdbCorruptionFailures}/${LMDB_MAX_CORRUPTION_FAILURES}):`, (e as Error).stack ?? e);
|
|
363
|
+
let wipe = lmdbCorruptionFailures >= LMDB_MAX_CORRUPTION_FAILURES;
|
|
364
|
+
await recreateTimeOffsetDb({ wipe });
|
|
365
|
+
if (wipe) lmdbCorruptionFailures = 0;
|
|
366
|
+
return fallback;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function getLmdbStore(): TimeOffsetStore {
|
|
311
371
|
return {
|
|
312
372
|
async getEntry() {
|
|
313
|
-
|
|
373
|
+
return runLmdbOp("getEntry", async (db) => {
|
|
314
374
|
const entry = await db.getEntry("timeOffset"); // Gets {value, version} atomically
|
|
315
375
|
if (!entry) return undefined;
|
|
316
376
|
if (typeof entry.version !== "number" || !isValidTimeOffsetData(entry.value)) return undefined;
|
|
317
377
|
return { data: entry.value, version: entry.version };
|
|
318
|
-
}
|
|
319
|
-
console.error("Error reading from LMDB database:", e);
|
|
320
|
-
return undefined;
|
|
321
|
-
}
|
|
378
|
+
}, undefined);
|
|
322
379
|
},
|
|
323
380
|
async putIfVersion(data, expectedVersion) {
|
|
324
|
-
|
|
381
|
+
return runLmdbOp("putIfVersion", async (db) => {
|
|
325
382
|
// Use random version to minimize collision probability on retries
|
|
326
383
|
const newVersion = Math.random();
|
|
327
384
|
const success = await db.ifVersion("timeOffset", expectedVersion, () => {
|
|
328
385
|
return db.put("timeOffset", data, newVersion);
|
|
329
386
|
});
|
|
330
387
|
return success !== undefined;
|
|
331
|
-
}
|
|
332
|
-
console.error("Error writing to LMDB database:", e);
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
388
|
+
}, false);
|
|
335
389
|
},
|
|
336
390
|
async putInitial(data) {
|
|
337
|
-
|
|
391
|
+
return runLmdbOp("putInitial", async (db) => {
|
|
338
392
|
const newVersion = Math.random();
|
|
339
393
|
await db.put("timeOffset", data, newVersion);
|
|
340
394
|
// Read back to see what actually got written
|
|
341
395
|
const actualEntry = await db.getEntry("timeOffset");
|
|
342
396
|
return actualEntry?.version === newVersion;
|
|
343
|
-
}
|
|
344
|
-
console.error("Error writing to LMDB database:", e);
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
397
|
+
}, false);
|
|
347
398
|
},
|
|
348
399
|
};
|
|
349
400
|
}
|
|
@@ -407,7 +458,7 @@ async function getTimeOffsetStore(): Promise<TimeOffsetStore | undefined> {
|
|
|
407
458
|
if (isNode()) {
|
|
408
459
|
const db = await getTimeOffsetDb();
|
|
409
460
|
if (db) {
|
|
410
|
-
timeOffsetStore = getLmdbStore(
|
|
461
|
+
timeOffsetStore = getLmdbStore();
|
|
411
462
|
}
|
|
412
463
|
} else {
|
|
413
464
|
timeOffsetStore = getLocalStorageStore();
|