@bradensbay/globals-core 0.2.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.
Files changed (98) hide show
  1. package/README.md +69 -0
  2. package/dist/src/allocator.d.ts +78 -0
  3. package/dist/src/allocator.d.ts.map +1 -0
  4. package/dist/src/allocator.js +211 -0
  5. package/dist/src/allocator.js.map +1 -0
  6. package/dist/src/arena.d.ts +81 -0
  7. package/dist/src/arena.d.ts.map +1 -0
  8. package/dist/src/arena.js +224 -0
  9. package/dist/src/arena.js.map +1 -0
  10. package/dist/src/checksum.d.ts +5 -0
  11. package/dist/src/checksum.d.ts.map +1 -0
  12. package/dist/src/checksum.js +35 -0
  13. package/dist/src/checksum.js.map +1 -0
  14. package/dist/src/draft.d.ts +48 -0
  15. package/dist/src/draft.d.ts.map +1 -0
  16. package/dist/src/draft.js +427 -0
  17. package/dist/src/draft.js.map +1 -0
  18. package/dist/src/errors.d.ts +56 -0
  19. package/dist/src/errors.d.ts.map +1 -0
  20. package/dist/src/errors.js +69 -0
  21. package/dist/src/errors.js.map +1 -0
  22. package/dist/src/external.d.ts +52 -0
  23. package/dist/src/external.d.ts.map +1 -0
  24. package/dist/src/external.js +68 -0
  25. package/dist/src/external.js.map +1 -0
  26. package/dist/src/hamt.d.ts +87 -0
  27. package/dist/src/hamt.d.ts.map +1 -0
  28. package/dist/src/hamt.js +643 -0
  29. package/dist/src/hamt.js.map +1 -0
  30. package/dist/src/history.d.ts +45 -0
  31. package/dist/src/history.d.ts.map +1 -0
  32. package/dist/src/history.js +49 -0
  33. package/dist/src/history.js.map +1 -0
  34. package/dist/src/index.d.ts +55 -0
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/index.js +38 -0
  37. package/dist/src/index.js.map +1 -0
  38. package/dist/src/inspect.d.ts +74 -0
  39. package/dist/src/inspect.d.ts.map +1 -0
  40. package/dist/src/inspect.js +127 -0
  41. package/dist/src/inspect.js.map +1 -0
  42. package/dist/src/layout.d.ts +130 -0
  43. package/dist/src/layout.d.ts.map +1 -0
  44. package/dist/src/layout.js +149 -0
  45. package/dist/src/layout.js.map +1 -0
  46. package/dist/src/liveness.d.ts +48 -0
  47. package/dist/src/liveness.d.ts.map +1 -0
  48. package/dist/src/liveness.js +92 -0
  49. package/dist/src/liveness.js.map +1 -0
  50. package/dist/src/owner.d.ts +130 -0
  51. package/dist/src/owner.d.ts.map +1 -0
  52. package/dist/src/owner.js +368 -0
  53. package/dist/src/owner.js.map +1 -0
  54. package/dist/src/reader.d.ts +122 -0
  55. package/dist/src/reader.d.ts.map +1 -0
  56. package/dist/src/reader.js +325 -0
  57. package/dist/src/reader.js.map +1 -0
  58. package/dist/src/readers.d.ts +60 -0
  59. package/dist/src/readers.d.ts.map +1 -0
  60. package/dist/src/readers.js +122 -0
  61. package/dist/src/readers.js.map +1 -0
  62. package/dist/src/retained.d.ts +39 -0
  63. package/dist/src/retained.d.ts.map +1 -0
  64. package/dist/src/retained.js +99 -0
  65. package/dist/src/retained.js.map +1 -0
  66. package/dist/src/schema.d.ts +82 -0
  67. package/dist/src/schema.d.ts.map +1 -0
  68. package/dist/src/schema.js +18 -0
  69. package/dist/src/schema.js.map +1 -0
  70. package/dist/src/store.d.ts +83 -0
  71. package/dist/src/store.d.ts.map +1 -0
  72. package/dist/src/store.js +127 -0
  73. package/dist/src/store.js.map +1 -0
  74. package/dist/src/strings.d.ts +37 -0
  75. package/dist/src/strings.d.ts.map +1 -0
  76. package/dist/src/strings.js +144 -0
  77. package/dist/src/strings.js.map +1 -0
  78. package/dist/src/tags.d.ts +47 -0
  79. package/dist/src/tags.d.ts.map +1 -0
  80. package/dist/src/tags.js +53 -0
  81. package/dist/src/tags.js.map +1 -0
  82. package/dist/src/values.d.ts +90 -0
  83. package/dist/src/values.d.ts.map +1 -0
  84. package/dist/src/values.js +458 -0
  85. package/dist/src/values.js.map +1 -0
  86. package/dist/src/vector.d.ts +64 -0
  87. package/dist/src/vector.d.ts.map +1 -0
  88. package/dist/src/vector.js +387 -0
  89. package/dist/src/vector.js.map +1 -0
  90. package/dist/src/verify.d.ts +38 -0
  91. package/dist/src/verify.d.ts.map +1 -0
  92. package/dist/src/verify.js +179 -0
  93. package/dist/src/verify.js.map +1 -0
  94. package/dist/src/view.d.ts +66 -0
  95. package/dist/src/view.d.ts.map +1 -0
  96. package/dist/src/view.js +278 -0
  97. package/dist/src/view.js.map +1 -0
  98. package/package.json +28 -0
@@ -0,0 +1,325 @@
1
+ import { SharedArena } from "./arena.js";
2
+ import { ReaderTable } from "./readers.js";
3
+ import { RetainedRing } from "./retained.js";
4
+ import { Header } from "./layout.js";
5
+ import { StaleSnapshotError } from "./errors.js";
6
+ import { decodeValue } from "./values.js";
7
+ import { readPath, viewValue } from "./view.js";
8
+ import { verifyRoot } from "./verify.js";
9
+ /**
10
+ * How many times a reader retries the seqlock before it gives up on acquiring a fresh
11
+ * version. The window a commit holds the sequence odd is a handful of stores, so a reader
12
+ * that spins this many times is looking at a stalled or hostile writer, not contention.
13
+ */
14
+ const MAX_SPIN = 128;
15
+ /**
16
+ * A pinned version.
17
+ *
18
+ * Holding one guarantees the graph beneath it cannot change, because the writer never
19
+ * mutates a published node. It does not guarantee the version stays retained forever: past
20
+ * the retention cap the owner force advances, and the next decode raises
21
+ * StaleSnapshotError rather than reading recycled memory.
22
+ */
23
+ export class Snapshot {
24
+ versionId;
25
+ ownerGeneration;
26
+ #reader;
27
+ #slot;
28
+ #released = false;
29
+ #view;
30
+ constructor(reader, versionId, ownerGeneration, slot) {
31
+ this.#reader = reader;
32
+ this.versionId = versionId;
33
+ this.ownerGeneration = ownerGeneration;
34
+ this.#slot = slot;
35
+ }
36
+ get released() {
37
+ return this.#released;
38
+ }
39
+ /**
40
+ * The root value. Synchronous, no await, no round trip. This is the whole product.
41
+ *
42
+ * Validated on both sides of the decode. The check before it rejects a version that is
43
+ * already gone. The check after it is the one that matters: the owner raises the reclaim
44
+ * floor before it frees anything, so a version that is still above the floor once the
45
+ * decode has finished cannot have been freed while the decode was running. Validating
46
+ * only beforehand leaves a window in which a forced reclaim recycles the block and the
47
+ * reader returns a well formed but wrong value, which the soak harness found in about two
48
+ * reads out of four million.
49
+ */
50
+ get value() {
51
+ this.validate();
52
+ const decoded = viewValue(this.#viewContext(), this.#slot);
53
+ this.validate();
54
+ return decoded;
55
+ }
56
+ /**
57
+ * The whole version as a detached plain value, with no proxies and no arena references.
58
+ *
59
+ * Use it to hand state to something that has to own it, such as a serialiser or a worker.
60
+ * It costs a full decode, which is exactly what the lazy path exists to avoid, so it is a
61
+ * deliberate call rather than the default.
62
+ */
63
+ toJSON() {
64
+ this.validate();
65
+ const decoded = decodeValue(this.#reader.arena, this.#slot);
66
+ this.validate();
67
+ return decoded;
68
+ }
69
+ /**
70
+ * Read one path without building views for the nodes along it.
71
+ *
72
+ * The cheapest shape for a selector, and what the framework bindings use.
73
+ */
74
+ get(path) {
75
+ this.validate();
76
+ const value = readPath(this.#reader.arena, this.#slot, path);
77
+ this.validate();
78
+ return value;
79
+ }
80
+ /**
81
+ * The decode cache for this version.
82
+ *
83
+ * Built on first use and never shared with another version, so a reclaimed version cannot
84
+ * leave a decoded node reachable.
85
+ */
86
+ #viewContext() {
87
+ if (this.#view === undefined) {
88
+ this.#view = {
89
+ arena: this.#reader.arena,
90
+ validate: () => this.validate(),
91
+ cache: new Map(),
92
+ };
93
+ }
94
+ return this.#view;
95
+ }
96
+ /** The root slot, for callers decoding lazily rather than eagerly. */
97
+ get root() {
98
+ this.validate();
99
+ return this.#slot;
100
+ }
101
+ /**
102
+ * Prove this version is still retained. Two atomic loads and two comparisons, run on both
103
+ * sides of every decode. Cheap enough to sit on the read path, and the only thing standing
104
+ * between a stalled reader and freed memory.
105
+ */
106
+ validate() {
107
+ if (this.isValid())
108
+ return;
109
+ throw new StaleSnapshotError(this.versionId, this.#reader.reclaimFloor());
110
+ }
111
+ /**
112
+ * True when this snapshot can still be decoded. Never throws, and never constructs an
113
+ * error object, because it runs twice per read and a try/catch around the throwing form
114
+ * kept the whole path out of the optimising compiler.
115
+ */
116
+ isValid() {
117
+ return (!this.#released &&
118
+ this.versionId >= this.#reader.reclaimFloor() &&
119
+ this.#reader.ownerGeneration() === this.ownerGeneration);
120
+ }
121
+ release() {
122
+ if (this.#released)
123
+ return;
124
+ this.#released = true;
125
+ this.#reader.releaseSnapshot(this);
126
+ }
127
+ }
128
+ /**
129
+ * A reader.
130
+ *
131
+ * Attaching claims one slot in the reader table. Acquiring a snapshot is a seqlock read of
132
+ * the root, one atomic store to publish the pinned epoch, and one atomic load to confirm
133
+ * the version is above the reclaim floor. Nothing here blocks, allocates a promise, or
134
+ * sends a message.
135
+ */
136
+ export class ArenaReader {
137
+ arena;
138
+ slot;
139
+ generation;
140
+ #table;
141
+ #ring;
142
+ #heartbeat;
143
+ /**
144
+ * The header view, held directly.
145
+ *
146
+ * Every hop on the read path costs: `this.arena.loadHeader(field)` is a property load, a
147
+ * method call, and a private field read before the atomic load itself. A read validates
148
+ * four times per value, so holding the array here rather than reaching through the arena
149
+ * on each one is worth the small duplication.
150
+ */
151
+ #words;
152
+ #current;
153
+ #detached = false;
154
+ constructor(arena, options) {
155
+ this.arena = arena;
156
+ this.#words = arena.words;
157
+ this.#table = new ReaderTable(arena);
158
+ this.#ring = new RetainedRing(arena);
159
+ this.#heartbeat = options.heartbeat ?? true;
160
+ const claim = this.#table.claim();
161
+ this.slot = claim.slot;
162
+ this.generation = claim.generation;
163
+ }
164
+ static attach(buffer, options = {}) {
165
+ return new ArenaReader(SharedArena.attach(buffer), options);
166
+ }
167
+ get detached() {
168
+ return this.#detached;
169
+ }
170
+ reclaimFloor() {
171
+ return Atomics.load(this.#words, Header.ReclaimFloor);
172
+ }
173
+ ownerGeneration() {
174
+ return Atomics.load(this.#words, Header.OwnerGeneration);
175
+ }
176
+ /** The version the owner has currently published, without pinning it. */
177
+ publishedVersion() {
178
+ return Atomics.load(this.#words, Header.VersionId);
179
+ }
180
+ /**
181
+ * Acquire the current version and pin it.
182
+ *
183
+ * Returns the snapshot already held when the owner has not committed since, so a render
184
+ * loop that calls this every frame does not churn pins.
185
+ */
186
+ acquire() {
187
+ if (this.#detached)
188
+ throw new Error("this reader has been detached");
189
+ const held = this.#current;
190
+ if (held !== undefined && held.versionId === this.publishedVersion() && held.isValid()) {
191
+ return held;
192
+ }
193
+ // Only a version change can require a longer view, so the growth check belongs here
194
+ // rather than on every header access.
195
+ this.arena.refresh();
196
+ this.#words = this.arena.words;
197
+ if (this.#heartbeat)
198
+ this.#table.beat(this.slot);
199
+ for (let attempt = 0; attempt < MAX_SPIN; attempt += 1) {
200
+ const words = this.#words;
201
+ const before = Atomics.load(words, Header.Sequence);
202
+ if ((before & 1) !== 0)
203
+ continue; // a commit is in progress
204
+ const tag = Atomics.load(words, Header.RootTag);
205
+ const payload = Atomics.load(words, Header.RootPayload);
206
+ const versionId = Atomics.load(words, Header.VersionId);
207
+ const ownerGeneration = Atomics.load(words, Header.OwnerGeneration);
208
+ // Inside the seqlock window, so the checksum belongs to the root just read rather than
209
+ // to whatever the writer published in the meantime.
210
+ const checksum = Atomics.load(words, Header.RootChecksum);
211
+ if (Atomics.load(words, Header.Sequence) !== before)
212
+ continue; // torn, retry
213
+ // Publish the pin before checking the floor. The owner publishes the floor before it
214
+ // frees anything and rescans afterwards, so one of the two checks always catches a
215
+ // race. See ArenaOwner.reclaim for the full argument.
216
+ this.#table.pin(this.slot, versionId);
217
+ if (versionId < this.reclaimFloor())
218
+ continue;
219
+ if (!this.#ring.isLive(versionId))
220
+ continue;
221
+ // Verified read mode, once per version rather than once per read. A render loop that
222
+ // reads the same version a hundred times pays for this once.
223
+ verifyRoot(this.arena, { tag, payload }, versionId, checksum);
224
+ const snapshot = new Snapshot(this, versionId, ownerGeneration, { tag, payload });
225
+ this.#current = snapshot;
226
+ return snapshot;
227
+ }
228
+ // The bounded spin is exhausted. Fall back to the last good snapshot when it is still
229
+ // valid, which keeps a render alive through a pathological writer rather than throwing
230
+ // into a render path that cannot handle it.
231
+ this.#table.unpin(this.slot);
232
+ if (held !== undefined && held.isValid()) {
233
+ this.#table.pin(this.slot, held.versionId);
234
+ return held;
235
+ }
236
+ throw new StaleSnapshotError(this.publishedVersion(), this.reclaimFloor());
237
+ }
238
+ /** Read the current root value. The synchronous read the whole library exists for. */
239
+ read() {
240
+ return this.acquire().value;
241
+ }
242
+ /**
243
+ * Pin a specific retained version rather than the current one.
244
+ *
245
+ * For debugging and time travel. It pins that version and everything after it against
246
+ * reclamation, so a snapshot obtained this way must be released. A render path should
247
+ * never use it.
248
+ *
249
+ * A reader owns one epoch slot, so this suspends the pin the render path was holding until
250
+ * the returned snapshot is released. A debug panel that browses history while a render
251
+ * reads should attach its own reader rather than share one, which costs a slot and keeps
252
+ * the two independent.
253
+ */
254
+ acquireVersion(versionId) {
255
+ const entry = this.#ring.read(versionId);
256
+ if (entry === undefined || !this.#ring.isLive(versionId)) {
257
+ throw new StaleSnapshotError(versionId, this.reclaimFloor());
258
+ }
259
+ this.#table.pin(this.slot, versionId);
260
+ // Recheck after pinning, for the same reason acquire does: the pin has to be visible to
261
+ // the owner before the version can be trusted.
262
+ if (versionId < this.reclaimFloor() || !this.#ring.isLive(versionId)) {
263
+ this.#table.unpin(this.slot);
264
+ throw new StaleSnapshotError(versionId, this.reclaimFloor());
265
+ }
266
+ // A historical version is not verified: the published checksum covers the current root
267
+ // only, so there is nothing to compare a retained one against. Time travel is a debugging
268
+ // tool and this is a limit of it rather than an oversight.
269
+ return new Snapshot(this, versionId, this.ownerGeneration(), {
270
+ tag: entry.rootTag,
271
+ payload: entry.rootPayload,
272
+ });
273
+ }
274
+ /** Materialise a retained version, pinning it only for the duration of the read. */
275
+ readVersion(versionId) {
276
+ const held = this.#current;
277
+ const snapshot = this.acquireVersion(versionId);
278
+ try {
279
+ return snapshot.toJSON();
280
+ }
281
+ finally {
282
+ // Restore the pin the caller had, so browsing history does not silently unpin the
283
+ // version a render is using.
284
+ if (held !== undefined && held.isValid())
285
+ this.#table.pin(this.slot, held.versionId);
286
+ else
287
+ this.#table.unpin(this.slot);
288
+ }
289
+ }
290
+ releaseSnapshot(snapshot) {
291
+ if (this.#current === snapshot) {
292
+ this.#current = undefined;
293
+ this.#table.unpin(this.slot);
294
+ return;
295
+ }
296
+ // A historical snapshot from acquireVersion. A reader has one epoch slot, so pinning a
297
+ // past version suspends the pin the render path was holding. Releasing it restores that
298
+ // pin rather than leaving the reader unpinned or, worse, still holding the old version.
299
+ if (this.#table.pinnedEpoch(this.slot) !== snapshot.versionId)
300
+ return;
301
+ const held = this.#current;
302
+ if (held !== undefined && held.isValid())
303
+ this.#table.pin(this.slot, held.versionId);
304
+ else
305
+ this.#table.unpin(this.slot);
306
+ }
307
+ /** Release the pin and the reader slot. Always call this before dropping a reader. */
308
+ detach() {
309
+ if (this.#detached)
310
+ return;
311
+ this.#detached = true;
312
+ this.#current = undefined;
313
+ this.#table.release(this.slot);
314
+ }
315
+ stats() {
316
+ return {
317
+ slot: this.slot,
318
+ generation: this.generation,
319
+ pinnedEpoch: this.#table.pinnedEpoch(this.slot),
320
+ publishedVersion: this.publishedVersion(),
321
+ reclaimFloor: this.reclaimFloor(),
322
+ };
323
+ }
324
+ }
325
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../../src/reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { SharedArena } from "./arena.js";
2
+ /**
3
+ * The reader table.
4
+ *
5
+ * One slot per reader, claimed with a compare and exchange so two processes racing for the
6
+ * same slot cannot both win. A slot carries four words: state, generation, pinned epoch,
7
+ * and a heartbeat counter.
8
+ *
9
+ * The generation exists for the reload case. A renderer that reloads discards its heap
10
+ * while its slot is still claimed. When the owner declares that reader dead and frees the
11
+ * slot, the next claimant bumps the generation, so a snapshot taken by the previous
12
+ * occupant can be recognised as belonging to a different reader and fails closed.
13
+ */
14
+ export declare class ReaderTable {
15
+ #private;
16
+ constructor(arena: SharedArena);
17
+ get capacity(): number;
18
+ /** Claim a free slot. Throws when every slot is taken rather than silently sharing one. */
19
+ claim(): {
20
+ slot: number;
21
+ generation: number;
22
+ };
23
+ /**
24
+ * Release a slot. Unpins first, so a writer that observes the release can immediately
25
+ * reclaim the version this reader was holding.
26
+ */
27
+ release(slot: number): void;
28
+ /** Pin this reader to a version. One atomic store, which is the whole cost of entering. */
29
+ pin(slot: number, versionId: number): void;
30
+ unpin(slot: number): void;
31
+ pinnedEpoch(slot: number): number;
32
+ generation(slot: number): number;
33
+ isClaimed(slot: number): boolean;
34
+ beat(slot: number): void;
35
+ heartbeat(slot: number): number;
36
+ /**
37
+ * The oldest version any reader is pinned to, or `Infinity` when nothing is pinned.
38
+ * Everything strictly older can be reclaimed.
39
+ *
40
+ * This is a scan of the table on each commit. With a table of 32 slots that is 32 atomic
41
+ * loads, which is cheaper than maintaining a shared minimum that every reader would have
42
+ * to update on entry and exit.
43
+ */
44
+ minimumPinnedEpoch(): number;
45
+ /** Every claimed slot, for liveness checks and diagnostics. */
46
+ claimedSlots(): Array<{
47
+ slot: number;
48
+ epoch: number;
49
+ generation: number;
50
+ heartbeat: number;
51
+ }>;
52
+ /**
53
+ * Force a slot free. Used by the owner once it has declared a reader dead, and only
54
+ * then. A live reader whose slot is taken this way will fail its next validity check and
55
+ * fall back to reacquiring, which is the fail closed path rather than a crash.
56
+ */
57
+ forceRelease(slot: number): void;
58
+ ownerHeartbeat(): number;
59
+ }
60
+ //# sourceMappingURL=readers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readers.d.ts","sourceRoot":"","sources":["../../src/readers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAW9C;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;;IAGtB,YAAY,KAAK,EAAE,WAAW,EAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAQD,2FAA2F;IAC3F,KAAK,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAc5C;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI1B;IAED,2FAA2F;IAC3F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAEzC;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExB;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhC;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/B;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/B;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvB;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9B;IAED;;;;;;;OAOG;IACH,kBAAkB,IAAI,MAAM,CAS3B;IAED,+DAA+D;IAC/D,YAAY,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAY5F;IAED;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK/B;IAED,cAAc,IAAI,MAAM,CAEvB;CACF"}
@@ -0,0 +1,122 @@
1
+ import { NoReaderSlotError } from "./errors.js";
2
+ import { Header, READER_SLOT_WORDS, ReaderSlot, SLOT_CLAIMED, SLOT_FREE, WORD, } from "./layout.js";
3
+ /**
4
+ * The reader table.
5
+ *
6
+ * One slot per reader, claimed with a compare and exchange so two processes racing for the
7
+ * same slot cannot both win. A slot carries four words: state, generation, pinned epoch,
8
+ * and a heartbeat counter.
9
+ *
10
+ * The generation exists for the reload case. A renderer that reloads discards its heap
11
+ * while its slot is still claimed. When the owner declares that reader dead and frees the
12
+ * slot, the next claimant bumps the generation, so a snapshot taken by the previous
13
+ * occupant can be recognised as belonging to a different reader and fails closed.
14
+ */
15
+ export class ReaderTable {
16
+ #arena;
17
+ constructor(arena) {
18
+ this.#arena = arena;
19
+ }
20
+ get capacity() {
21
+ return this.#arena.geometry.maxReaders;
22
+ }
23
+ #wordIndex(slot, field) {
24
+ return (this.#arena.geometry.readerTableOffset / WORD + slot * READER_SLOT_WORDS + field);
25
+ }
26
+ /** Claim a free slot. Throws when every slot is taken rather than silently sharing one. */
27
+ claim() {
28
+ const words = this.#arena.words;
29
+ for (let slot = 0; slot < this.capacity; slot += 1) {
30
+ const stateIndex = this.#wordIndex(slot, ReaderSlot.State);
31
+ if (Atomics.compareExchange(words, stateIndex, SLOT_FREE, SLOT_CLAIMED) !== SLOT_FREE) {
32
+ continue;
33
+ }
34
+ const generation = Atomics.add(words, this.#wordIndex(slot, ReaderSlot.Generation), 1) + 1;
35
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.Epoch), 0);
36
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.Heartbeat), 0);
37
+ return { slot, generation };
38
+ }
39
+ throw new NoReaderSlotError(this.capacity);
40
+ }
41
+ /**
42
+ * Release a slot. Unpins first, so a writer that observes the release can immediately
43
+ * reclaim the version this reader was holding.
44
+ */
45
+ release(slot) {
46
+ const words = this.#arena.words;
47
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.Epoch), 0);
48
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.State), SLOT_FREE);
49
+ }
50
+ /** Pin this reader to a version. One atomic store, which is the whole cost of entering. */
51
+ pin(slot, versionId) {
52
+ Atomics.store(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Epoch), versionId);
53
+ }
54
+ unpin(slot) {
55
+ Atomics.store(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Epoch), 0);
56
+ }
57
+ pinnedEpoch(slot) {
58
+ return Atomics.load(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Epoch));
59
+ }
60
+ generation(slot) {
61
+ return Atomics.load(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Generation));
62
+ }
63
+ isClaimed(slot) {
64
+ return (Atomics.load(this.#arena.words, this.#wordIndex(slot, ReaderSlot.State)) === SLOT_CLAIMED);
65
+ }
66
+ beat(slot) {
67
+ Atomics.add(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Heartbeat), 1);
68
+ }
69
+ heartbeat(slot) {
70
+ return Atomics.load(this.#arena.words, this.#wordIndex(slot, ReaderSlot.Heartbeat));
71
+ }
72
+ /**
73
+ * The oldest version any reader is pinned to, or `Infinity` when nothing is pinned.
74
+ * Everything strictly older can be reclaimed.
75
+ *
76
+ * This is a scan of the table on each commit. With a table of 32 slots that is 32 atomic
77
+ * loads, which is cheaper than maintaining a shared minimum that every reader would have
78
+ * to update on entry and exit.
79
+ */
80
+ minimumPinnedEpoch() {
81
+ const words = this.#arena.words;
82
+ let minimum = Number.POSITIVE_INFINITY;
83
+ for (let slot = 0; slot < this.capacity; slot += 1) {
84
+ if (Atomics.load(words, this.#wordIndex(slot, ReaderSlot.State)) !== SLOT_CLAIMED)
85
+ continue;
86
+ const epoch = Atomics.load(words, this.#wordIndex(slot, ReaderSlot.Epoch));
87
+ if (epoch !== 0 && epoch < minimum)
88
+ minimum = epoch;
89
+ }
90
+ return minimum;
91
+ }
92
+ /** Every claimed slot, for liveness checks and diagnostics. */
93
+ claimedSlots() {
94
+ const out = [];
95
+ for (let slot = 0; slot < this.capacity; slot += 1) {
96
+ if (!this.isClaimed(slot))
97
+ continue;
98
+ out.push({
99
+ slot,
100
+ epoch: this.pinnedEpoch(slot),
101
+ generation: this.generation(slot),
102
+ heartbeat: this.heartbeat(slot),
103
+ });
104
+ }
105
+ return out;
106
+ }
107
+ /**
108
+ * Force a slot free. Used by the owner once it has declared a reader dead, and only
109
+ * then. A live reader whose slot is taken this way will fail its next validity check and
110
+ * fall back to reacquiring, which is the fail closed path rather than a crash.
111
+ */
112
+ forceRelease(slot) {
113
+ const words = this.#arena.words;
114
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.Epoch), 0);
115
+ Atomics.add(words, this.#wordIndex(slot, ReaderSlot.Generation), 1);
116
+ Atomics.store(words, this.#wordIndex(slot, ReaderSlot.State), SLOT_FREE);
117
+ }
118
+ ownerHeartbeat() {
119
+ return this.#arena.loadHeader(Header.OwnerHeartbeat);
120
+ }
121
+ }
122
+ //# sourceMappingURL=readers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readers.js","sourceRoot":"","sources":["../../src/readers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { SharedArena } from "./arena.js";
2
+ export interface RetainedVersion {
3
+ readonly versionId: number;
4
+ readonly rootTag: number;
5
+ readonly rootPayload: number;
6
+ readonly state: number;
7
+ }
8
+ /**
9
+ * The retained version ring.
10
+ *
11
+ * The owner appends one entry per commit. Readers only read it, and they read it for one
12
+ * reason: to prove that the version they are pinned to is still live before they decode
13
+ * anything through it.
14
+ *
15
+ * The ring is bounded on purpose. A reader that stops advancing cannot pin memory forever,
16
+ * because once the ring wraps the owner force reclaims the oldest entry and that reader
17
+ * fails closed on its next decode. Unbounded retention would be the alternative, and it
18
+ * turns one frozen window into an out of memory crash for the whole application.
19
+ */
20
+ export declare class RetainedRing {
21
+ #private;
22
+ constructor(arena: SharedArena);
23
+ get capacity(): number;
24
+ /** Owner only. Publish a version into the ring, overwriting the entry it wraps onto. */
25
+ publish(versionId: number, rootTag: number, rootPayload: number): void;
26
+ /** Owner only. Mark a version reclaimed. `forced` records that a reader was still pinned. */
27
+ retire(versionId: number, forced: boolean): void;
28
+ /**
29
+ * The check every reader runs after publishing its epoch. Returns true only when the
30
+ * version is still live in the ring, which proves the owner has not reclaimed the memory
31
+ * beneath it.
32
+ */
33
+ isLive(versionId: number): boolean;
34
+ read(versionId: number): RetainedVersion | undefined;
35
+ /** Live versions, oldest first. Diagnostics and the debug panel use this. */
36
+ live(): RetainedVersion[];
37
+ reclaimFloor(): number;
38
+ }
39
+ //# sourceMappingURL=retained.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retained.d.ts","sourceRoot":"","sources":["../../src/retained.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;;IAGvB,YAAY,KAAK,EAAE,WAAW,EAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAeD,wFAAwF;IACxF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAUrE;IAED,6FAA6F;IAC7F,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAS/C;IAED;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAQjC;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAYnD;IAED,6EAA6E;IAC7E,IAAI,IAAI,eAAe,EAAE,CAgBxB;IAED,YAAY,IAAI,MAAM,CAErB;CACF"}