@peerbit/shared-log 16.0.24 → 16.0.26
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 +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +147 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +41 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +203 -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 +12 -12
- package/src/exchange-heads.ts +9 -0
- package/src/index.ts +193 -22
- package/src/replication-info-v2-receive.ts +291 -0
- package/src/replication-info-v2-send.ts +16 -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
|
|
|
@@ -112,6 +117,16 @@ export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
|
112
117
|
firstAttempt?: Promise<void>;
|
|
113
118
|
};
|
|
114
119
|
|
|
120
|
+
type ReplicationInfoV2RemoteFullRearm = {
|
|
121
|
+
peerHash: string;
|
|
122
|
+
receiveEpoch: object | null;
|
|
123
|
+
receiverTransportSession: bigint;
|
|
124
|
+
nextAttemptAt: number;
|
|
125
|
+
outstandingAttempts: number;
|
|
126
|
+
inFlight?: Promise<void>;
|
|
127
|
+
controller?: AbortController;
|
|
128
|
+
};
|
|
129
|
+
|
|
115
130
|
export type ReplicationInfoV2LocalCapabilityRefresh = {
|
|
116
131
|
receiverTransportSession: bigint;
|
|
117
132
|
requestNotBeforeMs: number;
|
|
@@ -183,6 +198,7 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
183
198
|
peerSession: object;
|
|
184
199
|
receiveEpoch: object | null;
|
|
185
200
|
signal: AbortSignal;
|
|
201
|
+
requestRemoteFullRearm?: boolean;
|
|
186
202
|
}) => Promise<ReplicationInfoV2LocalCapabilityRefresh | undefined>;
|
|
187
203
|
onRequestError?: (error: unknown) => void;
|
|
188
204
|
onLocalCapabilityError?: (error: unknown) => void;
|
|
@@ -190,6 +206,9 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
190
206
|
requestRetryMs?: number;
|
|
191
207
|
maxRequestRetryMs?: number;
|
|
192
208
|
requestMaxAttempts?: number;
|
|
209
|
+
remoteFullRearmAttemptTimeoutMs?: number;
|
|
210
|
+
remoteFullRearmCooldownMs?: number;
|
|
211
|
+
maxRemoteFullRearmOutstandingGlobal?: number;
|
|
193
212
|
};
|
|
194
213
|
|
|
195
214
|
/**
|
|
@@ -217,12 +236,18 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
217
236
|
string,
|
|
218
237
|
ReplicationInfoV2LocalCapabilityAdvertisement
|
|
219
238
|
>;
|
|
239
|
+
_remoteFullRearmBySession!: WeakMap<object, ReplicationInfoV2RemoteFullRearm>;
|
|
240
|
+
_remoteFullRearmsInFlight!: Set<ReplicationInfoV2RemoteFullRearm>;
|
|
241
|
+
_remoteFullRearmOutstanding!: Set<object>;
|
|
220
242
|
_reservedAdmissionsByPeer!: Map<string, ReplicationInfoV2ReceiveAdmission>;
|
|
221
243
|
|
|
222
244
|
private readonly now: () => number;
|
|
223
245
|
private readonly requestRetryMs: number;
|
|
224
246
|
private readonly maxRequestRetryMs: number;
|
|
225
247
|
private readonly requestMaxAttempts: number;
|
|
248
|
+
private readonly remoteFullRearmAttemptTimeoutMs: number;
|
|
249
|
+
private readonly remoteFullRearmCooldownMs: number;
|
|
250
|
+
private readonly maxRemoteFullRearmOutstandingGlobal: number;
|
|
226
251
|
|
|
227
252
|
constructor(private readonly deps: ReplicationInfoV2ReceiveDeps) {
|
|
228
253
|
this.now = deps.now ?? Date.now;
|
|
@@ -238,11 +263,46 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
238
263
|
1,
|
|
239
264
|
Math.floor(deps.requestMaxAttempts ?? DEFAULT_REQUEST_MAX_ATTEMPTS),
|
|
240
265
|
);
|
|
266
|
+
this.remoteFullRearmAttemptTimeoutMs = Math.max(
|
|
267
|
+
1,
|
|
268
|
+
Math.min(
|
|
269
|
+
MAX_TIMER_MS,
|
|
270
|
+
Math.floor(
|
|
271
|
+
deps.remoteFullRearmAttemptTimeoutMs ??
|
|
272
|
+
DEFAULT_REMOTE_FULL_REARM_ATTEMPT_TIMEOUT_MS,
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
this.remoteFullRearmCooldownMs = Math.max(
|
|
277
|
+
this.requestRetryMs,
|
|
278
|
+
Math.min(
|
|
279
|
+
MAX_TIMER_MS,
|
|
280
|
+
Math.floor(
|
|
281
|
+
deps.remoteFullRearmCooldownMs ??
|
|
282
|
+
DEFAULT_REMOTE_FULL_REARM_COOLDOWN_MS,
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
// The injected value is a test seam that may only tighten this lifetime
|
|
287
|
+
// resource bound, never widen it.
|
|
288
|
+
this.maxRemoteFullRearmOutstandingGlobal = Math.min(
|
|
289
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
290
|
+
Math.max(
|
|
291
|
+
1,
|
|
292
|
+
Math.floor(
|
|
293
|
+
deps.maxRemoteFullRearmOutstandingGlobal ??
|
|
294
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_GLOBAL,
|
|
295
|
+
),
|
|
296
|
+
),
|
|
297
|
+
);
|
|
241
298
|
this._receiveStates = new Map();
|
|
242
299
|
this._cutoverPeerSessions = new WeakSet();
|
|
243
300
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
244
301
|
this._localCapabilityContextBySession = new WeakMap();
|
|
245
302
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
303
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
304
|
+
this._remoteFullRearmsInFlight = new Set();
|
|
305
|
+
this._remoteFullRearmOutstanding = new Set();
|
|
246
306
|
this._reservedAdmissionsByPeer = new Map();
|
|
247
307
|
}
|
|
248
308
|
|
|
@@ -253,10 +313,20 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
253
313
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
254
314
|
this._localCapabilityContextBySession = new WeakMap();
|
|
255
315
|
this._localCapabilityAdvertisementsByPeer = new Map();
|
|
316
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
317
|
+
this._remoteFullRearmsInFlight = new Set();
|
|
256
318
|
this._reservedAdmissionsByPeer = new Map();
|
|
257
319
|
}
|
|
258
320
|
|
|
259
321
|
clearForClose(): void {
|
|
322
|
+
for (const rearm of this._remoteFullRearmsInFlight ?? []) {
|
|
323
|
+
rearm.controller?.abort(
|
|
324
|
+
new Error(
|
|
325
|
+
"Replication-info V2 receiver closed during remote Full rearm",
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
this._remoteFullRearmsInFlight?.clear();
|
|
260
330
|
for (const advertisement of [
|
|
261
331
|
...(this._localCapabilityAdvertisementsByPeer?.values() ?? []),
|
|
262
332
|
]) {
|
|
@@ -270,6 +340,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
270
340
|
this._cutoverPeerSessions = new WeakSet();
|
|
271
341
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
272
342
|
this._localCapabilityContextBySession = new WeakMap();
|
|
343
|
+
this._remoteFullRearmBySession = new WeakMap();
|
|
273
344
|
}
|
|
274
345
|
|
|
275
346
|
clearPeer(peerHash: string, expectedSession?: object): void {
|
|
@@ -288,6 +359,25 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
288
359
|
this._localCapabilityContextBySession.delete(state.peerSession);
|
|
289
360
|
this._cutoverPeerSessions.delete(state.peerSession);
|
|
290
361
|
}
|
|
362
|
+
if (!expectedSession) {
|
|
363
|
+
for (const rearm of this._remoteFullRearmsInFlight) {
|
|
364
|
+
if (rearm.peerHash === peerHash) {
|
|
365
|
+
rearm.controller?.abort(
|
|
366
|
+
new Error("Replication-info V2 peer cleared during rearm"),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const rearmSession =
|
|
372
|
+
expectedSession ?? state?.peerSession ?? advertisement?.peerSession;
|
|
373
|
+
if (rearmSession) {
|
|
374
|
+
this._remoteFullRearmBySession
|
|
375
|
+
.get(rearmSession)
|
|
376
|
+
?.controller?.abort(
|
|
377
|
+
new Error("Replication-info V2 peer session cleared during rearm"),
|
|
378
|
+
);
|
|
379
|
+
this._remoteFullRearmBySession.delete(rearmSession);
|
|
380
|
+
}
|
|
291
381
|
if (expectedSession) {
|
|
292
382
|
this._localCapabilityReadyBySession.delete(expectedSession);
|
|
293
383
|
this._localCapabilityContextBySession.delete(expectedSession);
|
|
@@ -625,6 +715,207 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
625
715
|
};
|
|
626
716
|
}
|
|
627
717
|
|
|
718
|
+
/**
|
|
719
|
+
* Send one transient, authenticated rearm hint for the exact current local
|
|
720
|
+
* receive grant. The hint asks a patched remote receiver to rotate its
|
|
721
|
+
* challenge and issue another Full request, rebuilding an outbound sender
|
|
722
|
+
* stream that was lost while both directions otherwise remained current.
|
|
723
|
+
*
|
|
724
|
+
* This deliberately does not mutate the steady advertisement worker. One
|
|
725
|
+
* attempt is coalesced per exact PeerSession/receive generation and bound to
|
|
726
|
+
* its own deadline as well as the caller's signal. A cooldown avoids rotating
|
|
727
|
+
* a healthy in-flight challenge on every recovery tick. The remote rotates
|
|
728
|
+
* only when its receive state is active; later hints during resync only nudge
|
|
729
|
+
* that same bounded request generation. At most two transports that disregard
|
|
730
|
+
* abort remain outstanding for one exact session (64 across this coordinator's
|
|
731
|
+
* lifetime); reaching either cap stops new hints while the persisted-readiness
|
|
732
|
+
* gate remains fail closed.
|
|
733
|
+
*/
|
|
734
|
+
reAdvertiseLocalCapabilityForRemoteFull(properties: {
|
|
735
|
+
peerHash: string;
|
|
736
|
+
peerSession: object;
|
|
737
|
+
receiveEpoch: object | null;
|
|
738
|
+
signal: AbortSignal;
|
|
739
|
+
}): boolean {
|
|
740
|
+
const context = this._localCapabilityContextBySession.get(
|
|
741
|
+
properties.peerSession,
|
|
742
|
+
);
|
|
743
|
+
const ready = this._localCapabilityReadyBySession.get(
|
|
744
|
+
properties.peerSession,
|
|
745
|
+
);
|
|
746
|
+
if (
|
|
747
|
+
properties.signal.aborted ||
|
|
748
|
+
!context ||
|
|
749
|
+
context.peerHash !== properties.peerHash ||
|
|
750
|
+
context.lifecycleSignal.aborted ||
|
|
751
|
+
this.deps.isClosed() ||
|
|
752
|
+
!this.deps.isPeerStateCurrent(
|
|
753
|
+
properties.peerHash,
|
|
754
|
+
properties.peerSession,
|
|
755
|
+
properties.receiveEpoch,
|
|
756
|
+
) ||
|
|
757
|
+
!ready ||
|
|
758
|
+
ready.peerHash !== properties.peerHash ||
|
|
759
|
+
ready.receiveEpoch !== properties.receiveEpoch ||
|
|
760
|
+
ready.receiverTransportSession !== this.deps.getReceiverTransportSession()
|
|
761
|
+
) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const now = this.now();
|
|
766
|
+
const receiverTransportSession = ready.receiverTransportSession;
|
|
767
|
+
let rearm = this._remoteFullRearmBySession.get(properties.peerSession);
|
|
768
|
+
if (
|
|
769
|
+
rearm &&
|
|
770
|
+
(rearm.peerHash !== properties.peerHash ||
|
|
771
|
+
rearm.receiveEpoch !== properties.receiveEpoch ||
|
|
772
|
+
rearm.receiverTransportSession !== receiverTransportSession)
|
|
773
|
+
) {
|
|
774
|
+
rearm.controller?.abort(
|
|
775
|
+
new Error("Replication-info V2 remote Full rearm generation changed"),
|
|
776
|
+
);
|
|
777
|
+
rearm = undefined;
|
|
778
|
+
}
|
|
779
|
+
if (rearm?.inFlight) {
|
|
780
|
+
return true;
|
|
781
|
+
}
|
|
782
|
+
if (rearm !== undefined && now < rearm.nextAttemptAt) {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
if (
|
|
786
|
+
(rearm?.outstandingAttempts ?? 0) >=
|
|
787
|
+
MAX_REMOTE_FULL_REARM_OUTSTANDING_PER_SESSION ||
|
|
788
|
+
this._remoteFullRearmOutstanding.size >=
|
|
789
|
+
this.maxRemoteFullRearmOutstandingGlobal
|
|
790
|
+
) {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
if (!rearm) {
|
|
794
|
+
rearm = {
|
|
795
|
+
peerHash: properties.peerHash,
|
|
796
|
+
receiveEpoch: properties.receiveEpoch,
|
|
797
|
+
receiverTransportSession,
|
|
798
|
+
nextAttemptAt: now + this.remoteFullRearmCooldownMs,
|
|
799
|
+
outstandingAttempts: 0,
|
|
800
|
+
};
|
|
801
|
+
this._remoteFullRearmBySession.set(properties.peerSession, rearm);
|
|
802
|
+
} else {
|
|
803
|
+
rearm.nextAttemptAt = now + this.remoteFullRearmCooldownMs;
|
|
804
|
+
}
|
|
805
|
+
const attemptState = rearm;
|
|
806
|
+
const outstandingReservation = {};
|
|
807
|
+
attemptState.outstandingAttempts++;
|
|
808
|
+
this._remoteFullRearmOutstanding.add(outstandingReservation);
|
|
809
|
+
|
|
810
|
+
const attemptController = new AbortController();
|
|
811
|
+
const operationSignal = attemptController.signal;
|
|
812
|
+
attemptState.controller = attemptController;
|
|
813
|
+
this._remoteFullRearmsInFlight.add(attemptState);
|
|
814
|
+
const sourceSignals = Array.from(
|
|
815
|
+
new Set([context.lifecycleSignal, properties.signal]),
|
|
816
|
+
);
|
|
817
|
+
const onSourceAbort = (event: Event) => {
|
|
818
|
+
const source = event.currentTarget as AbortSignal;
|
|
819
|
+
attemptController.abort(source.reason);
|
|
820
|
+
};
|
|
821
|
+
for (const source of sourceSignals) {
|
|
822
|
+
source.addEventListener("abort", onSourceAbort, { once: true });
|
|
823
|
+
}
|
|
824
|
+
const attemptTimer = setTimeout(
|
|
825
|
+
() =>
|
|
826
|
+
attemptController.abort(
|
|
827
|
+
new Error("Replication-info V2 remote Full rearm attempt timed out"),
|
|
828
|
+
),
|
|
829
|
+
this.remoteFullRearmAttemptTimeoutMs,
|
|
830
|
+
);
|
|
831
|
+
attemptTimer.unref?.();
|
|
832
|
+
let releasedOutstanding = false;
|
|
833
|
+
const releaseOutstanding = () => {
|
|
834
|
+
if (releasedOutstanding) return;
|
|
835
|
+
releasedOutstanding = true;
|
|
836
|
+
this._remoteFullRearmOutstanding.delete(outstandingReservation);
|
|
837
|
+
attemptState.outstandingAttempts--;
|
|
838
|
+
};
|
|
839
|
+
const refresh = Promise.resolve().then(() => {
|
|
840
|
+
if (operationSignal.aborted) {
|
|
841
|
+
throw operationSignal.reason;
|
|
842
|
+
}
|
|
843
|
+
return this.deps.refreshLocalCapability({
|
|
844
|
+
peerHash: properties.peerHash,
|
|
845
|
+
target: context.target,
|
|
846
|
+
peerSession: properties.peerSession,
|
|
847
|
+
receiveEpoch: properties.receiveEpoch,
|
|
848
|
+
signal: operationSignal,
|
|
849
|
+
requestRemoteFullRearm: true,
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
void refresh.then(releaseOutstanding, releaseOutstanding);
|
|
853
|
+
|
|
854
|
+
let operation: Promise<void>;
|
|
855
|
+
const detach = () => {
|
|
856
|
+
clearTimeout(attemptTimer);
|
|
857
|
+
operationSignal.removeEventListener("abort", detach);
|
|
858
|
+
for (const source of sourceSignals) {
|
|
859
|
+
source.removeEventListener("abort", onSourceAbort);
|
|
860
|
+
}
|
|
861
|
+
if (attemptState.inFlight === operation) {
|
|
862
|
+
this._remoteFullRearmsInFlight.delete(attemptState);
|
|
863
|
+
attemptState.inFlight = undefined;
|
|
864
|
+
attemptState.controller = undefined;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
const raceWithAttemptSignal = <T>(promise: Promise<T>): Promise<T> =>
|
|
868
|
+
new Promise<T>((resolve, reject) => {
|
|
869
|
+
const onAbort = () => {
|
|
870
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
871
|
+
reject(operationSignal.reason);
|
|
872
|
+
};
|
|
873
|
+
operationSignal.addEventListener("abort", onAbort, { once: true });
|
|
874
|
+
if (operationSignal.aborted) {
|
|
875
|
+
onAbort();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
promise.then(
|
|
879
|
+
(value) => {
|
|
880
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
881
|
+
resolve(value);
|
|
882
|
+
},
|
|
883
|
+
(error) => {
|
|
884
|
+
operationSignal.removeEventListener("abort", onAbort);
|
|
885
|
+
reject(error);
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
});
|
|
889
|
+
operation = raceWithAttemptSignal(refresh)
|
|
890
|
+
.then(() => undefined)
|
|
891
|
+
.catch((error) => {
|
|
892
|
+
if (
|
|
893
|
+
!operationSignal.aborted &&
|
|
894
|
+
!this.deps.isClosed() &&
|
|
895
|
+
this.deps.isPeerStateCurrent(
|
|
896
|
+
properties.peerHash,
|
|
897
|
+
properties.peerSession,
|
|
898
|
+
properties.receiveEpoch,
|
|
899
|
+
)
|
|
900
|
+
) {
|
|
901
|
+
this.deps.onLocalCapabilityError?.(error);
|
|
902
|
+
}
|
|
903
|
+
})
|
|
904
|
+
.finally(() => {
|
|
905
|
+
detach();
|
|
906
|
+
});
|
|
907
|
+
attemptState.inFlight = operation;
|
|
908
|
+
operationSignal.addEventListener("abort", detach, { once: true });
|
|
909
|
+
for (const source of sourceSignals) {
|
|
910
|
+
if (source.aborted) {
|
|
911
|
+
attemptController.abort(source.reason);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
void operation;
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
|
|
628
919
|
private promoteLocalCapabilityAdvertisement(
|
|
629
920
|
state: ReplicationInfoV2LocalCapabilityAdvertisement,
|
|
630
921
|
): 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) {
|