@peerbit/native-backbone 0.2.9 → 0.2.11
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 +55 -20
- package/dist/src/index.d.ts +61 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +948 -74
- package/dist/src/index.js.map +1 -1
- package/dist/wasm/README.md +55 -20
- package/dist/wasm/native_backbone.d.ts +124 -79
- package/dist/wasm/native_backbone.js +297 -0
- package/dist/wasm/native_backbone_bg.wasm +0 -0
- package/dist/wasm/native_backbone_bg.wasm.d.ts +90 -79
- package/package.json +2 -2
- package/src/append_tx/committed_latest.rs +10 -5
- package/src/append_tx/committed_no_next.rs +6 -3
- package/src/append_tx/facts.rs +427 -1
- package/src/append_tx/mod.rs +51 -18
- package/src/append_tx/storage.rs +12 -2
- package/src/index.ts +1418 -227
- package/src/shared_log_plan.rs +30 -0
package/dist/src/index.js
CHANGED
|
@@ -191,9 +191,9 @@ const nativeBackboneCoordinatePersistenceFiles = {
|
|
|
191
191
|
};
|
|
192
192
|
export const nativeBackboneCoordinateDropTombstoneFile = "native-backbone-drop.tombstone";
|
|
193
193
|
export const defaultNativeBackboneCoordinateFlushMaxPendingBytes = 1024 * 1024;
|
|
194
|
-
/**
|
|
194
|
+
/** Recommended explicit byte threshold for crash-safe Node checkpointing. */
|
|
195
195
|
export const defaultNativeBackboneCoordinateCompactMaxJournalBytes = 64 * 1024 * 1024;
|
|
196
|
-
const nativeBackboneCoordinateCompactionDisabledMessage = "Native backbone coordinate persistence compaction is disabled
|
|
196
|
+
const nativeBackboneCoordinateCompactionDisabledMessage = "Native backbone coordinate persistence compaction is disabled for stores without crash-safe atomic replacement";
|
|
197
197
|
const nativeBackboneCoordinateJournalMagic = Uint8Array.from([
|
|
198
198
|
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x57, 0x31,
|
|
199
199
|
]);
|
|
@@ -209,6 +209,171 @@ const coordinateJournalChecksum = (bytes) => {
|
|
|
209
209
|
}
|
|
210
210
|
return checksum;
|
|
211
211
|
};
|
|
212
|
+
const nativeBackboneCoordinateCheckpointMagic = Uint8Array.from([
|
|
213
|
+
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x43, 0x31,
|
|
214
|
+
]);
|
|
215
|
+
const nativeBackboneCoordinateCheckpointStateMagic = Uint8Array.from([
|
|
216
|
+
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x53, 0x31,
|
|
217
|
+
]);
|
|
218
|
+
const nativeBackboneCoordinateLegacyMigrationSentinel = (() => {
|
|
219
|
+
const payload = Uint8Array.of(0xff);
|
|
220
|
+
const bytes = new Uint8Array(nativeBackboneCoordinateJournalMagic.byteLength + 8 + payload.byteLength);
|
|
221
|
+
bytes.set(nativeBackboneCoordinateJournalMagic, 0);
|
|
222
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
223
|
+
view.setUint32(nativeBackboneCoordinateJournalMagic.byteLength, 1, true);
|
|
224
|
+
view.setUint32(nativeBackboneCoordinateJournalMagic.byteLength + 4, coordinateJournalChecksum(payload), true);
|
|
225
|
+
bytes.set(payload, nativeBackboneCoordinateJournalMagic.byteLength + 8);
|
|
226
|
+
return bytes;
|
|
227
|
+
})();
|
|
228
|
+
const nativeBackboneCoordinateCheckpointVersion = 1;
|
|
229
|
+
const nativeBackboneCoordinateCheckpointHeaderBytes = 56;
|
|
230
|
+
const nativeBackboneCoordinateCheckpointStateBytes = 60;
|
|
231
|
+
const nativeBackboneCoordinateMaxU64 = (1n << 64n) - 1n;
|
|
232
|
+
const hasBytesPrefix = (bytes, prefix) => bytes.byteLength >= prefix.byteLength &&
|
|
233
|
+
prefix.every((byte, index) => bytes[index] === byte);
|
|
234
|
+
const writeCoordinateCheckpointU64 = (view, offset, value) => {
|
|
235
|
+
if (value < 0n || value > nativeBackboneCoordinateMaxU64) {
|
|
236
|
+
throw new RangeError("Native backbone checkpoint generation exceeds u64");
|
|
237
|
+
}
|
|
238
|
+
view.setUint32(offset, Number(value & 0xffffffffn), true);
|
|
239
|
+
view.setUint32(offset + 4, Number(value >> 32n), true);
|
|
240
|
+
};
|
|
241
|
+
const readCoordinateCheckpointU64 = (view, offset) => BigInt(view.getUint32(offset, true)) |
|
|
242
|
+
(BigInt(view.getUint32(offset + 4, true)) << 32n);
|
|
243
|
+
const encodeNativeBackboneCoordinateCheckpoint = (checkpoint) => {
|
|
244
|
+
const coordinateLength = checkpoint.coordinateSnapshot.byteLength;
|
|
245
|
+
const documentLength = checkpoint.documentSnapshot.byteLength;
|
|
246
|
+
const signerLength = checkpoint.documentSignerSnapshot.byteLength;
|
|
247
|
+
const byteLength = nativeBackboneCoordinateCheckpointHeaderBytes +
|
|
248
|
+
coordinateLength +
|
|
249
|
+
documentLength +
|
|
250
|
+
signerLength;
|
|
251
|
+
if (!Number.isSafeInteger(byteLength) || byteLength > 0xffff_ffff) {
|
|
252
|
+
throw new RangeError("Native backbone coordinate checkpoint is too large");
|
|
253
|
+
}
|
|
254
|
+
const bytes = new Uint8Array(byteLength);
|
|
255
|
+
bytes.set(nativeBackboneCoordinateCheckpointMagic, 0);
|
|
256
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
257
|
+
view.setUint32(8, nativeBackboneCoordinateCheckpointVersion, true);
|
|
258
|
+
view.setUint32(12, nativeBackboneCoordinateCheckpointHeaderBytes, true);
|
|
259
|
+
writeCoordinateCheckpointU64(view, 16, checkpoint.generation);
|
|
260
|
+
view.setUint32(24, coordinateLength, true);
|
|
261
|
+
view.setUint32(28, documentLength, true);
|
|
262
|
+
view.setUint32(32, signerLength, true);
|
|
263
|
+
view.setUint32(36, coordinateJournalChecksum(checkpoint.coordinateSnapshot), true);
|
|
264
|
+
view.setUint32(40, coordinateJournalChecksum(checkpoint.documentSnapshot), true);
|
|
265
|
+
view.setUint32(44, coordinateJournalChecksum(checkpoint.documentSignerSnapshot), true);
|
|
266
|
+
view.setUint32(48, checkpoint.configurationChecksum, true);
|
|
267
|
+
view.setUint32(52, coordinateJournalChecksum(bytes.subarray(8, 52)), true);
|
|
268
|
+
let offset = nativeBackboneCoordinateCheckpointHeaderBytes;
|
|
269
|
+
bytes.set(checkpoint.coordinateSnapshot, offset);
|
|
270
|
+
offset += coordinateLength;
|
|
271
|
+
bytes.set(checkpoint.documentSnapshot, offset);
|
|
272
|
+
offset += documentLength;
|
|
273
|
+
bytes.set(checkpoint.documentSignerSnapshot, offset);
|
|
274
|
+
return bytes;
|
|
275
|
+
};
|
|
276
|
+
const parseNativeBackboneCoordinateCheckpoint = (bytes, name) => {
|
|
277
|
+
if (bytes.byteLength < nativeBackboneCoordinateCheckpointHeaderBytes ||
|
|
278
|
+
!hasBytesPrefix(bytes, nativeBackboneCoordinateCheckpointMagic)) {
|
|
279
|
+
throw new Error(`Native backbone ${name} has an invalid checkpoint header`);
|
|
280
|
+
}
|
|
281
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
282
|
+
if (view.getUint32(8, true) !== nativeBackboneCoordinateCheckpointVersion ||
|
|
283
|
+
view.getUint32(12, true) !==
|
|
284
|
+
nativeBackboneCoordinateCheckpointHeaderBytes ||
|
|
285
|
+
view.getUint32(52, true) !==
|
|
286
|
+
coordinateJournalChecksum(bytes.subarray(8, 52))) {
|
|
287
|
+
throw new Error(`Native backbone ${name} has invalid checkpoint metadata`);
|
|
288
|
+
}
|
|
289
|
+
const coordinateLength = view.getUint32(24, true);
|
|
290
|
+
const documentLength = view.getUint32(28, true);
|
|
291
|
+
const signerLength = view.getUint32(32, true);
|
|
292
|
+
const expectedLength = nativeBackboneCoordinateCheckpointHeaderBytes +
|
|
293
|
+
coordinateLength +
|
|
294
|
+
documentLength +
|
|
295
|
+
signerLength;
|
|
296
|
+
if (!Number.isSafeInteger(expectedLength) ||
|
|
297
|
+
expectedLength !== bytes.byteLength) {
|
|
298
|
+
throw new Error(`Native backbone ${name} has an invalid checkpoint length`);
|
|
299
|
+
}
|
|
300
|
+
let offset = nativeBackboneCoordinateCheckpointHeaderBytes;
|
|
301
|
+
const coordinateSnapshot = bytes.subarray(offset, offset + coordinateLength);
|
|
302
|
+
offset += coordinateLength;
|
|
303
|
+
const documentSnapshot = bytes.subarray(offset, offset + documentLength);
|
|
304
|
+
offset += documentLength;
|
|
305
|
+
const documentSignerSnapshot = bytes.subarray(offset, offset + signerLength);
|
|
306
|
+
if (coordinateJournalChecksum(coordinateSnapshot) !==
|
|
307
|
+
view.getUint32(36, true) ||
|
|
308
|
+
coordinateJournalChecksum(documentSnapshot) !== view.getUint32(40, true) ||
|
|
309
|
+
coordinateJournalChecksum(documentSignerSnapshot) !==
|
|
310
|
+
view.getUint32(44, true)) {
|
|
311
|
+
throw new Error(`Native backbone ${name} has a checkpoint checksum mismatch`);
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
configurationChecksum: view.getUint32(48, true),
|
|
315
|
+
generation: readCoordinateCheckpointU64(view, 16),
|
|
316
|
+
coordinateSnapshot,
|
|
317
|
+
documentSnapshot,
|
|
318
|
+
documentSignerSnapshot,
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
const encodeNativeBackboneCoordinateCheckpointState = (state) => {
|
|
322
|
+
const bytes = new Uint8Array(nativeBackboneCoordinateCheckpointStateBytes);
|
|
323
|
+
bytes.set(nativeBackboneCoordinateCheckpointStateMagic, 0);
|
|
324
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
325
|
+
view.setUint32(8, nativeBackboneCoordinateCheckpointVersion, true);
|
|
326
|
+
view.setUint32(12, state.configurationChecksum, true);
|
|
327
|
+
writeCoordinateCheckpointU64(view, 16, state.highwater);
|
|
328
|
+
writeCoordinateCheckpointU64(view, 24, state.pending?.generation ?? 0n);
|
|
329
|
+
view.setUint32(32, state.pending?.byteLength ?? 0, true);
|
|
330
|
+
view.setUint32(36, state.pending?.checksum ?? 0, true);
|
|
331
|
+
writeCoordinateCheckpointU64(view, 40, state.completed?.generation ?? 0n);
|
|
332
|
+
view.setUint32(48, state.completed?.byteLength ?? 0, true);
|
|
333
|
+
view.setUint32(52, state.completed?.checksum ?? 0, true);
|
|
334
|
+
view.setUint32(56, coordinateJournalChecksum(bytes.subarray(8, 56)), true);
|
|
335
|
+
return bytes;
|
|
336
|
+
};
|
|
337
|
+
const parseNativeBackboneCoordinateCheckpointState = (bytes, name) => {
|
|
338
|
+
if (bytes.byteLength !== nativeBackboneCoordinateCheckpointStateBytes ||
|
|
339
|
+
!hasBytesPrefix(bytes, nativeBackboneCoordinateCheckpointStateMagic)) {
|
|
340
|
+
throw new Error(`Native backbone ${name} has an invalid checkpoint authority`);
|
|
341
|
+
}
|
|
342
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
343
|
+
if (view.getUint32(8, true) !== nativeBackboneCoordinateCheckpointVersion ||
|
|
344
|
+
view.getUint32(56, true) !==
|
|
345
|
+
coordinateJournalChecksum(bytes.subarray(8, 56))) {
|
|
346
|
+
throw new Error(`Native backbone ${name} has corrupt checkpoint authority`);
|
|
347
|
+
}
|
|
348
|
+
const configurationChecksum = view.getUint32(12, true);
|
|
349
|
+
const highwater = readCoordinateCheckpointU64(view, 16);
|
|
350
|
+
const pendingGeneration = readCoordinateCheckpointU64(view, 24);
|
|
351
|
+
const completedGeneration = readCoordinateCheckpointU64(view, 40);
|
|
352
|
+
const authority = (generation, lengthOffset, checksumOffset) => {
|
|
353
|
+
const byteLength = view.getUint32(lengthOffset, true);
|
|
354
|
+
const checksum = view.getUint32(checksumOffset, true);
|
|
355
|
+
if (generation === 0n) {
|
|
356
|
+
if (byteLength !== 0 || checksum !== 0) {
|
|
357
|
+
throw new Error(`Native backbone ${name} has empty authority metadata`);
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
if (byteLength < nativeBackboneCoordinateCheckpointHeaderBytes) {
|
|
362
|
+
throw new Error(`Native backbone ${name} has invalid authority length`);
|
|
363
|
+
}
|
|
364
|
+
return { generation, byteLength, checksum };
|
|
365
|
+
};
|
|
366
|
+
const pending = authority(pendingGeneration, 32, 36);
|
|
367
|
+
const completed = authority(completedGeneration, 48, 52);
|
|
368
|
+
if ((completed && completed.generation > highwater) ||
|
|
369
|
+
(pending && pending.generation !== highwater) ||
|
|
370
|
+
(pending && completed && pending.generation <= completed.generation) ||
|
|
371
|
+
(!pending && completed && completed.generation !== highwater) ||
|
|
372
|
+
(!pending && !completed && highwater !== 0n)) {
|
|
373
|
+
throw new Error(`Native backbone ${name} has invalid generation authority`);
|
|
374
|
+
}
|
|
375
|
+
return { configurationChecksum, highwater, pending, completed };
|
|
376
|
+
};
|
|
212
377
|
const hasCoordinateJournalMagic = (bytes) => bytes.byteLength >= nativeBackboneCoordinateJournalMagic.byteLength &&
|
|
213
378
|
nativeBackboneCoordinateJournalMagic.every((byte, index) => bytes[index] === byte);
|
|
214
379
|
/**
|
|
@@ -246,6 +411,12 @@ const resolveCoordinateFlushMaxPendingBytes = (options) => options.flushMaxPendi
|
|
|
246
411
|
: options.flushOnAppend === false
|
|
247
412
|
? defaultNativeBackboneCoordinateFlushMaxPendingBytes
|
|
248
413
|
: undefined;
|
|
414
|
+
const validateCoordinateCompactionThreshold = (value, name) => {
|
|
415
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
416
|
+
throw new RangeError(`Native backbone ${name} must be a positive safe integer`);
|
|
417
|
+
}
|
|
418
|
+
return value;
|
|
419
|
+
};
|
|
249
420
|
const isNotFoundError = (error) => {
|
|
250
421
|
const maybeError = error;
|
|
251
422
|
return maybeError?.code === "ENOENT" || maybeError?.name === "NotFoundError";
|
|
@@ -656,7 +827,7 @@ const requestPruneEntryFromRow = (row) => {
|
|
|
656
827
|
return entry;
|
|
657
828
|
};
|
|
658
829
|
const storageAppendResultFromRow = (resolution, row) => {
|
|
659
|
-
const [entryRow, leaderRows, isLeader, assignedToRangeBoundary, coordinateRow, trimRows, trimHashRows, documentTrimmedHeadsProcessed, documentPreviousContextRow,] = row;
|
|
830
|
+
const [entryRow, leaderRows, isLeader, assignedToRangeBoundary, coordinateRow, trimRows, trimHashRows, documentTrimmedHeadsProcessed, documentPreviousContextRow, _trimGidExtension, trimGidRows,] = row;
|
|
660
831
|
return {
|
|
661
832
|
entry: storageFactsEntryFromRow(entryRow),
|
|
662
833
|
leaders: rowsToSamples(leaderRows),
|
|
@@ -664,12 +835,13 @@ const storageAppendResultFromRow = (resolution, row) => {
|
|
|
664
835
|
assignedToRangeBoundary,
|
|
665
836
|
coordinate: appendCoordinatePlanFromRow(resolution, coordinateRow),
|
|
666
837
|
...trimmedRowsAndHashesResult(trimRows, trimHashRows),
|
|
838
|
+
trimmedGids: trimGidRows ?? trimRows.map((trim) => trim[1]),
|
|
667
839
|
documentTrimmedHeadsProcessed,
|
|
668
840
|
documentPreviousContext: documentContextFactsFromRow(documentPreviousContextRow),
|
|
669
841
|
};
|
|
670
842
|
};
|
|
671
843
|
const committedStorageAppendResultFromRow = (resolution, row) => {
|
|
672
|
-
const [entryRow, leaderRows, isLeader, assignedToRangeBoundary, coordinateRow, trimRows, trimHashRows, documentTrimmedHeadsProcessed, documentPreviousContextRow,] = row;
|
|
844
|
+
const [entryRow, leaderRows, isLeader, assignedToRangeBoundary, coordinateRow, trimRows, trimHashRows, documentTrimmedHeadsProcessed, documentPreviousContextRow, _trimGidExtension, trimGidRows,] = row;
|
|
673
845
|
return {
|
|
674
846
|
entry: committedStorageFactsEntryFromRow(entryRow),
|
|
675
847
|
leaders: rowsToSamples(leaderRows),
|
|
@@ -677,15 +849,22 @@ const committedStorageAppendResultFromRow = (resolution, row) => {
|
|
|
677
849
|
assignedToRangeBoundary,
|
|
678
850
|
coordinate: appendCoordinatePlanFromRow(resolution, coordinateRow),
|
|
679
851
|
...trimmedRowsAndHashesResult(trimRows, trimHashRows),
|
|
852
|
+
trimmedGids: trimGidRows ?? trimRows.map((trim) => trim[1]),
|
|
680
853
|
documentTrimmedHeadsProcessed,
|
|
681
854
|
documentPreviousContext: documentContextFactsFromRow(documentPreviousContextRow),
|
|
682
855
|
};
|
|
683
856
|
};
|
|
684
857
|
const compactCommittedNoNextStorageAppendResultFromRow = (resolution, row) => {
|
|
685
|
-
const
|
|
858
|
+
const appendedTrimGids = row.length >= 12 &&
|
|
859
|
+
row[row.length - 2] === undefined &&
|
|
860
|
+
Array.isArray(row[row.length - 1])
|
|
861
|
+
? row[row.length - 1]
|
|
862
|
+
: undefined;
|
|
863
|
+
const baseRow = appendedTrimGids ? row.slice(0, -2) : row;
|
|
864
|
+
const [hash, byteLength, metaBytes, fourth] = baseRow;
|
|
686
865
|
const hasDigestRow = fourth instanceof Uint8Array;
|
|
687
866
|
const hashDigestBytes = hasDigestRow ? fourth : undefined;
|
|
688
|
-
const rest =
|
|
867
|
+
const rest = baseRow.slice(hasDigestRow ? 4 : 3);
|
|
689
868
|
const usesNestedCoordinateRow = Array.isArray(rest[0]);
|
|
690
869
|
let coordinate;
|
|
691
870
|
let leaderRows;
|
|
@@ -733,11 +912,18 @@ const compactCommittedNoNextStorageAppendResultFromRow = (resolution, row) => {
|
|
|
733
912
|
coordinate,
|
|
734
913
|
trimmed: [],
|
|
735
914
|
trimmedHashes: trimHashRows ?? [],
|
|
915
|
+
trimmedGids: appendedTrimGids,
|
|
736
916
|
documentTrimmedHeadsProcessed,
|
|
737
917
|
};
|
|
738
918
|
};
|
|
739
919
|
const compactCommittedLatestStorageAppendResultFromRow = (resolution, row) => {
|
|
740
|
-
const
|
|
920
|
+
const appendedTrimGids = row.length >= 13 &&
|
|
921
|
+
row[row.length - 2] === undefined &&
|
|
922
|
+
Array.isArray(row[row.length - 1])
|
|
923
|
+
? row[row.length - 1]
|
|
924
|
+
: undefined;
|
|
925
|
+
const baseRow = appendedTrimGids ? row.slice(0, -2) : row;
|
|
926
|
+
const [hash, byteLength, metaBytes, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth,] = baseRow;
|
|
741
927
|
const hasDigestRow = fourth instanceof Uint8Array;
|
|
742
928
|
const hashDigestBytes = hasDigestRow ? fourth : undefined;
|
|
743
929
|
const next = (hasDigestRow ? fifth : fourth);
|
|
@@ -765,6 +951,7 @@ const compactCommittedLatestStorageAppendResultFromRow = (resolution, row) => {
|
|
|
765
951
|
coordinate,
|
|
766
952
|
trimmed: [],
|
|
767
953
|
trimmedHashes: trimHashRows ?? [],
|
|
954
|
+
trimmedGids: appendedTrimGids,
|
|
768
955
|
documentTrimmedHeadsProcessed,
|
|
769
956
|
documentPreviousContext: documentContextFactsFromRow(documentPreviousContextRow),
|
|
770
957
|
};
|
|
@@ -784,6 +971,14 @@ const preparedCommitFactsFromRow = (row) => {
|
|
|
784
971
|
}
|
|
785
972
|
return prepared;
|
|
786
973
|
};
|
|
974
|
+
const preparedCommitFactsWithTrimRefsFromRow = (row) => {
|
|
975
|
+
const prepared = committedStorageFactsEntryFromRow(row[0]);
|
|
976
|
+
return {
|
|
977
|
+
...prepared,
|
|
978
|
+
trimmedEntryHashes: row[1],
|
|
979
|
+
trimmedEntryGids: row[2],
|
|
980
|
+
};
|
|
981
|
+
};
|
|
787
982
|
const preparedCommitFactsWithLatestDocumentContextFromRow = (row) => {
|
|
788
983
|
const [entryRow, trimHashRows, documentTrimmedHeadsProcessed, contextRow] = row;
|
|
789
984
|
return {
|
|
@@ -793,24 +988,53 @@ const preparedCommitFactsWithLatestDocumentContextFromRow = (row) => {
|
|
|
793
988
|
documentPreviousContext: documentContextFactsFromRow(contextRow),
|
|
794
989
|
};
|
|
795
990
|
};
|
|
796
|
-
const
|
|
991
|
+
const preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow = (row) => {
|
|
992
|
+
const [entryRow, trimHashRows, trimGidRows, documentTrimmedHeadsProcessed, contextRow,] = row;
|
|
993
|
+
return {
|
|
994
|
+
...committedStorageFactsEntryFromRow(entryRow),
|
|
995
|
+
trimmedEntryHashes: trimHashRows,
|
|
996
|
+
trimmedEntryGids: trimGidRows,
|
|
997
|
+
documentTrimmedHeadsProcessed,
|
|
998
|
+
documentPreviousContext: documentContextFactsFromRow(contextRow),
|
|
999
|
+
};
|
|
1000
|
+
};
|
|
1001
|
+
const compactPreparedCommitFactsBaseFromRow = (row) => {
|
|
797
1002
|
const [hash, byteLength, metaBytes, fourth] = row;
|
|
798
1003
|
const hasDigestRow = fourth instanceof Uint8Array;
|
|
799
|
-
const hashDigestBytes = hasDigestRow ? fourth : undefined;
|
|
800
|
-
const trimHashOffset = hasDigestRow ? 4 : 3;
|
|
801
|
-
const trimHashRows = row[trimHashOffset];
|
|
802
|
-
const documentTrimmedHeadsProcessed = row[trimHashOffset + 1];
|
|
803
1004
|
return {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1005
|
+
entry: {
|
|
1006
|
+
cid: hash,
|
|
1007
|
+
hash,
|
|
1008
|
+
next: [],
|
|
1009
|
+
metaBytes,
|
|
1010
|
+
byteLength,
|
|
1011
|
+
hashDigestBytes: hasDigestRow ? fourth : undefined,
|
|
1012
|
+
},
|
|
1013
|
+
trimRowOffset: hasDigestRow ? 4 : 3,
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
const compactPreparedCommitFactsWithTrimHashesFromRow = (row) => {
|
|
1017
|
+
const { entry, trimRowOffset } = compactPreparedCommitFactsBaseFromRow(row);
|
|
1018
|
+
const trimHashRows = row[trimRowOffset];
|
|
1019
|
+
const documentTrimmedHeadsProcessed = row[trimRowOffset + 1];
|
|
1020
|
+
return {
|
|
1021
|
+
...entry,
|
|
810
1022
|
trimmedEntryHashes: trimHashRows ?? [],
|
|
811
1023
|
documentTrimmedHeadsProcessed,
|
|
812
1024
|
};
|
|
813
1025
|
};
|
|
1026
|
+
const compactPreparedCommitFactsWithTrimRefsFromRow = (row) => {
|
|
1027
|
+
const { entry, trimRowOffset } = compactPreparedCommitFactsBaseFromRow(row);
|
|
1028
|
+
const trimHashRows = row[trimRowOffset];
|
|
1029
|
+
const trimGidRows = row[trimRowOffset + 1];
|
|
1030
|
+
const documentTrimmedHeadsProcessed = row[trimRowOffset + 2];
|
|
1031
|
+
return {
|
|
1032
|
+
...entry,
|
|
1033
|
+
trimmedEntryHashes: trimHashRows,
|
|
1034
|
+
trimmedEntryGids: trimGidRows,
|
|
1035
|
+
documentTrimmedHeadsProcessed,
|
|
1036
|
+
};
|
|
1037
|
+
};
|
|
814
1038
|
const nativeLogCommitEntryColumns = (entries) => {
|
|
815
1039
|
const hashes = new Array(entries.length);
|
|
816
1040
|
const blockBytes = new Array(entries.length);
|
|
@@ -1266,13 +1490,60 @@ class NativeBackboneLogGraph {
|
|
|
1266
1490
|
const documentIndex = input.documentIndex;
|
|
1267
1491
|
const documentIndexArgs = nativeDocumentIndexArgs(documentIndex);
|
|
1268
1492
|
const projection = documentIndex?.projection;
|
|
1493
|
+
if (!documentIndexArgs &&
|
|
1494
|
+
input.resolveTrimmedEntries === false &&
|
|
1495
|
+
input.trimLengthTo != null &&
|
|
1496
|
+
this.native.prepare_plain_entry_commit_facts_trim_refs) {
|
|
1497
|
+
return preparedCommitFactsWithTrimRefsFromRow(this.native.prepare_plain_entry_commit_facts_trim_refs(wallTime, logical, input.gid, input.next ?? [], entryType, input.metaData, input.payloadData, input.trimLengthTo));
|
|
1498
|
+
}
|
|
1269
1499
|
if (documentIndex?.useLatestContext &&
|
|
1270
1500
|
documentIndexArgs &&
|
|
1271
1501
|
input.resolveTrimmedEntries === false) {
|
|
1272
1502
|
if (projection && this.options?.documentProjectionPlanId) {
|
|
1273
|
-
|
|
1503
|
+
const args = [
|
|
1504
|
+
wallTime,
|
|
1505
|
+
logical,
|
|
1506
|
+
input.gid,
|
|
1507
|
+
entryType,
|
|
1508
|
+
input.metaData,
|
|
1509
|
+
input.payloadData,
|
|
1510
|
+
input.trimLengthTo,
|
|
1511
|
+
documentIndex.key,
|
|
1512
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
1513
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
1514
|
+
this.options.documentProjectionPlanId(projection.plan),
|
|
1515
|
+
projection.encodedDocument,
|
|
1516
|
+
projection.signer,
|
|
1517
|
+
];
|
|
1518
|
+
const prepareTrimRefs = this.native
|
|
1519
|
+
.prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_refs;
|
|
1520
|
+
if (prepareTrimRefs) {
|
|
1521
|
+
return preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow(prepareTrimRefs.call(this.native, ...args));
|
|
1522
|
+
}
|
|
1523
|
+
return preparedCommitFactsWithLatestDocumentContextFromRow(this.native.prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_hashes(...args));
|
|
1524
|
+
}
|
|
1525
|
+
const args = [
|
|
1526
|
+
wallTime,
|
|
1527
|
+
logical,
|
|
1528
|
+
input.gid,
|
|
1529
|
+
entryType,
|
|
1530
|
+
input.metaData,
|
|
1531
|
+
input.payloadData,
|
|
1532
|
+
input.trimLengthTo,
|
|
1533
|
+
documentIndex.key,
|
|
1534
|
+
documentIndex.valuePrefixBytes ?? EMPTY_UINT8_ARRAY,
|
|
1535
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
1536
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
1537
|
+
projection?.plan,
|
|
1538
|
+
projection?.encodedDocument,
|
|
1539
|
+
projection?.signer,
|
|
1540
|
+
];
|
|
1541
|
+
const prepareTrimRefs = this.native
|
|
1542
|
+
.prepare_plain_entry_commit_latest_facts_document_index_trim_refs;
|
|
1543
|
+
if (prepareTrimRefs) {
|
|
1544
|
+
return preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow(prepareTrimRefs.call(this.native, ...args));
|
|
1274
1545
|
}
|
|
1275
|
-
return preparedCommitFactsWithLatestDocumentContextFromRow(this.native.prepare_plain_entry_commit_latest_facts_document_index_trim_hashes(
|
|
1546
|
+
return preparedCommitFactsWithLatestDocumentContextFromRow(this.native.prepare_plain_entry_commit_latest_facts_document_index_trim_hashes(...args));
|
|
1276
1547
|
}
|
|
1277
1548
|
if (documentIndexArgs &&
|
|
1278
1549
|
projection &&
|
|
@@ -1281,16 +1552,57 @@ class NativeBackboneLogGraph {
|
|
|
1281
1552
|
input.trimLengthTo != null &&
|
|
1282
1553
|
hasNoNext) {
|
|
1283
1554
|
const projectionPlanId = this.options.documentProjectionPlanId(projection.plan);
|
|
1555
|
+
const plainPutPayloadArgs = [
|
|
1556
|
+
wallTime,
|
|
1557
|
+
logical,
|
|
1558
|
+
input.gid,
|
|
1559
|
+
entryType,
|
|
1560
|
+
input.metaData,
|
|
1561
|
+
input.payloadData,
|
|
1562
|
+
input.trimLengthTo,
|
|
1563
|
+
documentIndex.key,
|
|
1564
|
+
documentIndex.existingCreated == null
|
|
1565
|
+
? ""
|
|
1566
|
+
: integerString(documentIndex.existingCreated),
|
|
1567
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
1568
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
1569
|
+
projectionPlanId,
|
|
1570
|
+
projection.signer,
|
|
1571
|
+
];
|
|
1572
|
+
const plainPutPayloadCommitWithTrimRefs = this.native
|
|
1573
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs_plain_put_payload;
|
|
1574
|
+
if (plainPutPayloadCommitWithTrimRefs) {
|
|
1575
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(plainPutPayloadCommitWithTrimRefs.call(this.native, ...plainPutPayloadArgs));
|
|
1576
|
+
}
|
|
1284
1577
|
const plainPutPayloadCommit = this.native
|
|
1285
1578
|
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes_plain_put_payload;
|
|
1286
1579
|
if (plainPutPayloadCommit) {
|
|
1287
|
-
return compactPreparedCommitFactsWithTrimHashesFromRow(plainPutPayloadCommit.call(this.native,
|
|
1580
|
+
return compactPreparedCommitFactsWithTrimHashesFromRow(plainPutPayloadCommit.call(this.native, ...plainPutPayloadArgs));
|
|
1581
|
+
}
|
|
1582
|
+
const args = [
|
|
1583
|
+
wallTime,
|
|
1584
|
+
logical,
|
|
1585
|
+
input.gid,
|
|
1586
|
+
entryType,
|
|
1587
|
+
input.metaData,
|
|
1588
|
+
input.payloadData,
|
|
1589
|
+
input.trimLengthTo,
|
|
1590
|
+
documentIndex.key,
|
|
1591
|
+
documentIndex.existingCreated == null
|
|
1288
1592
|
? ""
|
|
1289
|
-
: integerString(documentIndex.existingCreated),
|
|
1593
|
+
: integerString(documentIndex.existingCreated),
|
|
1594
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
1595
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
1596
|
+
projectionPlanId,
|
|
1597
|
+
projection.encodedDocument,
|
|
1598
|
+
projection.signer,
|
|
1599
|
+
];
|
|
1600
|
+
const prepareTrimRefs = this.native
|
|
1601
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs;
|
|
1602
|
+
if (prepareTrimRefs) {
|
|
1603
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(prepareTrimRefs.call(this.native, ...args));
|
|
1290
1604
|
}
|
|
1291
|
-
return compactPreparedCommitFactsWithTrimHashesFromRow(this.native.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes(
|
|
1292
|
-
? ""
|
|
1293
|
-
: integerString(documentIndex.existingCreated), documentIndex.byteElementIndexLimit ?? 0, documentIndex.deleteTrimmedHeads === true, projectionPlanId, projection.encodedDocument, projection.signer));
|
|
1605
|
+
return compactPreparedCommitFactsWithTrimHashesFromRow(this.native.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes(...args));
|
|
1294
1606
|
}
|
|
1295
1607
|
if (documentIndexArgs &&
|
|
1296
1608
|
projection &&
|
|
@@ -1333,6 +1645,11 @@ class NativeBackboneLogGraph {
|
|
|
1333
1645
|
input.resolveTrimmedEntries === false &&
|
|
1334
1646
|
input.trimLengthTo != null &&
|
|
1335
1647
|
hasNoNext) {
|
|
1648
|
+
const prepareTrimRefs = this.native
|
|
1649
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_refs;
|
|
1650
|
+
if (prepareTrimRefs) {
|
|
1651
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(prepareTrimRefs.call(this.native, wallTime, logical, input.gid, entryType, input.metaData, input.payloadData, input.trimLengthTo, ...documentIndexArgs));
|
|
1652
|
+
}
|
|
1336
1653
|
return compactPreparedCommitFactsWithTrimHashesFromRow(this.native.prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_hashes(wallTime, logical, input.gid, entryType, input.metaData, input.payloadData, input.trimLengthTo, ...documentIndexArgs));
|
|
1337
1654
|
}
|
|
1338
1655
|
const baseArgs = [
|
|
@@ -2263,6 +2580,12 @@ export class NativePeerbitBackbone {
|
|
|
2263
2580
|
fullReplicaCandidatesFor(minReplicas, selfHash) {
|
|
2264
2581
|
return this.native.full_replica_candidates_for(minReplicas, selfHash);
|
|
2265
2582
|
}
|
|
2583
|
+
getRoutingFullReplicaLeaders(replicas, options) {
|
|
2584
|
+
if (!this.native.get_routing_full_replica_leaders) {
|
|
2585
|
+
return undefined;
|
|
2586
|
+
}
|
|
2587
|
+
return rowsToSamples(this.native.get_routing_full_replica_leaders(replicas, ...findLeaderArguments(options)));
|
|
2588
|
+
}
|
|
2266
2589
|
clearEntryCoordinates() {
|
|
2267
2590
|
this.native.clear_entry_coordinates();
|
|
2268
2591
|
}
|
|
@@ -3016,6 +3339,7 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
3016
3339
|
appendFailure;
|
|
3017
3340
|
readLimited;
|
|
3018
3341
|
durableBarrier;
|
|
3342
|
+
atomicReplace;
|
|
3019
3343
|
constructor(directory, fs) {
|
|
3020
3344
|
this.directory = directory;
|
|
3021
3345
|
this.fs = fs;
|
|
@@ -3028,6 +3352,10 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
3028
3352
|
if (!fs || typeof fs.openBoundedRead === "function") {
|
|
3029
3353
|
this.readLimited = (name, maxBytes) => this.readWithinLimit(name, maxBytes);
|
|
3030
3354
|
}
|
|
3355
|
+
if (!fs ||
|
|
3356
|
+
(typeof fs.open === "function" && typeof fs.rename === "function")) {
|
|
3357
|
+
this.atomicReplace = (name, bytes) => this.replaceAtomically(name, bytes);
|
|
3358
|
+
}
|
|
3031
3359
|
}
|
|
3032
3360
|
async nodeFs() {
|
|
3033
3361
|
return this.fs ?? (await importNodeFs());
|
|
@@ -3159,6 +3487,43 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
3159
3487
|
await this.closeAppendHandle(path);
|
|
3160
3488
|
await fs.writeFile(path, bytes);
|
|
3161
3489
|
}
|
|
3490
|
+
async replaceAtomically(name, bytes) {
|
|
3491
|
+
const fs = await this.ensureDirectory();
|
|
3492
|
+
if (!fs.open || !fs.rename) {
|
|
3493
|
+
throw new Error("Node coordinate persistence does not expose atomic file replacement");
|
|
3494
|
+
}
|
|
3495
|
+
const validName = validateCoordinatePersistenceName(name);
|
|
3496
|
+
const temporaryName = validateCoordinatePersistenceName(`${validName}.tmp`);
|
|
3497
|
+
const path = await this.filePath(validName);
|
|
3498
|
+
const temporaryPath = await this.filePath(temporaryName);
|
|
3499
|
+
await this.closeAppendHandle(path);
|
|
3500
|
+
await this.closeAppendHandle(temporaryPath);
|
|
3501
|
+
await fs.rm(temporaryPath, { force: true });
|
|
3502
|
+
await fs.writeFile(temporaryPath, bytes);
|
|
3503
|
+
let temporaryHandle;
|
|
3504
|
+
try {
|
|
3505
|
+
temporaryHandle = await fs.open(temporaryPath, "r");
|
|
3506
|
+
if (typeof temporaryHandle.sync !== "function") {
|
|
3507
|
+
throw new Error("Node coordinate persistence atomic replacement does not expose FileHandle.sync");
|
|
3508
|
+
}
|
|
3509
|
+
await temporaryHandle.sync();
|
|
3510
|
+
}
|
|
3511
|
+
finally {
|
|
3512
|
+
await temporaryHandle?.close();
|
|
3513
|
+
}
|
|
3514
|
+
await fs.rename(temporaryPath, path);
|
|
3515
|
+
let directoryHandle;
|
|
3516
|
+
try {
|
|
3517
|
+
directoryHandle = await fs.open(this.directory, "r");
|
|
3518
|
+
if (typeof directoryHandle.sync !== "function") {
|
|
3519
|
+
throw new Error("Node coordinate persistence atomic replacement does not expose directory sync");
|
|
3520
|
+
}
|
|
3521
|
+
await directoryHandle.sync();
|
|
3522
|
+
}
|
|
3523
|
+
finally {
|
|
3524
|
+
await directoryHandle?.close();
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3162
3527
|
async append(name, bytes) {
|
|
3163
3528
|
if (this.appendFailure !== undefined) {
|
|
3164
3529
|
throw this.appendFailure;
|
|
@@ -3458,6 +3823,7 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
|
|
|
3458
3823
|
readLimited;
|
|
3459
3824
|
supportsRemoval;
|
|
3460
3825
|
durableBarrier;
|
|
3826
|
+
atomicReplace;
|
|
3461
3827
|
constructor(inner, options = {}) {
|
|
3462
3828
|
this.inner = inner;
|
|
3463
3829
|
this.options = options;
|
|
@@ -3473,6 +3839,12 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
|
|
|
3473
3839
|
await inner.durableBarrier(name);
|
|
3474
3840
|
};
|
|
3475
3841
|
}
|
|
3842
|
+
if (typeof inner.atomicReplace === "function") {
|
|
3843
|
+
this.atomicReplace = async (name, bytes) => {
|
|
3844
|
+
await this.flush(name);
|
|
3845
|
+
await inner.atomicReplace(name, bytes);
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3476
3848
|
}
|
|
3477
3849
|
buffer(name) {
|
|
3478
3850
|
const validName = validateCoordinatePersistenceName(name);
|
|
@@ -3589,7 +3961,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3589
3961
|
flushIntervalMs;
|
|
3590
3962
|
compactMaxJournalBytes;
|
|
3591
3963
|
compactMaxJournalRecords;
|
|
3592
|
-
crashSafeCompaction
|
|
3964
|
+
crashSafeCompaction;
|
|
3593
3965
|
durableBarrier;
|
|
3594
3966
|
supportsDrop;
|
|
3595
3967
|
dropIsTerminal = true;
|
|
@@ -3599,9 +3971,22 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3599
3971
|
documentJournalFile;
|
|
3600
3972
|
documentSignerSnapshotFile;
|
|
3601
3973
|
documentSignerJournalFile;
|
|
3974
|
+
checkpointConfigurationChecksum;
|
|
3975
|
+
checkpointStateFile;
|
|
3976
|
+
checkpointFiles;
|
|
3977
|
+
checkpointJournalFiles;
|
|
3602
3978
|
journalInitialized;
|
|
3979
|
+
journalByteLength = 0;
|
|
3980
|
+
journalRecordCount = 0;
|
|
3603
3981
|
documentJournalInitialized;
|
|
3982
|
+
documentJournalByteLength = 0;
|
|
3983
|
+
documentJournalRecordCount = 0;
|
|
3604
3984
|
documentSignerJournalInitialized;
|
|
3985
|
+
documentSignerJournalByteLength = 0;
|
|
3986
|
+
documentSignerJournalRecordCount = 0;
|
|
3987
|
+
checkpointHighwater = 0n;
|
|
3988
|
+
checkpointCompleted;
|
|
3989
|
+
checkpointPending;
|
|
3605
3990
|
lastFlushMs = Date.now();
|
|
3606
3991
|
persistenceQueue;
|
|
3607
3992
|
persistenceLifecycle = "active";
|
|
@@ -3627,24 +4012,61 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3627
4012
|
nativeBackboneCoordinatePersistenceFiles.documentSignerSnapshot);
|
|
3628
4013
|
this.documentSignerJournalFile = validateCoordinatePersistenceName(options.documentSignerJournal ??
|
|
3629
4014
|
nativeBackboneCoordinatePersistenceFiles.documentSignerJournal);
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
4015
|
+
this.checkpointConfigurationChecksum = coordinateJournalChecksum(new TextEncoder().encode(JSON.stringify([
|
|
4016
|
+
this.snapshotFile,
|
|
4017
|
+
this.journalFile,
|
|
4018
|
+
this.documentSnapshotFile,
|
|
4019
|
+
this.documentJournalFile,
|
|
4020
|
+
this.documentSignerSnapshotFile,
|
|
4021
|
+
this.documentSignerJournalFile,
|
|
4022
|
+
])));
|
|
4023
|
+
this.checkpointStateFile = validateCoordinatePersistenceName(`${this.snapshotFile}.checkpoint-state`);
|
|
4024
|
+
this.checkpointFiles = {
|
|
4025
|
+
a: validateCoordinatePersistenceName(`${this.snapshotFile}.checkpoint-a`),
|
|
4026
|
+
b: validateCoordinatePersistenceName(`${this.snapshotFile}.checkpoint-b`),
|
|
4027
|
+
};
|
|
4028
|
+
this.checkpointJournalFiles = {
|
|
4029
|
+
a: {
|
|
4030
|
+
coordinate: validateCoordinatePersistenceName(`${this.journalFile}.checkpoint-a`),
|
|
4031
|
+
document: validateCoordinatePersistenceName(`${this.documentJournalFile}.checkpoint-a`),
|
|
4032
|
+
signer: validateCoordinatePersistenceName(`${this.documentSignerJournalFile}.checkpoint-a`),
|
|
4033
|
+
},
|
|
4034
|
+
b: {
|
|
4035
|
+
coordinate: validateCoordinatePersistenceName(`${this.journalFile}.checkpoint-b`),
|
|
4036
|
+
document: validateCoordinatePersistenceName(`${this.documentJournalFile}.checkpoint-b`),
|
|
4037
|
+
signer: validateCoordinatePersistenceName(`${this.documentSignerJournalFile}.checkpoint-b`),
|
|
4038
|
+
},
|
|
4039
|
+
};
|
|
4040
|
+
if (this.configuredFiles().includes(nativeBackboneCoordinateDropTombstoneFile) ||
|
|
4041
|
+
new Set(this.configuredFiles()).size !== this.configuredFiles().length) {
|
|
4042
|
+
throw new Error("Native backbone coordinate persistence files conflict with one another or with its drop tombstone");
|
|
4043
|
+
}
|
|
4044
|
+
this.crashSafeCompaction =
|
|
4045
|
+
typeof store.atomicReplace === "function" &&
|
|
4046
|
+
this.supportsDrop &&
|
|
4047
|
+
this.durableBarrier;
|
|
3633
4048
|
this.flushOnAppend = options.flushOnAppend ?? true;
|
|
3634
4049
|
this.flushMaxPendingBytes = resolveCoordinateFlushMaxPendingBytes(options);
|
|
3635
4050
|
if (options.flushIntervalMs != null) {
|
|
3636
4051
|
this.flushIntervalMs = Math.max(0, options.flushIntervalMs);
|
|
3637
4052
|
}
|
|
3638
|
-
if (options.compactMaxJournalBytes != null ||
|
|
3639
|
-
options.compactMaxJournalRecords != null)
|
|
4053
|
+
if ((options.compactMaxJournalBytes != null ||
|
|
4054
|
+
options.compactMaxJournalRecords != null) &&
|
|
4055
|
+
!this.crashSafeCompaction) {
|
|
3640
4056
|
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
3641
4057
|
}
|
|
4058
|
+
if (options.compactMaxJournalBytes != null) {
|
|
4059
|
+
this.compactMaxJournalBytes = validateCoordinateCompactionThreshold(options.compactMaxJournalBytes, "compactMaxJournalBytes");
|
|
4060
|
+
}
|
|
4061
|
+
if (options.compactMaxJournalRecords != null) {
|
|
4062
|
+
this.compactMaxJournalRecords = validateCoordinateCompactionThreshold(options.compactMaxJournalRecords, "compactMaxJournalRecords");
|
|
4063
|
+
}
|
|
3642
4064
|
}
|
|
3643
4065
|
/** Durable operation-intent capability for strict shared-log transactions. */
|
|
3644
4066
|
get intentStore() {
|
|
3645
4067
|
return this.store;
|
|
3646
4068
|
}
|
|
3647
|
-
|
|
4069
|
+
legacyConfiguredFiles() {
|
|
3648
4070
|
return [
|
|
3649
4071
|
this.snapshotFile,
|
|
3650
4072
|
this.journalFile,
|
|
@@ -3654,26 +4076,79 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3654
4076
|
this.documentSignerJournalFile,
|
|
3655
4077
|
];
|
|
3656
4078
|
}
|
|
4079
|
+
configuredFiles() {
|
|
4080
|
+
return [
|
|
4081
|
+
...this.legacyConfiguredFiles(),
|
|
4082
|
+
this.checkpointStateFile,
|
|
4083
|
+
this.checkpointFiles.a,
|
|
4084
|
+
this.checkpointFiles.b,
|
|
4085
|
+
this.checkpointJournalFiles.a.coordinate,
|
|
4086
|
+
this.checkpointJournalFiles.a.document,
|
|
4087
|
+
this.checkpointJournalFiles.a.signer,
|
|
4088
|
+
this.checkpointJournalFiles.b.coordinate,
|
|
4089
|
+
this.checkpointJournalFiles.b.document,
|
|
4090
|
+
this.checkpointJournalFiles.b.signer,
|
|
4091
|
+
`${this.checkpointStateFile}.tmp`,
|
|
4092
|
+
`${this.checkpointFiles.a}.tmp`,
|
|
4093
|
+
`${this.checkpointFiles.b}.tmp`,
|
|
4094
|
+
`${this.journalFile}.tmp`,
|
|
4095
|
+
`${nativeBackboneCoordinateDropTombstoneFile}.tmp`,
|
|
4096
|
+
];
|
|
4097
|
+
}
|
|
4098
|
+
checkpointSlot(generation) {
|
|
4099
|
+
return generation % 2n === 1n ? "a" : "b";
|
|
4100
|
+
}
|
|
4101
|
+
activeJournalFiles() {
|
|
4102
|
+
const completed = this.checkpointCompleted;
|
|
4103
|
+
return completed
|
|
4104
|
+
? this.checkpointJournalFiles[this.checkpointSlot(completed.generation)]
|
|
4105
|
+
: {
|
|
4106
|
+
coordinate: this.journalFile,
|
|
4107
|
+
document: this.documentJournalFile,
|
|
4108
|
+
signer: this.documentSignerJournalFile,
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
3657
4111
|
assertLifecycleActive(operation) {
|
|
3658
4112
|
if (this.persistenceLifecycle !== "active") {
|
|
3659
4113
|
throw new Error(`Native backbone coordinate persistence can not ${operation} while ${this.persistenceLifecycle}`);
|
|
3660
4114
|
}
|
|
3661
4115
|
}
|
|
3662
4116
|
assertActive(operation) {
|
|
3663
|
-
|
|
3664
|
-
throw this.persistenceFailure;
|
|
3665
|
-
}
|
|
4117
|
+
this.assertPersistenceHealthy();
|
|
3666
4118
|
this.assertLifecycleActive(operation);
|
|
3667
4119
|
if (this.dropInitiatedOnGeneration) {
|
|
3668
4120
|
throw new Error(`Native backbone coordinate persistence can not ${operation} after drop was initiated; retry drop or resume drop first`);
|
|
3669
4121
|
}
|
|
3670
4122
|
}
|
|
4123
|
+
assertPersistenceHealthy() {
|
|
4124
|
+
if (this.persistenceFailure !== undefined) {
|
|
4125
|
+
throw this.persistenceFailure;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
3671
4128
|
resetJournalTracking() {
|
|
3672
4129
|
this.journalInitialized = undefined;
|
|
4130
|
+
this.journalByteLength = 0;
|
|
4131
|
+
this.journalRecordCount = 0;
|
|
3673
4132
|
this.documentJournalInitialized = undefined;
|
|
4133
|
+
this.documentJournalByteLength = 0;
|
|
4134
|
+
this.documentJournalRecordCount = 0;
|
|
3674
4135
|
this.documentSignerJournalInitialized = undefined;
|
|
4136
|
+
this.documentSignerJournalByteLength = 0;
|
|
4137
|
+
this.documentSignerJournalRecordCount = 0;
|
|
4138
|
+
this.checkpointHighwater = 0n;
|
|
4139
|
+
this.checkpointCompleted = undefined;
|
|
4140
|
+
this.checkpointPending = undefined;
|
|
3675
4141
|
this.lastFlushMs = Date.now();
|
|
3676
4142
|
}
|
|
4143
|
+
async persistDropTombstone(files, preferAtomicReplace = false) {
|
|
4144
|
+
const bytes = nativeBackboneCoordinateDropTombstoneBytes(files);
|
|
4145
|
+
if (preferAtomicReplace && this.store.atomicReplace) {
|
|
4146
|
+
await this.store.atomicReplace(nativeBackboneCoordinateDropTombstoneFile, bytes);
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
await this.store.write(nativeBackboneCoordinateDropTombstoneFile, bytes);
|
|
4150
|
+
await this.barrierFile(nativeBackboneCoordinateDropTombstoneFile);
|
|
4151
|
+
}
|
|
3677
4152
|
async eraseDropFiles(files) {
|
|
3678
4153
|
if (!this.supportsDrop || !this.store.remove) {
|
|
3679
4154
|
throw new Error("Native backbone coordinate persistence store does not support removal");
|
|
@@ -3709,13 +4184,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3709
4184
|
this.dropInitiatedOnGeneration = true;
|
|
3710
4185
|
this.persistenceLifecycle = "dropping";
|
|
3711
4186
|
await this.enqueuePersistence(async () => {
|
|
3712
|
-
await this.
|
|
3713
|
-
if (this.store.durableBarrier) {
|
|
3714
|
-
await this.store.durableBarrier(nativeBackboneCoordinateDropTombstoneFile);
|
|
3715
|
-
}
|
|
3716
|
-
else {
|
|
3717
|
-
await this.store.flush?.(nativeBackboneCoordinateDropTombstoneFile);
|
|
3718
|
-
}
|
|
4187
|
+
await this.persistDropTombstone(files);
|
|
3719
4188
|
await this.eraseDropFiles(files);
|
|
3720
4189
|
this.resetJournalTracking();
|
|
3721
4190
|
this.persistenceLifecycle = "dropped";
|
|
@@ -3748,6 +4217,43 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3748
4217
|
return this.resumeDropInternal(completesInitiatedDrop ? "dropped" : "active");
|
|
3749
4218
|
});
|
|
3750
4219
|
}
|
|
4220
|
+
async barrierFile(name) {
|
|
4221
|
+
if (this.store.durableBarrier) {
|
|
4222
|
+
await this.store.durableBarrier(name);
|
|
4223
|
+
}
|
|
4224
|
+
else {
|
|
4225
|
+
await this.store.flush?.(name);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
async hasDurableLegacyMigrationSentinel() {
|
|
4229
|
+
const existing = await this.store.read(this.journalFile);
|
|
4230
|
+
if (existing &&
|
|
4231
|
+
existing.byteLength ===
|
|
4232
|
+
nativeBackboneCoordinateLegacyMigrationSentinel.byteLength &&
|
|
4233
|
+
nativeBackboneCoordinateLegacyMigrationSentinel.every((byte, index) => existing[index] === byte)) {
|
|
4234
|
+
// A previous process may have observed the bytes from page cache before
|
|
4235
|
+
// its durability barrier rejected. Cross a fresh barrier on every reopen
|
|
4236
|
+
// before any post-checkpoint WAL append can be admitted.
|
|
4237
|
+
await this.barrierFile(this.journalFile);
|
|
4238
|
+
return true;
|
|
4239
|
+
}
|
|
4240
|
+
return false;
|
|
4241
|
+
}
|
|
4242
|
+
async installLegacyMigrationSentinel() {
|
|
4243
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
if (!this.store.atomicReplace) {
|
|
4247
|
+
throw new Error("Native backbone completed checkpoint can not install its downgrade sentinel atomically");
|
|
4248
|
+
}
|
|
4249
|
+
await this.store.atomicReplace(this.journalFile, nativeBackboneCoordinateLegacyMigrationSentinel);
|
|
4250
|
+
}
|
|
4251
|
+
async requireLegacyMigrationSentinel() {
|
|
4252
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
throw new Error("Native backbone completed checkpoint downgrade sentinel is missing or has been replaced; refusing to overwrite possible legacy writes");
|
|
4256
|
+
}
|
|
3751
4257
|
async hydrate(backbone) {
|
|
3752
4258
|
this.assertActive("hydrate");
|
|
3753
4259
|
// Claim the lifecycle synchronously. close() queues after this complete
|
|
@@ -3758,24 +4264,147 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3758
4264
|
this.assertHydrating();
|
|
3759
4265
|
await this.resumeDropInternal("hydrating");
|
|
3760
4266
|
this.assertHydrating();
|
|
3761
|
-
const
|
|
3762
|
-
this.store.read(this.snapshotFile),
|
|
3763
|
-
this.store.read(this.journalFile),
|
|
3764
|
-
this.store.read(this.documentSnapshotFile),
|
|
3765
|
-
this.store.read(this.documentJournalFile),
|
|
3766
|
-
this.store.read(this.documentSignerSnapshotFile),
|
|
3767
|
-
this.store.read(this.documentSignerJournalFile),
|
|
3768
|
-
]);
|
|
4267
|
+
const checkpointStateBytes = await this.store.read(this.checkpointStateFile);
|
|
3769
4268
|
this.assertHydrating();
|
|
4269
|
+
let checkpointState;
|
|
4270
|
+
if (checkpointStateBytes) {
|
|
4271
|
+
try {
|
|
4272
|
+
checkpointState = parseNativeBackboneCoordinateCheckpointState(checkpointStateBytes, this.checkpointStateFile);
|
|
4273
|
+
if (checkpointState.configurationChecksum !==
|
|
4274
|
+
this.checkpointConfigurationChecksum) {
|
|
4275
|
+
throw new Error("Native backbone checkpoint authority does not match the configured persistence files");
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
catch (error) {
|
|
4279
|
+
this.persistenceFailure ??= error;
|
|
4280
|
+
throw this.persistenceFailure;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
this.checkpointHighwater = checkpointState?.highwater ?? 0n;
|
|
4284
|
+
this.checkpointPending = checkpointState?.pending;
|
|
4285
|
+
this.checkpointCompleted = checkpointState?.completed;
|
|
4286
|
+
let checkpointAuthority = this.checkpointCompleted;
|
|
4287
|
+
let promotePendingCheckpoint = false;
|
|
4288
|
+
if (!checkpointAuthority && this.checkpointPending) {
|
|
4289
|
+
try {
|
|
4290
|
+
// On the first generation only, the sentinel is the durable stage
|
|
4291
|
+
// boundary: publication writes it after the pending checkpoint and its
|
|
4292
|
+
// WAL headers, but before completed authority. A pre-checkpoint reader
|
|
4293
|
+
// can no longer consume legacy state, so recovery may validate and
|
|
4294
|
+
// promote the staged generation. Once a completed generation exists the
|
|
4295
|
+
// sentinel predates later pending work and is not a promotion signal.
|
|
4296
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
4297
|
+
checkpointAuthority = this.checkpointPending;
|
|
4298
|
+
promotePendingCheckpoint = true;
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
catch (error) {
|
|
4302
|
+
this.persistenceFailure ??= error;
|
|
4303
|
+
throw this.persistenceFailure;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
let snapshot;
|
|
4307
|
+
let journal;
|
|
4308
|
+
let documentSnapshot;
|
|
4309
|
+
let documentJournal;
|
|
4310
|
+
let documentSignerSnapshot;
|
|
4311
|
+
let documentSignerJournal;
|
|
4312
|
+
let journalNames = {
|
|
4313
|
+
coordinate: this.journalFile,
|
|
4314
|
+
document: this.documentJournalFile,
|
|
4315
|
+
signer: this.documentSignerJournalFile,
|
|
4316
|
+
};
|
|
4317
|
+
if (checkpointAuthority) {
|
|
4318
|
+
const authority = checkpointAuthority;
|
|
4319
|
+
const authorityKind = promotePendingCheckpoint
|
|
4320
|
+
? "promotable pending"
|
|
4321
|
+
: "completed";
|
|
4322
|
+
const slot = this.checkpointSlot(authority.generation);
|
|
4323
|
+
const checkpointFile = this.checkpointFiles[slot];
|
|
4324
|
+
journalNames = this.checkpointJournalFiles[slot];
|
|
4325
|
+
const [checkpointBytes, coordinateJournal, valueJournal, signerJournal,] = await Promise.all([
|
|
4326
|
+
this.store.read(checkpointFile),
|
|
4327
|
+
this.store.read(journalNames.coordinate),
|
|
4328
|
+
this.store.read(journalNames.document),
|
|
4329
|
+
this.store.read(journalNames.signer),
|
|
4330
|
+
]);
|
|
4331
|
+
this.assertHydrating();
|
|
4332
|
+
try {
|
|
4333
|
+
if (!checkpointBytes ||
|
|
4334
|
+
checkpointBytes.byteLength !== authority.byteLength ||
|
|
4335
|
+
coordinateJournalChecksum(checkpointBytes) !== authority.checksum) {
|
|
4336
|
+
throw new Error(`Native backbone ${authorityKind} checkpoint authority is missing or corrupt`);
|
|
4337
|
+
}
|
|
4338
|
+
if (!coordinateJournal || !valueJournal || !signerJournal) {
|
|
4339
|
+
throw new Error(`Native backbone ${authorityKind} checkpoint WAL generation is incomplete`);
|
|
4340
|
+
}
|
|
4341
|
+
const checkpoint = parseNativeBackboneCoordinateCheckpoint(checkpointBytes, checkpointFile);
|
|
4342
|
+
if (checkpoint.generation !== authority.generation ||
|
|
4343
|
+
checkpoint.configurationChecksum !==
|
|
4344
|
+
this.checkpointConfigurationChecksum) {
|
|
4345
|
+
throw new Error(`Native backbone checkpoint generation or configuration does not match its ${authorityKind} authority`);
|
|
4346
|
+
}
|
|
4347
|
+
snapshot = checkpoint.coordinateSnapshot;
|
|
4348
|
+
documentSnapshot = checkpoint.documentSnapshot;
|
|
4349
|
+
documentSignerSnapshot = checkpoint.documentSignerSnapshot;
|
|
4350
|
+
journal = coordinateJournal;
|
|
4351
|
+
documentJournal = valueJournal;
|
|
4352
|
+
documentSignerJournal = signerJournal;
|
|
4353
|
+
}
|
|
4354
|
+
catch (error) {
|
|
4355
|
+
this.persistenceFailure ??= error;
|
|
4356
|
+
throw this.persistenceFailure;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
else {
|
|
4360
|
+
[
|
|
4361
|
+
snapshot,
|
|
4362
|
+
journal,
|
|
4363
|
+
documentSnapshot,
|
|
4364
|
+
documentJournal,
|
|
4365
|
+
documentSignerSnapshot,
|
|
4366
|
+
documentSignerJournal,
|
|
4367
|
+
] = await Promise.all([
|
|
4368
|
+
this.store.read(this.snapshotFile),
|
|
4369
|
+
this.store.read(this.journalFile),
|
|
4370
|
+
this.store.read(this.documentSnapshotFile),
|
|
4371
|
+
this.store.read(this.documentJournalFile),
|
|
4372
|
+
this.store.read(this.documentSignerSnapshotFile),
|
|
4373
|
+
this.store.read(this.documentSignerJournalFile),
|
|
4374
|
+
]);
|
|
4375
|
+
this.assertHydrating();
|
|
4376
|
+
}
|
|
3770
4377
|
try {
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4378
|
+
if (checkpointAuthority &&
|
|
4379
|
+
(!journal ||
|
|
4380
|
+
!hasCoordinateJournalMagic(journal) ||
|
|
4381
|
+
!documentJournal ||
|
|
4382
|
+
!hasCoordinateJournalMagic(documentJournal) ||
|
|
4383
|
+
!documentSignerJournal ||
|
|
4384
|
+
!hasCoordinateJournalMagic(documentSignerJournal))) {
|
|
4385
|
+
throw new Error(`Native backbone ${promotePendingCheckpoint ? "promotable pending" : "completed"} checkpoint WAL generation has a missing or invalid header`);
|
|
4386
|
+
}
|
|
4387
|
+
validateCoordinateJournal(journal, journalNames.coordinate);
|
|
4388
|
+
validateCoordinateJournal(documentJournal, journalNames.document);
|
|
4389
|
+
validateCoordinateJournal(documentSignerJournal, journalNames.signer);
|
|
3774
4390
|
}
|
|
3775
4391
|
catch (error) {
|
|
3776
4392
|
this.persistenceFailure ??= error;
|
|
3777
4393
|
throw this.persistenceFailure;
|
|
3778
4394
|
}
|
|
4395
|
+
if (checkpointAuthority && !promotePendingCheckpoint) {
|
|
4396
|
+
try {
|
|
4397
|
+
// Completed authority is never permission to overwrite an ordinary
|
|
4398
|
+
// legacy WAL: it may contain writes made by a downgraded process in the
|
|
4399
|
+
// publication cut. Require the exact durable sentinel before loading.
|
|
4400
|
+
await this.requireLegacyMigrationSentinel();
|
|
4401
|
+
this.assertHydrating();
|
|
4402
|
+
}
|
|
4403
|
+
catch (error) {
|
|
4404
|
+
this.persistenceFailure ??= error;
|
|
4405
|
+
throw this.persistenceFailure;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
3779
4408
|
let operations;
|
|
3780
4409
|
let documentOperations;
|
|
3781
4410
|
let documentSignerOperations;
|
|
@@ -3797,12 +4426,49 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3797
4426
|
}
|
|
3798
4427
|
throw error;
|
|
3799
4428
|
}
|
|
4429
|
+
if (promotePendingCheckpoint) {
|
|
4430
|
+
try {
|
|
4431
|
+
const authority = checkpointAuthority;
|
|
4432
|
+
if (!this.store.atomicReplace) {
|
|
4433
|
+
throw new Error("Native backbone pending checkpoint can not publish completed authority atomically");
|
|
4434
|
+
}
|
|
4435
|
+
await this.store.atomicReplace(this.checkpointStateFile, encodeNativeBackboneCoordinateCheckpointState({
|
|
4436
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
4437
|
+
highwater: authority.generation,
|
|
4438
|
+
completed: authority,
|
|
4439
|
+
}));
|
|
4440
|
+
this.checkpointCompleted = authority;
|
|
4441
|
+
this.checkpointPending = undefined;
|
|
4442
|
+
this.assertHydrating();
|
|
4443
|
+
}
|
|
4444
|
+
catch (error) {
|
|
4445
|
+
this.persistenceFailure ??= error;
|
|
4446
|
+
throw this.persistenceFailure;
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
if (this.checkpointCompleted || this.checkpointPending) {
|
|
4450
|
+
try {
|
|
4451
|
+
await this.cleanupRetiredCheckpointFiles();
|
|
4452
|
+
this.assertHydrating();
|
|
4453
|
+
}
|
|
4454
|
+
catch (error) {
|
|
4455
|
+
this.persistenceFailure ??= error;
|
|
4456
|
+
throw this.persistenceFailure;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
3800
4459
|
this.assertHydrating();
|
|
3801
4460
|
this.journalInitialized = !!journal && journal.byteLength > 0;
|
|
4461
|
+
this.journalByteLength = journal?.byteLength ?? 0;
|
|
4462
|
+
this.journalRecordCount = operations;
|
|
3802
4463
|
this.documentJournalInitialized =
|
|
3803
4464
|
!!documentJournal && documentJournal.byteLength > 0;
|
|
4465
|
+
this.documentJournalByteLength = documentJournal?.byteLength ?? 0;
|
|
4466
|
+
this.documentJournalRecordCount = documentOperations;
|
|
3804
4467
|
this.documentSignerJournalInitialized =
|
|
3805
4468
|
!!documentSignerJournal && documentSignerJournal.byteLength > 0;
|
|
4469
|
+
this.documentSignerJournalByteLength =
|
|
4470
|
+
documentSignerJournal?.byteLength ?? 0;
|
|
4471
|
+
this.documentSignerJournalRecordCount = documentSignerOperations;
|
|
3806
4472
|
backbone.setCoordinateJournalEnabled(true);
|
|
3807
4473
|
backbone.setDocumentJournalEnabled(true);
|
|
3808
4474
|
backbone.setDocumentSignerJournalEnabled(true);
|
|
@@ -3838,13 +4504,17 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3838
4504
|
return false;
|
|
3839
4505
|
}
|
|
3840
4506
|
let tombstone;
|
|
4507
|
+
let expandedFiles;
|
|
3841
4508
|
try {
|
|
3842
4509
|
tombstone = parseNativeBackboneCoordinateDropTombstone(bytes);
|
|
3843
|
-
for (const file of this.
|
|
4510
|
+
for (const file of this.legacyConfiguredFiles()) {
|
|
3844
4511
|
if (!tombstone.files.includes(file)) {
|
|
3845
4512
|
throw new Error("Native backbone drop tombstone does not cover the configured namespace");
|
|
3846
4513
|
}
|
|
3847
4514
|
}
|
|
4515
|
+
expandedFiles = [
|
|
4516
|
+
...new Set([...tombstone.files, ...this.configuredFiles()]),
|
|
4517
|
+
];
|
|
3848
4518
|
}
|
|
3849
4519
|
catch (error) {
|
|
3850
4520
|
// Corruption must never hydrate stale files, but an explicit drop must
|
|
@@ -3853,6 +4523,17 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3853
4523
|
this.persistenceLifecycle = this.closePromise ? "closing" : "active";
|
|
3854
4524
|
throw this.persistenceFailure;
|
|
3855
4525
|
}
|
|
4526
|
+
if (expandedFiles.length !== tombstone.files.length) {
|
|
4527
|
+
// Older valid markers only knew the six snapshot/WAL files. Widen the
|
|
4528
|
+
// destructive intent durably before erasing any checkpoint generation,
|
|
4529
|
+
// while preserving additional namespace files recorded by that version.
|
|
4530
|
+
await this.persistDropTombstone(expandedFiles, true);
|
|
4531
|
+
tombstone = { ...tombstone, files: expandedFiles };
|
|
4532
|
+
}
|
|
4533
|
+
// A prior replacement attempt may have made the expanded bytes visible before
|
|
4534
|
+
// its durability barrier rejected. Cross a fresh barrier on every recovery
|
|
4535
|
+
// before allowing any target removal.
|
|
4536
|
+
await this.barrierFile(nativeBackboneCoordinateDropTombstoneFile);
|
|
3856
4537
|
await this.eraseDropFiles(tombstone.files);
|
|
3857
4538
|
this.resetJournalTracking();
|
|
3858
4539
|
this.persistenceLifecycle =
|
|
@@ -3896,7 +4577,13 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3896
4577
|
this.assertActive("flush");
|
|
3897
4578
|
// Serialized with compact() so a flush never clears records appended to
|
|
3898
4579
|
// the wasm journal while a previous flush was awaiting its disk write.
|
|
3899
|
-
return this.enqueuePersistence(() =>
|
|
4580
|
+
return this.enqueuePersistence(() => {
|
|
4581
|
+
// A call can be admitted while an earlier queued write is still in flight.
|
|
4582
|
+
// Re-check the sticky failure at execution time so no suffix can ACK after
|
|
4583
|
+
// that earlier write or authority switch failed.
|
|
4584
|
+
this.assertPersistenceHealthy();
|
|
4585
|
+
return this.flushJournalInternal(backbone);
|
|
4586
|
+
});
|
|
3900
4587
|
}
|
|
3901
4588
|
enqueuePersistence(fn) {
|
|
3902
4589
|
// Runs `fn` immediately when no other persistence operation is in
|
|
@@ -3912,7 +4599,151 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3912
4599
|
});
|
|
3913
4600
|
return next;
|
|
3914
4601
|
}
|
|
3915
|
-
|
|
4602
|
+
shouldCompactJournal(additionalBytes, additionalRecords) {
|
|
4603
|
+
return ((this.compactMaxJournalBytes != null &&
|
|
4604
|
+
this.journalByteLength +
|
|
4605
|
+
this.documentJournalByteLength +
|
|
4606
|
+
this.documentSignerJournalByteLength +
|
|
4607
|
+
additionalBytes >=
|
|
4608
|
+
this.compactMaxJournalBytes) ||
|
|
4609
|
+
(this.compactMaxJournalRecords != null &&
|
|
4610
|
+
this.journalRecordCount +
|
|
4611
|
+
this.documentJournalRecordCount +
|
|
4612
|
+
this.documentSignerJournalRecordCount +
|
|
4613
|
+
additionalRecords >=
|
|
4614
|
+
this.compactMaxJournalRecords));
|
|
4615
|
+
}
|
|
4616
|
+
nextCheckpointGeneration() {
|
|
4617
|
+
let generation = this.checkpointHighwater + 1n;
|
|
4618
|
+
const active = this.checkpointCompleted;
|
|
4619
|
+
if (active &&
|
|
4620
|
+
this.checkpointSlot(generation) === this.checkpointSlot(active.generation)) {
|
|
4621
|
+
generation += 1n;
|
|
4622
|
+
}
|
|
4623
|
+
if (generation > nativeBackboneCoordinateMaxU64) {
|
|
4624
|
+
throw new RangeError("Native backbone checkpoint generation highwater is exhausted");
|
|
4625
|
+
}
|
|
4626
|
+
return generation;
|
|
4627
|
+
}
|
|
4628
|
+
async publishCheckpoint(checkpoint) {
|
|
4629
|
+
if (!this.crashSafeCompaction ||
|
|
4630
|
+
!this.store.atomicReplace ||
|
|
4631
|
+
!this.store.remove) {
|
|
4632
|
+
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
4633
|
+
}
|
|
4634
|
+
const generation = this.nextCheckpointGeneration();
|
|
4635
|
+
const slot = this.checkpointSlot(generation);
|
|
4636
|
+
const checkpointBytes = encodeNativeBackboneCoordinateCheckpoint({
|
|
4637
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
4638
|
+
generation,
|
|
4639
|
+
...checkpoint,
|
|
4640
|
+
});
|
|
4641
|
+
const authority = {
|
|
4642
|
+
generation,
|
|
4643
|
+
byteLength: checkpointBytes.byteLength,
|
|
4644
|
+
checksum: coordinateJournalChecksum(checkpointBytes),
|
|
4645
|
+
};
|
|
4646
|
+
const pendingState = {
|
|
4647
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
4648
|
+
highwater: generation,
|
|
4649
|
+
pending: authority,
|
|
4650
|
+
completed: this.checkpointCompleted,
|
|
4651
|
+
};
|
|
4652
|
+
await this.store.atomicReplace(this.checkpointStateFile, encodeNativeBackboneCoordinateCheckpointState(pendingState));
|
|
4653
|
+
this.checkpointHighwater = generation;
|
|
4654
|
+
this.checkpointPending = authority;
|
|
4655
|
+
const targetJournals = this.checkpointJournalFiles[slot];
|
|
4656
|
+
for (const file of [
|
|
4657
|
+
targetJournals.coordinate,
|
|
4658
|
+
targetJournals.document,
|
|
4659
|
+
targetJournals.signer,
|
|
4660
|
+
]) {
|
|
4661
|
+
await this.store.remove(file);
|
|
4662
|
+
}
|
|
4663
|
+
const journalHeaders = [
|
|
4664
|
+
[targetJournals.coordinate, nativeBackboneCoordinateJournalMagic],
|
|
4665
|
+
[targetJournals.document, nativeBackboneCoordinateJournalMagic],
|
|
4666
|
+
[targetJournals.signer, nativeBackboneCoordinateJournalMagic],
|
|
4667
|
+
];
|
|
4668
|
+
for (const [file, header] of journalHeaders) {
|
|
4669
|
+
await this.store.write(file, header);
|
|
4670
|
+
await this.barrierFile(file);
|
|
4671
|
+
}
|
|
4672
|
+
await this.store.atomicReplace(this.checkpointFiles[slot], checkpointBytes);
|
|
4673
|
+
// A prior release does not understand checkpoint authority. The sentinel is
|
|
4674
|
+
// the first-generation publication boundary: install and durably revalidate
|
|
4675
|
+
// it before completed authority becomes visible. On later generations it
|
|
4676
|
+
// must already be exact; never overwrite possible writes from a downgrade.
|
|
4677
|
+
if (this.checkpointCompleted) {
|
|
4678
|
+
await this.requireLegacyMigrationSentinel();
|
|
4679
|
+
}
|
|
4680
|
+
else {
|
|
4681
|
+
await this.installLegacyMigrationSentinel();
|
|
4682
|
+
}
|
|
4683
|
+
const completedState = {
|
|
4684
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
4685
|
+
highwater: generation,
|
|
4686
|
+
completed: authority,
|
|
4687
|
+
};
|
|
4688
|
+
await this.store.atomicReplace(this.checkpointStateFile, encodeNativeBackboneCoordinateCheckpointState(completedState));
|
|
4689
|
+
// The completed authority is now the only writable generation. Update all
|
|
4690
|
+
// in-memory routing synchronously before another queued flush can start.
|
|
4691
|
+
this.checkpointCompleted = authority;
|
|
4692
|
+
this.checkpointPending = undefined;
|
|
4693
|
+
this.journalInitialized = true;
|
|
4694
|
+
this.documentJournalInitialized = true;
|
|
4695
|
+
this.documentSignerJournalInitialized = true;
|
|
4696
|
+
this.journalByteLength = nativeBackboneCoordinateJournalMagic.byteLength;
|
|
4697
|
+
this.documentJournalByteLength =
|
|
4698
|
+
nativeBackboneCoordinateJournalMagic.byteLength;
|
|
4699
|
+
this.documentSignerJournalByteLength =
|
|
4700
|
+
nativeBackboneCoordinateJournalMagic.byteLength;
|
|
4701
|
+
this.journalRecordCount = 0;
|
|
4702
|
+
this.documentJournalRecordCount = 0;
|
|
4703
|
+
this.documentSignerJournalRecordCount = 0;
|
|
4704
|
+
}
|
|
4705
|
+
async cleanupRetiredCheckpointFiles() {
|
|
4706
|
+
if (!this.store.remove ||
|
|
4707
|
+
(!this.checkpointCompleted && !this.checkpointPending)) {
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
const retiredSlot = this
|
|
4711
|
+
.checkpointCompleted
|
|
4712
|
+
? this.checkpointSlot(this.checkpointCompleted.generation) === "a"
|
|
4713
|
+
? "b"
|
|
4714
|
+
: "a"
|
|
4715
|
+
: this.checkpointSlot(this.checkpointPending.generation);
|
|
4716
|
+
const retiredJournals = this.checkpointJournalFiles[retiredSlot];
|
|
4717
|
+
const files = [
|
|
4718
|
+
...(this.checkpointCompleted
|
|
4719
|
+
? [
|
|
4720
|
+
this.snapshotFile,
|
|
4721
|
+
this.documentSnapshotFile,
|
|
4722
|
+
this.documentJournalFile,
|
|
4723
|
+
this.documentSignerSnapshotFile,
|
|
4724
|
+
this.documentSignerJournalFile,
|
|
4725
|
+
]
|
|
4726
|
+
: []),
|
|
4727
|
+
this.checkpointFiles[retiredSlot],
|
|
4728
|
+
`${this.checkpointFiles[retiredSlot]}.tmp`,
|
|
4729
|
+
retiredJournals.coordinate,
|
|
4730
|
+
retiredJournals.document,
|
|
4731
|
+
retiredJournals.signer,
|
|
4732
|
+
];
|
|
4733
|
+
const removals = await Promise.allSettled(files.map((file) => this.store.remove(file)));
|
|
4734
|
+
if (removals.every((result) => result.status === "fulfilled")) {
|
|
4735
|
+
try {
|
|
4736
|
+
await this.store.durableBarrier?.();
|
|
4737
|
+
}
|
|
4738
|
+
catch {
|
|
4739
|
+
// Cleanup is retryable debt. Publication and the caller's ACK already
|
|
4740
|
+
// belong to the new generation, so never turn this into an ambiguous
|
|
4741
|
+
// append failure after the authority switch.
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
async flushJournalInternal(backbone, forceCheckpoint = false) {
|
|
4746
|
+
this.assertPersistenceHealthy();
|
|
3916
4747
|
let written = 0;
|
|
3917
4748
|
let persistenceMutationStarted = false;
|
|
3918
4749
|
let coordinateBytes;
|
|
@@ -3924,10 +4755,39 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3924
4755
|
const documentRecordCount = backbone.documentPendingJournalLength;
|
|
3925
4756
|
const signerRecords = backbone.documentSignerJournal();
|
|
3926
4757
|
const signerRecordCount = backbone.documentSignerPendingJournalLength;
|
|
4758
|
+
const additionalBytes = coordinateRecords.byteLength +
|
|
4759
|
+
documentRecords.byteLength +
|
|
4760
|
+
signerRecords.byteLength +
|
|
4761
|
+
(coordinateRecords.byteLength > 0 && this.journalInitialized !== true
|
|
4762
|
+
? backbone.coordinateJournalHeader().byteLength
|
|
4763
|
+
: 0) +
|
|
4764
|
+
(documentRecords.byteLength > 0 &&
|
|
4765
|
+
this.documentJournalInitialized !== true
|
|
4766
|
+
? backbone.documentJournalHeader().byteLength
|
|
4767
|
+
: 0) +
|
|
4768
|
+
(signerRecords.byteLength > 0 &&
|
|
4769
|
+
this.documentSignerJournalInitialized !== true
|
|
4770
|
+
? backbone.documentSignerJournalHeader().byteLength
|
|
4771
|
+
: 0);
|
|
4772
|
+
const additionalRecords = coordinateRecordCount + documentRecordCount + signerRecordCount;
|
|
4773
|
+
const checkpointRequested = forceCheckpoint ||
|
|
4774
|
+
(this.crashSafeCompaction &&
|
|
4775
|
+
this.shouldCompactJournal(additionalBytes, additionalRecords));
|
|
4776
|
+
// These three synchronous copies and the journal prefixes above are one
|
|
4777
|
+
// logical cut. No mutation can slip into the checkpoint without also being
|
|
4778
|
+
// represented by one of the captured prefixes.
|
|
4779
|
+
const checkpoint = checkpointRequested
|
|
4780
|
+
? {
|
|
4781
|
+
coordinateSnapshot: backbone.coordinateSnapshot(),
|
|
4782
|
+
documentSnapshot: backbone.documentSnapshot(),
|
|
4783
|
+
documentSignerSnapshot: backbone.documentSignerSnapshot(),
|
|
4784
|
+
}
|
|
4785
|
+
: undefined;
|
|
3927
4786
|
try {
|
|
4787
|
+
const activeJournals = this.activeJournalFiles();
|
|
3928
4788
|
if (coordinateRecords.byteLength > 0) {
|
|
3929
4789
|
if (this.journalInitialized === undefined) {
|
|
3930
|
-
const existing = await this.store.read(
|
|
4790
|
+
const existing = await this.store.read(activeJournals.coordinate);
|
|
3931
4791
|
this.journalInitialized = !!existing && existing.byteLength > 0;
|
|
3932
4792
|
}
|
|
3933
4793
|
coordinateBytes = this.journalInitialized
|
|
@@ -3936,26 +4796,26 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3936
4796
|
backbone.coordinateJournalHeader(),
|
|
3937
4797
|
coordinateRecords,
|
|
3938
4798
|
]);
|
|
3939
|
-
await this.store.append(
|
|
4799
|
+
await this.store.append(activeJournals.coordinate, coordinateBytes);
|
|
3940
4800
|
persistenceMutationStarted = true;
|
|
3941
4801
|
written += coordinateRecords.byteLength;
|
|
3942
4802
|
}
|
|
3943
4803
|
if (documentRecords.byteLength > 0) {
|
|
3944
4804
|
if (this.documentJournalInitialized === undefined) {
|
|
3945
|
-
const existing = await this.store.read(
|
|
4805
|
+
const existing = await this.store.read(activeJournals.document);
|
|
3946
4806
|
this.documentJournalInitialized =
|
|
3947
4807
|
!!existing && existing.byteLength > 0;
|
|
3948
4808
|
}
|
|
3949
4809
|
documentBytes = this.documentJournalInitialized
|
|
3950
4810
|
? documentRecords
|
|
3951
4811
|
: concatBytes([backbone.documentJournalHeader(), documentRecords]);
|
|
3952
|
-
await this.store.append(
|
|
4812
|
+
await this.store.append(activeJournals.document, documentBytes);
|
|
3953
4813
|
persistenceMutationStarted = true;
|
|
3954
4814
|
written += documentRecords.byteLength;
|
|
3955
4815
|
}
|
|
3956
4816
|
if (signerRecords.byteLength > 0) {
|
|
3957
4817
|
if (this.documentSignerJournalInitialized === undefined) {
|
|
3958
|
-
const existing = await this.store.read(
|
|
4818
|
+
const existing = await this.store.read(activeJournals.signer);
|
|
3959
4819
|
this.documentSignerJournalInitialized =
|
|
3960
4820
|
!!existing && existing.byteLength > 0;
|
|
3961
4821
|
}
|
|
@@ -3965,42 +4825,50 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
3965
4825
|
backbone.documentSignerJournalHeader(),
|
|
3966
4826
|
signerRecords,
|
|
3967
4827
|
]);
|
|
3968
|
-
await this.store.append(
|
|
4828
|
+
await this.store.append(activeJournals.signer, signerBytes);
|
|
3969
4829
|
persistenceMutationStarted = true;
|
|
3970
4830
|
written += signerRecords.byteLength;
|
|
3971
4831
|
}
|
|
3972
|
-
if (written === 0) {
|
|
4832
|
+
if (written === 0 && !checkpoint) {
|
|
3973
4833
|
this.lastFlushMs = Date.now();
|
|
3974
4834
|
return 0;
|
|
3975
4835
|
}
|
|
3976
4836
|
// `append` may only enqueue bytes in a buffered store. Drain and fsync
|
|
3977
4837
|
// every affected WAL before clearing its wasm prefix or returning an ACK.
|
|
3978
4838
|
for (const file of [
|
|
3979
|
-
coordinateBytes ?
|
|
3980
|
-
documentBytes ?
|
|
3981
|
-
signerBytes ?
|
|
4839
|
+
coordinateBytes ? activeJournals.coordinate : undefined,
|
|
4840
|
+
documentBytes ? activeJournals.document : undefined,
|
|
4841
|
+
signerBytes ? activeJournals.signer : undefined,
|
|
3982
4842
|
]) {
|
|
3983
4843
|
if (file) {
|
|
3984
|
-
|
|
3985
|
-
await this.store.durableBarrier(file);
|
|
3986
|
-
}
|
|
3987
|
-
else {
|
|
3988
|
-
await this.store.flush?.(file);
|
|
3989
|
-
}
|
|
4844
|
+
await this.barrierFile(file);
|
|
3990
4845
|
}
|
|
3991
4846
|
}
|
|
3992
4847
|
if (coordinateBytes) {
|
|
3993
4848
|
this.journalInitialized = true;
|
|
4849
|
+
this.journalByteLength += coordinateBytes.byteLength;
|
|
4850
|
+
this.journalRecordCount += coordinateRecordCount;
|
|
3994
4851
|
}
|
|
3995
4852
|
if (documentBytes) {
|
|
3996
4853
|
this.documentJournalInitialized = true;
|
|
4854
|
+
this.documentJournalByteLength += documentBytes.byteLength;
|
|
4855
|
+
this.documentJournalRecordCount += documentRecordCount;
|
|
3997
4856
|
}
|
|
3998
4857
|
if (signerBytes) {
|
|
3999
4858
|
this.documentSignerJournalInitialized = true;
|
|
4859
|
+
this.documentSignerJournalByteLength += signerBytes.byteLength;
|
|
4860
|
+
this.documentSignerJournalRecordCount += signerRecordCount;
|
|
4861
|
+
}
|
|
4862
|
+
if (checkpoint) {
|
|
4863
|
+
persistenceMutationStarted = true;
|
|
4864
|
+
await this.publishCheckpoint(checkpoint);
|
|
4000
4865
|
}
|
|
4001
4866
|
backbone.clearCoordinateJournalPrefix(coordinateRecords.byteLength, coordinateRecordCount);
|
|
4002
4867
|
backbone.clearDocumentJournalPrefix(documentRecords.byteLength, documentRecordCount);
|
|
4003
4868
|
backbone.clearDocumentSignerJournalPrefix(signerRecords.byteLength, signerRecordCount);
|
|
4869
|
+
if (checkpoint) {
|
|
4870
|
+
await this.cleanupRetiredCheckpointFiles();
|
|
4871
|
+
}
|
|
4004
4872
|
this.lastFlushMs = Date.now();
|
|
4005
4873
|
return written;
|
|
4006
4874
|
}
|
|
@@ -4012,9 +4880,15 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
4012
4880
|
throw error;
|
|
4013
4881
|
}
|
|
4014
4882
|
}
|
|
4015
|
-
async compact(
|
|
4883
|
+
async compact(backbone) {
|
|
4016
4884
|
this.assertActive("compact");
|
|
4017
|
-
|
|
4885
|
+
if (!this.crashSafeCompaction) {
|
|
4886
|
+
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
4887
|
+
}
|
|
4888
|
+
await this.enqueuePersistence(async () => {
|
|
4889
|
+
this.assertPersistenceHealthy();
|
|
4890
|
+
await this.flushJournalInternal(backbone, true);
|
|
4891
|
+
});
|
|
4018
4892
|
}
|
|
4019
4893
|
close() {
|
|
4020
4894
|
if (this.closePromise) {
|