@peerbit/shared-log 16.0.24 → 16.0.25
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/exchange-heads.d.ts +8 -0
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +8 -0
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +71 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +23 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +91 -0
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +6 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +13 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +13 -13
- package/src/exchange-heads.ts +9 -0
- package/src/index.ts +97 -14
- package/src/replication-info-v2-receive.ts +122 -0
- package/src/replication-info-v2-send.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/shared-log",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.25",
|
|
4
4
|
"description": "Shared log",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -62,35 +62,35 @@
|
|
|
62
62
|
"pidusage": "^4.0.1",
|
|
63
63
|
"pino": "^9.4.0",
|
|
64
64
|
"uint8arrays": "^5.1.0",
|
|
65
|
-
"@peerbit/any-store": "2.2.17",
|
|
66
65
|
"@peerbit/blocks": "4.2.16",
|
|
67
|
-
"@peerbit/blocks-interface": "2.1.8",
|
|
68
66
|
"@peerbit/cache": "3.1.1",
|
|
69
67
|
"@peerbit/crypto": "3.1.6",
|
|
68
|
+
"@peerbit/blocks-interface": "2.1.8",
|
|
70
69
|
"@peerbit/diagnostics": "0.0.1",
|
|
70
|
+
"@peerbit/any-store": "2.2.17",
|
|
71
71
|
"@peerbit/indexer-interface": "3.0.13",
|
|
72
|
-
"@peerbit/log": "6.2.31",
|
|
73
72
|
"@peerbit/logger": "2.0.2",
|
|
74
|
-
"@peerbit/indexer-sqlite3": "3.0.20",
|
|
75
73
|
"@peerbit/program": "6.0.58",
|
|
76
|
-
"@peerbit/
|
|
77
|
-
"@peerbit/
|
|
74
|
+
"@peerbit/indexer-sqlite3": "3.0.20",
|
|
75
|
+
"@peerbit/pubsub": "5.4.5",
|
|
76
|
+
"@peerbit/pubsub-interface": "5.2.2",
|
|
77
|
+
"@peerbit/log": "6.2.31",
|
|
78
78
|
"@peerbit/rpc": "6.1.26",
|
|
79
|
+
"@peerbit/riblt": "1.2.0",
|
|
79
80
|
"@peerbit/stream-interface": "6.0.16",
|
|
80
|
-
"@peerbit/
|
|
81
|
-
"@peerbit/pubsub": "5.4.5"
|
|
81
|
+
"@peerbit/time": "3.0.1"
|
|
82
82
|
},
|
|
83
83
|
"optionalDependencies": {
|
|
84
|
-
"@peerbit/
|
|
85
|
-
"@peerbit/
|
|
84
|
+
"@peerbit/native-backbone": "0.2.12",
|
|
85
|
+
"@peerbit/shared-log-rust": "0.1.7"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
89
89
|
"@types/pidusage": "^2.0.5",
|
|
90
90
|
"uuid": "^11.1.1",
|
|
91
|
-
"@peerbit/any-store-rust": "0.1.7",
|
|
92
|
-
"@peerbit/indexer-rust": "1.0.14",
|
|
93
91
|
"@peerbit/indexer-simple": "1.2.17",
|
|
92
|
+
"@peerbit/indexer-rust": "1.0.14",
|
|
93
|
+
"@peerbit/any-store-rust": "0.1.7",
|
|
94
94
|
"@peerbit/test-utils": "3.1.38",
|
|
95
95
|
"peerbit": "5.3.38"
|
|
96
96
|
},
|
package/src/exchange-heads.ts
CHANGED
|
@@ -383,6 +383,15 @@ export const SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM = 1 << 4;
|
|
|
383
383
|
*/
|
|
384
384
|
export const SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS = 1 << 5;
|
|
385
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Transient authenticated recovery hint: the receiver of this exact
|
|
388
|
+
* `SyncCapabilitiesMessage` should re-arm its current replication-info V2
|
|
389
|
+
* receiver and issue a fresh Full request. This bit is deliberately omitted
|
|
390
|
+
* from steady capability advertisements and must never be retained as a
|
|
391
|
+
* negotiated capability. Older peers safely ignore the unknown bit.
|
|
392
|
+
*/
|
|
393
|
+
export const SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM = 1 << 6;
|
|
394
|
+
|
|
386
395
|
/**
|
|
387
396
|
* One-shot capability advertisement, sent to a peer when it (or we) subscribe
|
|
388
397
|
* to the program topic. Peers that do not know this message drop it as an
|
package/src/index.ts
CHANGED
|
@@ -169,6 +169,7 @@ import {
|
|
|
169
169
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
170
170
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM,
|
|
171
171
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
172
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM,
|
|
172
173
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
173
174
|
StashBackedRawExchangeHeadsMessage,
|
|
174
175
|
SyncCapabilitiesMessage,
|
|
@@ -4071,6 +4072,7 @@ export class SharedLog<
|
|
|
4071
4072
|
peerSession: PeerSession;
|
|
4072
4073
|
receiveEpoch: object | null;
|
|
4073
4074
|
signal: AbortSignal;
|
|
4075
|
+
requestRemoteFullRearm?: boolean;
|
|
4074
4076
|
}): Promise<
|
|
4075
4077
|
{ receiverTransportSession: bigint; requestNotBeforeMs: number } | undefined
|
|
4076
4078
|
> {
|
|
@@ -4078,7 +4080,11 @@ export class SharedLog<
|
|
|
4078
4080
|
const receiverTransportSession = this.ownTransportSession();
|
|
4079
4081
|
await this.rpc.send(
|
|
4080
4082
|
new SyncCapabilitiesMessage({
|
|
4081
|
-
capabilities:
|
|
4083
|
+
capabilities:
|
|
4084
|
+
this.replicationInfoV2ReceiveCapabilities() |
|
|
4085
|
+
(properties.requestRemoteFullRearm
|
|
4086
|
+
? SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM
|
|
4087
|
+
: 0),
|
|
4082
4088
|
}),
|
|
4083
4089
|
{
|
|
4084
4090
|
mode: new AcknowledgeDelivery({
|
|
@@ -4145,6 +4151,7 @@ export class SharedLog<
|
|
|
4145
4151
|
peerSession: properties.peerSession as PeerSession,
|
|
4146
4152
|
receiveEpoch: properties.receiveEpoch,
|
|
4147
4153
|
signal: properties.signal,
|
|
4154
|
+
requestRemoteFullRearm: properties.requestRemoteFullRearm,
|
|
4148
4155
|
}),
|
|
4149
4156
|
onRequestError: (error) => {
|
|
4150
4157
|
if (
|
|
@@ -5851,7 +5858,13 @@ export class SharedLog<
|
|
|
5851
5858
|
|
|
5852
5859
|
private persistedReceiptPeerSession(
|
|
5853
5860
|
peerHash: string,
|
|
5854
|
-
):
|
|
5861
|
+
):
|
|
5862
|
+
| {
|
|
5863
|
+
capabilitySession: bigint;
|
|
5864
|
+
peerSession: PeerSession;
|
|
5865
|
+
receiveEpoch: object | null;
|
|
5866
|
+
}
|
|
5867
|
+
| undefined {
|
|
5855
5868
|
// This is a hot receipt/transfer-loop predicate. Keep it allocation-light,
|
|
5856
5869
|
// while mirroring every exact-session gate in
|
|
5857
5870
|
// persistedReceiptReadinessCandidate (which additionally creates public
|
|
@@ -5884,7 +5897,7 @@ export class SharedLog<
|
|
|
5884
5897
|
) {
|
|
5885
5898
|
return undefined;
|
|
5886
5899
|
}
|
|
5887
|
-
return { capabilitySession, peerSession };
|
|
5900
|
+
return { capabilitySession, peerSession, receiveEpoch };
|
|
5888
5901
|
}
|
|
5889
5902
|
|
|
5890
5903
|
private async waitPersistedReceiptRetry(
|
|
@@ -23168,6 +23181,17 @@ export class SharedLog<
|
|
|
23168
23181
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
23169
23182
|
const capabilityTransportSession = context.message?.header?.session;
|
|
23170
23183
|
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
23184
|
+
const previousCapabilitySession =
|
|
23185
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash);
|
|
23186
|
+
const previousCapabilityTimestamp =
|
|
23187
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash);
|
|
23188
|
+
const requestsRemoteFullRearm =
|
|
23189
|
+
(msg.capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM) !==
|
|
23190
|
+
0;
|
|
23191
|
+
// The rearm bit is a one-shot authenticated command, not a sticky
|
|
23192
|
+
// negotiated capability. Store and promote only the steady bits.
|
|
23193
|
+
const steadyCapabilities =
|
|
23194
|
+
msg.capabilities & ~SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM;
|
|
23171
23195
|
// No await separates this from the capture above, so the captured
|
|
23172
23196
|
// window state is exact: the legacy re-read of the opening map here
|
|
23173
23197
|
// could never observe a different value.
|
|
@@ -23180,7 +23204,7 @@ export class SharedLog<
|
|
|
23180
23204
|
// cleanup cannot erase it before the opening transition commits.
|
|
23181
23205
|
this.observePeerSyncCapabilities({
|
|
23182
23206
|
peerHash: receiveFromHash,
|
|
23183
|
-
capabilities:
|
|
23207
|
+
capabilities: steadyCapabilities,
|
|
23184
23208
|
transportSession: capabilityTransportSession,
|
|
23185
23209
|
timestamp: capabilityTimestamp,
|
|
23186
23210
|
openingSession: receiveSession!,
|
|
@@ -23188,7 +23212,7 @@ export class SharedLog<
|
|
|
23188
23212
|
} else {
|
|
23189
23213
|
const observed = this.observePeerSyncCapabilities({
|
|
23190
23214
|
peerHash: receiveFromHash,
|
|
23191
|
-
capabilities:
|
|
23215
|
+
capabilities: steadyCapabilities,
|
|
23192
23216
|
transportSession: capabilityTransportSession,
|
|
23193
23217
|
timestamp: capabilityTimestamp,
|
|
23194
23218
|
});
|
|
@@ -23197,6 +23221,33 @@ export class SharedLog<
|
|
|
23197
23221
|
context.from,
|
|
23198
23222
|
receiveSession,
|
|
23199
23223
|
);
|
|
23224
|
+
const freshExactRearm =
|
|
23225
|
+
requestsRemoteFullRearm &&
|
|
23226
|
+
capabilityTransportSession !== undefined &&
|
|
23227
|
+
capabilityTimestamp !== undefined &&
|
|
23228
|
+
previousCapabilitySession === capabilityTransportSession &&
|
|
23229
|
+
previousCapabilityTimestamp !== undefined &&
|
|
23230
|
+
capabilityTimestamp > previousCapabilityTimestamp;
|
|
23231
|
+
if (
|
|
23232
|
+
freshExactRearm &&
|
|
23233
|
+
this._v2Receive.isCurrentActive({
|
|
23234
|
+
peerHash: receiveFromHash,
|
|
23235
|
+
peerSession: receiveSession,
|
|
23236
|
+
receiveEpoch:
|
|
23237
|
+
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23238
|
+
senderTransportSession: capabilityTransportSession,
|
|
23239
|
+
})
|
|
23240
|
+
) {
|
|
23241
|
+
// Rotate the receiver grant/challenge before requesting Full. A
|
|
23242
|
+
// sender rebuilt from no state starts at sequence one, which an
|
|
23243
|
+
// active receiver's old sequence fence must otherwise reject.
|
|
23244
|
+
this._v2Receive.advanceRecovery({
|
|
23245
|
+
peerHash: receiveFromHash,
|
|
23246
|
+
peerSession: receiveSession,
|
|
23247
|
+
receiveEpoch:
|
|
23248
|
+
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23249
|
+
});
|
|
23250
|
+
}
|
|
23200
23251
|
} else if (observed && receiveSession === null) {
|
|
23201
23252
|
// A capability can arrive before the sender's topic Subscribe after
|
|
23202
23253
|
// reconnect. Ask that authenticated peer for its authoritative
|
|
@@ -24327,8 +24378,11 @@ export class SharedLog<
|
|
|
24327
24378
|
throwIfInactive();
|
|
24328
24379
|
}
|
|
24329
24380
|
|
|
24330
|
-
private nudgePersistedReceiptPeerReadiness(
|
|
24331
|
-
|
|
24381
|
+
private nudgePersistedReceiptPeerReadiness(
|
|
24382
|
+
publicKey: PublicSignKey,
|
|
24383
|
+
signal: AbortSignal,
|
|
24384
|
+
): void {
|
|
24385
|
+
if (this.closed || signal.aborted) return;
|
|
24332
24386
|
const peerHash = publicKey.hashcode();
|
|
24333
24387
|
const peerSession = this._peerSessions.current(peerHash);
|
|
24334
24388
|
if (
|
|
@@ -24359,6 +24413,22 @@ export class SharedLog<
|
|
|
24359
24413
|
peerSession,
|
|
24360
24414
|
receiveEpoch,
|
|
24361
24415
|
});
|
|
24416
|
+
const receiptTarget = this.persistedReceiptPeerSession(peerHash);
|
|
24417
|
+
if (
|
|
24418
|
+
receiptTarget &&
|
|
24419
|
+
!this._v2Send.hasCurrentStateForPeer({
|
|
24420
|
+
peerHash,
|
|
24421
|
+
peerSession: receiptTarget.peerSession,
|
|
24422
|
+
receiverTransportSession: receiptTarget.capabilitySession,
|
|
24423
|
+
})
|
|
24424
|
+
) {
|
|
24425
|
+
this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
|
|
24426
|
+
peerHash,
|
|
24427
|
+
peerSession: receiptTarget.peerSession,
|
|
24428
|
+
receiveEpoch: receiptTarget.receiveEpoch,
|
|
24429
|
+
signal,
|
|
24430
|
+
});
|
|
24431
|
+
}
|
|
24362
24432
|
this.scheduleReplicationInfoV2Recovery(publicKey);
|
|
24363
24433
|
}
|
|
24364
24434
|
|
|
@@ -24627,18 +24697,26 @@ export class SharedLog<
|
|
|
24627
24697
|
throw new AbortError("Persisted-receipt readiness wait settled");
|
|
24628
24698
|
}
|
|
24629
24699
|
};
|
|
24700
|
+
const recoveryDelayMs = Math.max(
|
|
24701
|
+
50,
|
|
24702
|
+
Math.min(1_000, this.waitForReplicatorRequestIntervalMs),
|
|
24703
|
+
);
|
|
24630
24704
|
const armRecoveryTick = () => {
|
|
24631
24705
|
if (settled || recoveryTimer) return;
|
|
24632
|
-
const delayMs = Math.max(
|
|
24633
|
-
50,
|
|
24634
|
-
Math.min(1_000, this.waitForReplicatorRequestIntervalMs),
|
|
24635
|
-
);
|
|
24636
24706
|
recoveryTimer = setTimeout(() => {
|
|
24637
24707
|
recoveryTimer = undefined;
|
|
24638
24708
|
if (!continueWait()) return;
|
|
24639
|
-
this.nudgePersistedReceiptPeerReadiness(key);
|
|
24709
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24710
|
+
if (checkInFlight) {
|
|
24711
|
+
// Confirmation can legitimately span several recovery intervals.
|
|
24712
|
+
// Keep repairing the exact current generation without aborting its
|
|
24713
|
+
// one application-confirmation waiter on every tick.
|
|
24714
|
+
rerun = true;
|
|
24715
|
+
armRecoveryTick();
|
|
24716
|
+
return;
|
|
24717
|
+
}
|
|
24640
24718
|
scheduleCheck();
|
|
24641
|
-
},
|
|
24719
|
+
}, recoveryDelayMs);
|
|
24642
24720
|
recoveryTimer.unref?.();
|
|
24643
24721
|
};
|
|
24644
24722
|
const runCheck = async () => {
|
|
@@ -24671,6 +24749,11 @@ export class SharedLog<
|
|
|
24671
24749
|
) {
|
|
24672
24750
|
const target = this.persistedReceiptPeerSession(peerHash);
|
|
24673
24751
|
if (target) {
|
|
24752
|
+
// A confirmation waiter cannot create an outbound V2 stream when
|
|
24753
|
+
// that exact session state is missing. Nudge first, then keep the
|
|
24754
|
+
// recovery tick alive while the single confirmation wait remains.
|
|
24755
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24756
|
+
armRecoveryTick();
|
|
24674
24757
|
const currentConfirmationController = new AbortController();
|
|
24675
24758
|
confirmationController = currentConfirmationController;
|
|
24676
24759
|
try {
|
|
@@ -24715,7 +24798,7 @@ export class SharedLog<
|
|
|
24715
24798
|
}
|
|
24716
24799
|
}
|
|
24717
24800
|
if (!continueWait()) return;
|
|
24718
|
-
this.nudgePersistedReceiptPeerReadiness(key);
|
|
24801
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24719
24802
|
} catch (error) {
|
|
24720
24803
|
if (!settled) reject(error);
|
|
24721
24804
|
} finally {
|
|
@@ -112,6 +112,11 @@ export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
|
112
112
|
firstAttempt?: Promise<void>;
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
type ReplicationInfoV2RemoteFullRearm = {
|
|
116
|
+
lastAttemptAt: number;
|
|
117
|
+
inFlight?: Promise<void>;
|
|
118
|
+
};
|
|
119
|
+
|
|
115
120
|
export type ReplicationInfoV2LocalCapabilityRefresh = {
|
|
116
121
|
receiverTransportSession: bigint;
|
|
117
122
|
requestNotBeforeMs: number;
|
|
@@ -183,6 +188,7 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
183
188
|
peerSession: object;
|
|
184
189
|
receiveEpoch: object | null;
|
|
185
190
|
signal: AbortSignal;
|
|
191
|
+
requestRemoteFullRearm?: boolean;
|
|
186
192
|
}) => Promise<ReplicationInfoV2LocalCapabilityRefresh | undefined>;
|
|
187
193
|
onRequestError?: (error: unknown) => void;
|
|
188
194
|
onLocalCapabilityError?: (error: unknown) => void;
|
|
@@ -217,6 +223,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
217
223
|
string,
|
|
218
224
|
ReplicationInfoV2LocalCapabilityAdvertisement
|
|
219
225
|
>;
|
|
226
|
+
_remoteFullRearmBySession!: WeakMap<object, ReplicationInfoV2RemoteFullRearm>;
|
|
220
227
|
_reservedAdmissionsByPeer!: Map<string, ReplicationInfoV2ReceiveAdmission>;
|
|
221
228
|
|
|
222
229
|
private readonly now: () => number;
|
|
@@ -243,6 +250,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
243
250
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
244
251
|
this._localCapabilityContextBySession = new WeakMap();
|
|
245
252
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
253
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
246
254
|
this._reservedAdmissionsByPeer = new Map();
|
|
247
255
|
}
|
|
248
256
|
|
|
@@ -253,6 +261,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
253
261
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
254
262
|
this._localCapabilityContextBySession = new WeakMap();
|
|
255
263
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
264
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
256
265
|
this._reservedAdmissionsByPeer = new Map();
|
|
257
266
|
}
|
|
258
267
|
|
|
@@ -270,6 +279,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
270
279
|
this._cutoverPeerSessions = new WeakSet();
|
|
271
280
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
272
281
|
this._localCapabilityContextBySession = new WeakMap();
|
|
282
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
clearPeer(peerHash: string, expectedSession?: object): void {
|
|
@@ -291,6 +301,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
291
301
|
if (expectedSession) {
|
|
292
302
|
this._localCapabilityReadyBySession.delete(expectedSession);
|
|
293
303
|
this._localCapabilityContextBySession.delete(expectedSession);
|
|
304
|
+
this._remoteFullRearmBySession.delete(expectedSession);
|
|
294
305
|
this._cutoverPeerSessions.delete(expectedSession);
|
|
295
306
|
}
|
|
296
307
|
}
|
|
@@ -625,6 +636,117 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
625
636
|
};
|
|
626
637
|
}
|
|
627
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Send one transient, authenticated rearm hint for the exact current local
|
|
641
|
+
* receive grant. The hint asks a patched remote receiver to rotate its
|
|
642
|
+
* challenge and issue another Full request, rebuilding an outbound sender
|
|
643
|
+
* stream that was lost while both directions otherwise remained current.
|
|
644
|
+
*
|
|
645
|
+
* This deliberately does not mutate the steady advertisement worker. One
|
|
646
|
+
* attempt is coalesced per PeerSession and bound to the caller's signal; the
|
|
647
|
+
* readiness wait's bounded recovery tick decides whether another attempt is
|
|
648
|
+
* needed.
|
|
649
|
+
*/
|
|
650
|
+
reAdvertiseLocalCapabilityForRemoteFull(properties: {
|
|
651
|
+
peerHash: string;
|
|
652
|
+
peerSession: object;
|
|
653
|
+
receiveEpoch: object | null;
|
|
654
|
+
signal: AbortSignal;
|
|
655
|
+
}): boolean {
|
|
656
|
+
const context = this._localCapabilityContextBySession.get(
|
|
657
|
+
properties.peerSession,
|
|
658
|
+
);
|
|
659
|
+
const ready = this._localCapabilityReadyBySession.get(
|
|
660
|
+
properties.peerSession,
|
|
661
|
+
);
|
|
662
|
+
if (
|
|
663
|
+
properties.signal.aborted ||
|
|
664
|
+
!context ||
|
|
665
|
+
context.peerHash !== properties.peerHash ||
|
|
666
|
+
context.lifecycleSignal.aborted ||
|
|
667
|
+
this.deps.isClosed() ||
|
|
668
|
+
!this.deps.isPeerStateCurrent(
|
|
669
|
+
properties.peerHash,
|
|
670
|
+
properties.peerSession,
|
|
671
|
+
properties.receiveEpoch,
|
|
672
|
+
) ||
|
|
673
|
+
!ready ||
|
|
674
|
+
ready.peerHash !== properties.peerHash ||
|
|
675
|
+
ready.receiveEpoch !== properties.receiveEpoch ||
|
|
676
|
+
ready.receiverTransportSession !== this.deps.getReceiverTransportSession()
|
|
677
|
+
) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const now = this.now();
|
|
682
|
+
let rearm = this._remoteFullRearmBySession.get(properties.peerSession);
|
|
683
|
+
if (rearm?.inFlight) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
if (
|
|
687
|
+
rearm !== undefined &&
|
|
688
|
+
now - rearm.lastAttemptAt < this.requestRetryMs
|
|
689
|
+
) {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
if (!rearm) {
|
|
693
|
+
rearm = { lastAttemptAt: now };
|
|
694
|
+
this._remoteFullRearmBySession.set(properties.peerSession, rearm);
|
|
695
|
+
} else {
|
|
696
|
+
rearm.lastAttemptAt = now;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const operationSignal = AbortSignal.any([
|
|
700
|
+
context.lifecycleSignal,
|
|
701
|
+
properties.signal,
|
|
702
|
+
]);
|
|
703
|
+
let operation: Promise<void>;
|
|
704
|
+
const detach = () => {
|
|
705
|
+
if (rearm?.inFlight === operation) {
|
|
706
|
+
rearm.inFlight = undefined;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
operation = Promise.resolve()
|
|
710
|
+
.then(() => {
|
|
711
|
+
if (operationSignal.aborted) {
|
|
712
|
+
throw operationSignal.reason;
|
|
713
|
+
}
|
|
714
|
+
return this.deps.refreshLocalCapability({
|
|
715
|
+
peerHash: properties.peerHash,
|
|
716
|
+
target: context.target,
|
|
717
|
+
peerSession: properties.peerSession,
|
|
718
|
+
receiveEpoch: properties.receiveEpoch,
|
|
719
|
+
signal: operationSignal,
|
|
720
|
+
requestRemoteFullRearm: true,
|
|
721
|
+
});
|
|
722
|
+
})
|
|
723
|
+
.then(() => undefined)
|
|
724
|
+
.catch((error) => {
|
|
725
|
+
if (
|
|
726
|
+
!operationSignal.aborted &&
|
|
727
|
+
!this.deps.isClosed() &&
|
|
728
|
+
this.deps.isPeerStateCurrent(
|
|
729
|
+
properties.peerHash,
|
|
730
|
+
properties.peerSession,
|
|
731
|
+
properties.receiveEpoch,
|
|
732
|
+
)
|
|
733
|
+
) {
|
|
734
|
+
this.deps.onLocalCapabilityError?.(error);
|
|
735
|
+
}
|
|
736
|
+
})
|
|
737
|
+
.finally(() => {
|
|
738
|
+
operationSignal.removeEventListener("abort", detach);
|
|
739
|
+
detach();
|
|
740
|
+
});
|
|
741
|
+
rearm.inFlight = operation;
|
|
742
|
+
operationSignal.addEventListener("abort", detach, { once: true });
|
|
743
|
+
if (operationSignal.aborted) {
|
|
744
|
+
detach();
|
|
745
|
+
}
|
|
746
|
+
void operation;
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
|
|
628
750
|
private promoteLocalCapabilityAdvertisement(
|
|
629
751
|
state: ReplicationInfoV2LocalCapabilityAdvertisement,
|
|
630
752
|
): boolean {
|
|
@@ -561,6 +561,22 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
561
561
|
);
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Whether the exact current destination generation already has an outbound
|
|
566
|
+
* V2 stream. A missing/stale stream cannot be repaired by confirmation
|
|
567
|
+
* retries alone: the remote receiver must issue another Full request first.
|
|
568
|
+
*/
|
|
569
|
+
hasCurrentStateForPeer(target: ApplicationConfirmationTarget): boolean {
|
|
570
|
+
const state = this._sendStates.get(target.peerHash);
|
|
571
|
+
return (
|
|
572
|
+
state !== undefined &&
|
|
573
|
+
state.peerSession === target.peerSession &&
|
|
574
|
+
state.receiverTransportSession === target.receiverTransportSession &&
|
|
575
|
+
this.isCurrent(state) &&
|
|
576
|
+
this.supportsApplicationConfirmation(state)
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
564
580
|
private trackRetiringWorker(state: ReplicationInfoV2SendState): void {
|
|
565
581
|
const worker = state.worker;
|
|
566
582
|
if (!worker) {
|