@peerbit/shared-log 13.2.34 → 13.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +26 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +307 -38
- package/dist/src/index.js.map +1 -1
- package/dist/src/peer-session.d.ts +2 -1
- package/dist/src/peer-session.d.ts.map +1 -1
- package/dist/src/peer-session.js +8 -2
- package/dist/src/peer-session.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +1 -0
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +16 -2
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +21 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +106 -5
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +22 -1
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/sync/factory.js +1 -1
- package/dist/src/sync/factory.js.map +1 -1
- package/package.json +13 -13
- package/src/index.ts +371 -48
- package/src/peer-session.ts +8 -0
- package/src/replication-announcement.ts +17 -2
- package/src/replication-info-v2-receive.ts +135 -5
- package/src/replication-info-v2-send.ts +21 -1
- package/src/sync/factory.ts +1 -1
|
@@ -204,9 +204,10 @@ export type ReplicationAnnouncementDeps<R extends "u32" | "u64"> = {
|
|
|
204
204
|
// Owner-routed so coordinator spies keep observing re-entrant queueing.
|
|
205
205
|
queueCurrentReplicationStateAnnouncementRepair: () => void;
|
|
206
206
|
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) => boolean;
|
|
207
|
-
//
|
|
208
|
-
// retains its exact rejection/retry semantics
|
|
207
|
+
// V2 is always current. Legacy publication is enabled only for an explicit
|
|
208
|
+
// compatibility open and retains its exact rejection/retry semantics there.
|
|
209
209
|
enqueueReplicationInfoV2: (message: LegacyReplicationInfoMessage) => void;
|
|
210
|
+
isLegacyReplicationInfoEnabled: () => boolean;
|
|
210
211
|
};
|
|
211
212
|
|
|
212
213
|
type LegacyReplicationInfoMessage =
|
|
@@ -270,6 +271,7 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
270
271
|
|
|
271
272
|
queueCurrentReplicationStateAnnouncementRetry(error: unknown): boolean {
|
|
272
273
|
if (
|
|
274
|
+
!this.deps.isLegacyReplicationInfoEnabled() ||
|
|
273
275
|
this.deps.isClosed() ||
|
|
274
276
|
this.deps.getCloseSignal().aborted ||
|
|
275
277
|
this._replicationAnnouncementRetryController.signal.aborted ||
|
|
@@ -362,6 +364,7 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
362
364
|
|
|
363
365
|
queueCurrentReplicationStateAnnouncementRepair(): void {
|
|
364
366
|
if (
|
|
367
|
+
!this.deps.isLegacyReplicationInfoEnabled() ||
|
|
365
368
|
this.deps.isClosed() ||
|
|
366
369
|
this.deps.getCloseSignal().aborted ||
|
|
367
370
|
this._replicationAnnouncementRepairController.signal.aborted ||
|
|
@@ -437,6 +440,11 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
437
440
|
async repairCurrentReplicationStateAnnouncement(
|
|
438
441
|
context?: ReplicationAnnouncementRepairWorkerContext,
|
|
439
442
|
): Promise<void> {
|
|
443
|
+
if (!this.deps.isLegacyReplicationInfoEnabled()) {
|
|
444
|
+
this._announcementRepairBinding.pending = false;
|
|
445
|
+
this._announcementRepairBinding.targets.clear();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
440
448
|
if (!this._announcementRepairBinding.pending) {
|
|
441
449
|
return;
|
|
442
450
|
}
|
|
@@ -645,6 +653,9 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
645
653
|
this.rotateAnnouncementSession();
|
|
646
654
|
this.advanceCurrentReplicationStateAnnouncementRepairGeneration();
|
|
647
655
|
this.deps.enqueueReplicationInfoV2(message);
|
|
656
|
+
if (!this.deps.isLegacyReplicationInfoEnabled()) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
648
659
|
try {
|
|
649
660
|
await this.deps.getRpc().send(message, {
|
|
650
661
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
@@ -677,6 +688,10 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
677
688
|
}
|
|
678
689
|
|
|
679
690
|
async retryCurrentReplicationStateAnnouncement(): Promise<void> {
|
|
691
|
+
if (!this.deps.isLegacyReplicationInfoEnabled()) {
|
|
692
|
+
this._replicationAnnouncementRetryPending = false;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
680
695
|
const session = this._announcementSession;
|
|
681
696
|
const controller = this._replicationAnnouncementRetryController;
|
|
682
697
|
try {
|
|
@@ -991,6 +991,83 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
991
991
|
return true;
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
/**
|
|
995
|
+
* Whether the request generation for this exact peer state is parked: the
|
|
996
|
+
* bounded retry cycle exhausted its attempts and no timer or send is in
|
|
997
|
+
* flight. Read-only probe for the host's recovery scheduler.
|
|
998
|
+
*/
|
|
999
|
+
isRequestParked(properties: {
|
|
1000
|
+
peerHash: string;
|
|
1001
|
+
peerSession: object;
|
|
1002
|
+
receiveEpoch: object | null;
|
|
1003
|
+
}): boolean {
|
|
1004
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
1005
|
+
return (
|
|
1006
|
+
state !== undefined &&
|
|
1007
|
+
state.peerSession === properties.peerSession &&
|
|
1008
|
+
state.receiveEpoch === properties.receiveEpoch &&
|
|
1009
|
+
state.phase !== "active" &&
|
|
1010
|
+
state.requestParked &&
|
|
1011
|
+
state.requestTimer === undefined &&
|
|
1012
|
+
state.requestInFlight === undefined
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Resume only a request generation that exhausted its bounded retries.
|
|
1018
|
+
* Wait/liveness callers may nudge recovery without invalidating an active,
|
|
1019
|
+
* timed or in-flight request and without advancing the receive epoch.
|
|
1020
|
+
*/
|
|
1021
|
+
resumeParkedRequest(properties: {
|
|
1022
|
+
peerHash: string;
|
|
1023
|
+
peerSession: object;
|
|
1024
|
+
receiveEpoch: object | null;
|
|
1025
|
+
}): boolean {
|
|
1026
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
1027
|
+
if (
|
|
1028
|
+
!state ||
|
|
1029
|
+
state.peerSession !== properties.peerSession ||
|
|
1030
|
+
state.receiveEpoch !== properties.receiveEpoch ||
|
|
1031
|
+
!this.deps.isPeerStateCurrent(
|
|
1032
|
+
properties.peerHash,
|
|
1033
|
+
properties.peerSession,
|
|
1034
|
+
properties.receiveEpoch,
|
|
1035
|
+
) ||
|
|
1036
|
+
state.phase === "active" ||
|
|
1037
|
+
!state.requestParked ||
|
|
1038
|
+
state.requestTimer !== undefined ||
|
|
1039
|
+
state.requestInFlight !== undefined ||
|
|
1040
|
+
this._reservedAdmissionsByPeer.has(properties.peerHash) ||
|
|
1041
|
+
state.receiverBinding === undefined ||
|
|
1042
|
+
state.lastSequence === MAX_U64
|
|
1043
|
+
) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
state.requestAttempts = 0;
|
|
1047
|
+
state.requestsSinceCapabilityRefresh = 0;
|
|
1048
|
+
if (!state.capabilityRefreshRequired) {
|
|
1049
|
+
// Only rotate the grant when it is genuinely stale: the peer's
|
|
1050
|
+
// capability rotation paths already flagged a refresh, and a missing
|
|
1051
|
+
// or transport-outdated local grant cannot authorize a request. A
|
|
1052
|
+
// still-current grant must keep its challenge so a Full already in
|
|
1053
|
+
// flight for the pre-park request generation still applies.
|
|
1054
|
+
const ready = this._localCapabilityReadyBySession.get(state.peerSession);
|
|
1055
|
+
const grantCurrent =
|
|
1056
|
+
ready !== undefined &&
|
|
1057
|
+
ready.peerHash === state.peerHash &&
|
|
1058
|
+
ready.receiveEpoch === state.receiveEpoch &&
|
|
1059
|
+
ready.receiverTransportSession === state.receiverTransportSession &&
|
|
1060
|
+
ready.receiverTransportSession ===
|
|
1061
|
+
this.deps.getReceiverTransportSession();
|
|
1062
|
+
if (!grantCurrent) {
|
|
1063
|
+
state.capabilityRefreshRequired = true;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
state.requestParked = false;
|
|
1067
|
+
this.armRequest(state, 0);
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
994
1071
|
private transitionToResync(
|
|
995
1072
|
state: ReplicationInfoV2ReceiveState,
|
|
996
1073
|
options?: { force?: boolean; refreshCapability?: boolean },
|
|
@@ -1154,6 +1231,14 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1154
1231
|
const { state } = admission;
|
|
1155
1232
|
this._reservedAdmissionsByPeer.set(peerHash, admission);
|
|
1156
1233
|
state.reservedAdmission = admission;
|
|
1234
|
+
if (state.requestTimer) {
|
|
1235
|
+
clearTimeout(state.requestTimer);
|
|
1236
|
+
state.requestTimer = undefined;
|
|
1237
|
+
}
|
|
1238
|
+
if (state.legacyFallbackTimer) {
|
|
1239
|
+
clearTimeout(state.legacyFallbackTimer);
|
|
1240
|
+
state.legacyFallbackTimer = undefined;
|
|
1241
|
+
}
|
|
1157
1242
|
return admission;
|
|
1158
1243
|
}
|
|
1159
1244
|
|
|
@@ -1174,6 +1259,30 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1174
1259
|
(currentState !== state && currentState.phase !== "active"))
|
|
1175
1260
|
) {
|
|
1176
1261
|
this.transitionToResync(currentState, { force: true });
|
|
1262
|
+
} else if (
|
|
1263
|
+
currentState === state &&
|
|
1264
|
+
!admission.committed &&
|
|
1265
|
+
this.isStateCurrent(state) &&
|
|
1266
|
+
state.phase !== "active" &&
|
|
1267
|
+
state.requestTimer === undefined &&
|
|
1268
|
+
state.requestInFlight === undefined &&
|
|
1269
|
+
!state.requestParked
|
|
1270
|
+
) {
|
|
1271
|
+
// Reserving a Full pauses this generation's retry timer. If its host
|
|
1272
|
+
// apply lane releases without committing, restore the bounded request
|
|
1273
|
+
// worker so the exact current generation cannot become stranded.
|
|
1274
|
+
this.armRequest(state, 0);
|
|
1275
|
+
}
|
|
1276
|
+
if (
|
|
1277
|
+
currentState === state &&
|
|
1278
|
+
this.isStateCurrent(state) &&
|
|
1279
|
+
state.phase === "active" &&
|
|
1280
|
+
state.legacyFallbackFingerprint !== undefined
|
|
1281
|
+
) {
|
|
1282
|
+
// A reservation also pauses compat sidecar recovery. Matching commits
|
|
1283
|
+
// clear the evidence; an uncommitted or non-matching frame restores its
|
|
1284
|
+
// bounded fallback without invalidating work in the host apply lane.
|
|
1285
|
+
this.armLegacyFallback(state);
|
|
1177
1286
|
}
|
|
1178
1287
|
}
|
|
1179
1288
|
|
|
@@ -1281,6 +1390,19 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1281
1390
|
if (state.legacyFallbackTimer) {
|
|
1282
1391
|
return true;
|
|
1283
1392
|
}
|
|
1393
|
+
this.armLegacyFallback(state);
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
private armLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
1398
|
+
if (
|
|
1399
|
+
state.legacyFallbackTimer ||
|
|
1400
|
+
state.phase !== "active" ||
|
|
1401
|
+
state.legacyFallbackFingerprint === undefined ||
|
|
1402
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1403
|
+
) {
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1284
1406
|
state.legacyFallbackTimer = setTimeout(() => {
|
|
1285
1407
|
state.legacyFallbackTimer = undefined;
|
|
1286
1408
|
if (this.isStateCurrent(state) && state.phase === "active") {
|
|
@@ -1291,7 +1413,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1291
1413
|
}
|
|
1292
1414
|
}, this.legacyFallbackDelayMs);
|
|
1293
1415
|
state.legacyFallbackTimer.unref?.();
|
|
1294
|
-
return true;
|
|
1295
1416
|
}
|
|
1296
1417
|
|
|
1297
1418
|
private clearLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
@@ -1418,8 +1539,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1418
1539
|
this._receiveStates.get(state.peerHash) !== state ||
|
|
1419
1540
|
state.controller.signal.aborted ||
|
|
1420
1541
|
this.deps.isClosed() ||
|
|
1421
|
-
|
|
1422
|
-
this._reservedAdmissionsByPeer.get(state.peerHash)?.state !== state) ||
|
|
1542
|
+
this._reservedAdmissionsByPeer.has(state.peerHash) ||
|
|
1423
1543
|
state.receiverBinding === undefined ||
|
|
1424
1544
|
state.phase === "active" ||
|
|
1425
1545
|
state.requestParked ||
|
|
@@ -1449,6 +1569,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1449
1569
|
private async refreshGrant(
|
|
1450
1570
|
state: ReplicationInfoV2ReceiveState,
|
|
1451
1571
|
): Promise<boolean> {
|
|
1572
|
+
const version = state.version;
|
|
1452
1573
|
const refreshed = await this.deps.refreshLocalCapability({
|
|
1453
1574
|
peerHash: state.peerHash,
|
|
1454
1575
|
target: state.target,
|
|
@@ -1458,6 +1579,8 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1458
1579
|
});
|
|
1459
1580
|
if (
|
|
1460
1581
|
!refreshed ||
|
|
1582
|
+
state.version !== version ||
|
|
1583
|
+
this._reservedAdmissionsByPeer.has(state.peerHash) ||
|
|
1461
1584
|
!this.isStateCurrent(state) ||
|
|
1462
1585
|
this.deps.getReceiverTransportSession() !==
|
|
1463
1586
|
refreshed.receiverTransportSession
|
|
@@ -1489,7 +1612,10 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1489
1612
|
if (state.requestInFlight) {
|
|
1490
1613
|
return;
|
|
1491
1614
|
}
|
|
1492
|
-
if (
|
|
1615
|
+
if (
|
|
1616
|
+
!this.isStateCurrent(state) ||
|
|
1617
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1618
|
+
) {
|
|
1493
1619
|
return;
|
|
1494
1620
|
}
|
|
1495
1621
|
if (
|
|
@@ -1512,7 +1638,10 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1512
1638
|
return;
|
|
1513
1639
|
}
|
|
1514
1640
|
}
|
|
1515
|
-
if (
|
|
1641
|
+
if (
|
|
1642
|
+
!this.isStateCurrent(state) ||
|
|
1643
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1644
|
+
) {
|
|
1516
1645
|
return;
|
|
1517
1646
|
}
|
|
1518
1647
|
const ready = this._localCapabilityReadyBySession.get(state.peerSession);
|
|
@@ -1558,6 +1687,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1558
1687
|
if (
|
|
1559
1688
|
this._receiveStates.get(state.peerHash) === state &&
|
|
1560
1689
|
!state.controller.signal.aborted &&
|
|
1690
|
+
!this._reservedAdmissionsByPeer.has(state.peerHash) &&
|
|
1561
1691
|
!state.requestTimer &&
|
|
1562
1692
|
state.phase !== "active"
|
|
1563
1693
|
) {
|
|
@@ -649,7 +649,27 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
649
649
|
sends.push(Promise.reject(error));
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
|
-
|
|
652
|
+
if (sends.length === 0 || signal.aborted) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Terminal reset is best-effort and the caller owns its close/drop bound.
|
|
657
|
+
// A transport is expected to observe the signal, but a disconnected
|
|
658
|
+
// acknowledgement path can leave its promise pending indefinitely. Do not
|
|
659
|
+
// let such a transport retain the whole terminal operation past the bound.
|
|
660
|
+
await new Promise<void>((resolve) => {
|
|
661
|
+
let finished = false;
|
|
662
|
+
const finish = () => {
|
|
663
|
+
if (finished) return;
|
|
664
|
+
finished = true;
|
|
665
|
+
signal.removeEventListener("abort", finish);
|
|
666
|
+
resolve();
|
|
667
|
+
};
|
|
668
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
669
|
+
void Promise.allSettled(sends).then(finish);
|
|
670
|
+
// Close the check/listener race if the caller aborted synchronously.
|
|
671
|
+
if (signal.aborted) finish();
|
|
672
|
+
});
|
|
653
673
|
}
|
|
654
674
|
|
|
655
675
|
async drain(signal?: AbortSignal): Promise<void> {
|
package/src/sync/factory.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function createSyncronizer<R extends "u32" | "u64">(
|
|
|
114
114
|
sendRawExchangeHeads: props.sendRawExchangeHeads,
|
|
115
115
|
});
|
|
116
116
|
} else {
|
|
117
|
-
if (props.compatibility && props.compatibility < 10) {
|
|
117
|
+
if (props.compatibility !== undefined && props.compatibility < 10) {
|
|
118
118
|
return new SimpleSyncronizer({
|
|
119
119
|
log: props.log,
|
|
120
120
|
rpc: props.rpc,
|