sliftutils 1.7.117 → 1.7.119
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 +4 -1
- package/package.json +1 -1
- package/storage/remoteStorage/blobStore.ts +5 -6
- package/storage/remoteStorage/chainStartup.ts +10 -1
- package/storage/remoteStorage/createArchives.ts +5 -1
- package/storage/remoteStorage/dist/blobStore.ts.cache +7 -10
- package/storage/remoteStorage/dist/chainStartup.ts.cache +12 -3
- package/storage/remoteStorage/dist/createArchives.ts.cache +7 -3
- package/storage/remoteStorage/dist/intermediateManagement.ts.cache +5 -8
- package/storage/remoteStorage/dist/remoteConfig.ts.cache +7 -4
- package/storage/remoteStorage/dist/sourceWrapper.ts.cache +17 -2
- package/storage/remoteStorage/dist/storageServerState.ts.cache +5 -2
- package/storage/remoteStorage/dist/storeSync.ts.cache +6 -3
- package/storage/remoteStorage/intermediateManagement.ts +3 -5
- package/storage/remoteStorage/remoteConfig.d.ts +2 -1
- package/storage/remoteStorage/remoteConfig.ts +6 -3
- package/storage/remoteStorage/sourceWrapper.d.ts +2 -0
- package/storage/remoteStorage/sourceWrapper.ts +13 -0
- package/storage/remoteStorage/storageServerState.ts +3 -0
- package/storage/remoteStorage/storeSync.ts +3 -1
package/index.d.ts
CHANGED
|
@@ -4217,7 +4217,8 @@ declare module "sliftutils/storage/remoteStorage/remoteConfig" {
|
|
|
4217
4217
|
export declare function sourceIdentity(sourceConfig: SourceConfig | undefined): string;
|
|
4218
4218
|
/** What an index entry records as the holder of its bytes (see ArchivesSource.url), so it must name the endpoint FOREVER. An intermediate is a switchover's temporary alternate port onto another source, and that port is gone for good once its window passes - so it is recorded as the source it was split out of, which holds the same bucket and outlives it. */
|
|
4219
4219
|
export declare function sourcePersistentUrl(sourceConfig: SourceConfig | undefined, folder: string): string;
|
|
4220
|
-
|
|
4220
|
+
/** Reads a stored routing config. NEVER throws: this runs on every READ of a stored config, and a torn/corrupt file must not brick the paths that would fix it - above all writeRoutingConfig, where throwing while reading the OLD config blocks the write of the NEW one forever. Unreadable data is logged and read as undefined - the same as the file not existing. Judging a config on its way IN is the writer's job (see assertValidRemoteConfig at the write entry points), where rejecting bad data with a throw is correct. */
|
|
4221
|
+
export declare function parseRoutingData(data: Buffer): RemoteConfig | undefined;
|
|
4221
4222
|
export declare function serializeRemoteConfig(config: RemoteConfig): Buffer;
|
|
4222
4223
|
|
|
4223
4224
|
}
|
|
@@ -4299,6 +4300,8 @@ declare module "sliftutils/storage/remoteStorage/sourceWrapper" {
|
|
|
4299
4300
|
seedLatency(ms: number): void;
|
|
4300
4301
|
/** Median of the recent pings (API or URL-form, whichever this source measures), plus DISCONNECTED_LATENCY_PENALTY while the source is disconnected - so a down source still sorts and can still be picked, just after every connected one. Sources with no measurements yet sort last (Infinity), except our own in-process server, which is the best possible target (0). */
|
|
4301
4302
|
getLatency(): number;
|
|
4303
|
+
/** writeBlocked from missing backblaze credentials is re-checked on every write attempt: the secret load can fail TRANSIENTLY (early startup, a file-read hiccup), and one failed load must never permanently stop this process from writing to the bucket. The other writeBlocked reasons (read-only mode, browsers) are static properties of the process, so there is nothing to re-check. */
|
|
4304
|
+
recheckWriteBlocked(): Promise<void>;
|
|
4302
4305
|
/** Writes always go through the API, so a permission error throws to the caller on every write (and access granted in the meantime is picked up automatically). */
|
|
4303
4306
|
write<T>(run: (archives: IArchives) => Promise<T>): Promise<T>;
|
|
4304
4307
|
dispose(): void;
|
package/package.json
CHANGED
|
@@ -213,12 +213,8 @@ export class BlobStore {
|
|
|
213
213
|
private async readRoutingConfig(): Promise<RemoteConfig | undefined> {
|
|
214
214
|
let data = await this.ownDisk.get(ROUTING_FILE);
|
|
215
215
|
if (!data || !data.length) return undefined;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} catch (e) {
|
|
219
|
-
logStorageError(`Ignoring the routing config in store ${this.folder}: it could not be parsed: ${(e as Error).stack ?? e}`);
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
216
|
+
// Unparseable reads as "no config" (parseRoutingData logs it) - a store must never be unable to start because its own routing file is torn
|
|
217
|
+
return parseRoutingData(data);
|
|
222
218
|
}
|
|
223
219
|
|
|
224
220
|
/** The version of the routing config this store is running, so a copy found on a peer is only taken when it is genuinely newer. -1 means it has none. */
|
|
@@ -975,6 +971,9 @@ export class BlobStore {
|
|
|
975
971
|
*/
|
|
976
972
|
private assertRoutingConfigWritable(data: Buffer): void {
|
|
977
973
|
let routing = parseRoutingData(data);
|
|
974
|
+
if (!routing) {
|
|
975
|
+
throw new Error(`Routing config write rejected - not a parseable config. Refusing to write ${ROUTING_FILE} to store ${this.folder}: the data is not a valid { version?, sources: [...] } JSON config (${data.length} bytes)`);
|
|
976
|
+
}
|
|
978
977
|
assertValidRemoteConfig(routing);
|
|
979
978
|
let incoming = getConfigVersion(routing);
|
|
980
979
|
let current = this.routingVersion();
|
|
@@ -394,14 +394,23 @@ export async function writeRoutingToAllStores(config: { configured: RemoteConfig
|
|
|
394
394
|
let routingData = serializeRemoteConfig(configured);
|
|
395
395
|
let routingWriteTime = Math.round(Date.now());
|
|
396
396
|
let targets: SourceWrapper[] = [];
|
|
397
|
+
let skipped: SourceWrapper[] = [];
|
|
397
398
|
let seen = new Set<string>();
|
|
398
399
|
for (let source of sources) {
|
|
399
400
|
let key = `${source.config.url}|${source.config.name}`;
|
|
400
401
|
if (seen.has(key)) continue;
|
|
401
402
|
seen.add(key);
|
|
403
|
+
// writeBlocked is a capability of this process (read-only mode, a browser, no backblaze credentials) - not connectivity, so skipping is not a pre-check race. Counting these as failures put the retry loop on its 5-minute cadence FOREVER over writes this process cannot make; the stores get the config from the writers that can (servers, node clients, peers pulling it from each other). The recheck first: the backblaze-credentials block can be a TRANSIENT secret-load failure at startup, and skipping on the stale verdict would otherwise make it permanent (see SourceWrapper.recheckWriteBlocked).
|
|
404
|
+
if (source.writeBlocked) {
|
|
405
|
+
await source.recheckWriteBlocked();
|
|
406
|
+
}
|
|
407
|
+
if (source.writeBlocked || !source.api) {
|
|
408
|
+
skipped.push(source);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
402
411
|
targets.push(source);
|
|
403
412
|
}
|
|
404
|
-
console.log(`Writing routing config version ${getConfigVersion(configured)} for ${debugName} to all ${targets.length} stores (write time ${formatDateTimeDetailed(routingWriteTime)}): ${targets.map(x => `${x.config.url} (store ${JSON.stringify(x.config.name)})`).join(", ")}`);
|
|
413
|
+
console.log(`Writing routing config version ${getConfigVersion(configured)} for ${debugName} to all ${targets.length} writable stores (write time ${formatDateTimeDetailed(routingWriteTime)}): ${targets.map(x => `${x.config.url} (store ${JSON.stringify(x.config.name)})`).join(", ") || "none"}${skipped.length && `. Skipping ${skipped.length} not writable from this process: ${skipped.map(x => `${x.config.url} (store ${JSON.stringify(x.config.name)}, ${x.writeBlocked || "no API access"})`).join(", ")}` || ""}`);
|
|
405
414
|
let failures: string[] = [];
|
|
406
415
|
await Promise.all(targets.map(async source => {
|
|
407
416
|
try {
|
|
@@ -563,7 +563,11 @@ export class ArchivesChain implements IArchives {
|
|
|
563
563
|
|
|
564
564
|
private async setRoutingConfig(data: Buffer, config?: { lastModified?: number }): Promise<string> {
|
|
565
565
|
// Checked before the first node is written to, not just by each node as it arrives: every server would reject it anyway, and finding that out one node at a time is how a config write ends up half-applied
|
|
566
|
-
|
|
566
|
+
let parsedConfig = parseRoutingData(data);
|
|
567
|
+
if (!parsedConfig) {
|
|
568
|
+
throw new Error(`Routing config write rejected - not a parseable config. The data is not a valid { version?, sources: [...] } JSON config (${data.length} bytes, for ${this.getDebugName()})`);
|
|
569
|
+
}
|
|
570
|
+
assertValidRemoteConfig(parsedConfig);
|
|
567
571
|
let state = await this.state.getState();
|
|
568
572
|
let writeTime = Math.round(config?.lastModified || Date.now());
|
|
569
573
|
let written: string[] = [];
|