@cloudbitmaps/core 0.20.0 → 0.21.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/README.md +85 -0
- package/dist/{chunk-YSC7NJWV.js → chunk-IGLIZBR7.js} +740 -137
- package/dist/chunk-IGLIZBR7.js.map +7 -0
- package/dist/core/absent-operands.d.ts +8 -0
- package/dist/core/audit.d.ts +12 -2
- package/dist/core/bit-route.d.ts +2 -1
- package/dist/core/blob.d.ts +12 -1
- package/dist/core/bulk-load.d.ts +148 -0
- package/dist/core/cas-retry.d.ts +27 -0
- package/dist/core/check.d.ts +22 -0
- package/dist/core/chunk-checks.d.ts +6 -1
- package/dist/core/chunk-window.d.ts +11 -4
- package/dist/core/combine-expr.d.ts +1 -3
- package/dist/core/combine-feed.d.ts +2 -16
- package/dist/core/combine-many.d.ts +12 -9
- package/dist/core/concurrency.d.ts +8 -0
- package/dist/core/consistency.d.ts +69 -17
- package/dist/core/crbm/fingerprint.d.ts +5 -0
- package/dist/core/crbm/format.d.ts +0 -1
- package/dist/core/crbm/plan-reads.d.ts +9 -3
- package/dist/core/crbm/reader.d.ts +52 -6
- package/dist/core/crbm/varint.d.ts +2 -1
- package/dist/core/crbm/writer.d.ts +5 -1
- package/dist/core/crbm-open.d.ts +154 -0
- package/dist/core/crbm-storage-source.d.ts +65 -452
- package/dist/core/crbm-write.d.ts +75 -0
- package/dist/core/crypto.d.ts +6 -1
- package/dist/core/engine.d.ts +222 -33
- package/dist/core/erase-id.d.ts +42 -7
- package/dist/core/erasure.d.ts +4 -3
- package/dist/core/errors.d.ts +8 -2
- package/dist/core/generation-gc.d.ts +129 -15
- package/dist/core/generation-list.d.ts +11 -0
- package/dist/core/id-window.d.ts +46 -0
- package/dist/core/invalidation-scope.d.ts +77 -0
- package/dist/core/known-generations-shape.d.ts +13 -0
- package/dist/core/known-generations.d.ts +147 -0
- package/dist/core/leases.d.ts +4 -3
- package/dist/core/ledger-seed.d.ts +17 -0
- package/dist/core/load.d.ts +17 -38
- package/dist/core/lru.d.ts +2 -15
- package/dist/core/metrics.d.ts +4 -2
- package/dist/core/order.d.ts +2 -0
- package/dist/core/pinned-storage-source.d.ts +4 -4
- package/dist/core/ports.d.ts +89 -16
- package/dist/core/publish.d.ts +207 -0
- package/dist/core/registry-scan.d.ts +0 -2
- package/dist/core/retention-sweep.d.ts +69 -65
- package/dist/core/retention.d.ts +1 -1
- package/dist/core/retry.d.ts +12 -19
- package/dist/core/rollback.d.ts +60 -31
- package/dist/core/summary.d.ts +50 -18
- package/dist/core/token.d.ts +2 -2
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.d.ts +4 -0
- package/dist/driver-kit.js +80 -7
- package/dist/driver-kit.js.map +3 -3
- package/dist/drivers/_shared/bytes.d.ts +6 -0
- package/dist/drivers/_shared/faults.d.ts +15 -0
- package/dist/drivers/_shared/object-keys.d.ts +18 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
- package/dist/drivers/_shared/object-registry.d.ts +2 -0
- package/dist/drivers/_shared/options.d.ts +19 -0
- package/dist/drivers/_shared/registry.d.ts +13 -10
- package/dist/drivers/localfs/exact-case.d.ts +0 -3
- package/dist/drivers/localfs/fs-util.d.ts +22 -0
- package/dist/drivers/localfs/registry.d.ts +10 -4
- package/dist/drivers/localfs/storage.d.ts +9 -2
- package/dist/drivers/memory.d.ts +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3647 -2410
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-YSC7NJWV.js.map +0 -7
- package/dist/core/kept-generations.d.ts +0 -40
|
@@ -1,9 +1,84 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NotFoundError } from './errors.js';
|
|
2
|
+
import type { GenKey, IStorageDriver, IRegistryDriver, KnownGeneration, RegistryRecord, SegmentRef, Token } from './ports.js';
|
|
2
3
|
/** The two ports generation bookkeeping needs: the objects, and the pointer that says which one is current. */
|
|
3
|
-
|
|
4
|
+
interface GenerationDeps {
|
|
4
5
|
readonly storage: IStorageDriver;
|
|
5
6
|
readonly registry: IRegistryDriver;
|
|
7
|
+
/** What a delete loop waits on before it reads the row again ({@link watchDeletes}). Absent, it does not wait. */
|
|
8
|
+
readonly clock?: {
|
|
9
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
10
|
+
};
|
|
6
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* The waits, in milliseconds, before each further read of the row when a delete loop finds the pointer on a generation
|
|
14
|
+
* it deleted, with no object under it ({@link watchDeletes}). A rollback that moved the pointer there checks the bucket
|
|
15
|
+
* after its move, and when the check runs after the delete it finds the object gone and puts the pointer back: one
|
|
16
|
+
* listing of the segment's objects (one page while the segment holds under a thousand) and one compare-and-swap, two
|
|
17
|
+
* round trips, three when a lease write lands first. A rollback client farther from the store than the deleting
|
|
18
|
+
* client takes that much longer, so the loop reads the row once at once and, with a clock that can wait, three more
|
|
19
|
+
* times over 1.75 s in all, which covers clients a few hundred milliseconds away. Paid only when a read finds the
|
|
20
|
+
* pointer on a missing generation the loop deleted, never on a pass where nothing raced.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DELETE_SETTLE_WAITS_MS: readonly number[];
|
|
23
|
+
/** What a delete loop has deleted, held to every read of the segment's row it makes after a delete. */
|
|
24
|
+
interface DeleteWatch {
|
|
25
|
+
/** Note a delete that landed. */
|
|
26
|
+
deleted(generation: number): void;
|
|
27
|
+
/** Every generation noted so far. */
|
|
28
|
+
readonly gone: ReadonlySet<number>;
|
|
29
|
+
/**
|
|
30
|
+
* `row`, a read of the row made after `held`, the row a delete was decided on, held to what was deleted: the deleted
|
|
31
|
+
* generation it leaves the pointer on, with no object under it once the row is read again, or `undefined`.
|
|
32
|
+
*/
|
|
33
|
+
check(held: RegistryRecord, row: RegistryRecord | null): Promise<number | undefined>;
|
|
34
|
+
/** The same for one more read of the row, made only when a delete landed since the last {@link check}. */
|
|
35
|
+
settle(held: RegistryRecord): Promise<number | undefined>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Hold a delete loop's reads of the row to what it deleted.
|
|
39
|
+
*
|
|
40
|
+
* Every loop that deletes generations reads the row before each delete, and one round trip remains between that read
|
|
41
|
+
* and the delete. A rollback that lands inside it onto the generation being deleted passes its own check (a listing made
|
|
42
|
+
* before the delete) and leaves the pointer on an object that is gone. Nothing can put the object back, so the loop
|
|
43
|
+
* detects it instead: every read of the row it makes after a delete, and one more after its last delete when it made no
|
|
44
|
+
* read since, is held to what it deleted.
|
|
45
|
+
*
|
|
46
|
+
* A read names a deleted generation when its row is active, of the incarnation `held` was read from, and its pointer
|
|
47
|
+
* names one. A tombstone never does, since no rollback moves a `destroyed` row's pointer, and neither does a row purged
|
|
48
|
+
* and made again, whose numbers are its own. Then:
|
|
49
|
+
*
|
|
50
|
+
* - The bucket is asked, with one zero-byte read, a metadata request on every shipped driver. A number can be taken
|
|
51
|
+
* again once its object is deleted (invariant 2): after a rollback below it, a load can number it afresh and
|
|
52
|
+
* publish, and then the object there is that load's, and nothing is missing. Where the storage driver ignores a
|
|
53
|
+
* delete's condition, the loop's own delete can take that object too, and the read finds it missing, which it is.
|
|
54
|
+
* - A missing object is not yet the answer. A rollback whose own check ran after the delete finds the object gone and
|
|
55
|
+
* puts the pointer back, and the loop's read can fall between its move and that undo. So the row is read again, at
|
|
56
|
+
* once and, with a clock that can wait, after each of {@link DELETE_SETTLE_WAITS_MS}, each read judged as the first
|
|
57
|
+
* was, the bucket asked again; the answer is the generation the last of them names, only if every one names a
|
|
58
|
+
* deleted generation with no object under it.
|
|
59
|
+
*
|
|
60
|
+
* A read of the bucket that fails in any other way than "not found" throws: it says nothing about the object.
|
|
61
|
+
*/
|
|
62
|
+
export declare function watchDeletes(deps: GenerationDeps, ref: SegmentRef): DeleteWatch;
|
|
63
|
+
/** Who deleted the generation a {@link pointerOnDeletedError} names, which leads its message. */
|
|
64
|
+
type Deleter = {
|
|
65
|
+
readonly kind: 'collection';
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: 'erasure';
|
|
68
|
+
} | {
|
|
69
|
+
readonly kind: 'load';
|
|
70
|
+
readonly published: number;
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: 'refused-load';
|
|
73
|
+
readonly refusal: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The error for a pointer a delete loop found on a generation it deleted ({@link watchDeletes}). The writer that moved
|
|
77
|
+
* the pointer there is a rollback, or, where the storage driver ignores a delete's condition, a load that took the
|
|
78
|
+
* number again and published. A rollback slower than the loop's further reads can still put the pointer back after
|
|
79
|
+
* them, so the message says to look before acting.
|
|
80
|
+
*/
|
|
81
|
+
export declare function pointerOnDeletedError(ref: SegmentRef, deleter: Deleter, generation: number): NotFoundError;
|
|
7
82
|
/**
|
|
8
83
|
* The generation number a writer should use for the segment's **next** object: one above the highest generation
|
|
9
84
|
* the registry points at *or* that is present in Storage — whichever is higher. The erasure rewrite numbers this
|
|
@@ -20,7 +95,7 @@ export interface GenerationDeps {
|
|
|
20
95
|
*/
|
|
21
96
|
export declare function nextGeneration(ref: SegmentRef, deps: GenerationDeps): Promise<number>;
|
|
22
97
|
/** The number a load takes, and whether one existence check proved it free (so nothing is above the pointer). */
|
|
23
|
-
|
|
98
|
+
interface LoadNumber {
|
|
24
99
|
readonly generation: number;
|
|
25
100
|
/**
|
|
26
101
|
* `true` when the number is `currentGen + 1` and the check found no object holding it. Collection by name rests
|
|
@@ -28,6 +103,8 @@ export interface LoadNumber {
|
|
|
28
103
|
* the listing would have kept is a name the pass takes. `false` when the listing numbered the load.
|
|
29
104
|
*/
|
|
30
105
|
readonly checked: boolean;
|
|
106
|
+
/** The generations that listing found, when the load listed: a seed of the ledger reads them instead of listing again. */
|
|
107
|
+
readonly listed?: readonly number[];
|
|
31
108
|
}
|
|
32
109
|
/**
|
|
33
110
|
* The generation number a load takes, from the row it has already read: `currentGen + 1` (0 with no row or no
|
|
@@ -73,6 +150,11 @@ export declare const LIST_COLLECTION_CADENCE = 16;
|
|
|
73
150
|
* a pin taken while it was still active re-checks the status on every open — and nothing else in the library
|
|
74
151
|
* would ever collect them, so without this those objects are billed forever.
|
|
75
152
|
*
|
|
153
|
+
* **Throws {@link NotFoundError} when a rollback moved the pointer onto a generation it deleted**: the rollback landed
|
|
154
|
+
* between the pass's re-proof of the row and that delete, so the pointer names an object that is gone. The pass finds
|
|
155
|
+
* it at its next re-proof, or, after its last delete, by reading the row once more. A deleted object cannot be
|
|
156
|
+
* restored: the answer is a rollback to a generation still in the bucket.
|
|
157
|
+
*
|
|
76
158
|
* **Throws {@link WriteConflictError} when it cannot act on the row it read**: the row is gone when it is re-read,
|
|
77
159
|
* after the listing or before a delete; on an active segment, the pointer has fallen below the cutoff before a
|
|
78
160
|
* delete; on a `destroyed` segment, the row's token has changed. Not to be confused with the empty array it still
|
|
@@ -97,33 +179,52 @@ export declare const LIST_COLLECTION_CADENCE = 16;
|
|
|
97
179
|
export declare function gcOrphanGenerations(ref: SegmentRef, deps: GenerationDeps, options?: {
|
|
98
180
|
keep?: number;
|
|
99
181
|
}): Promise<number[]>;
|
|
182
|
+
/**
|
|
183
|
+
* {@link gcOrphanGenerations}, reporting a rollback onto a generation it deleted in `missing` instead of throwing it: the
|
|
184
|
+
* pass stopped there. For a caller that records what it deleted before it raises, as an erasure does. With `skipKnown` the
|
|
185
|
+
* pass also spares every generation the row names when it reads the row before a delete (an erasure's passes on an active
|
|
186
|
+
* row, which remove from the row what they mean to delete first).
|
|
187
|
+
*/
|
|
188
|
+
export declare function collectBelowPointer(ref: SegmentRef, deps: GenerationDeps, options?: {
|
|
189
|
+
keep?: number;
|
|
190
|
+
skipKnown?: boolean;
|
|
191
|
+
}): Promise<{
|
|
192
|
+
readonly deleted: number[];
|
|
193
|
+
readonly missing?: number;
|
|
194
|
+
readonly knownSpared?: number[];
|
|
195
|
+
}>;
|
|
100
196
|
/**
|
|
101
197
|
* What makes a collection spare leased generations: the clock that says whether a lease has ended. Only a load's
|
|
102
198
|
* collection passes one, and a load with no clock passes none: it cannot tell a live lease from an ended one, so it
|
|
103
199
|
* reads none. A pass without a guard never reads the row's leases, which is what keeps erasure, shred and drop deleting
|
|
104
200
|
* what a lease names.
|
|
105
201
|
*/
|
|
106
|
-
|
|
202
|
+
interface LeaseGuard {
|
|
107
203
|
readonly now: () => number;
|
|
108
204
|
}
|
|
109
205
|
/** What a load's publish recorded in the row, as `publishGeneration` reports it. */
|
|
110
206
|
export interface PublishedKept {
|
|
111
|
-
/** The
|
|
207
|
+
/** The window the publish recorded, as numbers, or `undefined` when it recorded none (the row then does not know which are kept). */
|
|
112
208
|
readonly list: readonly number[] | undefined;
|
|
209
|
+
/** The ledger the publish wrote, in the order the generations were made current, when it wrote one. */
|
|
210
|
+
readonly known?: readonly KnownGeneration[];
|
|
113
211
|
/** The names the publish pushed out of the window, ascending. */
|
|
114
212
|
readonly evict: readonly number[];
|
|
115
213
|
/** The token of the row the publish wrote, when it is known: a write settled by reading the row has none. */
|
|
116
214
|
readonly token: Token | undefined;
|
|
117
215
|
}
|
|
118
216
|
/**
|
|
119
|
-
* Collect after a load's publish. The row records the generations the load keeps, so the pass deletes by name
|
|
120
|
-
* publish pushed out of that window ({@link deleteEvicted}) and lists the segment ({@link gcOrphanGenerations}'s
|
|
217
|
+
* Collect after a load's publish. The row's ledger records the generations the load keeps, so the pass deletes by name
|
|
218
|
+
* what the publish pushed out of that window ({@link deleteEvicted}) and lists the segment ({@link gcOrphanGenerations}'s
|
|
121
219
|
* pass) only when it cannot rely on the row or must catch what a name does not:
|
|
122
220
|
*
|
|
123
|
-
* - the
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
221
|
+
* - the publish recorded no window to collect by name (a `keep` above {@link MAX_KEPT_GENERATIONS}, a publish that
|
|
222
|
+
* seeded its ledger from the bucket, a row with no usable ledger, or a publish whose write was settled by reading the
|
|
223
|
+
* row): the pass keeps `keep` of the generations present below the pointer, the ledger's own most recently current
|
|
224
|
+
* names first and then the newest others by number, or, over a row with no usable ledger, `previous` first and then
|
|
225
|
+
* the newest by number ({@link keptWindow}), and writes nothing: the publish already wrote the ledger, which is what
|
|
226
|
+
* the next load derives its window from. It spares every name the row knows when it re-reads the row before a
|
|
227
|
+
* delete, which a publish or a rollback that landed since can have added to;
|
|
127
228
|
* - the guard found the current generation's object gone, or, for a `keep` of 1 or more when the guard took the size
|
|
128
229
|
* from the row's summary and opened nothing, one zero-byte read of it does not find it (`proveCurrent`): the
|
|
129
230
|
* window then counts a generation that is not there, so the same pass keeps what is;
|
|
@@ -135,10 +236,13 @@ export interface PublishedKept {
|
|
|
135
236
|
* below it. A load onto a `destroyed` row is refused at its publish and never reaches here; a drop that lands after
|
|
136
237
|
* the publish leaves this pass collecting the tombstone's generations, and the drop's own sweep takes the rest.
|
|
137
238
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* a
|
|
141
|
-
*
|
|
239
|
+
* Any fault of the collection is raised, as every fault of a collection after a landed publish is.
|
|
240
|
+
*
|
|
241
|
+
* So is a rollback onto a generation the pass deleted, which the pass finds by holding every read of the row it makes
|
|
242
|
+
* after a delete to what it deleted, and by reading the row once more after its last delete
|
|
243
|
+
* ({@link watchDeletes}): it throws {@link NotFoundError}, saying the load took effect at `generation` and the
|
|
244
|
+
* generation the pointer names is missing. That read is the one request this adds to a load whose pass deleted
|
|
245
|
+
* anything.
|
|
142
246
|
*/
|
|
143
247
|
export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
144
248
|
generation: number;
|
|
@@ -156,6 +260,11 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
|
|
|
156
260
|
proveCurrent?: GenKey;
|
|
157
261
|
/** Spare the generations the row's live leases hold. Absent, the pass ignores leases. */
|
|
158
262
|
leases?: LeaseGuard;
|
|
263
|
+
/**
|
|
264
|
+
* The generation the row named as current when the load read it, which its publish superseded. A window kept by
|
|
265
|
+
* listing holds it first, while the pointer is still the load's. Absent: the load found no pointer.
|
|
266
|
+
*/
|
|
267
|
+
previous?: number;
|
|
159
268
|
}): Promise<number[]>;
|
|
160
269
|
/**
|
|
161
270
|
* Delete, with no listing, the generations a publish of `generation` pushed out of its row's window, `evict`, taken
|
|
@@ -171,9 +280,14 @@ export declare function collectAfterLoad(ref: SegmentRef, deps: GenerationDeps,
|
|
|
171
280
|
* from taking a generation that a later publish, after a rollback, put in the window. A pointer that has moved above
|
|
172
281
|
* `generation` does not stop it: a publish landing in the meantime leaves routine collection working. A fault, a
|
|
173
282
|
* registry read or a delete that throws, propagates, with the pointer at `generation`.
|
|
283
|
+
*
|
|
284
|
+
* A rollback onto a name it deleted is not a stop: every read of the row after a delete is held to what it deleted, and
|
|
285
|
+
* so is one more read after its last delete when it made none since ({@link watchDeletes}). One that names a
|
|
286
|
+
* deleted generation throws {@link NotFoundError}, saying the load took effect at `generation`.
|
|
174
287
|
*/
|
|
175
288
|
export declare function deleteEvicted(ref: SegmentRef, deps: GenerationDeps, options: {
|
|
176
289
|
generation: number;
|
|
177
290
|
evict: readonly number[];
|
|
178
291
|
leases?: LeaseGuard;
|
|
179
292
|
}): Promise<number[]>;
|
|
293
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generations a segment's bucket holds.
|
|
3
|
+
*/
|
|
4
|
+
import type { IStorageDriver, SegmentRef } from './ports.js';
|
|
5
|
+
/**
|
|
6
|
+
* The generations the bucket holds for `ref`, from one listing, each once and in ascending order. A listing that spans
|
|
7
|
+
* a purge and a re-create can yield one number twice (the objects are re-created under the numbers just swept), so the
|
|
8
|
+
* duplicates are dropped here rather than counted by each caller. A caller that stops at the first object it wants
|
|
9
|
+
* reads the listing itself.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generationsOf(storage: IStorageDriver, ref: SegmentRef): Promise<number[]>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
2
|
+
/** A validated range: inclusive id bounds, as the chunk key and the remainder at each end. */
|
|
3
|
+
export interface IdWindow {
|
|
4
|
+
readonly loKey: number;
|
|
5
|
+
readonly loRem: number;
|
|
6
|
+
readonly hiKey: number;
|
|
7
|
+
readonly hiRem: number;
|
|
8
|
+
}
|
|
9
|
+
/** A range as a read is asked for it: each bound optional. */
|
|
10
|
+
interface IdBounds {
|
|
11
|
+
readonly after?: number | undefined;
|
|
12
|
+
readonly through?: number | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Throws {@link ValidationError} unless `bound` is left out or is an id: an integer from 0 to 4294967295. The message
|
|
16
|
+
* says what is wrong and never carries the value, which is an id.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertIdBound(name: 'after' | 'through', bound: number | undefined): void;
|
|
19
|
+
/**
|
|
20
|
+
* Validate a read's range. `null` when none was asked for, so the read does no extra work; `'empty'` when no id can be
|
|
21
|
+
* in it (`after >= through`, which is a cursor at the end of its window, not an error).
|
|
22
|
+
*/
|
|
23
|
+
export declare function windowOf(range: IdBounds | undefined): IdWindow | 'empty' | null;
|
|
24
|
+
/** The window a range with neither bound set describes: every id. */
|
|
25
|
+
export declare const WHOLE_ID_SPACE: IdWindow;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the window cuts inside `chunkKey`: one of its two edge chunks, and only where the bound falls inside the chunk
|
|
28
|
+
* rather than on its boundary. A chunk the window covers whole is read as it is.
|
|
29
|
+
*/
|
|
30
|
+
export declare const isEdge: (chunkKey: number, w: IdWindow) => boolean;
|
|
31
|
+
/** The ids of an edge chunk that lie inside the window. The chunk iterates ascending, so it stops at the top. */
|
|
32
|
+
export declare function edgeIds(chunk: CodecBitmap, chunkKey: number, w: IdWindow): Generator<number>;
|
|
33
|
+
/** The ascending `keys` inside the window's chunk span, as a new array: two binary searches, no scan. */
|
|
34
|
+
export declare function keysWithin(keys: readonly number[], w: IdWindow): number[];
|
|
35
|
+
/**
|
|
36
|
+
* The ascending `keys` inside the window's chunk span (every key, for `null`) and above the key `after`: what a read
|
|
37
|
+
* that plans again after taking `after` still reads.
|
|
38
|
+
*/
|
|
39
|
+
export declare function keysAbove(keys: readonly number[], w: IdWindow | null, after: number): number[];
|
|
40
|
+
/** The index of the first element of ascending `a` greater than `value`: a binary search. */
|
|
41
|
+
export declare function firstAbove(a: Uint32Array, value: number): number;
|
|
42
|
+
/** The part of an edge chunk's ascending remainders `rem` inside the window, as a view of `rem`. */
|
|
43
|
+
export declare function remaindersWithin(rem: Uint32Array, chunkKey: number, w: IdWindow): Uint32Array;
|
|
44
|
+
/** An edge chunk cut to the window, as a new bitmap: the remainders {@link edgeIds} would yield. */
|
|
45
|
+
export declare function cutToWindow(codec: CodecInterface, chunk: CodecBitmap, chunkKey: number, w: IdWindow): CodecBitmap;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engines reading through one chunk cache share about invalidations: how many there have been, the segment
|
|
3
|
+
* each of the latest was of, and the chunk reads open right now. A store's own engine, each pin's and each combine's
|
|
4
|
+
* with a pinned operand read through one cache, and an invalidation through any of them must reach a read running on
|
|
5
|
+
* another: a read that had its bytes before the invalidation would otherwise write them back into the cache it
|
|
6
|
+
* shares, and later reads would be answered from them, an erased id included, until the cache evicted them.
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* engine (store) ──┐
|
|
10
|
+
* engine (pin) ──┼── one InvalidationScope ── epoch · latest invalidation of each segment · open reads
|
|
11
|
+
* engine (combine)─┘ └── one chunk cache
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* A stream records the epoch it opened at and asks {@link InvalidationRecord.invalidatedSince} before it caches a
|
|
15
|
+
* chunk, so the scope holds no reference to a stream: a read its caller drops without closing holds nothing here. The
|
|
16
|
+
* `.crbm` source keeps an {@link InvalidationRecord} of its own, by segment, which a pinned stream asks before it hands
|
|
17
|
+
* out a chunk, so only its own segment's invalidation makes it open its object again.
|
|
18
|
+
*
|
|
19
|
+
* Internal to core: not exported from any entry point.
|
|
20
|
+
*/
|
|
21
|
+
import type { CodecBitmap } from './codec.js';
|
|
22
|
+
/**
|
|
23
|
+
* How many segments' latest invalidation a record remembers. Past it the oldest is forgotten, and a segment the record
|
|
24
|
+
* does not remember, never-invalidated ones included, counts as invalidated at the newest invalidation it has
|
|
25
|
+
* forgotten, which is always on the safe side. In the engines' scope it means a stream that opened before then caches
|
|
26
|
+
* no more of that segment's chunks, which costs it cache entries, never correctness: so a sweep that invalidates more
|
|
27
|
+
* than this many segments at once (a `retireExpired` or an `eraseSubject` over a large fleet) stops every stream open
|
|
28
|
+
* at the time caching, whatever segment it reads. In the `.crbm` source it means a pinned stream looks its pin up
|
|
29
|
+
* again, which costs a request only where the reader cache has let the pin's reader go. With names at their
|
|
30
|
+
* 256-character limit a record is about 0.6 MB, and about 0.1 MB with 30-character names; a store keeps two.
|
|
31
|
+
*/
|
|
32
|
+
export declare const REMEMBERED_INVALIDATIONS = 1024;
|
|
33
|
+
/** A chunk read in flight: `token` tells the read whether its entry is still the registered one. */
|
|
34
|
+
export interface OpenRead {
|
|
35
|
+
readonly token: object;
|
|
36
|
+
readonly read: Promise<CodecBitmap | null>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* How many invalidations there have been, and the epoch of each of the latest {@link REMEMBERED_INVALIDATIONS}
|
|
40
|
+
* segments' latest one, by whatever key names a segment to its owner: a cache-key prefix in the engines' scope, a
|
|
41
|
+
* segment key in the `.crbm` source.
|
|
42
|
+
*/
|
|
43
|
+
export declare class InvalidationRecord {
|
|
44
|
+
private count;
|
|
45
|
+
/** The epoch of each remembered segment's latest invalidation, the oldest first. */
|
|
46
|
+
private readonly latest;
|
|
47
|
+
/** The epoch of the newest invalidation the record has forgotten: what a segment it does not remember counts as. */
|
|
48
|
+
private floor;
|
|
49
|
+
/** How many invalidations there have been: a read compares it with the epoch it began under. */
|
|
50
|
+
get epoch(): number;
|
|
51
|
+
/** Record an invalidation of the segment `key` names. */
|
|
52
|
+
invalidate(key: string): void;
|
|
53
|
+
/** Whether the segment `key` names has been invalidated since epoch `since`. */
|
|
54
|
+
invalidatedSince(key: string, since: number): boolean;
|
|
55
|
+
}
|
|
56
|
+
/** The engines' record, by cache-key prefix, and the chunk reads open on all of them. */
|
|
57
|
+
export declare class InvalidationScope extends InvalidationRecord {
|
|
58
|
+
/**
|
|
59
|
+
* The chunk reads open right now, by cache key: a caller that misses the cache and finds its key here awaits that
|
|
60
|
+
* read instead of making its own. An entry lives from the request until it settles, so the map holds at most one
|
|
61
|
+
* promise per distinct key in flight. An invalidation drops the segment's entries, and a read whose entry is gone
|
|
62
|
+
* caches nothing.
|
|
63
|
+
*/
|
|
64
|
+
private readonly openReads;
|
|
65
|
+
/** Record an invalidation of the segment whose cache keys start with `prefix`, and drop its open reads. */
|
|
66
|
+
invalidate(prefix: string): void;
|
|
67
|
+
/** The read of `key` open now, for a caller that misses the cache to await instead of making its own. */
|
|
68
|
+
openRead(key: string): Promise<CodecBitmap | null> | undefined;
|
|
69
|
+
/** Whether a read of `key` is open, without joining it. */
|
|
70
|
+
hasOpenRead(key: string): boolean;
|
|
71
|
+
/** Register `entry` as the open read of `key`. */
|
|
72
|
+
beginRead(key: string, entry: OpenRead): void;
|
|
73
|
+
/** `entry` has settled: forget it, unless an invalidation dropped it and a newer read has taken the key. */
|
|
74
|
+
endRead(key: string, entry: OpenRead): void;
|
|
75
|
+
/** Whether the read `token` names is still the open read of `key`: one an invalidation dropped caches nothing. */
|
|
76
|
+
isOpen(key: string, token: object): boolean;
|
|
77
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a row's known-generations ledger (`knownGens`), in one place. A registry holds a row to it when it writes
|
|
3
|
+
* and when it reads back, and `checkConsistency` holds a row to it before it reads or walks a ledger a registry of your
|
|
4
|
+
* own handed back, so the two cannot part on what a ledger is. Pure: no I/O, time or randomness.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Why `value` is not a ledger, or `undefined` when it is one (an absent ledger, `undefined`, passes: whether a row
|
|
8
|
+
* without one is usable is another rule). A ledger is an array of at most `max` entries, each exactly `{ generation,
|
|
9
|
+
* footerCrc }`, the generation a non-negative safe integer and unique, the checksum an integer from 0 to 2^32 - 1. The
|
|
10
|
+
* order is not checked: it is not derivable from the numbers. `show` renders a value for the description, which a
|
|
11
|
+
* caller can truncate.
|
|
12
|
+
*/
|
|
13
|
+
export declare function knownGensShapeFault(value: unknown, max: number, show?: (v: unknown) => string): string | undefined;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { KnownGeneration, RegistryRecord } from './ports.js';
|
|
2
|
+
/**
|
|
3
|
+
* The most generations a load that collects by name records as its window. A `keep` above it collects by listing.
|
|
4
|
+
*/
|
|
5
|
+
export declare const MAX_KEPT_GENERATIONS = 64;
|
|
6
|
+
/**
|
|
7
|
+
* A writer keeps entries that nothing protects and no lease holds only while the ledger holds fewer than this many.
|
|
8
|
+
*/
|
|
9
|
+
export declare const KNOWN_FILL = 128;
|
|
10
|
+
/** The most entries a writer writes, and the most the shared validator accepts at a write. */
|
|
11
|
+
export declare const MAX_KNOWN_GENERATIONS = 192;
|
|
12
|
+
/** The most entries a stored row may hold: twice what a writer writes. */
|
|
13
|
+
export declare const MAX_STORED_KNOWN_GENERATIONS = 384;
|
|
14
|
+
/**
|
|
15
|
+
* The row's ledger when a collection or a rollback may rely on it: present and not empty, on an active row with a
|
|
16
|
+
* pointer, its last entry the pointer, and, where the row's summary is in the clear and describes that generation, its
|
|
17
|
+
* checksum the one the summary's fingerprint carries. A well-formed ledger that disagrees with its row is read but not
|
|
18
|
+
* used (the row counts as one with no ledger), as a summary that names another generation is.
|
|
19
|
+
*/
|
|
20
|
+
export declare function usableKnownGens(record: RegistryRecord | null): readonly KnownGeneration[] | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The generations the row keeps as its window: the numbers of the usable ledger's entries below the pointer, in the order
|
|
23
|
+
* they were made current, or `undefined` when the ledger is not usable. A collection compares against these. An entry
|
|
24
|
+
* numbered above the pointer is one a rollback left; no collection can take it, so it is no part of the window.
|
|
25
|
+
*/
|
|
26
|
+
export declare function windowOf(record: RegistryRecord | null): readonly number[] | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the row's ledger is a window a load with a small `keep` can extend: usable, and naming no more than
|
|
29
|
+
* {@link MAX_KEPT_GENERATIONS} generations below the pointer that no live lease holds. A ledger carried through
|
|
30
|
+
* publishes with a larger `keep` (`*Into`, `materializeMany`) names up to 127, and a count by a small `keep` would keep
|
|
31
|
+
* fewer than it names, so the next small-`keep` load collects by listing instead of by name, over the same ledger. A
|
|
32
|
+
* generation held only for a lease is no part of the window and does not count toward the limit.
|
|
33
|
+
*/
|
|
34
|
+
export declare function carriesWindow(record: RegistryRecord | null, leased: ReadonlySet<number>): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* How many generations below its own a publish vouches for at most: what the writer cap leaves beside the new
|
|
37
|
+
* generation and the 64 generations live leases can hold.
|
|
38
|
+
*/
|
|
39
|
+
export declare const KNOWN_SPAN = 127;
|
|
40
|
+
/** Which generations below a new pointer a grace window holds, and which it lets go: both ascending. */
|
|
41
|
+
interface KeptWindow {
|
|
42
|
+
readonly kept: number[];
|
|
43
|
+
readonly evict: number[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The generations below a new pointer that a grace window of `keep` holds, out of `below`, when no ledger says which
|
|
47
|
+
* were made current most recently: the generation the publish superseded first, when it is among them, then the newest
|
|
48
|
+
* of the rest by number. When it is not among them (no generation was current, or its object has gone from the bucket),
|
|
49
|
+
* the newest `keep`.
|
|
50
|
+
*
|
|
51
|
+
* The superseded generation is the one reads were on until the publish, and, after a rollback, the one the operator
|
|
52
|
+
* restored, which a pin taken since reads, and the one to roll back to if the new load is bad too. The generations a
|
|
53
|
+
* rollback moved away from are newer by number, so a window by number alone would keep those and delete it. The window
|
|
54
|
+
* a listing keeps over a row with no usable ledger comes from here, and so does a ledger's first window
|
|
55
|
+
* (`ledger-seed.ts`); once the row has a ledger, its order says which were made current most recently.
|
|
56
|
+
*/
|
|
57
|
+
export declare function keptWindow(below: readonly number[], superseded: number | undefined, keep: number): KeptWindow;
|
|
58
|
+
/**
|
|
59
|
+
* `known`, held to the cap, in the same order. It always keeps the entries numbered in `protect`, then the ones numbered
|
|
60
|
+
* in `leased`, most recently current first, up to {@link MAX_KNOWN_GENERATIONS}, then the rest, most recently current
|
|
61
|
+
* first, while the ledger holds fewer than {@link KNOWN_FILL}. So a protected or leased entry is never dropped for room,
|
|
62
|
+
* and an unprotected one only when the ledger is full. "Most recently current" is the end of the list.
|
|
63
|
+
*/
|
|
64
|
+
export declare function capKnown(known: readonly KnownGeneration[], protect: ReadonlySet<number>, leased?: ReadonlySet<number>): KnownGeneration[];
|
|
65
|
+
/** `known` with `entry` made the most recent: any entry for its generation removed, and the entry added last. */
|
|
66
|
+
export declare function makeCurrent(known: readonly KnownGeneration[], entry: KnownGeneration): KnownGeneration[];
|
|
67
|
+
/** What a publish of a generation writes into the row, and which names it pushes out of the window. */
|
|
68
|
+
export interface KnownAfter {
|
|
69
|
+
/** The ledger to write: the generation published is its last entry. */
|
|
70
|
+
readonly known: readonly KnownGeneration[];
|
|
71
|
+
/** The window as numbers to collect by name, or `undefined` to collect by listing. */
|
|
72
|
+
readonly list: readonly number[] | undefined;
|
|
73
|
+
/** The generations the window no longer keeps, ascending: the names to delete. */
|
|
74
|
+
readonly evict: readonly number[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* What a publish of `generation` (its object's footer checksum `footerCrc`) over `prev` writes into the row.
|
|
78
|
+
*
|
|
79
|
+
* The ledger is in the order the generations were made current, so the order says which were current most recently.
|
|
80
|
+
* That ranks the window, and the window is drawn from the entries numbered below the new generation only: collection
|
|
81
|
+
* can only take generations numbered below the pointer. The window is the `keep` most recently current of them (at most
|
|
82
|
+
* {@link KNOWN_SPAN}), so the generation the publish superseded, the ledger's last entry, comes first, and after a
|
|
83
|
+
* rollback the ones the operator restored outrank the ones the rollback moved away from.
|
|
84
|
+
*
|
|
85
|
+
* What it writes, in the order the entries were made current and the new generation last:
|
|
86
|
+
*
|
|
87
|
+
* - the window;
|
|
88
|
+
* - the entries below the new generation that `leased` names (a generation a live lease holds stays known, so it stays a
|
|
89
|
+
* rollback target while the lease lives), whatever `keep` says;
|
|
90
|
+
* - the entries numbered above the new generation, which a rollback left and a load numbered under: they are carried and
|
|
91
|
+
* never evicted, and take no slot of `keep`, while the ledger holds fewer than {@link KNOWN_FILL} entries (see
|
|
92
|
+
* {@link capKnown});
|
|
93
|
+
* - the new generation. An entry already numbered `generation` is a stale one (its object went outside the ledger) and
|
|
94
|
+
* is replaced.
|
|
95
|
+
*
|
|
96
|
+
* `evict` is the entries below the new generation that it leaves out, ascending: never a name at or above the new
|
|
97
|
+
* generation. With a `keep` of at most {@link MAX_KEPT_GENERATIONS} the publish collects by name, so `list` is the
|
|
98
|
+
* generations of the ledger below the new one; above that, or with no `keep`, `list` is `undefined` and `evict` empty, and
|
|
99
|
+
* a load lists. A row with a pointer and no usable ledger writes the new generation alone and lists, unless the caller
|
|
100
|
+
* passes a `seed` (see `ledger-seed.ts`): the window a count keeps, read from the bucket, then the new generation, and
|
|
101
|
+
* the publish collects by that count. A publish that clears leases is an erasure's rewrite, which deletes every older
|
|
102
|
+
* generation, so its ledger is the new generation alone.
|
|
103
|
+
*/
|
|
104
|
+
export declare function nextKnown(prev: RegistryRecord | null, generation: number, footerCrc: number, keep: number | undefined, options?: {
|
|
105
|
+
readonly clearsLeases?: boolean;
|
|
106
|
+
readonly seed?: readonly KnownGeneration[];
|
|
107
|
+
readonly leased?: ReadonlySet<number>;
|
|
108
|
+
}): KnownAfter;
|
|
109
|
+
/**
|
|
110
|
+
* The ledger a rollback onto `target` writes over `prev`: the ledger it found with the target moved to the end, as the
|
|
111
|
+
* most recently current. A rollback never starts a ledger: over a row with no usable ledger it writes none, and the
|
|
112
|
+
* next publish seeds one. A target the ledger did not name
|
|
113
|
+
* is an insert, and the ledger it makes is held to the cap with the target and the generation the rollback leaves
|
|
114
|
+
* protected; a known target's ledger is written as read, unless another build wrote one that is past the writer's cap.
|
|
115
|
+
*/
|
|
116
|
+
export declare function rollbackKnown(prev: RegistryRecord, target: KnownGeneration): readonly KnownGeneration[] | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* The row's ledger with the entries numbered in `doomed` removed, which a write that is about to delete those
|
|
119
|
+
* generations makes first: the entries left keep their order, and the current generation is never removed (the ledger
|
|
120
|
+
* must end with it). Another build's ledger past the writer's cap is held to it, with the current generation protected
|
|
121
|
+
* and the generations the row's leases name kept. `undefined` for a row with no usable ledger, which names nothing to
|
|
122
|
+
* remove.
|
|
123
|
+
*/
|
|
124
|
+
export declare function pruneKnown(record: RegistryRecord, doomed: ReadonlySet<number>): readonly KnownGeneration[] | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* The ledger that puts a row back as it was, for the undo of a rollback: the row's own when it is usable (the
|
|
127
|
+
* generation it left is its last entry), held to the writer's cap when another build wrote one past it, with the
|
|
128
|
+
* generation the undo leaves and the one it moves from protected; `undefined` for a row that had none to put back.
|
|
129
|
+
*/
|
|
130
|
+
export declare function describingKnown(record: RegistryRecord, movedTo: number): readonly KnownGeneration[] | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Whether the ledger is full: it names at least {@link KNOWN_FILL} generations, which is when a writer may have let an
|
|
133
|
+
* older one go (a publish vouches for at most {@link KNOWN_SPAN} below its own, and an inserting rollback drops an entry
|
|
134
|
+
* once 128 that no lease holds are named). Leased entries do not switch it off: a lease on an entry inside the span
|
|
135
|
+
* leaves the ledger as full as it was. Past the least recently current entry of a full ledger, a rollback can no longer
|
|
136
|
+
* tell a generation the row forgot from one it never knew, so it judges a target numbered below that entry as it did
|
|
137
|
+
* before the ledger existed.
|
|
138
|
+
*/
|
|
139
|
+
export declare function isFullLedger(known: readonly KnownGeneration[]): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* The number a target below the pointer is judged against on a full ledger: the least recently current entry that no
|
|
142
|
+
* lease entry of the row names (live or ended: a rollback has no clock). A leased entry stays in the ledger for its lease,
|
|
143
|
+
* however far the window has moved on since, so it does not mark where the ledger stopped remembering; the first entry no
|
|
144
|
+
* lease holds does. A ledger whose every entry is leased is judged against its first entry.
|
|
145
|
+
*/
|
|
146
|
+
export declare function fallbackBound(record: RegistryRecord, known: readonly KnownGeneration[]): number;
|
|
147
|
+
export {};
|
package/dist/core/leases.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export declare const MAX_STORED_LEASES = 256;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const LEASE_CAS_ATTEMPTS: number;
|
|
20
20
|
/**
|
|
21
|
-
* Fresh writes sent after one that got no answer and left the row as it was, by the lease writers
|
|
22
|
-
* renewal of the row.
|
|
21
|
+
* Fresh writes sent after one that got no answer and left the row as it was, by the lease writers, by an erasure's
|
|
22
|
+
* renewal of the row and by a publish: four sends of the row write in all.
|
|
23
23
|
*/
|
|
24
24
|
export declare const UNANSWERED_RESENDS = 3;
|
|
25
25
|
/** The upper bound of the first of those waits, in ms; each next one doubles it. */
|
|
@@ -65,7 +65,7 @@ export declare const LEASE_ONLY_RETRIES: number;
|
|
|
65
65
|
*/
|
|
66
66
|
export declare function onlyLeasesDiffer(held: RegistryRecord, now: RegistryRecord, alsoIgnore?: readonly (keyof RegistryRecord)[]): boolean;
|
|
67
67
|
/** A writer's count of the lease-only changes it has waited out, and the wait. One per call of the writer. */
|
|
68
|
-
|
|
68
|
+
interface LeaseChurn {
|
|
69
69
|
/**
|
|
70
70
|
* When `now` is `held` changed only in its leases and the bound is not spent: wait, then read the row again with
|
|
71
71
|
* `reread`, and return what it found. The wait comes before the read, so the writer's next write is made against a row
|
|
@@ -124,3 +124,4 @@ export declare function takeLease(ref: SegmentRef, deps: LeaseDeps, take: LeaseT
|
|
|
124
124
|
* races and the registry's `TransientError` for a write that stayed unanswered over an unchanged row.
|
|
125
125
|
*/
|
|
126
126
|
export declare function releaseLease(ref: SegmentRef, deps: LeaseDeps, holder: string): Promise<void>;
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReadRetry } from './retry.js';
|
|
2
|
+
import type { IStorageDriver, KnownGeneration, RegistryRecord, SegmentRef } from './ports.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a publish over `row` seeds its ledger: only at a birth, over an active row that has a pointer and no usable
|
|
5
|
+
* ledger. A usable ledger is never seeded again, however wide it is.
|
|
6
|
+
*/
|
|
7
|
+
export declare function wantsSeed(row: RegistryRecord | null): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The entries a seed names: the listed generations below the new one that a live lease holds and the window does not
|
|
10
|
+
* keep, then the window oldest first by number with the generation the row named as current last. `listed` is the
|
|
11
|
+
* listing the load's numbering made, when it made one; otherwise the bucket is listed once. Every entry's checksum is
|
|
12
|
+
* read from its object's footer (a 104-byte read, under the store's read retry, {@link SEED_READS} at a time), the
|
|
13
|
+
* superseded generation's too: when a clear summary describes it with another checksum, the row and the bucket
|
|
14
|
+
* disagree about that number and it is left out. A generation gone from the bucket, or whose footer fails its own
|
|
15
|
+
* checks, is left out; any other failed read throws, before anything is published.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readSeed(storage: IStorageDriver, ref: SegmentRef, row: RegistryRecord | null, generation: number, keep: number, listed: readonly number[] | undefined, leased: ReadonlySet<number>, readRetry?: ReadRetry): Promise<KnownGeneration[]>;
|