sliftutils 1.7.85 → 1.7.87
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 +11 -11
- package/misc/dist/types.ts.cache +31 -0
- package/package.json +1 -1
- package/storage/IArchives.d.ts +6 -5
- package/storage/IArchives.ts +11 -7
- package/storage/dist/IArchives.ts.cache +8 -3
- package/storage/remoteStorage/blobStore.d.ts +3 -2
- package/storage/remoteStorage/blobStore.ts +32 -22
- package/storage/remoteStorage/dist/blobStore.ts.cache +31 -21
- package/storage/remoteStorage/dist/intermediateSources.ts.cache +7 -12
- package/storage/remoteStorage/dist/storageServerState.ts.cache +14 -9
- package/storage/remoteStorage/dist/storePlan.ts.cache +14 -5
- package/storage/remoteStorage/intermediateSources.d.ts +0 -2
- package/storage/remoteStorage/intermediateSources.ts +4 -9
- package/storage/remoteStorage/storageServerState.d.ts +1 -1
- package/storage/remoteStorage/storageServerState.ts +14 -8
- package/storage/remoteStorage/storePlan.d.ts +1 -1
- package/storage/remoteStorage/storePlan.ts +13 -4
|
@@ -5,7 +5,7 @@ import { timeInMinute, sort, promiseObj } from "socket-function/src/misc";
|
|
|
5
5
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
6
6
|
import {
|
|
7
7
|
IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, ChangesAfterConfig, FindConfig, HostedConfig, SourceConfig, assertValidLastModified,
|
|
8
|
-
windowAcceptsWrites, SyncActivity, FULL_ROUTE, STORAGE_WRONG_VALID_WINDOW, STORAGE_WRONG_ROUTE, copyArchiveFile,
|
|
8
|
+
windowAcceptsWrites, windowsAcceptWrites, SyncActivity, FULL_ROUTE, STORAGE_WRONG_VALID_WINDOW, STORAGE_WRONG_ROUTE, copyArchiveFile,
|
|
9
9
|
} from "../IArchives";
|
|
10
10
|
import { ArchivesDisk, applyFindInfoShape } from "../ArchivesDisk";
|
|
11
11
|
import { ArchivesBackblaze } from "../backblaze";
|
|
@@ -166,11 +166,11 @@ export type BlobSourceSpec = {
|
|
|
166
166
|
identity: string;
|
|
167
167
|
// See ArchivesSource.url
|
|
168
168
|
url: string;
|
|
169
|
-
|
|
169
|
+
validWindows: [number, number][];
|
|
170
170
|
route?: [number, number];
|
|
171
171
|
noFullSync?: boolean;
|
|
172
172
|
// See ArchivesSource.intermediate
|
|
173
|
-
intermediate?:
|
|
173
|
+
intermediate?: string;
|
|
174
174
|
// See ArchivesSource.sourceConfig (absent for the base disk source)
|
|
175
175
|
sourceConfig?: SourceConfig;
|
|
176
176
|
// Only called for sources that don't match an existing live slot
|
|
@@ -470,7 +470,7 @@ export class BlobStore implements IBucketStore {
|
|
|
470
470
|
indexSize: this.mem.size,
|
|
471
471
|
sources: this.sources.map((x, i) => ({
|
|
472
472
|
debugName: x.source.getDebugName(),
|
|
473
|
-
|
|
473
|
+
validWindows: x.validWindows,
|
|
474
474
|
route: x.route,
|
|
475
475
|
noFullSync: x.noFullSync,
|
|
476
476
|
supportsChangesAfter: this.sourceStates[i].supportsChangesAfter,
|
|
@@ -530,14 +530,14 @@ export class BlobStore implements IBucketStore {
|
|
|
530
530
|
if (entries) {
|
|
531
531
|
this.entries = entries;
|
|
532
532
|
}
|
|
533
|
-
let
|
|
534
|
-
let old = this.sources[i].
|
|
535
|
-
if (old
|
|
536
|
-
console.log(`Valid
|
|
537
|
-
this.sources[i].
|
|
533
|
+
let setWindows = (i: number, windows: [number, number][]) => {
|
|
534
|
+
let old = this.sources[i].validWindows;
|
|
535
|
+
if (JSON.stringify(old) === JSON.stringify(windows)) return;
|
|
536
|
+
console.log(`Valid windows changed for ${this.sources[i].source.getDebugName()} (store ${this.folder}): ${JSON.stringify(old)} -> ${JSON.stringify(windows)}`);
|
|
537
|
+
this.sources[i].validWindows = windows;
|
|
538
538
|
};
|
|
539
|
-
|
|
540
|
-
// Live slots pair with specs by identity,
|
|
539
|
+
setWindows(0, specs[0].validWindows);
|
|
540
|
+
// Live slots pair with specs by identity. Each endpoint is now ONE spec carrying all its windows, so this is a 1:1 pairing; any leftover duplicate slots from before (an endpoint that used to be split into several slots) go unmatched below and are collapsed away.
|
|
541
541
|
let liveByIdentity = new Map<string, number[]>();
|
|
542
542
|
let originalLength = this.sources.length;
|
|
543
543
|
for (let i = 1; i < originalLength; i++) {
|
|
@@ -556,7 +556,7 @@ export class BlobStore implements IBucketStore {
|
|
|
556
556
|
let slot = liveByIdentity.get(spec.identity)?.shift();
|
|
557
557
|
if (slot !== undefined) {
|
|
558
558
|
matched.add(slot);
|
|
559
|
-
|
|
559
|
+
setWindows(slot, spec.validWindows);
|
|
560
560
|
let existing = this.sources[slot];
|
|
561
561
|
if (JSON.stringify(existing.route) !== JSON.stringify(spec.route)) {
|
|
562
562
|
console.log(`Route changed for ${existing.source.getDebugName()} (store ${this.folder}): ${JSON.stringify(existing.route)} -> ${JSON.stringify(spec.route)}`);
|
|
@@ -567,7 +567,7 @@ export class BlobStore implements IBucketStore {
|
|
|
567
567
|
continue;
|
|
568
568
|
}
|
|
569
569
|
let source = spec.create();
|
|
570
|
-
this.sources.push({ source, url: spec.url,
|
|
570
|
+
this.sources.push({ source, url: spec.url, validWindows: spec.validWindows, route: spec.route, noFullSync: spec.noFullSync, intermediate: spec.intermediate, sourceConfig: spec.sourceConfig, identity: spec.identity });
|
|
571
571
|
this.sourceStates.push(newSourceState());
|
|
572
572
|
this.sourceFileCounts.push(0);
|
|
573
573
|
this.sourceByteCounts.push(0);
|
|
@@ -580,7 +580,7 @@ export class BlobStore implements IBucketStore {
|
|
|
580
580
|
if (!this.isLive(i) || matched.has(i)) continue;
|
|
581
581
|
this.removeSource(i);
|
|
582
582
|
}
|
|
583
|
-
let deadline = this.
|
|
583
|
+
let deadline = this.baseWriteWindowEnd() - WINDOW_END_FLUSH_MARGIN;
|
|
584
584
|
let recapped = 0;
|
|
585
585
|
for (let entry of this.overlay.values()) {
|
|
586
586
|
if (entry.fastFlushAt <= deadline && entry.slowFlushAt <= deadline) continue;
|
|
@@ -921,10 +921,10 @@ export class BlobStore implements IBucketStore {
|
|
|
921
921
|
}
|
|
922
922
|
}
|
|
923
923
|
|
|
924
|
-
// An intermediate is a deploy switchover's temporary alternate port: once its window is past, the port is gone for good, so scanning it (or retrying a failed scan) can never succeed - it would just log errors forever
|
|
924
|
+
// An intermediate is a deploy switchover's temporary alternate port: once its window is past, the port is gone for good, so scanning it (or retrying a failed scan) can never succeed - it would just log errors forever. (Dead only once EVERY window has ended - an intermediate normally has just one.)
|
|
925
925
|
private isDeadIntermediate(sourceIndex: number): boolean {
|
|
926
|
-
let { intermediate,
|
|
927
|
-
return !!intermediate &&
|
|
926
|
+
let { intermediate, validWindows } = this.sources[sourceIndex];
|
|
927
|
+
return !!intermediate && validWindows.every(w => w[1] <= Date.now());
|
|
928
928
|
}
|
|
929
929
|
|
|
930
930
|
// Full metadata scan (size, writeTime, path) of one source, applied to the index. Returns the source's listing (path -> write time), which reconcileSource uses for the push direction.
|
|
@@ -987,9 +987,9 @@ export class BlobStore implements IBucketStore {
|
|
|
987
987
|
|
|
988
988
|
// The push direction of synchronization: everything we know that the source is missing (or holds an older copy of) is written to it — including deletions, as tombstone writes. This is what heals a source whose background writes failed (e.g. it was down): the next scan sees what's missing and re-sends it. A failing file is skipped, not fatal (immutable targets are handled by forceSetImmutable, and one unreadable value must not stop the rest of the pass) - only a run of consecutive failures (the source itself is down) aborts until the next scan cycle.
|
|
989
989
|
private async reconcileSource(sourceIndex: number, listing: Map<string, number>): Promise<void> {
|
|
990
|
-
let { source,
|
|
990
|
+
let { source, validWindows, route } = this.sources[sourceIndex];
|
|
991
991
|
let state = this.sourceStates[sourceIndex];
|
|
992
|
-
let acceptsWrites =
|
|
992
|
+
let acceptsWrites = windowsAcceptWrites(validWindows);
|
|
993
993
|
let targetSourcesListIndex = this.sourcesListIndexOfSlot(sourceIndex);
|
|
994
994
|
let pushed = 0;
|
|
995
995
|
let failed = 0;
|
|
@@ -1253,7 +1253,7 @@ export class BlobStore implements IBucketStore {
|
|
|
1253
1253
|
writeDelay = DEFAULT_FAST_WRITE_DELAY;
|
|
1254
1254
|
}
|
|
1255
1255
|
// The delay never extends past our own valid window's end (minus the margin, so the writes are on disk before the next window's source takes over - a deploy switchover is just this too, since its remap ends our window). Past that point fast writes write through immediately.
|
|
1256
|
-
let deadline = this.
|
|
1256
|
+
let deadline = this.baseWriteWindowEnd() - WINDOW_END_FLUSH_MARGIN;
|
|
1257
1257
|
if (writeDelay > 0 && Date.now() < deadline) {
|
|
1258
1258
|
// Own disk + our own storage servers (type-"remote") flush within MAX_REMOTE_FAST_BUFFER; the non-"remote" sources (backblaze) wait the full writeDelay
|
|
1259
1259
|
let fastFlushAt = Math.min(Date.now() + Math.min(writeDelay, MAX_REMOTE_FAST_BUFFER), deadline);
|
|
@@ -1266,11 +1266,21 @@ export class BlobStore implements IBucketStore {
|
|
|
1266
1266
|
await this.writeToSources(key, data, writeTime);
|
|
1267
1267
|
}
|
|
1268
1268
|
|
|
1269
|
+
// The end of our own (base disk) write window that CONTAINS now - the deadline fast writes must flush before, so the next window's source has the data on handoff. The LATEST end among covering windows (overlapping windows hand off at the last one). No window contains now (an inert store, or a moment between our windows) -> 0, i.e. flush through immediately.
|
|
1270
|
+
private baseWriteWindowEnd(): number {
|
|
1271
|
+
let now = Date.now();
|
|
1272
|
+
let end = 0;
|
|
1273
|
+
for (let w of this.sources[0].validWindows) {
|
|
1274
|
+
if (w[0] <= now && now < w[1]) end = Math.max(end, w[1]);
|
|
1275
|
+
}
|
|
1276
|
+
return end;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1269
1279
|
private getWritableSources(config?: { ignoreWindow?: boolean }): number[] {
|
|
1270
1280
|
let writable: number[] = [];
|
|
1271
1281
|
for (let i = 0; i < this.sources.length; i++) {
|
|
1272
1282
|
if (!this.isLive(i)) continue;
|
|
1273
|
-
if (!config?.ignoreWindow && !
|
|
1283
|
+
if (!config?.ignoreWindow && !windowsAcceptWrites(this.sources[i].validWindows)) continue;
|
|
1274
1284
|
writable.push(i);
|
|
1275
1285
|
}
|
|
1276
1286
|
return writable;
|
|
@@ -1406,7 +1416,7 @@ export class BlobStore implements IBucketStore {
|
|
|
1406
1416
|
for (let i = 0; i < this.sources.length; i++) {
|
|
1407
1417
|
if (!this.isLive(i)) continue;
|
|
1408
1418
|
let sourceEntry = this.sources[i];
|
|
1409
|
-
if (!
|
|
1419
|
+
if (!windowsAcceptWrites(sourceEntry.validWindows)) continue;
|
|
1410
1420
|
let source = sourceEntry.source;
|
|
1411
1421
|
if (!(source instanceof ArchivesBackblaze)) continue;
|
|
1412
1422
|
void source.del(key).catch((e: Error) => {
|