@peerbit/shared-log 13.2.34 → 13.2.35
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 +7 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +223 -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 +11 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +75 -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 +12 -12
- package/src/index.ts +268 -48
- package/src/peer-session.ts +8 -0
- package/src/replication-announcement.ts +17 -2
- package/src/replication-info-v2-receive.ts +96 -5
- package/src/replication-info-v2-send.ts +21 -1
- package/src/sync/factory.ts +1 -1
|
@@ -991,6 +991,44 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
991
991
|
return true;
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
/**
|
|
995
|
+
* Resume only a request generation that exhausted its bounded retries.
|
|
996
|
+
* Wait/liveness callers may nudge recovery without invalidating an active,
|
|
997
|
+
* timed or in-flight request and without advancing the receive epoch.
|
|
998
|
+
*/
|
|
999
|
+
resumeParkedRequest(properties: {
|
|
1000
|
+
peerHash: string;
|
|
1001
|
+
peerSession: object;
|
|
1002
|
+
receiveEpoch: object | null;
|
|
1003
|
+
}): boolean {
|
|
1004
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
1005
|
+
if (
|
|
1006
|
+
!state ||
|
|
1007
|
+
state.peerSession !== properties.peerSession ||
|
|
1008
|
+
state.receiveEpoch !== properties.receiveEpoch ||
|
|
1009
|
+
!this.deps.isPeerStateCurrent(
|
|
1010
|
+
properties.peerHash,
|
|
1011
|
+
properties.peerSession,
|
|
1012
|
+
properties.receiveEpoch,
|
|
1013
|
+
) ||
|
|
1014
|
+
state.phase === "active" ||
|
|
1015
|
+
!state.requestParked ||
|
|
1016
|
+
state.requestTimer !== undefined ||
|
|
1017
|
+
state.requestInFlight !== undefined ||
|
|
1018
|
+
this._reservedAdmissionsByPeer.has(properties.peerHash) ||
|
|
1019
|
+
state.receiverBinding === undefined ||
|
|
1020
|
+
state.lastSequence === MAX_U64
|
|
1021
|
+
) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
state.requestAttempts = 0;
|
|
1025
|
+
state.requestsSinceCapabilityRefresh = 0;
|
|
1026
|
+
state.capabilityRefreshRequired = true;
|
|
1027
|
+
state.requestParked = false;
|
|
1028
|
+
this.armRequest(state, 0);
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
994
1032
|
private transitionToResync(
|
|
995
1033
|
state: ReplicationInfoV2ReceiveState,
|
|
996
1034
|
options?: { force?: boolean; refreshCapability?: boolean },
|
|
@@ -1154,6 +1192,14 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1154
1192
|
const { state } = admission;
|
|
1155
1193
|
this._reservedAdmissionsByPeer.set(peerHash, admission);
|
|
1156
1194
|
state.reservedAdmission = admission;
|
|
1195
|
+
if (state.requestTimer) {
|
|
1196
|
+
clearTimeout(state.requestTimer);
|
|
1197
|
+
state.requestTimer = undefined;
|
|
1198
|
+
}
|
|
1199
|
+
if (state.legacyFallbackTimer) {
|
|
1200
|
+
clearTimeout(state.legacyFallbackTimer);
|
|
1201
|
+
state.legacyFallbackTimer = undefined;
|
|
1202
|
+
}
|
|
1157
1203
|
return admission;
|
|
1158
1204
|
}
|
|
1159
1205
|
|
|
@@ -1174,6 +1220,30 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1174
1220
|
(currentState !== state && currentState.phase !== "active"))
|
|
1175
1221
|
) {
|
|
1176
1222
|
this.transitionToResync(currentState, { force: true });
|
|
1223
|
+
} else if (
|
|
1224
|
+
currentState === state &&
|
|
1225
|
+
!admission.committed &&
|
|
1226
|
+
this.isStateCurrent(state) &&
|
|
1227
|
+
state.phase !== "active" &&
|
|
1228
|
+
state.requestTimer === undefined &&
|
|
1229
|
+
state.requestInFlight === undefined &&
|
|
1230
|
+
!state.requestParked
|
|
1231
|
+
) {
|
|
1232
|
+
// Reserving a Full pauses this generation's retry timer. If its host
|
|
1233
|
+
// apply lane releases without committing, restore the bounded request
|
|
1234
|
+
// worker so the exact current generation cannot become stranded.
|
|
1235
|
+
this.armRequest(state, 0);
|
|
1236
|
+
}
|
|
1237
|
+
if (
|
|
1238
|
+
currentState === state &&
|
|
1239
|
+
this.isStateCurrent(state) &&
|
|
1240
|
+
state.phase === "active" &&
|
|
1241
|
+
state.legacyFallbackFingerprint !== undefined
|
|
1242
|
+
) {
|
|
1243
|
+
// A reservation also pauses compat sidecar recovery. Matching commits
|
|
1244
|
+
// clear the evidence; an uncommitted or non-matching frame restores its
|
|
1245
|
+
// bounded fallback without invalidating work in the host apply lane.
|
|
1246
|
+
this.armLegacyFallback(state);
|
|
1177
1247
|
}
|
|
1178
1248
|
}
|
|
1179
1249
|
|
|
@@ -1281,6 +1351,19 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1281
1351
|
if (state.legacyFallbackTimer) {
|
|
1282
1352
|
return true;
|
|
1283
1353
|
}
|
|
1354
|
+
this.armLegacyFallback(state);
|
|
1355
|
+
return true;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
private armLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
1359
|
+
if (
|
|
1360
|
+
state.legacyFallbackTimer ||
|
|
1361
|
+
state.phase !== "active" ||
|
|
1362
|
+
state.legacyFallbackFingerprint === undefined ||
|
|
1363
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1364
|
+
) {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1284
1367
|
state.legacyFallbackTimer = setTimeout(() => {
|
|
1285
1368
|
state.legacyFallbackTimer = undefined;
|
|
1286
1369
|
if (this.isStateCurrent(state) && state.phase === "active") {
|
|
@@ -1291,7 +1374,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1291
1374
|
}
|
|
1292
1375
|
}, this.legacyFallbackDelayMs);
|
|
1293
1376
|
state.legacyFallbackTimer.unref?.();
|
|
1294
|
-
return true;
|
|
1295
1377
|
}
|
|
1296
1378
|
|
|
1297
1379
|
private clearLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
@@ -1418,8 +1500,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1418
1500
|
this._receiveStates.get(state.peerHash) !== state ||
|
|
1419
1501
|
state.controller.signal.aborted ||
|
|
1420
1502
|
this.deps.isClosed() ||
|
|
1421
|
-
|
|
1422
|
-
this._reservedAdmissionsByPeer.get(state.peerHash)?.state !== state) ||
|
|
1503
|
+
this._reservedAdmissionsByPeer.has(state.peerHash) ||
|
|
1423
1504
|
state.receiverBinding === undefined ||
|
|
1424
1505
|
state.phase === "active" ||
|
|
1425
1506
|
state.requestParked ||
|
|
@@ -1449,6 +1530,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1449
1530
|
private async refreshGrant(
|
|
1450
1531
|
state: ReplicationInfoV2ReceiveState,
|
|
1451
1532
|
): Promise<boolean> {
|
|
1533
|
+
const version = state.version;
|
|
1452
1534
|
const refreshed = await this.deps.refreshLocalCapability({
|
|
1453
1535
|
peerHash: state.peerHash,
|
|
1454
1536
|
target: state.target,
|
|
@@ -1458,6 +1540,8 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1458
1540
|
});
|
|
1459
1541
|
if (
|
|
1460
1542
|
!refreshed ||
|
|
1543
|
+
state.version !== version ||
|
|
1544
|
+
this._reservedAdmissionsByPeer.has(state.peerHash) ||
|
|
1461
1545
|
!this.isStateCurrent(state) ||
|
|
1462
1546
|
this.deps.getReceiverTransportSession() !==
|
|
1463
1547
|
refreshed.receiverTransportSession
|
|
@@ -1489,7 +1573,10 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1489
1573
|
if (state.requestInFlight) {
|
|
1490
1574
|
return;
|
|
1491
1575
|
}
|
|
1492
|
-
if (
|
|
1576
|
+
if (
|
|
1577
|
+
!this.isStateCurrent(state) ||
|
|
1578
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1579
|
+
) {
|
|
1493
1580
|
return;
|
|
1494
1581
|
}
|
|
1495
1582
|
if (
|
|
@@ -1512,7 +1599,10 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1512
1599
|
return;
|
|
1513
1600
|
}
|
|
1514
1601
|
}
|
|
1515
|
-
if (
|
|
1602
|
+
if (
|
|
1603
|
+
!this.isStateCurrent(state) ||
|
|
1604
|
+
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1605
|
+
) {
|
|
1516
1606
|
return;
|
|
1517
1607
|
}
|
|
1518
1608
|
const ready = this._localCapabilityReadyBySession.get(state.peerSession);
|
|
@@ -1558,6 +1648,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1558
1648
|
if (
|
|
1559
1649
|
this._receiveStates.get(state.peerHash) === state &&
|
|
1560
1650
|
!state.controller.signal.aborted &&
|
|
1651
|
+
!this._reservedAdmissionsByPeer.has(state.peerHash) &&
|
|
1561
1652
|
!state.requestTimer &&
|
|
1562
1653
|
state.phase !== "active"
|
|
1563
1654
|
) {
|
|
@@ -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,
|