@peerbit/shared-log 16.0.39 → 16.0.40
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 +20 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +59 -22
- package/dist/src/index.js.map +1 -1
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +12 -0
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +10 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +23 -2
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +3 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +18 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +10 -10
- package/src/index.ts +71 -36
- package/src/ranges.ts +11 -0
- package/src/replication-info-v2-receive.ts +33 -2
- package/src/replication-info-v2-send.ts +21 -0
package/src/index.ts
CHANGED
|
@@ -4134,6 +4134,7 @@ export class SharedLog<
|
|
|
4134
4134
|
receiveEpoch: object | null;
|
|
4135
4135
|
signal: AbortSignal;
|
|
4136
4136
|
requestRemoteFullRearm?: boolean;
|
|
4137
|
+
acknowledgeRearm?: boolean;
|
|
4137
4138
|
}): Promise<
|
|
4138
4139
|
{ receiverTransportSession: bigint; requestNotBeforeMs: number } | undefined
|
|
4139
4140
|
> {
|
|
@@ -4148,15 +4149,16 @@ export class SharedLog<
|
|
|
4148
4149
|
: 0),
|
|
4149
4150
|
}),
|
|
4150
4151
|
{
|
|
4151
|
-
//
|
|
4152
|
-
//
|
|
4153
|
-
//
|
|
4154
|
-
mode:
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4152
|
+
// Watchdog hints stay silent so ACK latency cannot park that scheduler.
|
|
4153
|
+
// Opening adverts still need an ACK before granting local receive
|
|
4154
|
+
// readiness, even when they also solicit reciprocal recovery.
|
|
4155
|
+
mode:
|
|
4156
|
+
properties.requestRemoteFullRearm && !properties.acknowledgeRearm
|
|
4157
|
+
? new SilentDelivery({ redundancy: 1, to: [properties.target] })
|
|
4158
|
+
: new AcknowledgeDelivery({
|
|
4159
|
+
redundancy: 1,
|
|
4160
|
+
to: [properties.target],
|
|
4161
|
+
}),
|
|
4160
4162
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4161
4163
|
signal: properties.signal,
|
|
4162
4164
|
},
|
|
@@ -4218,6 +4220,7 @@ export class SharedLog<
|
|
|
4218
4220
|
receiveEpoch: properties.receiveEpoch,
|
|
4219
4221
|
signal: properties.signal,
|
|
4220
4222
|
requestRemoteFullRearm: properties.requestRemoteFullRearm,
|
|
4223
|
+
acknowledgeRearm: properties.acknowledgeRearm,
|
|
4221
4224
|
}),
|
|
4222
4225
|
onRequestError: (error) => {
|
|
4223
4226
|
if (
|
|
@@ -6461,7 +6464,9 @@ export class SharedLog<
|
|
|
6461
6464
|
});
|
|
6462
6465
|
}
|
|
6463
6466
|
};
|
|
6464
|
-
let
|
|
6467
|
+
let maxPrerequisiteAttemptMs = MAX_PERSISTED_RECEIPT_ATTEMPT_MS;
|
|
6468
|
+
let maxReceiptResponseAttemptMs = MAX_PERSISTED_RECEIPT_ATTEMPT_MS;
|
|
6469
|
+
let noProgressRetryMs = PERSISTED_RECEIPT_RETRY_MS;
|
|
6465
6470
|
let initialTransferPending = transferOnFirstRound;
|
|
6466
6471
|
let needsInitialLeaderCheck = true;
|
|
6467
6472
|
const carriedAcknowledgements = new Map<
|
|
@@ -6653,16 +6658,19 @@ export class SharedLog<
|
|
|
6653
6658
|
);
|
|
6654
6659
|
const roundController = new AbortController();
|
|
6655
6660
|
const roundSignal = AbortSignal.any([signal, roundController.signal]);
|
|
6656
|
-
const getAttemptTimeout = () =>
|
|
6661
|
+
const getAttemptTimeout = (maxMs = maxPrerequisiteAttemptMs) =>
|
|
6657
6662
|
Math.max(
|
|
6658
6663
|
1,
|
|
6659
6664
|
Math.min(
|
|
6660
|
-
|
|
6665
|
+
maxMs,
|
|
6661
6666
|
Math.floor((deadline.deadline - Date.now()) / candidateWaves),
|
|
6662
6667
|
),
|
|
6663
6668
|
);
|
|
6664
6669
|
const requests = new Set<Promise<void>>();
|
|
6665
6670
|
const transferAllOnRound = initialTransferPending;
|
|
6671
|
+
let receivedValidReceipt = false;
|
|
6672
|
+
let receivedNewReceipt = false;
|
|
6673
|
+
let missedReceiptResponse = false;
|
|
6666
6674
|
try {
|
|
6667
6675
|
for (const [peer, hashes] of hashesByPeer) {
|
|
6668
6676
|
let request!: Promise<void>;
|
|
@@ -6850,6 +6858,7 @@ export class SharedLog<
|
|
|
6850
6858
|
break;
|
|
6851
6859
|
}
|
|
6852
6860
|
let responses;
|
|
6861
|
+
let requestIssued = false;
|
|
6853
6862
|
const attempt = offset / PERSISTED_RECEIPT_CHUNK_SIZE + 1;
|
|
6854
6863
|
let requestStartedAt: number | undefined;
|
|
6855
6864
|
let endRequest:
|
|
@@ -6875,11 +6884,16 @@ export class SharedLog<
|
|
|
6875
6884
|
throw error;
|
|
6876
6885
|
}
|
|
6877
6886
|
if (!isPeerRoundCurrent()) break;
|
|
6878
|
-
const attemptTimeout = getAttemptTimeout();
|
|
6879
6887
|
responses =
|
|
6880
6888
|
(await operationQueue.add(async () => {
|
|
6881
6889
|
if (!isPeerRoundCurrent()) return [];
|
|
6890
|
+
// Only a missed response grows this bounded wait. Valid empty
|
|
6891
|
+
// receipts instead back off polling while durability catches up.
|
|
6892
|
+
const attemptTimeout = getAttemptTimeout(
|
|
6893
|
+
maxReceiptResponseAttemptMs,
|
|
6894
|
+
);
|
|
6882
6895
|
requestStartedAt = profile?.now();
|
|
6896
|
+
requestIssued = true;
|
|
6883
6897
|
const pending = this.rpc.request(
|
|
6884
6898
|
new RequestPersistedEntriesV1({
|
|
6885
6899
|
expectedReceiverSession: captured.capabilitySession,
|
|
@@ -6909,6 +6923,7 @@ export class SharedLog<
|
|
|
6909
6923
|
} catch {
|
|
6910
6924
|
endRequest?.("rejected", 0);
|
|
6911
6925
|
if (roundSignal.aborted) break;
|
|
6926
|
+
if (requestIssued) missedReceiptResponse = true;
|
|
6912
6927
|
// A peer can disconnect or miss this retry while the overall
|
|
6913
6928
|
// quorum deadline remains active. Replan on the next round.
|
|
6914
6929
|
break;
|
|
@@ -6947,11 +6962,17 @@ export class SharedLog<
|
|
|
6947
6962
|
continue;
|
|
6948
6963
|
}
|
|
6949
6964
|
receivedValidConfirmation = true;
|
|
6965
|
+
receivedValidReceipt = true;
|
|
6950
6966
|
for (const hash of unique) {
|
|
6951
6967
|
confirmed.add(hash);
|
|
6952
|
-
carriedAcknowledgements.get(hash)
|
|
6968
|
+
const acknowledgements = carriedAcknowledgements.get(hash)!;
|
|
6969
|
+
if (!acknowledgements.has(peer)) receivedNewReceipt = true;
|
|
6970
|
+
acknowledgements.set(peer, captured);
|
|
6953
6971
|
}
|
|
6954
6972
|
}
|
|
6973
|
+
if (requestIssued && !receivedValidConfirmation) {
|
|
6974
|
+
missedReceiptResponse = true;
|
|
6975
|
+
}
|
|
6955
6976
|
if (receivedValidConfirmation) {
|
|
6956
6977
|
const state = ensureRepairState();
|
|
6957
6978
|
for (const hash of requested) {
|
|
@@ -7064,21 +7085,34 @@ export class SharedLog<
|
|
|
7064
7085
|
if (isRoundOwnershipCurrent()) {
|
|
7065
7086
|
initialTransferPending = false;
|
|
7066
7087
|
}
|
|
7067
|
-
//
|
|
7068
|
-
//
|
|
7069
|
-
|
|
7088
|
+
// Confirmation and transfer can need a growing preparation window, but
|
|
7089
|
+
// receipt response waits stay bounded independently of that work.
|
|
7090
|
+
maxPrerequisiteAttemptMs = Math.min(
|
|
7070
7091
|
MAX_PERSISTED_DELIVERY_TIMEOUT_MS,
|
|
7071
|
-
|
|
7092
|
+
maxPrerequisiteAttemptMs * 2,
|
|
7072
7093
|
);
|
|
7094
|
+
// A genuinely slow receiver may need more than one response window.
|
|
7095
|
+
// Grow on any issued request's miss, even if another peer replied empty,
|
|
7096
|
+
// preserving the existing timer maximum and caller-deadline clamp.
|
|
7097
|
+
if (missedReceiptResponse) {
|
|
7098
|
+
maxReceiptResponseAttemptMs = Math.min(
|
|
7099
|
+
MAX_PERSISTED_DELIVERY_TIMEOUT_MS,
|
|
7100
|
+
maxReceiptResponseAttemptMs * 2,
|
|
7101
|
+
);
|
|
7102
|
+
}
|
|
7103
|
+
// A valid empty/unchanged receipt should reduce polling, not grow the
|
|
7104
|
+
// next RPC's response timeout. New receipts or a round without a valid
|
|
7105
|
+
// response reset this backoff; readiness recovery gets no extra pause.
|
|
7106
|
+
const noReceiptProgress = receivedValidReceipt && !receivedNewReceipt;
|
|
7107
|
+
const retryMs = noReceiptProgress
|
|
7108
|
+
? noProgressRetryMs
|
|
7109
|
+
: PERSISTED_RECEIPT_RETRY_MS;
|
|
7110
|
+
noProgressRetryMs = noReceiptProgress
|
|
7111
|
+
? Math.min(1_000, noProgressRetryMs * 2)
|
|
7112
|
+
: PERSISTED_RECEIPT_RETRY_MS;
|
|
7073
7113
|
await this.waitPersistedReceiptRetry(
|
|
7074
7114
|
signal,
|
|
7075
|
-
Math.max(
|
|
7076
|
-
0,
|
|
7077
|
-
Math.min(
|
|
7078
|
-
PERSISTED_RECEIPT_RETRY_MS,
|
|
7079
|
-
deadline.deadline - Date.now(),
|
|
7080
|
-
),
|
|
7081
|
-
),
|
|
7115
|
+
Math.max(0, Math.min(retryMs, deadline.deadline - Date.now())),
|
|
7082
7116
|
);
|
|
7083
7117
|
}
|
|
7084
7118
|
} catch (error) {
|
|
@@ -23350,6 +23384,7 @@ export class SharedLog<
|
|
|
23350
23384
|
));
|
|
23351
23385
|
if (!joinedPreparedFacts) {
|
|
23352
23386
|
await trustedLowerLog.join(materializeAllToMergeEntries(), {
|
|
23387
|
+
signal: releasePeerReceiveLease.signal,
|
|
23353
23388
|
__peerbitBatchIndependent: true,
|
|
23354
23389
|
__peerbitEntriesAlreadyMissing: true,
|
|
23355
23390
|
__peerbitCanAppendAlreadyValidated:
|
|
@@ -23788,23 +23823,18 @@ export class SharedLog<
|
|
|
23788
23823
|
capabilityTimestamp > previousCapabilityTimestamp;
|
|
23789
23824
|
if (
|
|
23790
23825
|
freshExactRearm &&
|
|
23791
|
-
this._v2Receive.
|
|
23826
|
+
this._v2Receive.acceptRemoteRearm({
|
|
23792
23827
|
peerHash: receiveFromHash,
|
|
23793
23828
|
peerSession: receiveSession,
|
|
23794
23829
|
receiveEpoch:
|
|
23795
23830
|
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23796
|
-
senderTransportSession: capabilityTransportSession,
|
|
23797
23831
|
})
|
|
23798
23832
|
) {
|
|
23799
|
-
//
|
|
23800
|
-
//
|
|
23801
|
-
//
|
|
23802
|
-
this.
|
|
23803
|
-
|
|
23804
|
-
peerSession: receiveSession,
|
|
23805
|
-
receiveEpoch:
|
|
23806
|
-
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23807
|
-
});
|
|
23833
|
+
// A one-sided topic reopening can discard the opposite receive
|
|
23834
|
+
// stream without changing transport. Retire its old Applied proof
|
|
23835
|
+
// before refreshing our capability and requesting a new Full.
|
|
23836
|
+
this._v2Send.reconfirmAfterPeerRecovery(receiveFromHash);
|
|
23837
|
+
this.dispatchPersistedReceiptReadinessChange(receiveFromHash);
|
|
23808
23838
|
}
|
|
23809
23839
|
} else if (observed && receiveSession === null) {
|
|
23810
23840
|
// A capability can arrive before the sender's topic Subscribe after
|
|
@@ -28880,6 +28910,11 @@ export class SharedLog<
|
|
|
28880
28910
|
peerSession: expectedSubscriptionEpoch,
|
|
28881
28911
|
receiveEpoch,
|
|
28882
28912
|
signal: replicationLifecycleController.signal,
|
|
28913
|
+
// Without a signed remote binding, even a fresh local instance may
|
|
28914
|
+
// face a peer that still considers its old topic session fully active.
|
|
28915
|
+
// Solicit one steady capability through the receiver-led Full handshake;
|
|
28916
|
+
// ordinary timestamp refreshes do not themselves request another reply.
|
|
28917
|
+
requestRemoteFullRearm: !this._peerSyncCapabilitySessions.has(peerHash),
|
|
28883
28918
|
});
|
|
28884
28919
|
this.scheduleReplicationInfoV2Recovery(
|
|
28885
28920
|
publicKey,
|
package/src/ranges.ts
CHANGED
|
@@ -3517,6 +3517,7 @@ export const toRebalance = <R extends "u32" | "u64">(
|
|
|
3517
3517
|
return {
|
|
3518
3518
|
[Symbol.asyncIterator]: async function* () {
|
|
3519
3519
|
let completed = false;
|
|
3520
|
+
let scannedPage = false;
|
|
3520
3521
|
try {
|
|
3521
3522
|
let cursor: RebalanceTaskCursor | undefined;
|
|
3522
3523
|
for (;;) {
|
|
@@ -3535,6 +3536,16 @@ export const toRebalance = <R extends "u32" | "u64">(
|
|
|
3535
3536
|
let taskCompleted = false;
|
|
3536
3537
|
try {
|
|
3537
3538
|
while (iterator.done() !== true) {
|
|
3539
|
+
if (scannedPage) {
|
|
3540
|
+
// SQLite pages can settle synchronously. Give transport and
|
|
3541
|
+
// lifecycle work a turn before reading the next bounded page.
|
|
3542
|
+
await new Promise<void>((resolve) => {
|
|
3543
|
+
if (typeof setImmediate === "function")
|
|
3544
|
+
setImmediate(resolve);
|
|
3545
|
+
else setTimeout(resolve, 0);
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3548
|
+
scannedPage = true;
|
|
3538
3549
|
const entries = await iterator.next(
|
|
3539
3550
|
REBALANCE_ITERATOR_BATCH_SIZE,
|
|
3540
3551
|
);
|
|
@@ -111,6 +111,7 @@ export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
|
111
111
|
context: ReplicationInfoV2LocalCapabilityContext;
|
|
112
112
|
attempts: number;
|
|
113
113
|
ready: boolean;
|
|
114
|
+
requestRemoteFullRearm?: boolean;
|
|
114
115
|
acknowledgedReady?: LocalCapabilityReady;
|
|
115
116
|
receiverTransportSession?: bigint;
|
|
116
117
|
timer?: ReturnType<typeof setTimeout>;
|
|
@@ -190,6 +191,7 @@ export type ReplicationInfoV2ReceiveState = {
|
|
|
190
191
|
requestsSinceCapabilityRefresh: number;
|
|
191
192
|
requestParked: boolean;
|
|
192
193
|
capabilityRefreshRequired: boolean;
|
|
194
|
+
remoteRearmPending?: boolean;
|
|
193
195
|
reservedAdmission?: ReplicationInfoV2ReceiveAdmission;
|
|
194
196
|
};
|
|
195
197
|
|
|
@@ -235,6 +237,7 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
235
237
|
receiveEpoch: object | null;
|
|
236
238
|
signal: AbortSignal;
|
|
237
239
|
requestRemoteFullRearm?: boolean;
|
|
240
|
+
acknowledgeRearm?: boolean;
|
|
238
241
|
}) => Promise<ReplicationInfoV2LocalCapabilityRefresh | undefined>;
|
|
239
242
|
onRequestError?: (error: unknown) => void;
|
|
240
243
|
onLocalCapabilityError?: (error: unknown) => void;
|
|
@@ -596,6 +599,8 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
596
599
|
state.controller.signal,
|
|
597
600
|
state.lifecycleSignal,
|
|
598
601
|
]),
|
|
602
|
+
requestRemoteFullRearm: state.requestRemoteFullRearm,
|
|
603
|
+
acknowledgeRearm: state.requestRemoteFullRearm,
|
|
599
604
|
});
|
|
600
605
|
if (
|
|
601
606
|
!refreshed ||
|
|
@@ -653,6 +658,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
653
658
|
peerSession: object;
|
|
654
659
|
receiveEpoch: object | null;
|
|
655
660
|
signal: AbortSignal;
|
|
661
|
+
requestRemoteFullRearm?: boolean;
|
|
656
662
|
}): ReplicationInfoV2LocalCapabilityAdvertisementHandle {
|
|
657
663
|
const peerHash = properties.target.hashcode();
|
|
658
664
|
if (
|
|
@@ -729,6 +735,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
729
735
|
context,
|
|
730
736
|
attempts: 0,
|
|
731
737
|
ready: false,
|
|
738
|
+
requestRemoteFullRearm: properties.requestRemoteFullRearm,
|
|
732
739
|
receiverTransportSession: this.deps.getReceiverTransportSession(),
|
|
733
740
|
};
|
|
734
741
|
advertisement.onLifecycleAbort = () =>
|
|
@@ -1418,6 +1425,28 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1418
1425
|
});
|
|
1419
1426
|
}
|
|
1420
1427
|
|
|
1428
|
+
/** Coalesce reciprocal capability recovery into the existing request worker. */
|
|
1429
|
+
acceptRemoteRearm(properties: {
|
|
1430
|
+
peerHash: string;
|
|
1431
|
+
peerSession: object;
|
|
1432
|
+
receiveEpoch: object | null;
|
|
1433
|
+
}): boolean {
|
|
1434
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
1435
|
+
if (
|
|
1436
|
+
!state ||
|
|
1437
|
+
state.peerSession !== properties.peerSession ||
|
|
1438
|
+
state.receiveEpoch !== properties.receiveEpoch ||
|
|
1439
|
+
!this.isStateCurrent(state)
|
|
1440
|
+
) {
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
// A newer solicitation can describe another remote topic opening before
|
|
1444
|
+
// the previous Full commits. Invalidating the captured version makes an
|
|
1445
|
+
// in-flight refresh yield to one trailing refresh, not a second worker.
|
|
1446
|
+
state.remoteRearmPending = true;
|
|
1447
|
+
return this.advanceRecovery(properties);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1421
1450
|
/** Require a fresh capability-bound grant and authoritative Full. */
|
|
1422
1451
|
advanceRecovery(properties: {
|
|
1423
1452
|
peerHash: string;
|
|
@@ -1796,11 +1825,11 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1796
1825
|
return false;
|
|
1797
1826
|
}
|
|
1798
1827
|
state.lastSequence = message.sequence;
|
|
1799
|
-
state.phase = "active";
|
|
1828
|
+
state.phase = state.remoteRearmPending ? "resync" : "active";
|
|
1800
1829
|
state.requestAttempts = 0;
|
|
1801
1830
|
state.requestsSinceCapabilityRefresh = 0;
|
|
1802
1831
|
state.requestParked = false;
|
|
1803
|
-
state.capabilityRefreshRequired =
|
|
1832
|
+
state.capabilityRefreshRequired = state.remoteRearmPending === true;
|
|
1804
1833
|
if (state.requestTimer) {
|
|
1805
1834
|
clearTimeout(state.requestTimer);
|
|
1806
1835
|
state.requestTimer = undefined;
|
|
@@ -1810,6 +1839,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1810
1839
|
admission.receiveEpoch = state.receiveEpoch;
|
|
1811
1840
|
admission.committed = true;
|
|
1812
1841
|
this.release(admission);
|
|
1842
|
+
if (state.remoteRearmPending) this.armRequest(state, 0);
|
|
1813
1843
|
return true;
|
|
1814
1844
|
}
|
|
1815
1845
|
|
|
@@ -1959,6 +1989,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1959
1989
|
state.lastSequence = undefined;
|
|
1960
1990
|
state.phase = "resync";
|
|
1961
1991
|
state.capabilityRefreshRequired = false;
|
|
1992
|
+
state.remoteRearmPending = false;
|
|
1962
1993
|
state.requestsSinceCapabilityRefresh = 0;
|
|
1963
1994
|
state.version++;
|
|
1964
1995
|
this.bindLocalCapability(state, ready);
|
|
@@ -132,6 +132,7 @@ export type ReplicationInfoV2SendState = {
|
|
|
132
132
|
worker?: Promise<void>;
|
|
133
133
|
applicationConfirmationRequest?: ApplicationConfirmationRequest;
|
|
134
134
|
appliedRevision?: bigint;
|
|
135
|
+
minimumConfirmationSequence?: bigint;
|
|
135
136
|
};
|
|
136
137
|
|
|
137
138
|
export type ReplicationInfoV2SendDeps<R extends "u32" | "u64"> = {
|
|
@@ -258,6 +259,22 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
258
259
|
}
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
/** Re-prove an unchanged transport stream after an authenticated recovery hint. */
|
|
263
|
+
reconfirmAfterPeerRecovery(peerHash: string): void {
|
|
264
|
+
const state = this._sendStates.get(peerHash);
|
|
265
|
+
if (!state || !this.isCurrent(state)) return;
|
|
266
|
+
state.appliedRevision = undefined;
|
|
267
|
+
state.minimumConfirmationSequence = state.nextSequence;
|
|
268
|
+
state.applicationConfirmationRequest?.controller.abort(
|
|
269
|
+
new AbortError("Replication confirmation peer recovery"),
|
|
270
|
+
);
|
|
271
|
+
state.applicationConfirmationRequest = undefined;
|
|
272
|
+
// Keep the stream: older rearm callers may still own its receive grant.
|
|
273
|
+
// A new Full sequence fences even an old Applied packet arriving after a
|
|
274
|
+
// replacement confirmation query has been registered for the same revision.
|
|
275
|
+
this.enqueueState(state, { kind: "snapshot", revision: this._revision });
|
|
276
|
+
}
|
|
277
|
+
|
|
261
278
|
private clearState(state: ReplicationInfoV2SendState): void {
|
|
262
279
|
this.trackRetiringWorker(state);
|
|
263
280
|
this.rejectTargetConfirmations(
|
|
@@ -1264,6 +1281,8 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
1264
1281
|
!bytesEqual(message.receiverChallenge, state.receiverChallenge) ||
|
|
1265
1282
|
!bytesEqual(message.senderEpoch, state.senderEpoch) ||
|
|
1266
1283
|
message.sequence !== requested.sequence ||
|
|
1284
|
+
(state.minimumConfirmationSequence !== undefined &&
|
|
1285
|
+
message.sequence < state.minimumConfirmationSequence) ||
|
|
1267
1286
|
message.revision !== requested.revision ||
|
|
1268
1287
|
message.revision > this._revision
|
|
1269
1288
|
) {
|
|
@@ -1359,6 +1378,8 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
1359
1378
|
if (
|
|
1360
1379
|
this.hasConfirmationFor(state, request.revision) &&
|
|
1361
1380
|
this.supportsApplicationConfirmation(state) &&
|
|
1381
|
+
(state.minimumConfirmationSequence === undefined ||
|
|
1382
|
+
message.sequence >= state.minimumConfirmationSequence) &&
|
|
1362
1383
|
this.isCurrent(state)
|
|
1363
1384
|
) {
|
|
1364
1385
|
// Keep one outstanding query until answered or no longer needed. A
|