@cloudbitmaps/core 0.16.0 → 0.18.0
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/dist/{chunk-LFUNPMXZ.js → chunk-CCP4U32U.js} +380 -23
- package/dist/chunk-CCP4U32U.js.map +7 -0
- package/dist/core/chunk-checks.d.ts +34 -0
- package/dist/core/codec.d.ts +1 -1
- package/dist/core/combine-expr.d.ts +75 -0
- package/dist/core/combine-feed.d.ts +142 -0
- package/dist/core/combine-held.d.ts +43 -0
- package/dist/core/combine-ledger.d.ts +43 -0
- package/dist/core/combine-many.d.ts +319 -0
- package/dist/core/cost.d.ts +3 -3
- package/dist/core/crbm-storage-source.d.ts +81 -66
- package/dist/core/engine.d.ts +24 -30
- package/dist/core/erasure.d.ts +2 -1
- package/dist/core/errors.d.ts +39 -0
- package/dist/core/generation-gc.d.ts +69 -45
- package/dist/core/kept-generations.d.ts +40 -0
- package/dist/core/leases.d.ts +119 -0
- package/dist/core/load-input.d.ts +14 -1
- package/dist/core/load.d.ts +13 -11
- package/dist/core/metrics.d.ts +24 -0
- package/dist/core/ports.d.ts +99 -2
- package/dist/core/registry-reap.d.ts +22 -0
- package/dist/core/retention.d.ts +2 -1
- package/dist/core/rollback.d.ts +2 -1
- package/dist/driver-kit.d.ts +1 -0
- package/dist/driver-kit.js +100 -1
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +19 -1
- package/dist/drivers/_shared/registry.d.ts +7 -9
- package/dist/export/index.d.ts +20 -8
- package/dist/index.d.ts +13 -3
- package/dist/index.js +2913 -243
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-LFUNPMXZ.js.map +0 -7
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type IAuditSink } from './audit.js';
|
|
2
2
|
import type { Yielder } from './cooperative.js';
|
|
3
3
|
import type { Clock, Rng } from './determinism.js';
|
|
4
|
+
import type { PublishedKept } from './generation-gc.js';
|
|
4
5
|
import type { Aead, CrbmCrypto, IKeystore, WrappedDek } from './crypto.js';
|
|
5
6
|
import type { ChunkRef, ChunkRead, ReadChunksOptions, StorageChunkSource, GenerationMetadata, GenerationSummary, GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, RegistrySummary, SegmentRef, SegmentSize, Token } from './ports.js';
|
|
6
7
|
import { CrbmReader } from './crbm/reader.js';
|
|
7
8
|
import type { CrbmReaderOptions } from './crbm/reader.js';
|
|
8
|
-
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
9
|
+
import type { CodecBitmap, CodecInterface, EncodedChunk } from './codec.js';
|
|
9
10
|
import { ChunkLoadInput, DecodedLoadInput } from './load-input.js';
|
|
10
11
|
export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
|
|
11
12
|
/**
|
|
@@ -78,6 +79,18 @@ export interface PinnedObject {
|
|
|
78
79
|
readonly version: string;
|
|
79
80
|
readonly fingerprint?: string;
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* What one resolution of a segment's pointer found: the target, and what its row says of the generation, each
|
|
84
|
+
* computed only when asked for. The key is unwrapped once however many ask, and a failure is not remembered, so a
|
|
85
|
+
* transient fault in the keystore is asked again.
|
|
86
|
+
*/
|
|
87
|
+
/** What a pin that leases its generation does between reading the row and opening the object. */
|
|
88
|
+
export interface PinLease {
|
|
89
|
+
/** Write the lease against the row the pin read: its token, or `'moved'` when the pointer is no longer at the generation. */
|
|
90
|
+
take(row: RegistryRecord): Promise<{
|
|
91
|
+
readonly token: Token;
|
|
92
|
+
} | 'moved'>;
|
|
93
|
+
}
|
|
81
94
|
export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
82
95
|
private readonly driver;
|
|
83
96
|
/**
|
|
@@ -216,9 +229,27 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
216
229
|
* and a `keep: 0` sweep can land in that gap, and nothing has been handed out yet, so the generation current
|
|
217
230
|
* once the swept one is gone is exactly what a pin taken now should hold. One retry, then propagate.
|
|
218
231
|
*/
|
|
219
|
-
pinGeneration(ref: SegmentRef): Promise<({
|
|
232
|
+
pinGeneration(ref: SegmentRef, lease?: PinLease): Promise<({
|
|
220
233
|
generation: number;
|
|
221
234
|
} & Required<PinnedObject>) | null>;
|
|
235
|
+
/**
|
|
236
|
+
* Open one named generation and verify it is the object `at.fingerprint` names, for a pin the caller reopens. One
|
|
237
|
+
* row read and one tail read with a registry (none of the row for a store without one): the open is the one a
|
|
238
|
+
* pinned read makes ({@link readerAt}), memoised under the version the pin will read by, so the pin's first read
|
|
239
|
+
* finds it open. A generation that is gone, above the row's pointer, on a destroyed or absent row, or another
|
|
240
|
+
* object than the fingerprint names, throws `NotFoundError`; nothing here reads empty.
|
|
241
|
+
*
|
|
242
|
+
* A generation counts as published while the pointer is at or above it. A rollback deletes nothing, so after one
|
|
243
|
+
* and a later load, a generation it rolled back from can be reopened while its object is still stored and the
|
|
244
|
+
* fingerprint matches. Like `pinGeneration`'s pinned reads, this opens the object without the live open's check
|
|
245
|
+
* of the row's summary against it, since that check is made where the row's own generation is read.
|
|
246
|
+
*/
|
|
247
|
+
pinGenerationAt(ref: SegmentRef, at: {
|
|
248
|
+
readonly generation: number;
|
|
249
|
+
readonly fingerprint: string;
|
|
250
|
+
}, lease?: PinLease): Promise<{
|
|
251
|
+
generation: number;
|
|
252
|
+
} & Required<PinnedObject>>;
|
|
222
253
|
private pinOnce;
|
|
223
254
|
/**
|
|
224
255
|
* Where a pinned reader of one object is memoised. With a registry that is its version, which names the object:
|
|
@@ -414,6 +445,13 @@ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey,
|
|
|
414
445
|
sha256: string;
|
|
415
446
|
fingerprint: string;
|
|
416
447
|
}>;
|
|
448
|
+
/**
|
|
449
|
+
* The chunks a load of ids, or of a decoded bitmap, writes: ascending by key, each non-empty and run-optimized. A decoded
|
|
450
|
+
* bitmap writes its own chunks, which never touches an id. Ids, and the bitmap of a codec that cannot encode its own
|
|
451
|
+
* chunks, are bucketed per chunk instead; both give the same bytes. Exported for a caller that holds the chunks itself
|
|
452
|
+
* instead of writing them.
|
|
453
|
+
*/
|
|
454
|
+
export declare function encodedChunksOfInput(ids: Iterable<number> | AsyncIterable<number> | DecodedLoadInput, codec: CodecInterface, clock: Clock | undefined): Promise<Iterable<EncodedChunk>>;
|
|
417
455
|
/**
|
|
418
456
|
* Whether the object under `key` is provably the one `fingerprint` names ({@link CrbmReader.fingerprint}), from one
|
|
419
457
|
* read of its footer. False when it is another object, when it is gone, and when the read fails in any way: a caller
|
|
@@ -460,72 +498,17 @@ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: G
|
|
|
460
498
|
clock?: Yielder;
|
|
461
499
|
metadata?: GenerationMetadata;
|
|
462
500
|
}): Promise<StreamWriteResult>;
|
|
463
|
-
|
|
464
|
-
* Point a segment's registry `currentGen` at `key.generation` — the publish step that makes a freshly-written
|
|
465
|
-
* generation the authoritative latest (so registry-aware readers see it). **Forward-only and idempotent:**
|
|
466
|
-
* if the registry has no row it creates one; if it's already at/ahead of `key.generation` it's a no-op (an
|
|
467
|
-
* out-of-order/duplicate publish never regresses the pointer); otherwise it advances via compare-and-swap,
|
|
468
|
-
* retrying a few times under contention. Separated from the Storage write so callers can publish atomically
|
|
469
|
-
* after the immutable object is durable (write-then-publish).
|
|
470
|
-
*
|
|
471
|
-
* **Throws** rather than returning, in two cases, because each would otherwise create a state no reader can
|
|
472
|
-
* recover from: a `destroyed` (crypto-shredded) row, where advancing the pointer would name an object encrypted
|
|
473
|
-
* under a shredded key; and an advance carrying **new** `wrappedDeks`, which would either strand that key or make
|
|
474
|
-
* the row advertise encryption over cleartext generations. Contention that never clears throws
|
|
475
|
-
* {@link WriteConflictError} after the retries.
|
|
476
|
-
*
|
|
477
|
-
* **`expectFrom` turns forward-only into read-modify-write.** Forward-only is the right rule for a writer whose
|
|
478
|
-
* content does not depend on what was current — a load computes its ids upstream, so publishing over a newer
|
|
479
|
-
* generation loses nothing the loader knew about. It is the WRONG rule for a writer that *derived* its content
|
|
480
|
-
* from a particular generation: the erasure rewrite streams generation `from` and clears one bit, so publishing
|
|
481
|
-
* it over a newer generation silently discards whatever that generation added. Passing `expectFrom` makes the
|
|
482
|
-
* publish land only while the pointer is still exactly there, and return `false` otherwise, so the caller can
|
|
483
|
-
* report `superseded` and re-derive. The compare-and-swap itself carries the token read in the same iteration,
|
|
484
|
-
* so the check and the write see the same row.
|
|
485
|
-
*
|
|
486
|
-
* **`expectToken` fences the LINEAGE, and a derived writer needs both.** A generation number identifies a
|
|
487
|
-
* generation only within one incarnation of a name: the numbering restarts at `0` once the row is purged and
|
|
488
|
-
* the bucket empty, so a name that is retired and re-created has a *different* segment wearing the *same*
|
|
489
|
-
* `currentGen`. `expectFrom` alone matched it — and an erasure rewrite then published one incarnation's content
|
|
490
|
-
* over another's, deleted the live objects with its `keep: 0` collection, and returned `erased: true`. The row's
|
|
491
|
-
* OCC token is the identity that survives this: a shipped registry's later `create` gets a token never issued before
|
|
492
|
-
* under that name, but for a collision of probability 2^-128 per pair of incarnations, so no token is reused across them.
|
|
493
|
-
*
|
|
494
|
-
* Pass the token read alongside `expectFrom` and the publish lands only on the same row it was derived from.
|
|
495
|
-
* The check is deliberately **conservative**: the token also changes on writes that are not supersessions at
|
|
496
|
-
* all (a `setRetention`, a due-index reindex), so such a write makes a derived publish report `superseded` and
|
|
497
|
-
* the caller re-derive. That costs a re-run on a rare, unrelated write; the alternative costs a segment.
|
|
498
|
-
*
|
|
499
|
-
* **The driver sends a registry write once, and an unanswered one is reconciled by its effect.** A `create` or
|
|
500
|
-
* `compareAndSwap` that fails with {@link TransientError} (a throttle, a lost response, a timeout) may or may not
|
|
501
|
-
* have landed, and may still be in flight. The publish reads the row once more and decides from what it holds:
|
|
502
|
-
*
|
|
503
|
-
* - the pointer names `key.generation`, on the incarnation the write was made against (the row's `createdAt`), and
|
|
504
|
-
* `holdsOwnObject` proves the object under it the caller's: the write landed, and the publish returns `true`. Only
|
|
505
|
-
* the writer of a write-once object publishes its number, and a number is never taken while an object holds it;
|
|
506
|
-
* - the row is still the one the write was made against (the same token, or still no row): the write may land yet. The
|
|
507
|
-
* publish then waits, on the injected clock, and sends a **new** write from the row it just read: a request of its
|
|
508
|
-
* own, not a replay of the driver's, carrying the same version, so under the registry's fence at most one of the two
|
|
509
|
-
* lands and the other meets a conflict, which the next read settles. At most {@link UNANSWERED_RESENDS} follow, after
|
|
510
|
-
* a wait under 500 ms, then 1 s, then 2 s (full jitter when an `rng` is given); each is settled the same way. Still
|
|
511
|
-
* unanswered, or with no clock to wait on, this throws the registry's own `TransientError` and writes nothing more.
|
|
512
|
-
* A caller must not delete its object on it;
|
|
513
|
-
* - anything else: the row has moved past the state the write was conditioned on, so that write can never land, and
|
|
514
|
-
* the publish goes on as after a lost race, from the row it just read. A pointer at `key.generation` that is not over
|
|
515
|
-
* the caller's own object, or is on another incarnation, is a lost race too and answers `false`, never an already
|
|
516
|
-
* current re-publish.
|
|
517
|
-
*
|
|
518
|
-
* A failed read throws `TransientError` too. The same check of the pointer runs after a {@link WriteConflictError},
|
|
519
|
-
* so a write that landed and then met itself (a registry whose client re-sent it) is not taken for a lost race.
|
|
520
|
-
*
|
|
521
|
-
* **A `summary` goes in the same write that moves the pointer**, so a reader that sees this generation as current
|
|
522
|
-
* sees the count and metadata that describe it, and every attempt sends the same one. It must be the shape the row's
|
|
523
|
-
* keys call for: sealed for an object written with a key, clear for one written without.
|
|
524
|
-
*/
|
|
525
|
-
export declare function publishGeneration(registry: IRegistryDriver, key: GenKey, options?: {
|
|
501
|
+
export declare function publishGenerationKept(registry: IRegistryDriver, key: GenKey, options?: {
|
|
526
502
|
wrappedDeks?: readonly WrappedDek[];
|
|
527
503
|
expectFrom?: number;
|
|
528
504
|
expectToken?: Token;
|
|
505
|
+
/**
|
|
506
|
+
* The row `expectToken` names, as the caller read it. With it, a row whose token has moved but which differs from
|
|
507
|
+
* this one only in its leases (a reader took or released one) does not refuse the publish: it goes on against the
|
|
508
|
+
* fresh row, after a jittered wait and without the caller redoing its work, up to a bound. Any other difference
|
|
509
|
+
* refuses as ever. Without it, a moved token refuses.
|
|
510
|
+
*/
|
|
511
|
+
expectRow?: RegistryRecord | null;
|
|
529
512
|
/**
|
|
530
513
|
* Publish only while the segment still has **no registry row**.
|
|
531
514
|
*
|
|
@@ -584,7 +567,39 @@ export declare function publishGeneration(registry: IRegistryDriver, key: GenKey
|
|
|
584
567
|
* held its generation for a while before another writer moved on.
|
|
585
568
|
*/
|
|
586
569
|
onUnanswered?: () => void;
|
|
587
|
-
|
|
570
|
+
/**
|
|
571
|
+
* The clock that judges whether a lease has ended. With it, a publish over a row that holds ended leases drops
|
|
572
|
+
* them in the write that moves the pointer, which rewrites the row anyway. Without it, leases are left as they are.
|
|
573
|
+
*/
|
|
574
|
+
leasesNow?: () => number;
|
|
575
|
+
/**
|
|
576
|
+
* Clear the row's leases in the write that moves the pointer: the caller's collection deletes every generation
|
|
577
|
+
* below the new pointer whatever a lease says, as a subject erasure's does.
|
|
578
|
+
*/
|
|
579
|
+
clearLeases?: boolean;
|
|
580
|
+
/**
|
|
581
|
+
* How many generations below the new pointer the caller keeps. With it, the write that moves the pointer also
|
|
582
|
+
* records the window in the row (`keptGens`: the newest `keep` of the row's own list and the generation that was
|
|
583
|
+
* current), derived from the very row the write is made against, and the result names the generations the window
|
|
584
|
+
* dropped. Without it, or above the most a row records, the row records none, and a collection lists.
|
|
585
|
+
*/
|
|
586
|
+
keep?: number;
|
|
587
|
+
}): Promise<PublishResult>;
|
|
588
|
+
/** What a publish did: whether the pointer is at the generation, and what the write recorded in the row. */
|
|
589
|
+
export interface PublishResult {
|
|
590
|
+
readonly published: boolean;
|
|
591
|
+
/**
|
|
592
|
+
* What the write that moved the pointer recorded: the list, the generations the window dropped, and the new row's
|
|
593
|
+
* token. Absent when no write of this call is known to have landed (the pointer was already there, or the outcome
|
|
594
|
+
* was settled without knowing which write it was), and a collection then lists.
|
|
595
|
+
*/
|
|
596
|
+
readonly kept?: PublishedKept;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* {@link publishGenerationKept}'s answer to "did the pointer move", for a caller with no use for what it recorded:
|
|
600
|
+
* a publish with no `keep` leaves the row recording no list.
|
|
601
|
+
*/
|
|
602
|
+
export declare function publishGeneration(registry: IRegistryDriver, key: GenKey, options?: Parameters<typeof publishGenerationKept>[2]): Promise<boolean>;
|
|
588
603
|
/**
|
|
589
604
|
* Whether the object under `key` is the one `fingerprint` names, from one read of its footer: false when it is another
|
|
590
605
|
* object or gone. Unlike {@link holdsObject}, a read that cannot tell throws, for a caller that must not take "could
|
package/dist/core/engine.d.ts
CHANGED
|
@@ -166,6 +166,29 @@ export declare class SegmentEngine {
|
|
|
166
166
|
* the caller's to keep.
|
|
167
167
|
*/
|
|
168
168
|
iterateBatches(seg: SegmentRef, range?: IdRange): AsyncGenerator<Uint32Array>;
|
|
169
|
+
/**
|
|
170
|
+
* The ids at 1-based ranks `n`, `2n`, `3n` … counted over the ids in `range`, ascending. The rank of each boundary
|
|
171
|
+
* is placed from the index's per-chunk counts, so only the chunks that hold a boundary are read, each once however
|
|
172
|
+
* many boundaries it holds, through the same coalesced stream, window and budget unit as {@link iterate}. A range with
|
|
173
|
+
* `after` inside a chunk reads that chunk too, to count the ids at or below `after`. A last partial window yields
|
|
174
|
+
* nothing.
|
|
175
|
+
*
|
|
176
|
+
* The counts place ids, and are trusted as `count()` trusts them: a chunk that is read must decode to the size its
|
|
177
|
+
* index says, or the read throws {@link IntegrityError} (invariant 5), but a chunk that is not read is not checked, so
|
|
178
|
+
* an index that lies consistently about one (on an unencrypted object, the sum still matches the footer's total,
|
|
179
|
+
* which opening the object checks before any boundary is placed) shifts the ranks after it. An encrypted object's
|
|
180
|
+
* index is authenticated and bound to the object, so a wrong count there needs the key. The index and the chunks must come from one
|
|
181
|
+
* generation (invariant 3): the caller reads through a source that holds one, a pinned one, and a source with no
|
|
182
|
+
* per-chunk counts is refused.
|
|
183
|
+
*
|
|
184
|
+
* Nothing is fetched when the chunks in range hold fewer than `n` ids. The budget is charged before the first fetch
|
|
185
|
+
* with an upper bound, the cut chunk plus one chunk per boundary the counts allow (capped by the chunks in range),
|
|
186
|
+
* which can exceed the chunks actually read. A range that cuts its first chunk reads it even when the ids above
|
|
187
|
+
* `after` turn out to be fewer than `n`, and one that cuts its last chunk may read it and yield nothing.
|
|
188
|
+
*/
|
|
189
|
+
everyNth(seg: SegmentRef, n: number, range?: IdRange): AsyncGenerator<number>;
|
|
190
|
+
/** A chunk's remainders ascending, once its size is confirmed against the index's count for it (invariant 5). */
|
|
191
|
+
private chunkRemainders;
|
|
169
192
|
/**
|
|
170
193
|
* `segs[0] ∩ segs[1] ∩ …`, minus every id in `options.exclude` — streamed ascending.
|
|
171
194
|
*
|
|
@@ -289,32 +312,8 @@ export declare class SegmentEngine {
|
|
|
289
312
|
private streamedVersion;
|
|
290
313
|
/** The cache key of a chunk: by the version it was read under, or by segment and key alone for a source with none. */
|
|
291
314
|
private chunkCacheKey;
|
|
292
|
-
/** The segment's chunk keys, ascending
|
|
315
|
+
/** The segment's chunk keys, ascending, checked as untrusted tier data. */
|
|
293
316
|
private chunkKeys;
|
|
294
|
-
/**
|
|
295
|
-
* Tier-derived keys are untrusted (invariant 5) — fail fast on a corrupt/out-of-range key rather than
|
|
296
|
-
* letting it flow into the id-routing step and produce a bogus id.
|
|
297
|
-
*/
|
|
298
|
-
private assertChunkKeyInRange;
|
|
299
|
-
/**
|
|
300
|
-
* The other half of invariant 5: a chunk payload holds **remainders** — 16-bit offsets within one chunk — so
|
|
301
|
-
* every value must be `<= 0xffff`.
|
|
302
|
-
*
|
|
303
|
-
* Nothing upstream establishes that. The byte cap bounds *size*, and CRC/AEAD prove the bytes are the bytes
|
|
304
|
-
* that were written — which anyone able to write the bucket satisfies trivially. A value `>= 65536` would then
|
|
305
|
-
* reach the id-routing step, which masks it (`remainder & 0xffff`) and emits a **fabricated id belonging to a different
|
|
306
|
-
* chunk's id space**: indistinguishable from real data, inflating `count()` and creating spurious `intersect`
|
|
307
|
-
* matches.
|
|
308
|
-
*
|
|
309
|
-
* Costs one `maximum()` per chunk, not per id — `maximum` is optional on the codec seam precisely so a codec
|
|
310
|
-
* that cannot answer in O(1) opts out instead of making the read path walk every value.
|
|
311
|
-
*
|
|
312
|
-
* One value stands for all of them only because the decode is structurally checked. Roaring answers `maximum()`
|
|
313
|
-
* from its last container, so a payload listing its containers out of order would report the wrong one's
|
|
314
|
-
* largest value and pass here while holding values above 65,535. The codec's `safeDeserialize` refuses that
|
|
315
|
-
* payload, and every other shape that would make this answer wrong, before it reaches this check.
|
|
316
|
-
*/
|
|
317
|
-
private assertChunkPayloadInRange;
|
|
318
317
|
/**
|
|
319
318
|
* Refuse a combine whose operand names a segment that was never created.
|
|
320
319
|
*
|
|
@@ -379,11 +378,6 @@ export declare class SegmentEngine {
|
|
|
379
378
|
* one): callers read it or clone it, never mutate it.
|
|
380
379
|
*/
|
|
381
380
|
private storageChunk;
|
|
382
|
-
/**
|
|
383
|
-
* A chunk's stored bytes decoded under the size cap (invariant 5) and range-checked: the one place a chunk becomes a
|
|
384
|
-
* bitmap, whether its bytes came from a request of its own or from a stream of coalesced ranges.
|
|
385
|
-
*/
|
|
386
|
-
private decodeChunk;
|
|
387
381
|
/**
|
|
388
382
|
* One chunk, decoded, range-checked and cached. A source with `getChunks` is read through it, as a one-key stream, so
|
|
389
383
|
* a `storage.get` is reported only for a request that was sent (a chunk the source holds in memory sends none);
|
package/dist/core/erasure.d.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* operation — `eraseIdFromSegment` rewrites the generation without it.
|
|
14
14
|
*/
|
|
15
15
|
import { type IAuditSink } from './audit.js';
|
|
16
|
+
import { type ChurnDeps } from './leases.js';
|
|
16
17
|
import type { IStorageDriver, IRegistryDriver, SegmentRef } from './ports.js';
|
|
17
|
-
export interface EraseDeps {
|
|
18
|
+
export interface EraseDeps extends ChurnDeps {
|
|
18
19
|
readonly registry: IRegistryDriver;
|
|
19
20
|
}
|
|
20
21
|
export interface DestroyResult {
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -42,6 +42,39 @@ export declare class IntegrityError extends CloudRoaringError {
|
|
|
42
42
|
*/
|
|
43
43
|
export declare class NotFoundError extends CloudRoaringError {
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* A read of a leased pin came after its lease ended, by `until` or by `release()`. Thrown at every read site of the
|
|
47
|
+
* handle, including a leased handle used as an operand or an exclude of a combine, and never answered empty: a
|
|
48
|
+
* handle that read empty here would be an opt-out list that suppresses nobody. Carries the instant the lease ended
|
|
49
|
+
* and why; never an id or a key.
|
|
50
|
+
*/
|
|
51
|
+
export declare class LeaseExpiredError extends CloudRoaringError {
|
|
52
|
+
/** Epoch-ms the lease was taken until. */
|
|
53
|
+
readonly until: number;
|
|
54
|
+
/** `'expired'` when the clock reached `until`, `'released'` after `release()`. */
|
|
55
|
+
readonly reason: 'expired' | 'released';
|
|
56
|
+
constructor(message: string, until: number, reason: 'expired' | 'released');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A segment already has as many live leases as one row records, so another cannot be taken. Nothing was written.
|
|
60
|
+
* Release one, wait for one to end, or share one lease across the tasks of a job.
|
|
61
|
+
*/
|
|
62
|
+
export declare class LeaseLimitError extends CloudRoaringError {
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* An operand a call relied on changed while the call ran, so what the call built from it is not published. Carries the
|
|
66
|
+
* operand's name in the call and why it is stale (`'moved'`: it was pinned at a generation and its pointer has since
|
|
67
|
+
* moved; `'erased'`: an erasure ran in the store after a call with a feed began, so what the feed gave it may hold an
|
|
68
|
+
* erased id), under the stable `code` `'stale-operand'`. Deterministic: run the call again against the current operand.
|
|
69
|
+
*/
|
|
70
|
+
export declare class StaleOperandError extends CloudRoaringError {
|
|
71
|
+
readonly code: "stale-operand";
|
|
72
|
+
/** The operand's name in the call that found it stale. */
|
|
73
|
+
readonly operand: string;
|
|
74
|
+
/** Why it is stale. */
|
|
75
|
+
readonly reason: 'moved' | 'erased';
|
|
76
|
+
constructor(message: string, operand: string, reason: 'moved' | 'erased');
|
|
77
|
+
}
|
|
45
78
|
/**
|
|
46
79
|
* This build/configuration cannot perform the requested operation, though nothing is malformed. Two uses:
|
|
47
80
|
* (1) **format** — the bytes are well-formed but unreadable here (an unknown `.crbm` major version) —
|
|
@@ -104,6 +137,12 @@ export declare function isCloudRoaringError(err: unknown): err is CloudRoaringEr
|
|
|
104
137
|
export declare function isWriteConflictError(err: unknown): err is WriteConflictError;
|
|
105
138
|
/** A retryable transient infrastructure fault. The retry layer keys on this. */
|
|
106
139
|
export declare function isTransientError(err: unknown): err is TransientError;
|
|
140
|
+
/** A read came after its pin's lease ended. */
|
|
141
|
+
export declare function isLeaseExpiredError(err: unknown): err is LeaseExpiredError;
|
|
142
|
+
/** An operand a call relied on changed while it ran. */
|
|
143
|
+
export declare function isStaleOperandError(err: unknown): err is StaleOperandError;
|
|
144
|
+
/** A segment has no room for another live lease. */
|
|
145
|
+
export declare function isLeaseLimitError(err: unknown): err is LeaseLimitError;
|
|
107
146
|
/** A requested object/row does not exist. */
|
|
108
147
|
export declare function isNotFoundError(err: unknown): err is NotFoundError;
|
|
109
148
|
/** Corrupt/oversized/failed-checksum bytes from a tier. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, SegmentRef } from './ports.js';
|
|
1
|
+
import type { GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, SegmentRef, Token } from './ports.js';
|
|
2
2
|
/** The two ports generation bookkeeping needs: the objects, and the pointer that says which one is current. */
|
|
3
3
|
export interface GenerationDeps {
|
|
4
4
|
readonly storage: IStorageDriver;
|
|
@@ -51,9 +51,9 @@ export interface LoadNumber {
|
|
|
51
51
|
export declare function nextLoadGeneration(ref: SegmentRef, deps: GenerationDeps, row: RegistryRecord | null): Promise<LoadNumber>;
|
|
52
52
|
/**
|
|
53
53
|
* How often a load that could collect by name lists instead: every generation divisible by this. A name-only pass
|
|
54
|
-
* takes
|
|
55
|
-
*
|
|
56
|
-
* stranded below the pointer) waits for a listing. The number is a generation's, not a clock's, so the choice needs
|
|
54
|
+
* takes the names its publish pushed out of the row's window and never looks at the rest, so what it misses (a crashed
|
|
55
|
+
* or refused load's object, a delete that failed, extras a fault left after a `keep` that shrank, an object a rollback
|
|
56
|
+
* or an erasure left stranded below the pointer) waits for a listing. The number is a generation's, not a clock's, so the choice needs
|
|
57
57
|
* no state and no time, and a segment that loads cleanly lists once in this many loads at most. The bound is in
|
|
58
58
|
* generation numbers: a rollback moves the pointer down, and the loads that then take the numbers above it count
|
|
59
59
|
* from there.
|
|
@@ -97,58 +97,82 @@ export declare function gcOrphanGenerations(ref: SegmentRef, deps: GenerationDep
|
|
|
97
97
|
keep?: number;
|
|
98
98
|
}): Promise<number[]>;
|
|
99
99
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
* What makes a collection spare leased generations: the clock that says whether a lease has ended. Only a load's
|
|
101
|
+
* collection passes one, and a load with no clock passes none: it cannot tell a live lease from an ended one, so it
|
|
102
|
+
* reads none. A pass without a guard never reads the row's leases, which is what keeps erasure, shred and drop deleting
|
|
103
|
+
* what a lease names.
|
|
104
|
+
*/
|
|
105
|
+
export interface LeaseGuard {
|
|
106
|
+
readonly now: () => number;
|
|
107
|
+
}
|
|
108
|
+
/** What a load's publish recorded in the row, as `publishGeneration` reports it. */
|
|
109
|
+
export interface PublishedKept {
|
|
110
|
+
/** The list the publish wrote, or `undefined` when it wrote none (the row then does not know which are kept). */
|
|
111
|
+
readonly list: readonly number[] | undefined;
|
|
112
|
+
/** The names the publish pushed out of the window, ascending. */
|
|
113
|
+
readonly evict: readonly number[];
|
|
114
|
+
/** The token of the row the publish wrote, when it is known: a write settled by reading the row has none. */
|
|
115
|
+
readonly token: Token | undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Collect after a load's publish. The row records the generations the load keeps, so the pass deletes by name what the
|
|
119
|
+
* publish pushed out of that window ({@link deleteEvicted}) and lists the segment ({@link gcOrphanGenerations}'s
|
|
120
|
+
* pass) only when it cannot rely on the row or must catch what a name does not:
|
|
121
|
+
*
|
|
122
|
+
* - the row recorded no list (a `keep` above {@link MAX_KEPT_GENERATIONS}, a row an earlier schema wrote, one a
|
|
123
|
+
* rollback or an erasure moved, or a publish whose write was settled by reading the row): the pass keeps the
|
|
124
|
+
* newest `keep` generations present below the pointer, and records them with one compare-and-swap, fenced on the
|
|
125
|
+
* publish's own token, so the next load collects by name;
|
|
126
|
+
* - the guard found the current generation's object gone, or, for a `keep` of 1 or more when the guard took the size
|
|
127
|
+
* from the row's summary and opened nothing, one zero-byte read of it does not find it (`proveCurrent`): the
|
|
128
|
+
* window then counts a generation that is not there, so the same pass keeps what is;
|
|
129
|
+
* - a number the listing chose (something sits above the pointer, or the existence check could not answer), a
|
|
130
|
+
* caller that asks for a listing, and every {@link LIST_COLLECTION_CADENCE}th generation: the pass deletes every
|
|
131
|
+
* generation below the pointer the row does not name, which collects what a name-only pass leaves.
|
|
132
|
+
*
|
|
133
|
+
* It makes no request when `keep` is at least the generation published, since no more generations than that exist
|
|
134
|
+
* below it. A load onto a `destroyed` row is refused at its publish and never reaches here; a drop that lands after
|
|
135
|
+
* the publish leaves this pass collecting the tombstone's generations, and the drop's own sweep takes the rest.
|
|
136
|
+
*
|
|
137
|
+
* The seed's compare-and-swap moves the row's token, so a derived writer in flight on the row (an erasure rewrite)
|
|
138
|
+
* meets a lost fence and re-derives: it happens once per row that recorded no list. A `WriteConflictError` from it is
|
|
139
|
+
* a lost race, and the load took effect, so it is not raised; the next load records the list. Any other fault is
|
|
140
|
+
* raised, as every fault of a collection after a landed publish is.
|
|
111
141
|
*/
|
|
112
142
|
export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
113
143
|
generation: number;
|
|
114
144
|
keep: number;
|
|
145
|
+
/** The load's number was proved free by one existence check, and no caller asked for a listing. */
|
|
115
146
|
byName: boolean;
|
|
147
|
+
/** The guard opened the current generation's object and found it gone. */
|
|
148
|
+
currentGone?: boolean;
|
|
149
|
+
kept: PublishedKept | undefined;
|
|
116
150
|
/**
|
|
117
|
-
* The object the load's row named as current, which the caller did not open. Before it collects by name
|
|
118
|
-
*
|
|
119
|
-
*
|
|
151
|
+
* The object the load's row named as current, which the caller did not open. Before it collects by name, the pass
|
|
152
|
+
* looks for this object with one zero-byte read, and lists instead unless that finds it: a name-only pass is
|
|
153
|
+
* safe only while the object the row named is in the bucket.
|
|
120
154
|
*/
|
|
121
155
|
proveCurrent?: GenKey;
|
|
156
|
+
/** Spare the generations the row's live leases hold. Absent, the pass ignores leases. */
|
|
157
|
+
leases?: LeaseGuard;
|
|
122
158
|
}): Promise<number[]>;
|
|
123
159
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
160
|
+
* Delete, with no listing, the generations a publish of `generation` pushed out of its row's window, `evict`, taken
|
|
161
|
+
* from the list the publish was written against. Returns the names asked for, whether or not an object was there: a
|
|
162
|
+
* delete of an absent object succeeds on every backend and says nothing, so a generation a concurrent pass, an erasure
|
|
163
|
+
* or a lifecycle rule had already taken is listed all the same. A list is not a receipt (invariant 4), and neither is
|
|
164
|
+
* this.
|
|
129
165
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* `
|
|
136
|
-
*
|
|
137
|
-
* it (see {@link collectAfterLoad}), lists instead; a load that made neither (`allowEmpty` without `minRetained`)
|
|
138
|
-
* cannot tell, and deletes by name the older generation a listing would have kept as the window. `keep` of 2 or more is refused,
|
|
139
|
-
* because there the window counts the generations that exist, which a name cannot know.
|
|
140
|
-
*
|
|
141
|
-
* Collection never touches the current generation, and the row is re-proved before the delete as every pass does.
|
|
142
|
-
* A row that is gone, or a pointer that has fallen below `generation` (a rollback, or a name purged and re-created
|
|
143
|
-
* with fewer loads), deletes nothing and returns `[]`: the publish already landed, nothing here protects it or
|
|
144
|
-
* needs the result, and a throw would hand the caller a failure for a load that took effect. A pointer that has
|
|
145
|
-
* moved above `generation` does not stop it: a publish landing in the meantime leaves routine collection working.
|
|
146
|
-
* A fault, a registry read or a delete that throws, still propagates, with the pointer at `generation`. The bound
|
|
147
|
-
* is `generation`, the pointer this load's own publish set, so the lower-of-two-pointers rule of a listing pass
|
|
148
|
-
* holds with one observation in place of two. Nothing is deleted when `generation` has nothing below its window,
|
|
149
|
-
* and no request is made.
|
|
166
|
+
* Each delete re-reads the row first (invariant 4) and stops the pass, deleting nothing more and raising nothing, unless
|
|
167
|
+
* the row still has a pointer at or above `generation` (a rollback, or a name purged and re-created with fewer loads,
|
|
168
|
+
* has moved it below: the publish already landed, nothing here protects it or needs the result), the name is below
|
|
169
|
+
* that pointer, and the row's own list is usable and does not name it. The last condition is what keeps a late delete
|
|
170
|
+
* from taking a generation that a later publish, after a rollback, put in the window. A pointer that has moved above
|
|
171
|
+
* `generation` does not stop it: a publish landing in the meantime leaves routine collection working. A fault, a
|
|
172
|
+
* registry read or a delete that throws, propagates, with the pointer at `generation`.
|
|
150
173
|
*/
|
|
151
|
-
export declare function
|
|
174
|
+
export declare function deleteEvicted(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
152
175
|
generation: number;
|
|
153
|
-
|
|
176
|
+
evict: readonly number[];
|
|
177
|
+
leases?: LeaseGuard;
|
|
154
178
|
}): Promise<number[]>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generations a segment's row records as kept: the grace window below its pointer, so a load collects what its
|
|
3
|
+
* publish pushed out of the window by name instead of listing the bucket.
|
|
4
|
+
*
|
|
5
|
+
* The record is a cache of what a listing would keep, never an inventory of the bucket. Absent means the row does not
|
|
6
|
+
* know, and the load lists; an empty list means nothing below the pointer is kept. A patch that moves the pointer
|
|
7
|
+
* without naming the list drops it (see `applyRegistryPatch`), so a writer that forgets the list leaves a row that
|
|
8
|
+
* lists, never one that deletes a generation it should keep. Pure: no I/O, time or randomness.
|
|
9
|
+
*/
|
|
10
|
+
import type { RegistryRecord } from './ports.js';
|
|
11
|
+
/**
|
|
12
|
+
* The most generations a writer records. A `keep` above it records none, and the load lists on every publish. A
|
|
13
|
+
* reader accepts up to {@link MAX_STORED_KEPT_GENERATIONS}, so a release can write more than this without a new row
|
|
14
|
+
* schema.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_KEPT_GENERATIONS = 64;
|
|
17
|
+
/** The most entries a stored row may hold: four times what a writer writes. */
|
|
18
|
+
export declare const MAX_STORED_KEPT_GENERATIONS = 256;
|
|
19
|
+
/**
|
|
20
|
+
* The row's list when a collection may rely on it: present, on a row with a pointer, and every entry below that
|
|
21
|
+
* pointer. A well-formed list that disagrees with the pointer is read but not used (the load lists instead), as a
|
|
22
|
+
* summary that names another generation is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function usableKeptGens(record: RegistryRecord | null): readonly number[] | undefined;
|
|
25
|
+
/** What a publish of a generation writes into the row, and which names it pushes out of the window. */
|
|
26
|
+
export interface KeptAfter {
|
|
27
|
+
/** The list to write, or `undefined` to write none (the row then does not know, and the next load lists). */
|
|
28
|
+
readonly list: readonly number[] | undefined;
|
|
29
|
+
/** The generations the window no longer keeps, ascending: the names to delete. */
|
|
30
|
+
readonly evict: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The list a publish of `generation` over `prev` writes: the newest `keep` of the row's list and the generation that
|
|
34
|
+
* was current, and the names that fell out of it. Only generations that were current are named, so a number a refused
|
|
35
|
+
* or crashed load took is never in it.
|
|
36
|
+
*
|
|
37
|
+
* No list is written when the row has no usable list to extend (except for generation 0, which has nothing below it),
|
|
38
|
+
* when `keep` is above {@link MAX_KEPT_GENERATIONS}, or when no `keep` is given.
|
|
39
|
+
*/
|
|
40
|
+
export declare function nextKept(prev: RegistryRecord | null, generation: number, keep: number | undefined): KeptAfter;
|