sliftutils 1.7.108 → 1.7.109
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
CHANGED
|
@@ -4820,7 +4820,7 @@ declare module "sliftutils/storage/remoteStorage/storeSync" {
|
|
|
4820
4820
|
waitForRequiredScans(): Promise<void>;
|
|
4821
4821
|
/** Rescans our own disk's metadata into the index - used around valid window handoffs, where another process wrote files to the shared folder that our index hasn't seen. */
|
|
4822
4822
|
rescanBase(): Promise<void>;
|
|
4823
|
-
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. */
|
|
4823
|
+
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. Only one round per SOURCE runs at a time (cache keys the serializer by source index). */
|
|
4824
4824
|
private syncSource;
|
|
4825
4825
|
/** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */
|
|
4826
4826
|
boundaryScanRemote(source: IArchives, config: {
|
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@ export declare class StoreSync {
|
|
|
37
37
|
waitForRequiredScans(): Promise<void>;
|
|
38
38
|
/** Rescans our own disk's metadata into the index - used around valid window handoffs, where another process wrote files to the shared folder that our index hasn't seen. */
|
|
39
39
|
rescanBase(): Promise<void>;
|
|
40
|
-
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. */
|
|
40
|
+
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. Only one round per SOURCE runs at a time (cache keys the serializer by source index). */
|
|
41
41
|
private syncSource;
|
|
42
42
|
/** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */
|
|
43
43
|
boundaryScanRemote(source: IArchives, config: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { runInfinitePoll, delay, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
1
|
+
import { runInfinitePoll, delay, runInfinitePollCallAtStart, runInSerial } from "socket-function/src/batching";
|
|
2
|
+
import { cache } from "socket-function/src/caching";
|
|
2
3
|
import { timeInMinute, sort, promiseObj } from "socket-function/src/misc";
|
|
3
4
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
4
5
|
import {
|
|
@@ -219,11 +220,11 @@ export class StoreSync {
|
|
|
219
220
|
|
|
220
221
|
/** Rescans our own disk's metadata into the index - used around valid window handoffs, where another process wrote files to the shared folder that our index hasn't seen. */
|
|
221
222
|
public async rescanBase(): Promise<void> {
|
|
222
|
-
await this.syncSource(0);
|
|
223
|
+
await this.syncSource(0)();
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. */
|
|
226
|
-
private
|
|
226
|
+
/** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. Only one round per SOURCE runs at a time (cache keys the serializer by source index). */
|
|
227
|
+
private syncSource = cache((sourceIndex: number) => runInSerial(async (push?: "push"): Promise<void> => {
|
|
227
228
|
let listing = await this.pullSource(sourceIndex);
|
|
228
229
|
let state = this.states[sourceIndex];
|
|
229
230
|
state.scanComplete = true;
|
|
@@ -231,7 +232,7 @@ export class StoreSync {
|
|
|
231
232
|
if (push && !this.store.stopped.stop && !state.stopped.stop) {
|
|
232
233
|
await this.pushSource(sourceIndex, listing);
|
|
233
234
|
}
|
|
234
|
-
}
|
|
235
|
+
}));
|
|
235
236
|
|
|
236
237
|
/** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */
|
|
237
238
|
public async boundaryScanRemote(source: IArchives, config: { since: number; route?: [number, number] }): Promise<void> {
|
|
@@ -304,12 +305,15 @@ export class StoreSync {
|
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
if (this.store.stopped.stop || state.stopped.stop) return;
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
// Hourly for our own disk (no sourceConfig - a cheap local walk, and how a sibling process's writes into the shared folder are found) and for remote peers (index-backed, cheap listings); the slow interval is only for sources where a full listing is genuinely expensive (backblaze)
|
|
309
|
+
let pollInterval = (!sourceObj.sourceConfig || sourceObj.sourceConfig.type === "remote") && FULL_RESCAN_INTERVAL || FULL_RESCAN_NON_REMOTE_INTERVAL;
|
|
310
|
+
// Both loops below run one at a time: a change-poll tick landing mid full round would otherwise start a second copy pass over the same pending list, downloading everything twice
|
|
311
|
+
let serial = runInSerial(async (fnc: () => Promise<void>) => await fnc());
|
|
312
|
+
await runInfinitePollCallAtStart(pollInterval, () => serial(async () => {
|
|
309
313
|
while (!this.store.stopped.stop && !state.stopped.stop) {
|
|
310
314
|
try {
|
|
311
|
-
await this.syncSource(sourceIndex
|
|
312
|
-
if (!noFullSync()) await this.copySourceFiles(sourceIndex);
|
|
315
|
+
await this.syncSource(sourceIndex)("push");
|
|
316
|
+
if (!noFullSync()) await this.copySourceFiles(sourceIndex)();
|
|
313
317
|
} catch (e) {
|
|
314
318
|
logSyncEvent({ event: "scanFailed", store: this.store.folder, source: source.getDebugName(), retryInMs: SCAN_RETRY_DELAY, error: String((e as Error).stack ?? e).slice(0, 2000) });
|
|
315
319
|
await delay(SCAN_RETRY_DELAY);
|
|
@@ -319,16 +323,16 @@ export class StoreSync {
|
|
|
319
323
|
state.initialScan.resolve(undefined);
|
|
320
324
|
break;
|
|
321
325
|
}
|
|
322
|
-
}, state.stopped);
|
|
326
|
+
}), state.stopped);
|
|
323
327
|
if (state.supportsChangesAfter) {
|
|
324
|
-
runInfinitePoll(CHANGES_POLL_INTERVAL, async () => {
|
|
328
|
+
runInfinitePoll(CHANGES_POLL_INTERVAL, () => serial(async () => {
|
|
325
329
|
// A scan that has not succeeded yet is retried HERE, before anything else - polling changes and copying against a never-scanned source would run on arbitrarily stale information
|
|
326
330
|
if (!state.scanSucceeded) {
|
|
327
|
-
await this.syncSource(sourceIndex
|
|
331
|
+
await this.syncSource(sourceIndex)("push");
|
|
328
332
|
}
|
|
329
333
|
await this.pollChanges(sourceIndex);
|
|
330
|
-
if (!noFullSync()) await this.copySourceFiles(sourceIndex);
|
|
331
|
-
}, state.stopped);
|
|
334
|
+
if (!noFullSync()) await this.copySourceFiles(sourceIndex)();
|
|
335
|
+
}), state.stopped);
|
|
332
336
|
}
|
|
333
337
|
}
|
|
334
338
|
|
|
@@ -522,8 +526,8 @@ export class StoreSync {
|
|
|
522
526
|
state.changesAfterTime = pollStart - CHANGES_POLL_OVERLAP;
|
|
523
527
|
}
|
|
524
528
|
|
|
525
|
-
// Downloads the files a source currently holds onto our own base source (the local disk), preserving their modified times — so a newer local write always wins. Skipped for noFullSync sources (fronting a large database without copying it); reads still down-cache lazily.
|
|
526
|
-
private
|
|
529
|
+
// Downloads the files a source currently holds onto our own base source (the local disk), preserving their modified times — so a newer local write always wins. Skipped for noFullSync sources (fronting a large database without copying it); reads still down-cache lazily. Only one pass per SOURCE runs at a time (cache keys the serializer by source index) - two passes over the same pending list would download everything twice.
|
|
530
|
+
private copySourceFiles = cache((sourceIndex: number) => runInSerial(async (): Promise<void> => {
|
|
527
531
|
if (sourceIndex === 0) return;
|
|
528
532
|
let { source } = this.store.sources[sourceIndex];
|
|
529
533
|
let state = this.states[sourceIndex];
|
|
@@ -648,7 +652,7 @@ export class StoreSync {
|
|
|
648
652
|
}
|
|
649
653
|
logSyncEvent({ event: "fullSyncFinish", 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 });
|
|
650
654
|
}
|
|
651
|
-
}
|
|
655
|
+
}));
|
|
652
656
|
|
|
653
657
|
// ── maintenance ──
|
|
654
658
|
|