@peerbit/shared-log 16.0.25 → 16.0.27
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 +45 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +167 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +49 -4
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +272 -25
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +27 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +112 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +16 -16
- package/src/index.ts +297 -34
- package/src/replication-info-v2-receive.ts +409 -31
- package/src/replication-info-v2-send.ts +164 -0
|
@@ -25,6 +25,11 @@ const REQUIRED_SENDER_CAPABILITIES =
|
|
|
25
25
|
const DEFAULT_REQUEST_RETRY_MS = 1_000;
|
|
26
26
|
const DEFAULT_MAX_REQUEST_RETRY_MS = 30_000;
|
|
27
27
|
const DEFAULT_REQUEST_MAX_ATTEMPTS = 7;
|
|
28
|
+
const DEFAULT_REMOTE_FULL_REARM_ATTEMPT_TIMEOUT_MS = 2_000;
|
|
29
|
+
const DEFAULT_REMOTE_FULL_REARM_COOLDOWN_MS = 5_000;
|
|
30
|
+
const MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION = 2;
|
|
31
|
+
const MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL = 64;
|
|
32
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
28
33
|
const MAX_U64 = (1n << 64n) - 1n;
|
|
29
34
|
const MAX_BACKOFF_EXPONENT = 20;
|
|
30
35
|
|
|
@@ -113,10 +118,50 @@ export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
|
113
118
|
};
|
|
114
119
|
|
|
115
120
|
type ReplicationInfoV2RemoteFullRearm = {
|
|
116
|
-
|
|
121
|
+
peerHash: string;
|
|
122
|
+
receiveEpoch: object | null;
|
|
123
|
+
receiverTransportSession: bigint;
|
|
124
|
+
nextAttemptAt: number;
|
|
125
|
+
attemptsStarted: number;
|
|
126
|
+
outstandingAttempts: number;
|
|
117
127
|
inFlight?: Promise<void>;
|
|
128
|
+
controller?: AbortController;
|
|
118
129
|
};
|
|
119
130
|
|
|
131
|
+
export type ReplicationInfoV2ReceiveDiagnostic = Readonly<{
|
|
132
|
+
state: "absent" | "current" | "stale";
|
|
133
|
+
/** Current-state classification; no historical packet/drop log is retained. */
|
|
134
|
+
reason:
|
|
135
|
+
| "state-absent"
|
|
136
|
+
| "peer-session-mismatch"
|
|
137
|
+
| "receive-epoch-mismatch"
|
|
138
|
+
| "transport-session-mismatch"
|
|
139
|
+
| "state-not-current"
|
|
140
|
+
| "admission-pending"
|
|
141
|
+
| "active"
|
|
142
|
+
| "request-parked"
|
|
143
|
+
| "request-in-flight"
|
|
144
|
+
| "request-scheduled"
|
|
145
|
+
| "awaiting-full"
|
|
146
|
+
| "resync";
|
|
147
|
+
phase?: ReplicationInfoV2ReceivePhase;
|
|
148
|
+
requestState?:
|
|
149
|
+
| "idle"
|
|
150
|
+
| "scheduled"
|
|
151
|
+
| "in-flight"
|
|
152
|
+
| "parked"
|
|
153
|
+
| "admission-pending";
|
|
154
|
+
lastAppliedSequence?: string;
|
|
155
|
+
requestAttempts?: number;
|
|
156
|
+
capabilityAdvertisementAttempts: number;
|
|
157
|
+
capabilityAdvertisement: "absent" | "advertising" | "ready" | "stale";
|
|
158
|
+
remoteFullRearm: "idle" | "in-flight" | "cooldown" | "limited";
|
|
159
|
+
remoteFullRearmAttempts: number;
|
|
160
|
+
remoteFullRearmOutstanding: number;
|
|
161
|
+
remoteFullRearmGlobalOutstanding: number;
|
|
162
|
+
remoteFullRearmCooldownRemainingMs?: number;
|
|
163
|
+
}>;
|
|
164
|
+
|
|
120
165
|
export type ReplicationInfoV2LocalCapabilityRefresh = {
|
|
121
166
|
receiverTransportSession: bigint;
|
|
122
167
|
requestNotBeforeMs: number;
|
|
@@ -196,6 +241,9 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
196
241
|
requestRetryMs?: number;
|
|
197
242
|
maxRequestRetryMs?: number;
|
|
198
243
|
requestMaxAttempts?: number;
|
|
244
|
+
remoteFullRearmAttemptTimeoutMs?: number;
|
|
245
|
+
remoteFullRearmCooldownMs?: number;
|
|
246
|
+
maxRemoteFullRearmOutstandingGlobal?: number;
|
|
199
247
|
};
|
|
200
248
|
|
|
201
249
|
/**
|
|
@@ -224,12 +272,17 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
224
272
|
ReplicationInfoV2LocalCapabilityAdvertisement
|
|
225
273
|
>;
|
|
226
274
|
_remoteFullRearmBySession!: WeakMap<object, ReplicationInfoV2RemoteFullRearm>;
|
|
275
|
+
_remoteFullRearmsInFlight!: Set<ReplicationInfoV2RemoteFullRearm>;
|
|
276
|
+
_remoteFullRearmOutstanding!: Set<object>;
|
|
227
277
|
_reservedAdmissionsByPeer!: Map<string, ReplicationInfoV2ReceiveAdmission>;
|
|
228
278
|
|
|
229
279
|
private readonly now: () => number;
|
|
230
280
|
private readonly requestRetryMs: number;
|
|
231
281
|
private readonly maxRequestRetryMs: number;
|
|
232
282
|
private readonly requestMaxAttempts: number;
|
|
283
|
+
private readonly remoteFullRearmAttemptTimeoutMs: number;
|
|
284
|
+
private readonly remoteFullRearmCooldownMs: number;
|
|
285
|
+
private readonly maxRemoteFullRearmOutstandingGlobal: number;
|
|
233
286
|
|
|
234
287
|
constructor(private readonly deps: ReplicationInfoV2ReceiveDeps) {
|
|
235
288
|
this.now = deps.now ?? Date.now;
|
|
@@ -245,12 +298,46 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
245
298
|
1,
|
|
246
299
|
Math.floor(deps.requestMaxAttempts ?? DEFAULT_REQUEST_MAX_ATTEMPTS),
|
|
247
300
|
);
|
|
301
|
+
this.remoteFullRearmAttemptTimeoutMs = Math.max(
|
|
302
|
+
1,
|
|
303
|
+
Math.min(
|
|
304
|
+
MAX_TIMER_MS,
|
|
305
|
+
Math.floor(
|
|
306
|
+
deps.remoteFullRearmAttemptTimeoutMs ??
|
|
307
|
+
DEFAULT_REMOTE_FULL_REARM_ATTEMPT_TIMEOUT_MS,
|
|
308
|
+
),
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
this.remoteFullRearmCooldownMs = Math.max(
|
|
312
|
+
this.requestRetryMs,
|
|
313
|
+
Math.min(
|
|
314
|
+
MAX_TIMER_MS,
|
|
315
|
+
Math.floor(
|
|
316
|
+
deps.remoteFullRearmCooldownMs ??
|
|
317
|
+
DEFAULT_REMOTE_FULL_REARM_COOLDOWN_MS,
|
|
318
|
+
),
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
// The injected value is a test seam that may only tighten this lifetime
|
|
322
|
+
// resource bound, never widen it.
|
|
323
|
+
this.maxRemoteFullRearmOutstandingGlobal = Math.min(
|
|
324
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
325
|
+
Math.max(
|
|
326
|
+
1,
|
|
327
|
+
Math.floor(
|
|
328
|
+
deps.maxRemoteFullRearmOutstandingGlobal ??
|
|
329
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
248
333
|
this._receiveStates = new Map();
|
|
249
334
|
this._cutoverPeerSessions = new WeakSet();
|
|
250
335
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
251
336
|
this._localCapabilityContextBySession = new WeakMap();
|
|
252
337
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
253
338
|
this._remoteFullRearmBySession = new WeakMap();
|
|
339
|
+
this._remoteFullRearmsInFlight = new Set();
|
|
340
|
+
this._remoteFullRearmOutstanding = new Set();
|
|
254
341
|
this._reservedAdmissionsByPeer = new Map();
|
|
255
342
|
}
|
|
256
343
|
|
|
@@ -262,10 +349,19 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
262
349
|
this._localCapabilityContextBySession = new WeakMap();
|
|
263
350
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
264
351
|
this._remoteFullRearmBySession = new WeakMap();
|
|
352
|
+
this._remoteFullRearmsInFlight = new Set();
|
|
265
353
|
this._reservedAdmissionsByPeer = new Map();
|
|
266
354
|
}
|
|
267
355
|
|
|
268
356
|
clearForClose(): void {
|
|
357
|
+
for (const rearm of this._remoteFullRearmsInFlight ?? []) {
|
|
358
|
+
rearm.controller?.abort(
|
|
359
|
+
new Error(
|
|
360
|
+
"Replication-info V2 receiver closed during remote Full rearm",
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
this._remoteFullRearmsInFlight?.clear();
|
|
269
365
|
for (const advertisement of [
|
|
270
366
|
...(this._localCapabilityAdvertisementsByPeer?.values() ?? []),
|
|
271
367
|
]) {
|
|
@@ -298,10 +394,28 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
298
394
|
this._localCapabilityContextBySession.delete(state.peerSession);
|
|
299
395
|
this._cutoverPeerSessions.delete(state.peerSession);
|
|
300
396
|
}
|
|
397
|
+
if (!expectedSession) {
|
|
398
|
+
for (const rearm of this._remoteFullRearmsInFlight) {
|
|
399
|
+
if (rearm.peerHash === peerHash) {
|
|
400
|
+
rearm.controller?.abort(
|
|
401
|
+
new Error("Replication-info V2 peer cleared during rearm"),
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const rearmSession =
|
|
407
|
+
expectedSession ?? state?.peerSession ?? advertisement?.peerSession;
|
|
408
|
+
if (rearmSession) {
|
|
409
|
+
this._remoteFullRearmBySession
|
|
410
|
+
.get(rearmSession)
|
|
411
|
+
?.controller?.abort(
|
|
412
|
+
new Error("Replication-info V2 peer session cleared during rearm"),
|
|
413
|
+
);
|
|
414
|
+
this._remoteFullRearmBySession.delete(rearmSession);
|
|
415
|
+
}
|
|
301
416
|
if (expectedSession) {
|
|
302
417
|
this._localCapabilityReadyBySession.delete(expectedSession);
|
|
303
418
|
this._localCapabilityContextBySession.delete(expectedSession);
|
|
304
|
-
this._remoteFullRearmBySession.delete(expectedSession);
|
|
305
419
|
this._cutoverPeerSessions.delete(expectedSession);
|
|
306
420
|
}
|
|
307
421
|
}
|
|
@@ -643,9 +757,14 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
643
757
|
* stream that was lost while both directions otherwise remained current.
|
|
644
758
|
*
|
|
645
759
|
* This deliberately does not mutate the steady advertisement worker. One
|
|
646
|
-
* attempt is coalesced per PeerSession and bound to
|
|
647
|
-
*
|
|
648
|
-
*
|
|
760
|
+
* attempt is coalesced per exact PeerSession/receive generation and bound to
|
|
761
|
+
* its own deadline as well as the caller's signal. A cooldown avoids rotating
|
|
762
|
+
* a healthy in-flight challenge on every recovery tick. The remote rotates
|
|
763
|
+
* only when its receive state is active; later hints during resync only nudge
|
|
764
|
+
* that same bounded request generation. At most two transports that disregard
|
|
765
|
+
* abort remain outstanding for one exact session (64 across this coordinator's
|
|
766
|
+
* lifetime); reaching either cap stops new hints while the persisted-readiness
|
|
767
|
+
* gate remains fail closed.
|
|
649
768
|
*/
|
|
650
769
|
reAdvertiseLocalCapabilityForRemoteFull(properties: {
|
|
651
770
|
peerHash: string;
|
|
@@ -679,47 +798,135 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
679
798
|
}
|
|
680
799
|
|
|
681
800
|
const now = this.now();
|
|
801
|
+
const receiverTransportSession = ready.receiverTransportSession;
|
|
682
802
|
let rearm = this._remoteFullRearmBySession.get(properties.peerSession);
|
|
803
|
+
if (
|
|
804
|
+
rearm &&
|
|
805
|
+
(rearm.peerHash !== properties.peerHash ||
|
|
806
|
+
rearm.receiveEpoch !== properties.receiveEpoch ||
|
|
807
|
+
rearm.receiverTransportSession !== receiverTransportSession)
|
|
808
|
+
) {
|
|
809
|
+
rearm.controller?.abort(
|
|
810
|
+
new Error("Replication-info V2 remote Full rearm generation changed"),
|
|
811
|
+
);
|
|
812
|
+
rearm = undefined;
|
|
813
|
+
}
|
|
683
814
|
if (rearm?.inFlight) {
|
|
684
815
|
return true;
|
|
685
816
|
}
|
|
817
|
+
if (rearm !== undefined && now < rearm.nextAttemptAt) {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
686
820
|
if (
|
|
687
|
-
rearm
|
|
688
|
-
|
|
821
|
+
(rearm?.outstandingAttempts ?? 0) >=
|
|
822
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION ||
|
|
823
|
+
this._remoteFullRearmOutstanding.size >=
|
|
824
|
+
this.maxRemoteFullRearmOutstandingGlobal
|
|
689
825
|
) {
|
|
690
826
|
return true;
|
|
691
827
|
}
|
|
692
828
|
if (!rearm) {
|
|
693
|
-
rearm = {
|
|
829
|
+
rearm = {
|
|
830
|
+
peerHash: properties.peerHash,
|
|
831
|
+
receiveEpoch: properties.receiveEpoch,
|
|
832
|
+
receiverTransportSession,
|
|
833
|
+
nextAttemptAt: now + this.remoteFullRearmCooldownMs,
|
|
834
|
+
attemptsStarted: 0,
|
|
835
|
+
outstandingAttempts: 0,
|
|
836
|
+
};
|
|
694
837
|
this._remoteFullRearmBySession.set(properties.peerSession, rearm);
|
|
695
838
|
} else {
|
|
696
|
-
rearm.
|
|
839
|
+
rearm.nextAttemptAt = now + this.remoteFullRearmCooldownMs;
|
|
697
840
|
}
|
|
841
|
+
const attemptState = rearm;
|
|
842
|
+
const outstandingReservation = {};
|
|
843
|
+
attemptState.attemptsStarted = Math.min(
|
|
844
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
845
|
+
attemptState.attemptsStarted + 1,
|
|
846
|
+
);
|
|
847
|
+
attemptState.outstandingAttempts++;
|
|
848
|
+
this._remoteFullRearmOutstanding.add(outstandingReservation);
|
|
849
|
+
|
|
850
|
+
const attemptController = new AbortController();
|
|
851
|
+
const operationSignal = attemptController.signal;
|
|
852
|
+
attemptState.controller = attemptController;
|
|
853
|
+
this._remoteFullRearmsInFlight.add(attemptState);
|
|
854
|
+
const sourceSignals = Array.from(
|
|
855
|
+
new Set([context.lifecycleSignal, properties.signal]),
|
|
856
|
+
);
|
|
857
|
+
const onSourceAbort = (event: Event) => {
|
|
858
|
+
const source = event.currentTarget as AbortSignal;
|
|
859
|
+
attemptController.abort(source.reason);
|
|
860
|
+
};
|
|
861
|
+
for (const source of sourceSignals) {
|
|
862
|
+
source.addEventListener("abort", onSourceAbort, { once: true });
|
|
863
|
+
}
|
|
864
|
+
const attemptTimer = setTimeout(
|
|
865
|
+
() =>
|
|
866
|
+
attemptController.abort(
|
|
867
|
+
new Error("Replication-info V2 remote Full rearm attempt timed out"),
|
|
868
|
+
),
|
|
869
|
+
this.remoteFullRearmAttemptTimeoutMs,
|
|
870
|
+
);
|
|
871
|
+
attemptTimer.unref?.();
|
|
872
|
+
let releasedOutstanding = false;
|
|
873
|
+
const releaseOutstanding = () => {
|
|
874
|
+
if (releasedOutstanding) return;
|
|
875
|
+
releasedOutstanding = true;
|
|
876
|
+
this._remoteFullRearmOutstanding.delete(outstandingReservation);
|
|
877
|
+
attemptState.outstandingAttempts--;
|
|
878
|
+
};
|
|
879
|
+
const refresh = Promise.resolve().then(() => {
|
|
880
|
+
if (operationSignal.aborted) {
|
|
881
|
+
throw operationSignal.reason;
|
|
882
|
+
}
|
|
883
|
+
return this.deps.refreshLocalCapability({
|
|
884
|
+
peerHash: properties.peerHash,
|
|
885
|
+
target: context.target,
|
|
886
|
+
peerSession: properties.peerSession,
|
|
887
|
+
receiveEpoch: properties.receiveEpoch,
|
|
888
|
+
signal: operationSignal,
|
|
889
|
+
requestRemoteFullRearm: true,
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
void refresh.then(releaseOutstanding, releaseOutstanding);
|
|
698
893
|
|
|
699
|
-
const operationSignal = AbortSignal.any([
|
|
700
|
-
context.lifecycleSignal,
|
|
701
|
-
properties.signal,
|
|
702
|
-
]);
|
|
703
894
|
let operation: Promise<void>;
|
|
704
895
|
const detach = () => {
|
|
705
|
-
|
|
706
|
-
|
|
896
|
+
clearTimeout(attemptTimer);
|
|
897
|
+
operationSignal.removeEventListener("abort", detach);
|
|
898
|
+
for (const source of sourceSignals) {
|
|
899
|
+
source.removeEventListener("abort", onSourceAbort);
|
|
900
|
+
}
|
|
901
|
+
if (attemptState.inFlight === operation) {
|
|
902
|
+
this._remoteFullRearmsInFlight.delete(attemptState);
|
|
903
|
+
attemptState.inFlight = undefined;
|
|
904
|
+
attemptState.controller = undefined;
|
|
707
905
|
}
|
|
708
906
|
};
|
|
709
|
-
|
|
710
|
-
|
|
907
|
+
const raceWithAttemptSignal = <T>(promise: Promise<T>): Promise<T> =>
|
|
908
|
+
new Promise<T>((resolve, reject) => {
|
|
909
|
+
const onAbort = () => {
|
|
910
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
911
|
+
reject(operationSignal.reason);
|
|
912
|
+
};
|
|
913
|
+
operationSignal.addEventListener("abort", onAbort, { once: true });
|
|
711
914
|
if (operationSignal.aborted) {
|
|
712
|
-
|
|
915
|
+
onAbort();
|
|
916
|
+
return;
|
|
713
917
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
918
|
+
promise.then(
|
|
919
|
+
(value) => {
|
|
920
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
921
|
+
resolve(value);
|
|
922
|
+
},
|
|
923
|
+
(error) => {
|
|
924
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
925
|
+
reject(error);
|
|
926
|
+
},
|
|
927
|
+
);
|
|
928
|
+
});
|
|
929
|
+
operation = raceWithAttemptSignal(refresh)
|
|
723
930
|
.then(() => undefined)
|
|
724
931
|
.catch((error) => {
|
|
725
932
|
if (
|
|
@@ -735,18 +942,189 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
735
942
|
}
|
|
736
943
|
})
|
|
737
944
|
.finally(() => {
|
|
738
|
-
operationSignal.removeEventListener("abort", detach);
|
|
739
945
|
detach();
|
|
740
946
|
});
|
|
741
|
-
|
|
947
|
+
attemptState.inFlight = operation;
|
|
742
948
|
operationSignal.addEventListener("abort", detach, { once: true });
|
|
743
|
-
|
|
744
|
-
|
|
949
|
+
for (const source of sourceSignals) {
|
|
950
|
+
if (source.aborted) {
|
|
951
|
+
attemptController.abort(source.reason);
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
745
954
|
}
|
|
746
955
|
void operation;
|
|
747
956
|
return true;
|
|
748
957
|
}
|
|
749
958
|
|
|
959
|
+
/**
|
|
960
|
+
* Bounded, read-only state for persisted-readiness troubleshooting. This
|
|
961
|
+
* intentionally omits transport sessions, challenges, payloads, and maps.
|
|
962
|
+
*/
|
|
963
|
+
diagnosePeer(properties: {
|
|
964
|
+
peerHash: string;
|
|
965
|
+
peerSession?: object;
|
|
966
|
+
receiveEpoch?: object | null;
|
|
967
|
+
senderTransportSession?: bigint;
|
|
968
|
+
}): ReplicationInfoV2ReceiveDiagnostic {
|
|
969
|
+
const state = this._receiveStates.get(properties.peerHash);
|
|
970
|
+
const advertisement = this._localCapabilityAdvertisementsByPeer.get(
|
|
971
|
+
properties.peerHash,
|
|
972
|
+
);
|
|
973
|
+
const session =
|
|
974
|
+
properties.peerSession ??
|
|
975
|
+
state?.peerSession ??
|
|
976
|
+
advertisement?.peerSession;
|
|
977
|
+
const ready = session
|
|
978
|
+
? this._localCapabilityReadyBySession.get(session)
|
|
979
|
+
: undefined;
|
|
980
|
+
let capabilityAdvertisement: ReplicationInfoV2ReceiveDiagnostic["capabilityAdvertisement"] =
|
|
981
|
+
"absent";
|
|
982
|
+
if (ready && ready.peerHash === properties.peerHash) {
|
|
983
|
+
capabilityAdvertisement =
|
|
984
|
+
session !== undefined &&
|
|
985
|
+
this.deps.isPeerSessionCurrent(properties.peerHash, session) &&
|
|
986
|
+
this.deps.isReceiveEpochCurrent(
|
|
987
|
+
properties.peerHash,
|
|
988
|
+
ready.receiveEpoch,
|
|
989
|
+
) &&
|
|
990
|
+
ready.receiverTransportSession ===
|
|
991
|
+
this.deps.getReceiverTransportSession() &&
|
|
992
|
+
(properties.receiveEpoch === undefined ||
|
|
993
|
+
ready.receiveEpoch === properties.receiveEpoch)
|
|
994
|
+
? "ready"
|
|
995
|
+
: "stale";
|
|
996
|
+
} else if (advertisement) {
|
|
997
|
+
capabilityAdvertisement =
|
|
998
|
+
advertisement.peerHash === properties.peerHash &&
|
|
999
|
+
advertisement.peerSession === session &&
|
|
1000
|
+
(properties.receiveEpoch === undefined ||
|
|
1001
|
+
advertisement.receiveEpoch === properties.receiveEpoch) &&
|
|
1002
|
+
this.isLocalCapabilityAdvertisementGenerationCurrent(advertisement)
|
|
1003
|
+
? "advertising"
|
|
1004
|
+
: "stale";
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const rearm = session
|
|
1008
|
+
? this._remoteFullRearmBySession.get(session)
|
|
1009
|
+
: undefined;
|
|
1010
|
+
const now = this.now();
|
|
1011
|
+
const rearmLimited =
|
|
1012
|
+
(rearm?.outstandingAttempts ?? 0) >=
|
|
1013
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION ||
|
|
1014
|
+
this._remoteFullRearmOutstanding.size >=
|
|
1015
|
+
this.maxRemoteFullRearmOutstandingGlobal;
|
|
1016
|
+
const rearmStatus: ReplicationInfoV2ReceiveDiagnostic["remoteFullRearm"] =
|
|
1017
|
+
rearm?.inFlight
|
|
1018
|
+
? "in-flight"
|
|
1019
|
+
: rearmLimited
|
|
1020
|
+
? "limited"
|
|
1021
|
+
: rearm && now < rearm.nextAttemptAt
|
|
1022
|
+
? "cooldown"
|
|
1023
|
+
: "idle";
|
|
1024
|
+
const common = {
|
|
1025
|
+
capabilityAdvertisementAttempts: Math.min(
|
|
1026
|
+
MAX_BACKOFF_EXPONENT + 1,
|
|
1027
|
+
Math.max(0, advertisement?.attempts ?? 0),
|
|
1028
|
+
),
|
|
1029
|
+
capabilityAdvertisement,
|
|
1030
|
+
remoteFullRearm: rearmStatus,
|
|
1031
|
+
remoteFullRearmAttempts: Math.min(
|
|
1032
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
1033
|
+
Math.max(0, rearm?.attemptsStarted ?? 0),
|
|
1034
|
+
),
|
|
1035
|
+
remoteFullRearmOutstanding: Math.min(
|
|
1036
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION,
|
|
1037
|
+
Math.max(0, rearm?.outstandingAttempts ?? 0),
|
|
1038
|
+
),
|
|
1039
|
+
remoteFullRearmGlobalOutstanding: Math.min(
|
|
1040
|
+
this.maxRemoteFullRearmOutstandingGlobal,
|
|
1041
|
+
this._remoteFullRearmOutstanding.size,
|
|
1042
|
+
),
|
|
1043
|
+
...(rearm && now < rearm.nextAttemptAt
|
|
1044
|
+
? {
|
|
1045
|
+
remoteFullRearmCooldownRemainingMs: Math.min(
|
|
1046
|
+
MAX_TIMER_MS,
|
|
1047
|
+
Math.max(0, rearm.nextAttemptAt - now),
|
|
1048
|
+
),
|
|
1049
|
+
}
|
|
1050
|
+
: {}),
|
|
1051
|
+
};
|
|
1052
|
+
if (!state) {
|
|
1053
|
+
return Object.freeze({
|
|
1054
|
+
state: "absent" as const,
|
|
1055
|
+
reason: "state-absent" as const,
|
|
1056
|
+
...common,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
let status: "current" | "stale" = "current";
|
|
1061
|
+
let reason: ReplicationInfoV2ReceiveDiagnostic["reason"];
|
|
1062
|
+
if (
|
|
1063
|
+
properties.peerSession !== undefined &&
|
|
1064
|
+
state.peerSession !== properties.peerSession
|
|
1065
|
+
) {
|
|
1066
|
+
status = "stale";
|
|
1067
|
+
reason = "peer-session-mismatch";
|
|
1068
|
+
} else if (
|
|
1069
|
+
properties.receiveEpoch !== undefined &&
|
|
1070
|
+
state.receiveEpoch !== properties.receiveEpoch
|
|
1071
|
+
) {
|
|
1072
|
+
status = "stale";
|
|
1073
|
+
reason = "receive-epoch-mismatch";
|
|
1074
|
+
} else if (
|
|
1075
|
+
properties.senderTransportSession !== undefined &&
|
|
1076
|
+
state.senderTransportSession !== properties.senderTransportSession
|
|
1077
|
+
) {
|
|
1078
|
+
status = "stale";
|
|
1079
|
+
reason = "transport-session-mismatch";
|
|
1080
|
+
} else if (!this.isStateCurrent(state)) {
|
|
1081
|
+
status = "stale";
|
|
1082
|
+
reason = "state-not-current";
|
|
1083
|
+
} else if (
|
|
1084
|
+
state.reservedAdmission !== undefined ||
|
|
1085
|
+
this._reservedAdmissionsByPeer.has(properties.peerHash)
|
|
1086
|
+
) {
|
|
1087
|
+
reason = "admission-pending";
|
|
1088
|
+
} else if (state.phase === "active") {
|
|
1089
|
+
reason = "active";
|
|
1090
|
+
} else if (state.requestParked) {
|
|
1091
|
+
reason = "request-parked";
|
|
1092
|
+
} else if (state.requestInFlight !== undefined) {
|
|
1093
|
+
reason = "request-in-flight";
|
|
1094
|
+
} else if (state.requestTimer !== undefined) {
|
|
1095
|
+
reason = "request-scheduled";
|
|
1096
|
+
} else {
|
|
1097
|
+
reason = state.phase;
|
|
1098
|
+
}
|
|
1099
|
+
const requestState: NonNullable<
|
|
1100
|
+
ReplicationInfoV2ReceiveDiagnostic["requestState"]
|
|
1101
|
+
> =
|
|
1102
|
+
state.reservedAdmission !== undefined ||
|
|
1103
|
+
this._reservedAdmissionsByPeer.has(properties.peerHash)
|
|
1104
|
+
? "admission-pending"
|
|
1105
|
+
: state.requestParked
|
|
1106
|
+
? "parked"
|
|
1107
|
+
: state.requestInFlight !== undefined
|
|
1108
|
+
? "in-flight"
|
|
1109
|
+
: state.requestTimer !== undefined
|
|
1110
|
+
? "scheduled"
|
|
1111
|
+
: "idle";
|
|
1112
|
+
return Object.freeze({
|
|
1113
|
+
state: status,
|
|
1114
|
+
reason,
|
|
1115
|
+
phase: state.phase,
|
|
1116
|
+
requestState,
|
|
1117
|
+
...(state.lastSequence === undefined
|
|
1118
|
+
? {}
|
|
1119
|
+
: { lastAppliedSequence: state.lastSequence.toString() }),
|
|
1120
|
+
requestAttempts: Math.min(
|
|
1121
|
+
this.requestMaxAttempts,
|
|
1122
|
+
Math.max(0, state.requestAttempts),
|
|
1123
|
+
),
|
|
1124
|
+
...common,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
750
1128
|
private promoteLocalCapabilityAdvertisement(
|
|
751
1129
|
state: ReplicationInfoV2LocalCapabilityAdvertisement,
|
|
752
1130
|
): boolean {
|