@peerbit/shared-log 16.0.30 → 16.0.31
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 +23 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +83 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +7 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +1 -0
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +8 -3
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +1 -0
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +16 -3
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +17 -17
- package/src/index.ts +102 -0
- package/src/sync/index.ts +7 -0
- package/src/sync/rateless-iblt.ts +9 -3
- package/src/sync/simple.ts +17 -3
package/src/index.ts
CHANGED
|
@@ -6352,6 +6352,95 @@ export class SharedLog<
|
|
|
6352
6352
|
records.size,
|
|
6353
6353
|
);
|
|
6354
6354
|
const signal = deadline.signal;
|
|
6355
|
+
const recoveryController = new AbortController();
|
|
6356
|
+
const recoverySignal = AbortSignal.any([signal, recoveryController.signal]);
|
|
6357
|
+
const recoveryByPeer = new Map<
|
|
6358
|
+
string,
|
|
6359
|
+
{
|
|
6360
|
+
controller: AbortController;
|
|
6361
|
+
timer: ReturnType<typeof setTimeout>;
|
|
6362
|
+
}
|
|
6363
|
+
>();
|
|
6364
|
+
let recoveryCursor = 0;
|
|
6365
|
+
const recoverSelectedPeers = (selected: Set<string>) => {
|
|
6366
|
+
// Recovery is advisory work, not a receipt or a replacement leader plan.
|
|
6367
|
+
// Never occupy transfer/request slots while waiting for it. The separate
|
|
6368
|
+
// bounded pool rotates through fresh candidates so quiet/incomplete peers
|
|
6369
|
+
// cannot indefinitely hide a later recoverable peer.
|
|
6370
|
+
for (const [peer, state] of recoveryByPeer) {
|
|
6371
|
+
if (!selected.has(peer)) {
|
|
6372
|
+
clearTimeout(state.timer);
|
|
6373
|
+
state.controller.abort();
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
const peers = [...selected];
|
|
6377
|
+
for (
|
|
6378
|
+
let visited = 0;
|
|
6379
|
+
visited < peers.length &&
|
|
6380
|
+
recoveryByPeer.size < MAX_PERSISTED_RECEIPT_REQUESTS_GLOBAL &&
|
|
6381
|
+
!recoverySignal.aborted;
|
|
6382
|
+
visited++
|
|
6383
|
+
) {
|
|
6384
|
+
const peer = peers[recoveryCursor++ % peers.length]!;
|
|
6385
|
+
if (recoveryByPeer.has(peer)) continue;
|
|
6386
|
+
const current = this.persistedReceiptPeerSession(peer);
|
|
6387
|
+
if (
|
|
6388
|
+
current &&
|
|
6389
|
+
this._v2Send.isLatestConfirmedForPeer({
|
|
6390
|
+
peerHash: peer,
|
|
6391
|
+
peerSession: current.peerSession,
|
|
6392
|
+
receiverTransportSession: current.capabilitySession,
|
|
6393
|
+
})
|
|
6394
|
+
) {
|
|
6395
|
+
continue;
|
|
6396
|
+
}
|
|
6397
|
+
const expiresAt = Math.min(
|
|
6398
|
+
deadline.deadline,
|
|
6399
|
+
Date.now() + MAX_PERSISTED_RECEIPT_ATTEMPT_MS,
|
|
6400
|
+
);
|
|
6401
|
+
const controller = new AbortController();
|
|
6402
|
+
const attemptSignal = AbortSignal.any([
|
|
6403
|
+
recoverySignal,
|
|
6404
|
+
controller.signal,
|
|
6405
|
+
]);
|
|
6406
|
+
const timer = setTimeout(
|
|
6407
|
+
() => controller.abort(),
|
|
6408
|
+
Math.max(1, expiresAt - Date.now()),
|
|
6409
|
+
);
|
|
6410
|
+
timer.unref?.();
|
|
6411
|
+
const state = { controller, timer };
|
|
6412
|
+
recoveryByPeer.set(peer, state);
|
|
6413
|
+
void (async () => {
|
|
6414
|
+
// Resolve only an authenticated transport-cache key. Keep this slot
|
|
6415
|
+
// reserved until the lookup actually settles, even if a custom
|
|
6416
|
+
// resolver ignores cancellation, rather than launching duplicates.
|
|
6417
|
+
const key = await this._resolvePublicKeyFromHash(peer);
|
|
6418
|
+
if (
|
|
6419
|
+
attemptSignal.aborted ||
|
|
6420
|
+
!key ||
|
|
6421
|
+
key.hashcode() !== peer ||
|
|
6422
|
+
Date.now() >= expiresAt
|
|
6423
|
+
) {
|
|
6424
|
+
return;
|
|
6425
|
+
}
|
|
6426
|
+
// Reuse the exact-session watchdog, capability/subscriber recovery,
|
|
6427
|
+
// and replacement-session handling from the public preflight. Its
|
|
6428
|
+
// result is ignored: settlement still checks the exact entry leaders
|
|
6429
|
+
// and accepts only the subsequent durable, session-bound receipts.
|
|
6430
|
+
await this.waitForPersistedReceiptPeerReadiness(key, {
|
|
6431
|
+
timeout: Math.max(1, expiresAt - Date.now()),
|
|
6432
|
+
signal: attemptSignal,
|
|
6433
|
+
});
|
|
6434
|
+
})()
|
|
6435
|
+
.catch(() => undefined)
|
|
6436
|
+
.finally(() => {
|
|
6437
|
+
clearTimeout(timer);
|
|
6438
|
+
if (recoveryByPeer.get(peer) === state) {
|
|
6439
|
+
recoveryByPeer.delete(peer);
|
|
6440
|
+
}
|
|
6441
|
+
});
|
|
6442
|
+
}
|
|
6443
|
+
};
|
|
6355
6444
|
let maxAttemptMs = MAX_PERSISTED_RECEIPT_ATTEMPT_MS;
|
|
6356
6445
|
let initialTransferPending = transferOnFirstRound;
|
|
6357
6446
|
let needsInitialLeaderCheck = true;
|
|
@@ -6418,6 +6507,7 @@ export class SharedLog<
|
|
|
6418
6507
|
// transport epoch. A revision/session change purges them before they can
|
|
6419
6508
|
// survive an away-and-back leader transition or combine with a later peer.
|
|
6420
6509
|
const hashesByPeer = new Map<string, string[]>();
|
|
6510
|
+
const recoveryCandidates = new Set<string>();
|
|
6421
6511
|
const entryArray = [...records.values()];
|
|
6422
6512
|
const leadersByEntry = await this.planPersistedDeliveryLeaders(
|
|
6423
6513
|
entryArray,
|
|
@@ -6462,6 +6552,7 @@ export class SharedLog<
|
|
|
6462
6552
|
if (acknowledgements.size >= minAcks) continue;
|
|
6463
6553
|
for (const peer of leaders.keys()) {
|
|
6464
6554
|
if (peer === selfHash) continue;
|
|
6555
|
+
recoveryCandidates.add(peer);
|
|
6465
6556
|
const current = this.persistedReceiptPeerSession(peer);
|
|
6466
6557
|
if (!current) continue;
|
|
6467
6558
|
if (acknowledgements.has(peer)) continue;
|
|
@@ -6471,6 +6562,7 @@ export class SharedLog<
|
|
|
6471
6562
|
}
|
|
6472
6563
|
}
|
|
6473
6564
|
if (!isRoundOwnershipCurrent()) continue;
|
|
6565
|
+
recoverSelectedPeers(recoveryCandidates);
|
|
6474
6566
|
|
|
6475
6567
|
const operationQueue = new PQueue({
|
|
6476
6568
|
concurrency: MAX_PERSISTED_RECEIPT_REQUESTS_GLOBAL,
|
|
@@ -6831,6 +6923,11 @@ export class SharedLog<
|
|
|
6831
6923
|
}
|
|
6832
6924
|
throw new PersistedDeliveryError(error, committedHashes);
|
|
6833
6925
|
} finally {
|
|
6926
|
+
recoveryController.abort();
|
|
6927
|
+
for (const state of recoveryByPeer.values()) {
|
|
6928
|
+
clearTimeout(state.timer);
|
|
6929
|
+
state.controller.abort();
|
|
6930
|
+
}
|
|
6834
6931
|
if (ownedDeadline) deadline.dispose();
|
|
6835
6932
|
}
|
|
6836
6933
|
}
|
|
@@ -20953,6 +21050,10 @@ export class SharedLog<
|
|
|
20953
21050
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
20954
21051
|
try {
|
|
20955
21052
|
this.stopSubscriptionChangeCallbackAdmission();
|
|
21053
|
+
// A receive may be awaiting a synchronizer response shipment. Cancel
|
|
21054
|
+
// its dispatch generation before draining that receive, rather than
|
|
21055
|
+
// waiting for _close() to reach the synchronizer's final teardown.
|
|
21056
|
+
this.syncronizer?.beginClose?.();
|
|
20956
21057
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
20957
21058
|
await this.drainSubscriptionChangeCallbacks();
|
|
20958
21059
|
// An already-admitted subscription callback can create a fresh warmup
|
|
@@ -21091,6 +21192,7 @@ export class SharedLog<
|
|
|
21091
21192
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
21092
21193
|
try {
|
|
21093
21194
|
this.stopSubscriptionChangeCallbackAdmission();
|
|
21195
|
+
this.syncronizer?.beginClose?.();
|
|
21094
21196
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
21095
21197
|
await this.drainSubscriptionChangeCallbacks();
|
|
21096
21198
|
// An already-admitted subscription callback can create a fresh warmup
|
package/src/sync/index.ts
CHANGED
|
@@ -251,6 +251,13 @@ export interface Syncronizer<R extends "u32" | "u64"> {
|
|
|
251
251
|
onPeerDisconnected(key: PublicSignKey | string): void;
|
|
252
252
|
|
|
253
253
|
open(): Promise<void> | void;
|
|
254
|
+
/**
|
|
255
|
+
* Synchronously fence dispatch admission and cancel the current generation's
|
|
256
|
+
* network waits before SharedLog drains admitted receives. Do not stop shared
|
|
257
|
+
* indexes or discard physical-work accounting here: close() still runs after
|
|
258
|
+
* those receives settle. Optional for existing custom synchronizers.
|
|
259
|
+
*/
|
|
260
|
+
beginClose?(): void;
|
|
254
261
|
close(): Promise<void> | void;
|
|
255
262
|
|
|
256
263
|
get pending(): number;
|
|
@@ -1127,6 +1127,9 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
1127
1127
|
timeoutMs?: number;
|
|
1128
1128
|
retryIntervalsMs?: number[];
|
|
1129
1129
|
}): RepairSession {
|
|
1130
|
+
if (this.ratelessClosed) {
|
|
1131
|
+
return this.simple.startRepairSession(properties);
|
|
1132
|
+
}
|
|
1130
1133
|
const mode = properties.mode ?? "best-effort";
|
|
1131
1134
|
const targets = [...new Set(properties.targets)];
|
|
1132
1135
|
const timeoutMs = Math.max(
|
|
@@ -3303,13 +3306,16 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
3303
3306
|
return this.simple.open();
|
|
3304
3307
|
}
|
|
3305
3308
|
|
|
3306
|
-
|
|
3309
|
+
beginClose(): void {
|
|
3307
3310
|
this.ratelessClosed = true;
|
|
3308
|
-
// Abort ownership first. Process abort listeners then cancel any in-flight
|
|
3309
|
-
// StartSync/MoreSymbols send before they detach or free native state.
|
|
3310
3311
|
const reason = new Error("rateless synchronizer closed");
|
|
3311
3312
|
this.cancelRatelessRepairSessions(reason);
|
|
3312
3313
|
this.ratelessDispatchLifecycleController.abort(reason);
|
|
3314
|
+
this.simple.beginClose();
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
close(): Promise<void> | void {
|
|
3318
|
+
this.beginClose();
|
|
3313
3319
|
for (const obj of [...this.ingoingSyncProcesses.values()]) {
|
|
3314
3320
|
obj.free();
|
|
3315
3321
|
}
|
package/src/sync/simple.ts
CHANGED
|
@@ -879,7 +879,9 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
879
879
|
const epoch =
|
|
880
880
|
expectedEpoch ??
|
|
881
881
|
currentEpoch ??
|
|
882
|
-
(options?.createTargetEpochs === false
|
|
882
|
+
(options?.createTargetEpochs === false ||
|
|
883
|
+
this.closed === true ||
|
|
884
|
+
ownershipLifecycleController.signal.aborted
|
|
883
885
|
? undefined
|
|
884
886
|
: this.getOrCreateSyncDispatchTargetEpoch(target));
|
|
885
887
|
if (!epoch) {
|
|
@@ -2362,7 +2364,11 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2362
2364
|
requestedCount: trackedHashes.length,
|
|
2363
2365
|
requestedTotalCount: allHashes.length,
|
|
2364
2366
|
attempts: 0,
|
|
2365
|
-
|
|
2367
|
+
// Rejected late repairs only need result metadata. Do not create a
|
|
2368
|
+
// retained dispatch epoch after the terminal admission fence.
|
|
2369
|
+
targetEpoch: this.closed
|
|
2370
|
+
? { id: 0 }
|
|
2371
|
+
: this.getOrCreateSyncDispatchTargetEpoch(target),
|
|
2366
2372
|
});
|
|
2367
2373
|
}
|
|
2368
2374
|
|
|
@@ -2377,6 +2383,10 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2377
2383
|
deferred,
|
|
2378
2384
|
cancelled: false,
|
|
2379
2385
|
};
|
|
2386
|
+
if (this.closed) {
|
|
2387
|
+
deferred.resolve(this.buildRepairSessionResult(session, false));
|
|
2388
|
+
return { id, done: deferred.promise, cancel: () => {} };
|
|
2389
|
+
}
|
|
2380
2390
|
|
|
2381
2391
|
if (allHashes.length === 0 || targets.length === 0) {
|
|
2382
2392
|
deferred.resolve(this.buildRepairSessionResult(session, true));
|
|
@@ -3755,9 +3765,13 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3755
3765
|
});
|
|
3756
3766
|
}
|
|
3757
3767
|
|
|
3758
|
-
|
|
3768
|
+
beginClose(): void {
|
|
3759
3769
|
this.closed = true;
|
|
3760
3770
|
this.syncDispatchLifecycleController.abort();
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
async close() {
|
|
3774
|
+
this.beginClose();
|
|
3761
3775
|
this.syncDispatchTargetEpochs.clear();
|
|
3762
3776
|
this.clearPendingSyncAdmissions();
|
|
3763
3777
|
this.syncInFlightRetryIterator = undefined;
|