sliftutils 1.7.89 → 1.7.90
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 +6 -6
- package/package.json +1 -1
- package/storage/dist/ArchivesDisk.ts.cache +2 -2
- package/storage/dist/IArchives.ts.cache +2 -2
- package/storage/dist/archiveHelpers.ts.cache +87 -0
- package/storage/dist/backblaze.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +2 -2
- package/storage/remoteStorage/dist/blobStore.ts.cache +2 -2
- package/storage/remoteStorage/dist/createArchives.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageController.ts.cache +15 -33
- package/storage/remoteStorage/dist/storageServerState.ts.cache +23 -29
- package/storage/remoteStorage/dist/storeSync.ts.cache +6 -5
- package/storage/remoteStorage/storageController.ts +17 -34
- package/storage/remoteStorage/storageServerState.d.ts +6 -6
- package/storage/remoteStorage/storageServerState.ts +15 -20
|
@@ -31,8 +31,8 @@ export type BucketState = {
|
|
|
31
31
|
};
|
|
32
32
|
/** The loaded bucket, loading it (which instantiates its stores and starts their synchronization) if needed. A bucket that does not exist on this server throws - callers never see undefined buckets. */
|
|
33
33
|
export declare function requireBucket(account: string, bucketName: string): Promise<BucketState>;
|
|
34
|
-
/** The store serving a request: the one the client's selected entry NAMES.
|
|
35
|
-
export declare function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined):
|
|
34
|
+
/** The store serving a request: the one the client's selected entry NAMES. Account, name, and bucket ARE the folder, so this is a direct lookup, never a search of what exists - and a name this server has never seen is CREATED, never rejected, because asking for a name is the instruction to have that store (one name, one folder, one index; it configures itself once the routing config lands in it). Nothing else about the request is compared - not the window, not the route, not the flags - which is the whole point of naming it: a client a config version behind on some flag still reaches the right store. */
|
|
35
|
+
export declare function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined): BlobStore;
|
|
36
36
|
/** Internal (store-to-store) reads skip store selection entirely: the caller is another store whose index says this MACHINE holds the bytes - the persisted holder identity is just a URL, which cannot name a store. Whichever store's folder has the newest copy answers. */
|
|
37
37
|
export declare function readBucketInternal(account: string, bucketName: string, config: {
|
|
38
38
|
path: string;
|
|
@@ -48,7 +48,7 @@ export declare function readBucketInternal(account: string, bucketName: string,
|
|
|
48
48
|
} | undefined>;
|
|
49
49
|
export declare function getBucketConfig(bucket: BucketState): ArchivesConfig;
|
|
50
50
|
export declare function bucketSyncStatus(bucket: BucketState): Promise<ArchivesSyncStatus>;
|
|
51
|
-
export declare function
|
|
51
|
+
export declare function debugBucketIndexTotals(bucket: BucketState): Promise<{
|
|
52
52
|
fileCount: number;
|
|
53
53
|
byteCount: number;
|
|
54
54
|
sources: {
|
|
@@ -104,10 +104,10 @@ export type ActiveBucketInfo = {
|
|
|
104
104
|
config: ArchivesConfig;
|
|
105
105
|
};
|
|
106
106
|
/** The live in-memory state of ONE bucket, answered without touching the disk (no routing file read, no statfs, no stored write stats). Returns an error string when the bucket is not loaded here, which is the normal state for a bucket nothing has accessed since startup. */
|
|
107
|
-
export declare function
|
|
107
|
+
export declare function debugGetActiveBucket(account: string, bucketName: string): Promise<ActiveBucketInfo | string>;
|
|
108
108
|
/** Loads a bucket that exists on this server's disk into memory, which starts its synchronization and window timers, and returns its live state. Nothing is written and no other server is contacted - unlike building an ArchivesChain for it, which would probe every source and could write the routing config. Already-loaded buckets just return their state. */
|
|
109
109
|
export declare function activateBucket(account: string, bucketName: string): Promise<ActiveBucketInfo | string>;
|
|
110
|
-
export declare function
|
|
110
|
+
export declare function debugListAccountBuckets(account: string): Promise<ServerBucketInfo[]>;
|
|
111
111
|
export type BucketWriteStats = {
|
|
112
112
|
/** Every set call the bucket accepted */
|
|
113
113
|
originalWrites: number;
|
|
@@ -117,4 +117,4 @@ export type BucketWriteStats = {
|
|
|
117
117
|
flushedBytes: number;
|
|
118
118
|
};
|
|
119
119
|
/** Zeroes the write statistics of every bucket in the account. */
|
|
120
|
-
export declare function
|
|
120
|
+
export declare function debugClearAccountWriteStats(account: string): number;
|
|
@@ -8,6 +8,7 @@ import { ROUTING_FILE, parseRoutingData, serializeRemoteConfig, normalizeSource
|
|
|
8
8
|
import { createStoreSource, applySourceConfig } from "./storeSources";
|
|
9
9
|
import { broadcastRoutingChanged } from "./storageController";
|
|
10
10
|
import { getStorageServerConfig, assertWritesAllowed } from "./serverConfig";
|
|
11
|
+
import { assertValidSourceName } from "./validation";
|
|
11
12
|
import { getBucketFolder, listAccountStoreFolders, listBucketStoreFolders, readRoutingFromDisk, getDiskInfo, BucketDiskInfo } from "./bucketDisk";
|
|
12
13
|
import { scheduleBoundaryWork, reinjectIntermediates } from "./intermediateManagement";
|
|
13
14
|
import { planSelfStores, isSelfSource, SelfSummary } from "./storePlan";
|
|
@@ -53,23 +54,14 @@ export async function requireBucket(account: string, bucketName: string): Promis
|
|
|
53
54
|
return bucket;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/** The store serving a request: the one the client's selected entry NAMES.
|
|
57
|
-
export
|
|
58
|
-
let bucket = await requireBucket(account, bucketName);
|
|
57
|
+
/** The store serving a request: the one the client's selected entry NAMES. Account, name, and bucket ARE the folder, so this is a direct lookup, never a search of what exists - and a name this server has never seen is CREATED, never rejected, because asking for a name is the instruction to have that store (one name, one folder, one index; it configures itself once the routing config lands in it). Nothing else about the request is compared - not the window, not the route, not the flags - which is the whole point of naming it: a client a config version behind on some flag still reaches the right store. */
|
|
58
|
+
export function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined): BlobStore {
|
|
59
59
|
if (!sourceConfig) {
|
|
60
|
-
throw new Error(`No remote source configuration was provided for bucket ${account}/${bucketName}: every request must say which configured source it selected
|
|
61
|
-
}
|
|
62
|
-
let store = bucket.stores.find(x => x.name === sourceConfig.name);
|
|
63
|
-
if (store) return store;
|
|
64
|
-
// The loaded bucket is a snapshot of the disk from when it loaded; a store created since (a new name in a rewritten routing config) is on the disk but not in the snapshot, so a miss re-reads the disk once before it is believed
|
|
65
|
-
let previousNames = bucket.stores.map(x => x.name);
|
|
66
|
-
let reloaded = await reloadBucket(account, bucketName);
|
|
67
|
-
store = reloaded?.stores.find(x => x.name === sourceConfig.name);
|
|
68
|
-
if (store) {
|
|
69
|
-
console.log(`Reloaded bucket ${account}/${bucketName} to find store ${JSON.stringify(sourceConfig.name)}: its stores changed from ${JSON.stringify(previousNames)} to ${JSON.stringify(reloaded?.stores.map(x => x.name))}`);
|
|
70
|
-
return store;
|
|
60
|
+
throw new Error(`No remote source configuration was provided for bucket ${account}/${bucketName}: every request must say which configured source it selected`);
|
|
71
61
|
}
|
|
72
|
-
|
|
62
|
+
// Not part of resolving the request (the name already says everything): loading the bucket is what schedules its switchover work and marks it active for deploy handoffs, and a bucket serving requests must be loaded
|
|
63
|
+
void getLoadedBucket(account, bucketName).catch((e: Error) => console.error(`Loading bucket ${account}/${bucketName} failed: ${e.stack ?? e}`));
|
|
64
|
+
return getStore(account, bucketName, sourceConfig.name);
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
/** Internal (store-to-store) reads skip store selection entirely: the caller is another store whose index says this MACHINE holds the bytes - the persisted holder identity is just a URL, which cannot name a store. Whichever store's folder has the newest copy answers. */
|
|
@@ -118,7 +110,7 @@ export async function bucketSyncStatus(bucket: BucketState): Promise<ArchivesSyn
|
|
|
118
110
|
};
|
|
119
111
|
}
|
|
120
112
|
|
|
121
|
-
export async function
|
|
113
|
+
export async function debugBucketIndexTotals(bucket: BucketState): Promise<{ fileCount: number; byteCount: number; sources: { debugName: string; fileCount: number; byteCount: number }[] }> {
|
|
122
114
|
let totals = await Promise.all(bucket.stores.map(s => s.store.computeIndexTotals()));
|
|
123
115
|
return {
|
|
124
116
|
fileCount: totals.reduce((sum, x) => sum + x.fileCount, 0),
|
|
@@ -166,9 +158,12 @@ function sourcePersistentUrl(sourceConfig: SourceConfig | undefined, folder: str
|
|
|
166
158
|
const stores = new Map<string, BlobStore>();
|
|
167
159
|
|
|
168
160
|
export function getStore(account: string, bucketName: string, name: string): BlobStore {
|
|
161
|
+
// The one place a name becomes a folder, so the one place it is validated (it can come straight off the wire)
|
|
162
|
+
assertValidSourceName(name);
|
|
169
163
|
let folder = getBucketFolder(name, account, bucketName);
|
|
170
164
|
let existing = stores.get(folder);
|
|
171
165
|
if (existing) return existing;
|
|
166
|
+
console.log(`Opening store ${JSON.stringify(name)} of bucket ${account}/${bucketName} (folder ${folder}) - created on this first use if it never existed`);
|
|
172
167
|
let store = new BlobStore(folder, name, {
|
|
173
168
|
// Which entries in ITS routing config are this server's copy of it
|
|
174
169
|
isSelf: source => isSelfSource(source, account, bucketName),
|
|
@@ -297,7 +292,7 @@ function toActiveBucketInfo(loaded: BucketState): ActiveBucketInfo {
|
|
|
297
292
|
}
|
|
298
293
|
|
|
299
294
|
/** The live in-memory state of ONE bucket, answered without touching the disk (no routing file read, no statfs, no stored write stats). Returns an error string when the bucket is not loaded here, which is the normal state for a bucket nothing has accessed since startup. */
|
|
300
|
-
export async function
|
|
295
|
+
export async function debugGetActiveBucket(account: string, bucketName: string): Promise<ActiveBucketInfo | string> {
|
|
301
296
|
let key = `${account}/${bucketName}`;
|
|
302
297
|
let loadedPromise = buckets.get(key);
|
|
303
298
|
if (!loadedPromise) {
|
|
@@ -338,7 +333,7 @@ export async function activateBucket(account: string, bucketName: string): Promi
|
|
|
338
333
|
return toActiveBucketInfo(loaded);
|
|
339
334
|
}
|
|
340
335
|
|
|
341
|
-
export async function
|
|
336
|
+
export async function debugListAccountBuckets(account: string): Promise<ServerBucketInfo[]> {
|
|
342
337
|
let start = Date.now();
|
|
343
338
|
let timings = new Map<string, number>();
|
|
344
339
|
async function timed<T>(name: string, run: () => Promise<T>): Promise<T> {
|
|
@@ -391,7 +386,7 @@ export async function listAccountBuckets(account: string): Promise<ServerBucketI
|
|
|
391
386
|
} finally {
|
|
392
387
|
// The parts run concurrently, so these are summed wall times per part, not a breakdown of the total
|
|
393
388
|
let parts = [...timings].map(([name, ms]) => `${name} ${ms}ms`).join(", ");
|
|
394
|
-
console.log(`
|
|
389
|
+
console.log(`debugListAccountBuckets(${account}) took ${Date.now() - start}ms for ${names.length} buckets: ${parts}`);
|
|
395
390
|
}
|
|
396
391
|
}
|
|
397
392
|
|
|
@@ -430,7 +425,7 @@ function getBucketWriteStats(key: string): BucketWriteStats {
|
|
|
430
425
|
}
|
|
431
426
|
|
|
432
427
|
/** Zeroes the write statistics of every bucket in the account. */
|
|
433
|
-
export function
|
|
428
|
+
export function debugClearAccountWriteStats(account: string): number {
|
|
434
429
|
let prefix = `${account}/`;
|
|
435
430
|
let cleared = 0;
|
|
436
431
|
for (let key of [...writeStats.keys()]) {
|