socket-function 1.2.0 → 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.
Files changed (38) hide show
  1. package/SocketFunction.d.ts +1 -1
  2. package/SocketFunctionTypes.d.ts +1 -1
  3. package/dist/SetProcessVariables.ts.cache +2 -2
  4. package/dist/SocketFunction.ts.cache +2 -2
  5. package/hot/HotReloadController.d.ts +3 -3
  6. package/hot/dist/HotReloadController.ts.cache +2 -2
  7. package/index.d.ts +9 -9
  8. package/package.json +1 -1
  9. package/require/CSSShim.d.ts +1 -1
  10. package/require/RequireController.d.ts +2 -2
  11. package/require/compileFlags.d.ts +1 -1
  12. package/require/dist/RequireController.ts.cache +2 -2
  13. package/require/dist/compileFlags.ts.cache +2 -2
  14. package/require/dist/extMapper.ts.cache +2 -2
  15. package/require/dist/require.ts.cache +2 -2
  16. package/require/require.d.ts +1 -1
  17. package/src/JSONLACKS/dist/JSONLACKS.ts.cache +2 -2
  18. package/src/callManager.d.ts +1 -1
  19. package/src/dist/CallFactory.ts.cache +2 -2
  20. package/src/dist/Zip.ts.cache +2 -2
  21. package/src/dist/bits.ts.cache +2 -2
  22. package/src/dist/buffers.ts.cache +2 -2
  23. package/src/dist/caching.ts.cache +2 -2
  24. package/src/dist/callHTTPHandler.ts.cache +2 -2
  25. package/src/dist/callManager.ts.cache +2 -2
  26. package/src/dist/certStore.ts.cache +2 -2
  27. package/src/dist/fixLargeNetworkCalls.ts.cache +2 -2
  28. package/src/dist/forwardPort.ts.cache +2 -2
  29. package/src/dist/https.ts.cache +2 -2
  30. package/src/dist/nodeCache.ts.cache +2 -2
  31. package/src/dist/nodeProxy.ts.cache +2 -2
  32. package/src/dist/protocolNegotiation.ts.cache +2 -2
  33. package/src/dist/tlsParsing.ts.cache +2 -2
  34. package/src/dist/webSocketServer.ts.cache +2 -2
  35. package/src/dist/websocketFactory.ts.cache +2 -2
  36. package/src/lz4/dist/LZ4.ts.cache +2 -2
  37. package/time/dist/trueTimeShim.ts.cache +86 -35
  38. package/time/trueTimeShim.ts +78 -27
@@ -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
- let timeOffsetDb: import("lmdb").RootDatabase<TimeOffsetData, string> | undefined = undefined;
287
- async function getTimeOffsetDb() {
288
- if (!USE_LMDB_PROCESS_SYNC) return undefined;
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
- const dbPath = path.join(os.tmpdir(), "socket-function-time-offset-2");
298
- timeOffsetDb = lmdb.open<TimeOffsetData, string>({
299
- path: dbPath,
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 getLmdbStore(db: import("lmdb").RootDatabase<TimeOffsetData, string>): TimeOffsetStore {
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
- try {
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
- } catch (e) {
319
- console.error("Error reading from LMDB database:", e);
320
- return undefined;
321
- }
378
+ }, undefined);
322
379
  },
323
380
  async putIfVersion(data, expectedVersion) {
324
- try {
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
- } catch (e) {
332
- console.error("Error writing to LMDB database:", e);
333
- return false;
334
- }
388
+ }, false);
335
389
  },
336
390
  async putInitial(data) {
337
- try {
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
- } catch (e) {
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(db);
461
+ timeOffsetStore = getLmdbStore();
411
462
  }
412
463
  } else {
413
464
  timeOffsetStore = getLocalStorageStore();