sliftutils 1.7.109 → 1.7.110
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/index.d.ts +7 -4
- package/package.json +1 -1
- package/storage/ArchivesDisk.ts +8 -0
- package/storage/archiveHelpers.d.ts +1 -1
- package/storage/archiveHelpers.ts +47 -26
- package/storage/dist/ArchivesDisk.ts.cache +11 -3
- package/storage/dist/archiveHelpers.ts.cache +52 -30
- package/storage/remoteStorage/blobStore.ts +17 -9
- package/storage/remoteStorage/createArchives.ts +5 -0
- package/storage/remoteStorage/dist/blobStore.ts.cache +21 -12
- package/storage/remoteStorage/dist/createArchives.ts.cache +8 -3
- package/storage/remoteStorage/dist/storageLogs.ts.cache +11 -5
- package/storage/remoteStorage/dist/storeSync.ts.cache +183 -161
- package/storage/remoteStorage/storageLogs.d.ts +6 -3
- package/storage/remoteStorage/storageLogs.ts +8 -2
- package/storage/remoteStorage/storeSync.ts +30 -11
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { LogFileInfo } from "../StreamingLogs";
|
|
4
4
|
export declare const LOGS_FOLDER_NAME = "logs";
|
|
5
|
-
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Stream-only - one entry per write is exactly what the console does NOT need. */
|
|
5
|
+
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Logged DELIBERATELY at two layers: the controller (which knows the account/bucket and the caller) AND BlobStore itself (which knows the folder, and sees the writes that never pass through the controller) - the redundancy is the point, because a write that only one layer saw is exactly the kind of masked issue these logs exist to expose. Stream-only - one entry per write is exactly what the console does NOT need. */
|
|
6
6
|
export declare function logMutation(entry: {
|
|
7
7
|
op: string;
|
|
8
|
-
account
|
|
9
|
-
bucketName
|
|
8
|
+
account?: string;
|
|
9
|
+
bucketName?: string;
|
|
10
10
|
store?: string;
|
|
11
|
+
folder?: string;
|
|
11
12
|
path: string;
|
|
12
13
|
toPath?: string;
|
|
13
14
|
size?: number;
|
|
@@ -24,6 +25,8 @@ export declare function logSyncEvent(entry: {
|
|
|
24
25
|
}): void;
|
|
25
26
|
/** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */
|
|
26
27
|
export declare function logStorageError(message: string): void;
|
|
28
|
+
/** logStorageError at warn level: guarantees the console.warn hook is installed before warning, so warnings from before the first logged mutation still reach the stream. */
|
|
29
|
+
export declare function logStorageWarn(message: string): void;
|
|
27
30
|
/** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */
|
|
28
31
|
export declare function listStorageLogFiles(): Promise<LogFileInfo[]>;
|
|
29
32
|
/** One log file's bytes, always LZ4-compressed - see StreamingLogs.readFileCompressed (decode with decodeLogFile). */
|
|
@@ -66,8 +66,8 @@ function write(kind: string, entry: { [key: string]: unknown }, alsoConsole: boo
|
|
|
66
66
|
logs.log({ kind, time: Date.now(), ...baseFields(), ...entry });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Stream-only - one entry per write is exactly what the console does NOT need. */
|
|
70
|
-
export function logMutation(entry: { op: string; account
|
|
69
|
+
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Logged DELIBERATELY at two layers: the controller (which knows the account/bucket and the caller) AND BlobStore itself (which knows the folder, and sees the writes that never pass through the controller) - the redundancy is the point, because a write that only one layer saw is exactly the kind of masked issue these logs exist to expose. Stream-only - one entry per write is exactly what the console does NOT need. */
|
|
70
|
+
export function logMutation(entry: { op: string; account?: string; bucketName?: string; store?: string; folder?: string; path: string; toPath?: string; size?: number; writeTime?: number; callerId?: string; internal?: boolean }): void {
|
|
71
71
|
write("mutation", entry, false);
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -82,6 +82,12 @@ export function logStorageError(message: string): void {
|
|
|
82
82
|
console.error(message);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** logStorageError at warn level: guarantees the console.warn hook is installed before warning, so warnings from before the first logged mutation still reach the stream. */
|
|
86
|
+
export function logStorageWarn(message: string): void {
|
|
87
|
+
getLogs();
|
|
88
|
+
console.warn(message);
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
// Everything console.errored or console.warned also lands in the log stream (with a reentrancy guard: a failure INSIDE logging must not log itself forever)
|
|
86
92
|
let writingHooked = false;
|
|
87
93
|
const hookErrorLogging = lazy(() => {
|
|
@@ -258,6 +258,14 @@ export class StoreSync {
|
|
|
258
258
|
}
|
|
259
259
|
let copied = await copyArchiveFile({ from: source, to: this.store.sources[0].source, path: file.path, forceSetImmutable: true, noChecks: true, internal: true });
|
|
260
260
|
if (!copied) {
|
|
261
|
+
// Undefined is two cases (see copyArchiveFile): our own disk already holding something newer is the boundary scan working as intended (our writes since the boundary outrank the neighbor's), only a genuinely unreadable file is worth a warning
|
|
262
|
+
let local = await this.store.sources[0].source.getInfo(file.path);
|
|
263
|
+
if (local && local.writeTime > file.createTime) {
|
|
264
|
+
logSyncEvent({ event: "boundaryScanLocalNewer", store: this.store.folder, source: source.getDebugName(), path: file.path, theirWriteTime: new Date(file.createTime).toISOString(), theirSize: file.size, localWriteTime: new Date(local.writeTime).toISOString(), localSize: local.size });
|
|
265
|
+
this.store.setIndexEntry(file.path, { writeTime: local.writeTime, size: local.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) });
|
|
266
|
+
tally.unchanged++;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
261
269
|
console.warn(`Boundary scan could not copy ${file.path} from ${source.getDebugName()} (store ${this.store.folder}): its change feed listed it (${file.size} bytes, writeTime ${new Date(file.createTime).toISOString()}) but the read found nothing`);
|
|
262
270
|
continue;
|
|
263
271
|
}
|
|
@@ -461,7 +469,17 @@ export class StoreSync {
|
|
|
461
469
|
let holder = await this.store.getEntryHolder(entry);
|
|
462
470
|
if (!holder) continue;
|
|
463
471
|
let copied = await copyArchiveFile({ from: holder, to: source, path: key, forceSetImmutable: true, noChecks: true, internal: true });
|
|
464
|
-
if (!copied)
|
|
472
|
+
if (!copied) {
|
|
473
|
+
// Undefined is two cases (see copyArchiveFile). The source having a NEWER file than our index (the listing we pushed from was stale) is adopted exactly the way the pull direction adopts a listing entry - and pushing our stale copy stops. Otherwise the HOLDER could not produce the file, which the next round's pull re-resolves; either way, silence here was the bug.
|
|
474
|
+
let theirs = await source.getInfo(key);
|
|
475
|
+
if (theirs && theirs.writeTime > writeTime) {
|
|
476
|
+
this.store.setIndexEntry(key, { writeTime: theirs.writeTime, size: theirs.size, sourcesListIndex: targetSourcesListIndex });
|
|
477
|
+
logSyncEvent({ event: "pushFoundNewerOnSource", store: this.store.folder, source: source.getDebugName(), path: key, ourWriteTime: new Date(writeTime).toISOString(), theirWriteTime: new Date(theirs.writeTime).toISOString(), ourSize: entry.size, theirSize: theirs.size });
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
logSyncEvent({ event: "pushCopyUnavailable", store: this.store.folder, source: source.getDebugName(), path: key, holder: holder.getDebugName(), expectedSize: entry.size, expectedWriteTime: new Date(writeTime).toISOString(), sourceHas: theirs && `${theirs.size} bytes at ${new Date(theirs.writeTime).toISOString()}` || "nothing" });
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
465
483
|
this.store.noteSyncTransfer("sync set", key, copied.size);
|
|
466
484
|
pushed++;
|
|
467
485
|
consecutiveFailures = 0;
|
|
@@ -550,11 +568,11 @@ export class StoreSync {
|
|
|
550
568
|
totalBytes,
|
|
551
569
|
};
|
|
552
570
|
this.activities.add(activity);
|
|
553
|
-
logSyncEvent({ event: "
|
|
571
|
+
logSyncEvent({ event: "deltaSyncStart", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, files: pending.length, bytes: totalBytes });
|
|
554
572
|
let progressLogged = false;
|
|
555
573
|
let logProgress = () => {
|
|
556
574
|
progressLogged = true;
|
|
557
|
-
console.log(magenta(`
|
|
575
|
+
console.log(magenta(`Delta sync from ${source.getDebugName()} (store ${this.store.folder}): ${activity.doneFiles}/${pending.length} files (${((activity.doneFiles || 0) / pending.length * 100).toFixed(1)}%), ${formatNumber(activity.doneBytes || 0)}B/${formatNumber(totalBytes)}B (${(totalBytes && (activity.doneBytes || 0) / totalBytes * 100 || 100).toFixed(1)}%)`));
|
|
558
576
|
};
|
|
559
577
|
let progressTimer = setInterval(logProgress, SYNC_PROGRESS_LOG_INTERVAL);
|
|
560
578
|
(progressTimer as { unref?: () => void }).unref?.();
|
|
@@ -569,7 +587,7 @@ export class StoreSync {
|
|
|
569
587
|
let remainingMs = remainingBytes / bytesPerSecond * 1000;
|
|
570
588
|
etaText = `${formatTime(remainingMs)} remaining, completing around ${new Date(Date.now() + remainingMs).toISOString()}`;
|
|
571
589
|
}
|
|
572
|
-
console.warn(`
|
|
590
|
+
console.warn(`Delta sync from ${source.getDebugName()} (store ${this.store.folder}) has been running for ${formatTime(elapsed)}: ${doneFiles}/${pending.length} files (${(doneFiles / pending.length * 100).toFixed(1)}%), ${formatNumber(doneBytes)}B/${formatNumber(totalBytes)}B (${(totalBytes && doneBytes / totalBytes * 100 || 100).toFixed(1)}%), ${formatNumber(bytesPerSecond)}B/s. Estimated ${etaText}.`);
|
|
573
591
|
}, FULL_SYNC_SLOW_ERROR_INTERVAL);
|
|
574
592
|
(slowErrorTimer as { unref?: () => void }).unref?.();
|
|
575
593
|
let failed = 0;
|
|
@@ -582,8 +600,9 @@ export class StoreSync {
|
|
|
582
600
|
let base = { event, store: this.store.folder, source: source.getDebugName(), path: key, expectedSize: entry.size, expectedWriteTime: entry.writeTime, ...extra };
|
|
583
601
|
let local = await this.store.sources[0].source.getInfo(key);
|
|
584
602
|
if (local) {
|
|
585
|
-
|
|
586
|
-
|
|
603
|
+
// The local copy can be NEWER than the entry (which is exactly why copyArchiveFile refuses to overwrite it - see its destination check), so the repoint keeps the newer of the two times rather than rolling the index back
|
|
604
|
+
this.store.setIndexEntry(key, { writeTime: Math.max(entry.writeTime, local.writeTime), size: local.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) });
|
|
605
|
+
logSyncEvent({ ...base, resolution: "repointed to our local copy", localSize: local.size, localWriteTime: new Date(local.writeTime).toISOString() });
|
|
587
606
|
return;
|
|
588
607
|
}
|
|
589
608
|
if (this.store.currentWriteTime(key) <= entry.writeTime) {
|
|
@@ -610,11 +629,11 @@ export class StoreSync {
|
|
|
610
629
|
this.store.noteSyncTransfer("sync get", key, copied.size);
|
|
611
630
|
// The copy carries the source's write time, and the index commits it under the normal ordering rule (>= the current time wins) - it refusing means a NEWER write landed while we copied, which must be said, not swallowed
|
|
612
631
|
if (!this.store.setIndexEntry(key, { writeTime: copied.writeTime, size: copied.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) })) {
|
|
613
|
-
logSyncEvent({ event: "
|
|
632
|
+
logSyncEvent({ event: "deltaSyncCommitSuperseded", store: this.store.folder, source: source.getDebugName(), path: key, copiedWriteTime: copied.writeTime, currentWriteTime: this.store.currentWriteTime(key) });
|
|
614
633
|
}
|
|
615
634
|
} else {
|
|
616
635
|
missingOnSource++;
|
|
617
|
-
await resolveUnavailable("
|
|
636
|
+
await resolveUnavailable("deltaSyncMissingOnSource", key, entry, {});
|
|
618
637
|
}
|
|
619
638
|
consecutiveFailures = 0;
|
|
620
639
|
} catch (e) {
|
|
@@ -622,9 +641,9 @@ export class StoreSync {
|
|
|
622
641
|
failed++;
|
|
623
642
|
consecutiveFailures++;
|
|
624
643
|
try {
|
|
625
|
-
await resolveUnavailable("
|
|
644
|
+
await resolveUnavailable("deltaSyncCopyFailed", key, entry, { error: String((e as Error).stack ?? e).slice(0, 2000) });
|
|
626
645
|
} catch (resolveError) {
|
|
627
|
-
logSyncEvent({ event: "
|
|
646
|
+
logSyncEvent({ event: "deltaSyncCopyFailed", store: this.store.folder, source: source.getDebugName(), path: key, error: String((e as Error).stack ?? e).slice(0, 2000), resolveError: String((resolveError as Error).stack ?? resolveError).slice(0, 2000) });
|
|
628
647
|
}
|
|
629
648
|
if (consecutiveFailures >= SYNC_MAX_CONSECUTIVE_FAILURES) {
|
|
630
649
|
aborted = true;
|
|
@@ -650,7 +669,7 @@ export class StoreSync {
|
|
|
650
669
|
if (progressLogged) {
|
|
651
670
|
logProgress();
|
|
652
671
|
}
|
|
653
|
-
logSyncEvent({ event: "
|
|
672
|
+
logSyncEvent({ event: "deltaSyncFinish", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, durationMs: Date.now() - activity.startTime, totalFiles: pending.length, totalBytes, processedFiles: activity.doneFiles, copiedFiles, copiedBytes, missingOnSource, failed, aborted });
|
|
654
673
|
}
|
|
655
674
|
}));
|
|
656
675
|
|