@peerbit/shared-log 14.0.0 → 16.0.0
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 +11 -15
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +84 -596
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +6 -110
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +2 -504
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-mutation.d.ts +34 -0
- package/dist/src/replication-info-mutation.d.ts.map +1 -0
- package/dist/src/replication-info-mutation.js +2 -0
- package/dist/src/replication-info-mutation.js.map +1 -0
- package/dist/src/replication-info-v2-receive.d.ts +10 -41
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +11 -192
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +5 -6
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +9 -14
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/replication.d.ts +0 -1
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +2 -17
- package/dist/src/replication.js.map +1 -1
- package/dist/src/sync/factory.d.ts +0 -1
- package/dist/src/sync/factory.d.ts.map +1 -1
- package/dist/src/sync/factory.js +20 -36
- package/dist/src/sync/factory.js.map +1 -1
- package/package.json +10 -10
- package/src/index.ts +115 -816
- package/src/replication-announcement.ts +12 -722
- package/src/replication-info-mutation.ts +34 -0
- package/src/replication-info-v2-receive.ts +17 -263
- package/src/replication-info-v2-send.ts +10 -23
- package/src/replication.ts +1 -20
- package/src/sync/factory.ts +24 -38
package/src/index.ts
CHANGED
|
@@ -231,10 +231,7 @@ import {
|
|
|
231
231
|
shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary,
|
|
232
232
|
toRebalance,
|
|
233
233
|
} from "./ranges.js";
|
|
234
|
-
import {
|
|
235
|
-
ReplicationAnnouncementCoordinator,
|
|
236
|
-
isTransientReplicationAnnouncementError,
|
|
237
|
-
} from "./replication-announcement.js";
|
|
234
|
+
import { ReplicationAnnouncementCoordinator } from "./replication-announcement.js";
|
|
238
235
|
import {
|
|
239
236
|
type ReplicationDomainHash,
|
|
240
237
|
createReplicationDomainHash,
|
|
@@ -249,6 +246,10 @@ import {
|
|
|
249
246
|
type ReplicationDomain,
|
|
250
247
|
type ReplicationDomainConstructor,
|
|
251
248
|
} from "./replication-domain.js";
|
|
249
|
+
import type {
|
|
250
|
+
AddedReplicationInfoMutation,
|
|
251
|
+
FullReplicationInfoMutation,
|
|
252
|
+
} from "./replication-info-mutation.js";
|
|
252
253
|
import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-receive.js";
|
|
253
254
|
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
254
255
|
import {
|
|
@@ -273,7 +274,6 @@ import {
|
|
|
273
274
|
maxReplicas,
|
|
274
275
|
} from "./replication.js";
|
|
275
276
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
276
|
-
import { Observer, Replicator } from "./role.js";
|
|
277
277
|
import { createSyncronizer } from "./sync/factory.js";
|
|
278
278
|
import type {
|
|
279
279
|
SharedLogNativeWireSync,
|
|
@@ -2074,34 +2074,26 @@ export class SharedLog<
|
|
|
2074
2074
|
// public key hash to range id to range
|
|
2075
2075
|
pendingMaturity!: Map<string, Map<string, PendingMaturityRecord<R>>>; // map of peerId to timeout
|
|
2076
2076
|
|
|
2077
|
-
//
|
|
2078
|
-
// FENCING role
|
|
2079
|
-
//
|
|
2080
|
-
// the
|
|
2081
|
-
//
|
|
2082
|
-
//
|
|
2083
|
-
// NOT subsumed: within one (lifecycle, session, epoch, unblocked) regime
|
|
2084
|
-
// the epoch token is constant across every message from the peer, so only
|
|
2085
|
-
// the two apply-lane timestamp comparisons can drop an older reset
|
|
2086
|
-
// delivered after a newer add (unordered pubsub / retransmits) — an
|
|
2087
|
-
// identity token carries no order. Deletion is blocked until
|
|
2088
|
-
// replication-info messages carry sender-authoritative sequence numbers
|
|
2089
|
-
// (stage-5 schema change); the `receive admission replication-info
|
|
2090
|
-
// ordering watermark` pins fail if the read sites are removed before then.
|
|
2091
|
-
private latestReplicationInfoMessage!: Map<string, bigint>;
|
|
2077
|
+
// The legacy replication-info ordering watermark (fence B8) is deleted:
|
|
2078
|
+
// its FENCING role was subsumed by the per-peer receive epoch, blocked set
|
|
2079
|
+
// and session identity, and its intra-epoch ORDERING role existed only for
|
|
2080
|
+
// the legacy apply lanes. The V2 lane orders by sender-authoritative
|
|
2081
|
+
// sequence numbers, and legacy frames are dropped unconditionally at the
|
|
2082
|
+
// B1 gate before any side effect.
|
|
2092
2083
|
// The replication-info blocked set (fence B5) lives on the peer-session
|
|
2093
2084
|
// registry: unsubscribed peers whose replication-info is ignored until a
|
|
2094
2085
|
// reconnect barrier commits. See PeerSessionRegistry._replicationInfoBlockedPeers.
|
|
2086
|
+
// V2 recovery scheduler state, one row per open peer session (B12: the
|
|
2087
|
+
// legacy request scheduler that shared this map is deleted).
|
|
2095
2088
|
private _replicationInfoRequestByPeer!: Map<
|
|
2096
2089
|
string,
|
|
2097
2090
|
{
|
|
2098
|
-
//
|
|
2099
|
-
//
|
|
2100
|
-
// for the next unpark delay, reset on any applied V2 progress.
|
|
2091
|
+
// Consecutive fruitless unparks — the escalation exponent for the
|
|
2092
|
+
// next unpark delay, reset on any applied V2 progress.
|
|
2101
2093
|
attempts: number;
|
|
2102
2094
|
timer?: ReturnType<typeof setTimeout>;
|
|
2103
|
-
peerSession
|
|
2104
|
-
//
|
|
2095
|
+
peerSession: PeerSession;
|
|
2096
|
+
// When the current park was first observed.
|
|
2105
2097
|
parkedSinceMs?: number;
|
|
2106
2098
|
}
|
|
2107
2099
|
>;
|
|
@@ -2262,7 +2254,6 @@ export class SharedLog<
|
|
|
2262
2254
|
for (const hash of this._checkedPrune?.retries.keys() ?? []) {
|
|
2263
2255
|
this._checkedPrune.clearRetry(hash);
|
|
2264
2256
|
}
|
|
2265
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
2266
2257
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
2267
2258
|
for (const timer of this._repairRetryTimers) {
|
|
2268
2259
|
clearTimeout(timer);
|
|
@@ -3264,7 +3255,7 @@ export class SharedLog<
|
|
|
3264
3255
|
private rebalanceParticipationDebounced:
|
|
3265
3256
|
| ReturnType<typeof debounceFixedInterval>
|
|
3266
3257
|
| undefined;
|
|
3267
|
-
private _announcements!: ReplicationAnnouncementCoordinator
|
|
3258
|
+
private _announcements!: ReplicationAnnouncementCoordinator;
|
|
3268
3259
|
private _v2Receive!: ReplicationInfoV2ReceiveCoordinator;
|
|
3269
3260
|
private _v2Send!: ReplicationInfoV2SendCoordinator<R>;
|
|
3270
3261
|
|
|
@@ -3392,37 +3383,13 @@ export class SharedLog<
|
|
|
3392
3383
|
});
|
|
3393
3384
|
}
|
|
3394
3385
|
|
|
3395
|
-
private createReplicationAnnouncementCoordinator(): ReplicationAnnouncementCoordinator
|
|
3396
|
-
return new ReplicationAnnouncementCoordinator
|
|
3397
|
-
// Route re-entrant queueing through the owner so coordinator spies keep
|
|
3398
|
-
// observing it (the poison guard assertions in events.spec.ts depend on
|
|
3399
|
-
// this).
|
|
3400
|
-
queueCurrentReplicationStateAnnouncementRepair: () =>
|
|
3401
|
-
this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
3402
|
-
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) =>
|
|
3403
|
-
this._announcements.queueCurrentReplicationStateAnnouncementRetry(
|
|
3404
|
-
error,
|
|
3405
|
-
),
|
|
3386
|
+
private createReplicationAnnouncementCoordinator(): ReplicationAnnouncementCoordinator {
|
|
3387
|
+
return new ReplicationAnnouncementCoordinator({
|
|
3406
3388
|
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
3407
|
-
isLegacyReplicationInfoEnabled: () => this.legacyReplicationInfoEnabled,
|
|
3408
|
-
isClosed: () => this.closed,
|
|
3409
|
-
getCloseSignal: () => this._closeController.signal,
|
|
3410
|
-
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
3411
|
-
validatePersistedReplicationRangeSnapshot: (ranges) =>
|
|
3412
|
-
this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
3413
|
-
getSubscribers: () =>
|
|
3414
|
-
this.node.services.pubsub.getSubscribers(this.topic),
|
|
3415
|
-
getSelfHash: () => this.node.identity.publicKey.hashcode(),
|
|
3416
|
-
isBlockedPeer: (hash) =>
|
|
3417
|
-
this._peerSessions.isReplicationInfoBlocked(hash),
|
|
3418
|
-
getRpc: () => this.rpc,
|
|
3419
3389
|
captureReplicationOwnershipLifecycle: () =>
|
|
3420
3390
|
this.captureReplicationOwnershipLifecycle(),
|
|
3421
3391
|
throwIfReplicationOwnershipLifecycleInactive: (controller) =>
|
|
3422
3392
|
this.throwIfReplicationOwnershipLifecycleInactive(controller),
|
|
3423
|
-
isAdaptiveReplicating: () => this._isAdaptiveReplicating,
|
|
3424
|
-
callRebalanceParticipationDebounced: () =>
|
|
3425
|
-
this.rebalanceParticipationDebounced?.call(),
|
|
3426
3393
|
});
|
|
3427
3394
|
}
|
|
3428
3395
|
|
|
@@ -3649,7 +3616,6 @@ export class SharedLog<
|
|
|
3649
3616
|
this._admittedPruneRemoves = new Set();
|
|
3650
3617
|
this._pendingIHave = new Map();
|
|
3651
3618
|
this._pendingIHaveCallbacks = new Set();
|
|
3652
|
-
this.latestReplicationInfoMessage = new Map();
|
|
3653
3619
|
this._replicationInfoRequestByPeer = new Map();
|
|
3654
3620
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
3655
3621
|
this._replicationInfoApplyQueueByPeer = new Map();
|
|
@@ -3760,29 +3726,10 @@ export class SharedLog<
|
|
|
3760
3726
|
this._nativeStrictDurableTransactionsClosing ??= false;
|
|
3761
3727
|
}
|
|
3762
3728
|
|
|
3763
|
-
get compatibility(): number | undefined {
|
|
3764
|
-
// B12: the open option was removed and any defined value rejects at
|
|
3765
|
-
// open(); this is permanently undefined and dies with the residual
|
|
3766
|
-
// gates in a later cleanup stage.
|
|
3767
|
-
return (this._logProperties as any)?.compatibility;
|
|
3768
|
-
}
|
|
3769
|
-
|
|
3770
|
-
/**
|
|
3771
|
-
* Legacy replication-info is an explicit compatibility fallback. Current
|
|
3772
|
-
* logs never infer or re-enable it from a remote peer's capabilities.
|
|
3773
|
-
*/
|
|
3774
|
-
private get legacyReplicationInfoEnabled(): boolean {
|
|
3775
|
-
return this.compatibility !== undefined && this.compatibility < 10;
|
|
3776
|
-
}
|
|
3777
|
-
|
|
3778
3729
|
get isAdaptiveReplicating() {
|
|
3779
3730
|
return this._isAdaptiveReplicating;
|
|
3780
3731
|
}
|
|
3781
3732
|
|
|
3782
|
-
private get v8Behaviour() {
|
|
3783
|
-
return (this.compatibility ?? Number.MAX_VALUE) < 9;
|
|
3784
|
-
}
|
|
3785
|
-
|
|
3786
3733
|
private getFanoutChannelOptions(
|
|
3787
3734
|
options?: SharedLogFanoutOptions,
|
|
3788
3735
|
): Omit<FanoutTreeChannelOptions, "role"> {
|
|
@@ -4687,8 +4634,7 @@ export class SharedLog<
|
|
|
4687
4634
|
(leaders.size === 0 || (leaders.size === 1 && leaders.has(selfHash)))
|
|
4688
4635
|
) {
|
|
4689
4636
|
const allowSubscriberFallback =
|
|
4690
|
-
this.syncronizer instanceof SimpleSyncronizer
|
|
4691
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
4637
|
+
this.syncronizer instanceof SimpleSyncronizer;
|
|
4692
4638
|
if (!allowSubscriberFallback) {
|
|
4693
4639
|
return;
|
|
4694
4640
|
}
|
|
@@ -4837,8 +4783,7 @@ export class SharedLog<
|
|
|
4837
4783
|
const set = new Set(leaders.keys());
|
|
4838
4784
|
let hasRemotePeers = set.has(selfHash) ? set.size > 1 : set.size > 0;
|
|
4839
4785
|
const allowSubscriberFallback =
|
|
4840
|
-
this.syncronizer instanceof SimpleSyncronizer
|
|
4841
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
4786
|
+
this.syncronizer instanceof SimpleSyncronizer;
|
|
4842
4787
|
if (!hasRemotePeers && allowSubscriberFallback) {
|
|
4843
4788
|
try {
|
|
4844
4789
|
const subscribers = await this._getTopicSubscribers(this.topic);
|
|
@@ -5301,28 +5246,6 @@ export class SharedLog<
|
|
|
5301
5246
|
};
|
|
5302
5247
|
}
|
|
5303
5248
|
|
|
5304
|
-
// @deprecated
|
|
5305
|
-
private getRoleFromReplicationSegments(
|
|
5306
|
-
segments: ReplicationRangeIndexable<R>[],
|
|
5307
|
-
) {
|
|
5308
|
-
if (segments.length > 1) {
|
|
5309
|
-
throw new Error(
|
|
5310
|
-
"More than one replication segment found. Can only use one segment for compatbility with v8",
|
|
5311
|
-
);
|
|
5312
|
-
}
|
|
5313
|
-
|
|
5314
|
-
if (segments.length > 0) {
|
|
5315
|
-
const segment = segments[0].toReplicationRange();
|
|
5316
|
-
return new Replicator({
|
|
5317
|
-
factor: (segment.factor as number) / MAX_U32,
|
|
5318
|
-
offset: (segment.offset as number) / MAX_U32,
|
|
5319
|
-
});
|
|
5320
|
-
}
|
|
5321
|
-
|
|
5322
|
-
// TODO this is not accurate but might be good enough
|
|
5323
|
-
return new Observer();
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
5249
|
private isTerminating() {
|
|
5327
5250
|
return (
|
|
5328
5251
|
this.acceptsParentAttachments === false ||
|
|
@@ -5600,12 +5523,7 @@ export class SharedLog<
|
|
|
5600
5523
|
}
|
|
5601
5524
|
|
|
5602
5525
|
private onRebalanceParticipationError(error: Error): void {
|
|
5603
|
-
if (
|
|
5604
|
-
this.closed ||
|
|
5605
|
-
isNotStartedError(error) ||
|
|
5606
|
-
(isTransientReplicationAnnouncementError(error) &&
|
|
5607
|
-
this._announcements._replicationAnnouncementRetryPending)
|
|
5608
|
-
) {
|
|
5526
|
+
if (this.closed || isNotStartedError(error)) {
|
|
5609
5527
|
return;
|
|
5610
5528
|
}
|
|
5611
5529
|
|
|
@@ -5714,7 +5632,7 @@ export class SharedLog<
|
|
|
5714
5632
|
mergeSegments?: boolean;
|
|
5715
5633
|
rebalance?: boolean;
|
|
5716
5634
|
announce?: (
|
|
5717
|
-
msg:
|
|
5635
|
+
msg: AddedReplicationInfoMutation | FullReplicationInfoMutation,
|
|
5718
5636
|
) => void;
|
|
5719
5637
|
} = {},
|
|
5720
5638
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
@@ -5950,9 +5868,11 @@ export class SharedLog<
|
|
|
5950
5868
|
|
|
5951
5869
|
if (confirmedPreliminaryRemovals.length > 0) {
|
|
5952
5870
|
await this._announcements.sendReplicationAnnouncement(
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5871
|
+
{
|
|
5872
|
+
stopped: {
|
|
5873
|
+
segmentIds: confirmedPreliminaryRemovals.map((x) => x.id),
|
|
5874
|
+
},
|
|
5875
|
+
},
|
|
5956
5876
|
replicationOwnershipLifecycleController,
|
|
5957
5877
|
);
|
|
5958
5878
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -5977,7 +5897,7 @@ export class SharedLog<
|
|
|
5977
5897
|
);
|
|
5978
5898
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
5979
5899
|
await this._announcements.sendReplicationAnnouncement(
|
|
5980
|
-
|
|
5900
|
+
{ full: { segments } },
|
|
5981
5901
|
replicationOwnershipLifecycleController,
|
|
5982
5902
|
);
|
|
5983
5903
|
} catch (error) {
|
|
@@ -6033,7 +5953,7 @@ export class SharedLog<
|
|
|
6033
5953
|
rebalance?: boolean;
|
|
6034
5954
|
mergeSegments?: boolean;
|
|
6035
5955
|
announce?: (
|
|
6036
|
-
msg:
|
|
5956
|
+
msg: FullReplicationInfoMutation | AddedReplicationInfoMutation,
|
|
6037
5957
|
) => void;
|
|
6038
5958
|
},
|
|
6039
5959
|
) {
|
|
@@ -6274,7 +6194,7 @@ export class SharedLog<
|
|
|
6274
6194
|
}
|
|
6275
6195
|
try {
|
|
6276
6196
|
await this._announcements.sendReplicationAnnouncement(
|
|
6277
|
-
|
|
6197
|
+
{ stopped: { segmentIds: removedSegmentIds } },
|
|
6278
6198
|
replicationOwnershipLifecycleController,
|
|
6279
6199
|
);
|
|
6280
6200
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -6571,7 +6491,7 @@ export class SharedLog<
|
|
|
6571
6491
|
if (isMe && !ownerHasRanges) {
|
|
6572
6492
|
try {
|
|
6573
6493
|
await this._announcements.sendReplicationAnnouncement(
|
|
6574
|
-
|
|
6494
|
+
{ full: { segments: [] } },
|
|
6575
6495
|
replicationOwnershipLifecycleController,
|
|
6576
6496
|
);
|
|
6577
6497
|
} catch (error) {
|
|
@@ -7494,7 +7414,7 @@ export class SharedLog<
|
|
|
7494
7414
|
rebalance?: boolean;
|
|
7495
7415
|
shouldApply?: () => boolean;
|
|
7496
7416
|
announce?: (
|
|
7497
|
-
msg:
|
|
7417
|
+
msg: FullReplicationInfoMutation | AddedReplicationInfoMutation,
|
|
7498
7418
|
) => void;
|
|
7499
7419
|
} = {},
|
|
7500
7420
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
@@ -7541,7 +7461,7 @@ export class SharedLog<
|
|
|
7541
7461
|
);
|
|
7542
7462
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
7543
7463
|
await this._announcements.sendReplicationAnnouncement(
|
|
7544
|
-
|
|
7464
|
+
{ full: { segments } },
|
|
7545
7465
|
replicationOwnershipLifecycleController,
|
|
7546
7466
|
);
|
|
7547
7467
|
} catch (error) {
|
|
@@ -7592,17 +7512,17 @@ export class SharedLog<
|
|
|
7592
7512
|
}
|
|
7593
7513
|
|
|
7594
7514
|
let message:
|
|
7595
|
-
|
|
|
7596
|
-
|
|
|
7515
|
+
| FullReplicationInfoMutation
|
|
7516
|
+
| AddedReplicationInfoMutation
|
|
7597
7517
|
| undefined = undefined;
|
|
7598
7518
|
if (options.reset) {
|
|
7599
|
-
message =
|
|
7600
|
-
segments: added.map((x) => x.range.toReplicationRange()),
|
|
7601
|
-
}
|
|
7519
|
+
message = {
|
|
7520
|
+
full: { segments: added.map((x) => x.range.toReplicationRange()) },
|
|
7521
|
+
};
|
|
7602
7522
|
} else {
|
|
7603
|
-
message =
|
|
7604
|
-
segments: added.map((x) => x.range.toReplicationRange()),
|
|
7605
|
-
}
|
|
7523
|
+
message = {
|
|
7524
|
+
added: { segments: added.map((x) => x.range.toReplicationRange()) },
|
|
7525
|
+
};
|
|
7606
7526
|
}
|
|
7607
7527
|
if (options.announce) {
|
|
7608
7528
|
return options.announce(message);
|
|
@@ -14241,12 +14161,7 @@ export class SharedLog<
|
|
|
14241
14161
|
|
|
14242
14162
|
this.domain = options?.domain
|
|
14243
14163
|
? (options.domain(this) as unknown as D)
|
|
14244
|
-
: (createReplicationDomainHash(
|
|
14245
|
-
(options as any)?.compatibility !== undefined &&
|
|
14246
|
-
(options as any).compatibility < 10
|
|
14247
|
-
? "u32"
|
|
14248
|
-
: "u64",
|
|
14249
|
-
)(this) as unknown as D);
|
|
14164
|
+
: (createReplicationDomainHash("u64")(this) as unknown as D);
|
|
14250
14165
|
this.indexableDomain = createIndexableDomainFromResolution(
|
|
14251
14166
|
this.domain.resolution,
|
|
14252
14167
|
);
|
|
@@ -14256,7 +14171,6 @@ export class SharedLog<
|
|
|
14256
14171
|
this._admittedPruneRemoves = new Set();
|
|
14257
14172
|
this._pendingIHave = new Map();
|
|
14258
14173
|
this._pendingIHaveCallbacks = new Set();
|
|
14259
|
-
this.latestReplicationInfoMessage = new Map();
|
|
14260
14174
|
this._replicationInfoRequestByPeer = new Map();
|
|
14261
14175
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
14262
14176
|
// Terminal close/drop drains the previous lifecycle before another open can
|
|
@@ -14329,7 +14243,6 @@ export class SharedLog<
|
|
|
14329
14243
|
this._liveness.resetForOpen();
|
|
14330
14244
|
this._lastLocalAppendAt = 0;
|
|
14331
14245
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
14332
|
-
this._announcements.resetForOpen();
|
|
14333
14246
|
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
14334
14247
|
this._v2Receive.resetForOpen();
|
|
14335
14248
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
@@ -14392,12 +14305,6 @@ export class SharedLog<
|
|
|
14392
14305
|
}
|
|
14393
14306
|
|
|
14394
14307
|
this._closeController = new AbortController();
|
|
14395
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
14396
|
-
this._announcements.setupReplicationAnnouncementRetryFunction();
|
|
14397
|
-
this._announcements.setupReplicationAnnouncementRepairFunction();
|
|
14398
|
-
} else {
|
|
14399
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
14400
|
-
}
|
|
14401
14308
|
this._closeController.signal.addEventListener("abort", () => {
|
|
14402
14309
|
for (const [_peer, state] of this._replicationInfoRequestByPeer) {
|
|
14403
14310
|
if (state.timer) clearTimeout(state.timer);
|
|
@@ -14853,7 +14760,6 @@ export class SharedLog<
|
|
|
14853
14760
|
sendOptions?: { priority?: number; signal?: AbortSignal },
|
|
14854
14761
|
) => this.trySendFusedRawExchangeHeads(hashes, to, sendOptions),
|
|
14855
14762
|
warn,
|
|
14856
|
-
compatibility: (this._logProperties as any)?.compatibility,
|
|
14857
14763
|
resolution: this.domain.resolution,
|
|
14858
14764
|
sync: options?.sync,
|
|
14859
14765
|
syncronizer: options?.syncronizer,
|
|
@@ -15857,7 +15763,7 @@ export class SharedLog<
|
|
|
15857
15763
|
const peerSession = this._peerSessions.current(peerHash);
|
|
15858
15764
|
const preserveV2Session =
|
|
15859
15765
|
peerSession?.phase === "open" && peerSession.isActive();
|
|
15860
|
-
if (
|
|
15766
|
+
if (!preserveV2Session) {
|
|
15861
15767
|
this.cancelReplicationInfoRequests(peerHash);
|
|
15862
15768
|
}
|
|
15863
15769
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
@@ -15889,11 +15795,8 @@ export class SharedLog<
|
|
|
15889
15795
|
|
|
15890
15796
|
private advanceReplicationInfoRecoveryEpoch(peerHash: string) {
|
|
15891
15797
|
// Handlers admitted before a successful peer removal must not restore state
|
|
15892
|
-
// when they eventually reach the apply lane.
|
|
15893
|
-
// ordering watermark with the local epoch so a later arrival can be
|
|
15894
|
-
// accepted without comparing its clock to this receiver's clock.
|
|
15798
|
+
// when they eventually reach the apply lane.
|
|
15895
15799
|
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
15896
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
15897
15800
|
const peerSession = this._peerSessions.current(peerHash);
|
|
15898
15801
|
if (peerSession?.phase === "open") {
|
|
15899
15802
|
this._v2Receive.advanceRecovery({
|
|
@@ -16782,15 +16685,6 @@ export class SharedLog<
|
|
|
16782
16685
|
firstError ??= error;
|
|
16783
16686
|
}
|
|
16784
16687
|
};
|
|
16785
|
-
// A borsh-deserialized instance that is closed before ever being opened
|
|
16786
|
-
// has no coordinators (they are created in open()); the old inline code
|
|
16787
|
-
// only touched plain fields here and never threw.
|
|
16788
|
-
captureSync(() =>
|
|
16789
|
-
this._announcements?.cancelCurrentReplicationStateAnnouncementRetry(),
|
|
16790
|
-
);
|
|
16791
|
-
if (this._announcements) {
|
|
16792
|
-
this._announcements.replicationAnnouncementRetryDebounced = undefined;
|
|
16793
|
-
}
|
|
16794
16688
|
captureSync(() => {
|
|
16795
16689
|
if (this._wireSyncSession) {
|
|
16796
16690
|
this._wireSyncSession.unregisterTopic(this.topic);
|
|
@@ -16884,7 +16778,6 @@ export class SharedLog<
|
|
|
16884
16778
|
captureSync(() => {
|
|
16885
16779
|
this._pendingIHave?.clear();
|
|
16886
16780
|
this._pendingIHaveCallbacks?.clear();
|
|
16887
|
-
this.latestReplicationInfoMessage?.clear();
|
|
16888
16781
|
this._peerSessions?.clearReceiveEpochsForClose();
|
|
16889
16782
|
this._peerSessions?.clearCleanupGatesForClose();
|
|
16890
16783
|
this._activeReceiveHandlersByPeer?.clear();
|
|
@@ -16985,7 +16878,6 @@ export class SharedLog<
|
|
|
16985
16878
|
await pruneRemoveTerminalFence.drained;
|
|
16986
16879
|
await this.drainPendingIHaveCallbacks();
|
|
16987
16880
|
this.ensureNativeDurabilityRuntimeState();
|
|
16988
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
16989
16881
|
} catch (error) {
|
|
16990
16882
|
// The terminal preamble has already disabled parent attachments and the
|
|
16991
16883
|
// network lifecycle. Keep mutation admission fenced for an exact retry.
|
|
@@ -17029,19 +16921,7 @@ export class SharedLog<
|
|
|
17029
16921
|
}
|
|
17030
16922
|
}, 2_000);
|
|
17031
16923
|
try {
|
|
17032
|
-
|
|
17033
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
17034
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
17035
|
-
resets.push(
|
|
17036
|
-
this.rpc
|
|
17037
|
-
.send(reset, {
|
|
17038
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
17039
|
-
signal: abort.signal,
|
|
17040
|
-
})
|
|
17041
|
-
.catch(() => {}),
|
|
17042
|
-
);
|
|
17043
|
-
}
|
|
17044
|
-
await Promise.all(resets);
|
|
16924
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
17045
16925
|
} finally {
|
|
17046
16926
|
clearTimeout(abortTimer);
|
|
17047
16927
|
}
|
|
@@ -17134,7 +17014,6 @@ export class SharedLog<
|
|
|
17134
17014
|
await replicationRangeTerminalFence.drained;
|
|
17135
17015
|
await pruneRemoveTerminalFence.drained;
|
|
17136
17016
|
await this.drainPendingIHaveCallbacks();
|
|
17137
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
17138
17017
|
} catch (error) {
|
|
17139
17018
|
// The terminal preamble is not safely reversible. Preserve the fence until
|
|
17140
17019
|
// a retry finishes cleanup.
|
|
@@ -17162,19 +17041,7 @@ export class SharedLog<
|
|
|
17162
17041
|
}
|
|
17163
17042
|
}, 2_000);
|
|
17164
17043
|
try {
|
|
17165
|
-
|
|
17166
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
17167
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
17168
|
-
resets.push(
|
|
17169
|
-
this.rpc
|
|
17170
|
-
.send(reset, {
|
|
17171
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
17172
|
-
signal: abort.signal,
|
|
17173
|
-
})
|
|
17174
|
-
.catch(() => {}),
|
|
17175
|
-
);
|
|
17176
|
-
}
|
|
17177
|
-
await Promise.all(resets);
|
|
17044
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
17178
17045
|
} finally {
|
|
17179
17046
|
clearTimeout(abortTimer);
|
|
17180
17047
|
}
|
|
@@ -17701,16 +17568,16 @@ export class SharedLog<
|
|
|
17701
17568
|
throw new Error("Missing from in update role message");
|
|
17702
17569
|
}
|
|
17703
17570
|
if (
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
msg instanceof StoppedReplicating)
|
|
17571
|
+
msg instanceof RequestReplicationInfoMessage ||
|
|
17572
|
+
msg instanceof ResponseRoleMessage ||
|
|
17573
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
17574
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
17575
|
+
msg instanceof StoppedReplicating
|
|
17710
17576
|
) {
|
|
17711
|
-
// These variants remain registered decode tombstones, but
|
|
17712
|
-
//
|
|
17713
|
-
//
|
|
17577
|
+
// These variants remain registered decode tombstones, but logs fail
|
|
17578
|
+
// closed unconditionally before leases, synchronizer work, liveness,
|
|
17579
|
+
// watermarks or mutations (B12: the compatibility opens that once
|
|
17580
|
+
// admitted them reject at open()).
|
|
17714
17581
|
return;
|
|
17715
17582
|
}
|
|
17716
17583
|
// Snapshot receive ownership before any async handler gets a chance to
|
|
@@ -17751,22 +17618,14 @@ export class SharedLog<
|
|
|
17751
17618
|
this.captureReplicationOwnershipLifecycle();
|
|
17752
17619
|
const receiveReplicationInfoReceiveEpoch =
|
|
17753
17620
|
this._peerSessions.receiveEpoch(receiveFromHash);
|
|
17754
|
-
if (msg instanceof ResponseRoleMessage) {
|
|
17755
|
-
msg = msg.toReplicationInfoMessage(); // migration
|
|
17756
|
-
}
|
|
17757
17621
|
if (
|
|
17758
|
-
msg instanceof AllReplicatingSegmentsMessage ||
|
|
17759
|
-
msg instanceof AddedReplicationSegmentMessage ||
|
|
17760
17622
|
msg instanceof FullReplicationInfoV2Message ||
|
|
17761
17623
|
msg instanceof AddedReplicationInfoV2Message
|
|
17762
17624
|
) {
|
|
17763
17625
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
17764
17626
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
17765
17627
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
17766
|
-
} else if (
|
|
17767
|
-
msg instanceof StoppedReplicating ||
|
|
17768
|
-
msg instanceof StoppedReplicationInfoV2Message
|
|
17769
|
-
) {
|
|
17628
|
+
} else if (msg instanceof StoppedReplicationInfoV2Message) {
|
|
17770
17629
|
// Bound the raw decoded vector before deduplication can hide the
|
|
17771
17630
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
17772
17631
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
@@ -17774,10 +17633,7 @@ export class SharedLog<
|
|
|
17774
17633
|
if (
|
|
17775
17634
|
!context.from.equals(this.node.identity.publicKey) &&
|
|
17776
17635
|
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
17777
|
-
!isReplicationInfoV2Message(msg)
|
|
17778
|
-
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
17779
|
-
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
17780
|
-
!(msg instanceof StoppedReplicating)
|
|
17636
|
+
!isReplicationInfoV2Message(msg)
|
|
17781
17637
|
) {
|
|
17782
17638
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
17783
17639
|
}
|
|
@@ -19492,23 +19348,6 @@ export class SharedLog<
|
|
|
19492
19348
|
});
|
|
19493
19349
|
} else if (msg instanceof ReplicationPingMessage) {
|
|
19494
19350
|
// No-op: used as an ACKed unicast liveness probe.
|
|
19495
|
-
} else if (msg instanceof RequestReplicationInfoMessage) {
|
|
19496
|
-
await this.handleRequestReplicationInfo(
|
|
19497
|
-
msg,
|
|
19498
|
-
laneRequestContext,
|
|
19499
|
-
lane,
|
|
19500
|
-
);
|
|
19501
|
-
} else if (
|
|
19502
|
-
msg instanceof AllReplicatingSegmentsMessage ||
|
|
19503
|
-
msg instanceof AddedReplicationSegmentMessage
|
|
19504
|
-
) {
|
|
19505
|
-
await this.handleReplicationInfoAnnouncement(
|
|
19506
|
-
msg,
|
|
19507
|
-
laneRequestContext,
|
|
19508
|
-
lane,
|
|
19509
|
-
);
|
|
19510
|
-
} else if (msg instanceof StoppedReplicating) {
|
|
19511
|
-
await this.handleStoppedReplicating(msg, laneRequestContext, lane);
|
|
19512
19351
|
} else {
|
|
19513
19352
|
throw new Error("Unexpected message");
|
|
19514
19353
|
}
|
|
@@ -19737,113 +19576,6 @@ export class SharedLog<
|
|
|
19737
19576
|
}
|
|
19738
19577
|
}
|
|
19739
19578
|
|
|
19740
|
-
private async handleRequestReplicationInfo(
|
|
19741
|
-
_msg: RequestReplicationInfoMessage,
|
|
19742
|
-
context: ReceiveRequestContext,
|
|
19743
|
-
lane: ReceiveLaneContext,
|
|
19744
|
-
): Promise<void> {
|
|
19745
|
-
const receiveFromHash = lane.fromHash;
|
|
19746
|
-
const receiveSession = lane.session;
|
|
19747
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
19748
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
19749
|
-
return;
|
|
19750
|
-
}
|
|
19751
|
-
const replicationLifecycleController =
|
|
19752
|
-
receiveReplicationLifecycleController;
|
|
19753
|
-
if (
|
|
19754
|
-
!replicationLifecycleController ||
|
|
19755
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19756
|
-
receiveFromHash,
|
|
19757
|
-
receiveSession,
|
|
19758
|
-
replicationLifecycleController,
|
|
19759
|
-
)
|
|
19760
|
-
) {
|
|
19761
|
-
return;
|
|
19762
|
-
}
|
|
19763
|
-
|
|
19764
|
-
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
19765
|
-
try {
|
|
19766
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
19767
|
-
} catch (error) {
|
|
19768
|
-
if (
|
|
19769
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19770
|
-
receiveFromHash,
|
|
19771
|
-
receiveSession,
|
|
19772
|
-
replicationLifecycleController,
|
|
19773
|
-
) &&
|
|
19774
|
-
isNotStartedError(error as Error)
|
|
19775
|
-
) {
|
|
19776
|
-
return;
|
|
19777
|
-
}
|
|
19778
|
-
throw error;
|
|
19779
|
-
}
|
|
19780
|
-
if (
|
|
19781
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19782
|
-
receiveFromHash,
|
|
19783
|
-
receiveSession,
|
|
19784
|
-
replicationLifecycleController,
|
|
19785
|
-
)
|
|
19786
|
-
) {
|
|
19787
|
-
return;
|
|
19788
|
-
}
|
|
19789
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
19790
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
19791
|
-
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
19792
|
-
|
|
19793
|
-
await this.rpc
|
|
19794
|
-
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
19795
|
-
mode: new AcknowledgeDelivery({
|
|
19796
|
-
to: [context.from],
|
|
19797
|
-
redundancy: 1,
|
|
19798
|
-
}),
|
|
19799
|
-
signal: replicationLifecycleController.signal,
|
|
19800
|
-
})
|
|
19801
|
-
.catch((error) =>
|
|
19802
|
-
this.handleReplicationLifecycleSendError(
|
|
19803
|
-
error,
|
|
19804
|
-
replicationLifecycleController,
|
|
19805
|
-
),
|
|
19806
|
-
);
|
|
19807
|
-
if (
|
|
19808
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19809
|
-
receiveFromHash,
|
|
19810
|
-
receiveSession,
|
|
19811
|
-
replicationLifecycleController,
|
|
19812
|
-
)
|
|
19813
|
-
) {
|
|
19814
|
-
return;
|
|
19815
|
-
}
|
|
19816
|
-
|
|
19817
|
-
// for backwards compatibility (v8) remove this when we are sure that all nodes are v9+
|
|
19818
|
-
if (this.v8Behaviour) {
|
|
19819
|
-
const role = this.getRoleFromReplicationSegments(replicationSegments);
|
|
19820
|
-
if (role instanceof Replicator) {
|
|
19821
|
-
const fixedSettings = !this._isAdaptiveReplicating;
|
|
19822
|
-
if (fixedSettings) {
|
|
19823
|
-
await this.rpc
|
|
19824
|
-
.send(
|
|
19825
|
-
new ResponseRoleMessage({
|
|
19826
|
-
role,
|
|
19827
|
-
}),
|
|
19828
|
-
{
|
|
19829
|
-
mode: new SilentDelivery({
|
|
19830
|
-
to: [context.from],
|
|
19831
|
-
redundancy: 1,
|
|
19832
|
-
}),
|
|
19833
|
-
signal: replicationLifecycleController.signal,
|
|
19834
|
-
},
|
|
19835
|
-
)
|
|
19836
|
-
.catch((error) =>
|
|
19837
|
-
this.handleReplicationLifecycleSendError(
|
|
19838
|
-
error,
|
|
19839
|
-
replicationLifecycleController,
|
|
19840
|
-
),
|
|
19841
|
-
);
|
|
19842
|
-
}
|
|
19843
|
-
}
|
|
19844
|
-
}
|
|
19845
|
-
}
|
|
19846
|
-
|
|
19847
19579
|
private async handleReplicationInfoV2Announcement(
|
|
19848
19580
|
msg: ReplicationInfoV2Message,
|
|
19849
19581
|
context: ReceiveRequestContext,
|
|
@@ -20023,251 +19755,12 @@ export class SharedLog<
|
|
|
20023
19755
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
20024
19756
|
// so recovery re-solicitation may restart from the base interval.
|
|
20025
19757
|
this.resetReplicationInfoV2RecoveryEscalation(fromHash);
|
|
20026
|
-
if (
|
|
20027
|
-
msg instanceof FullReplicationInfoV2Message &&
|
|
20028
|
-
this.legacyReplicationInfoEnabled
|
|
20029
|
-
) {
|
|
20030
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
20031
|
-
}
|
|
20032
19758
|
});
|
|
20033
19759
|
} finally {
|
|
20034
19760
|
this._v2Receive.release(admission);
|
|
20035
19761
|
}
|
|
20036
19762
|
}
|
|
20037
19763
|
|
|
20038
|
-
private async handleReplicationInfoAnnouncement(
|
|
20039
|
-
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
20040
|
-
context: ReceiveRequestContext,
|
|
20041
|
-
lane: ReceiveLaneContext,
|
|
20042
|
-
): Promise<void> {
|
|
20043
|
-
const receiveFromHash = lane.fromHash;
|
|
20044
|
-
const receiveSession = lane.session;
|
|
20045
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
20046
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
20047
|
-
const peerReceiveLease = lane.lease;
|
|
20048
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
20049
|
-
return;
|
|
20050
|
-
}
|
|
20051
|
-
|
|
20052
|
-
const replicationInfoMessage = msg as
|
|
20053
|
-
| AllReplicatingSegmentsMessage
|
|
20054
|
-
| AddedReplicationSegmentMessage;
|
|
20055
|
-
|
|
20056
|
-
// Process replication updates even if the sender isn't yet considered "ready" by
|
|
20057
|
-
// `Program.waitFor()`. Dropping these messages can lead to missing replicator info
|
|
20058
|
-
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
20059
|
-
const from = context.from!;
|
|
20060
|
-
const fromHash = from.hashcode();
|
|
20061
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20062
|
-
if (receiveSession) {
|
|
20063
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20064
|
-
peerHash: fromHash,
|
|
20065
|
-
peerSession: receiveSession,
|
|
20066
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20067
|
-
senderTransportSession: context.message.header.session,
|
|
20068
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20069
|
-
message: msg,
|
|
20070
|
-
});
|
|
20071
|
-
}
|
|
20072
|
-
return;
|
|
20073
|
-
}
|
|
20074
|
-
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
20075
|
-
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
20076
|
-
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
20077
|
-
// relocated `===`-with-`?? null`. The DELIBERATE absence of a
|
|
20078
|
-
// subscription-epoch term is preserved: the lease already validated
|
|
20079
|
-
// it, and the in-lane recheck owns post-await staleness.
|
|
20080
|
-
if (
|
|
20081
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20082
|
-
receiveReplicationLifecycleController,
|
|
20083
|
-
) ||
|
|
20084
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20085
|
-
receiveFromHash,
|
|
20086
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20087
|
-
) ||
|
|
20088
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20089
|
-
) {
|
|
20090
|
-
return;
|
|
20091
|
-
}
|
|
20092
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
20093
|
-
peerReceiveLease.release();
|
|
20094
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
20095
|
-
try {
|
|
20096
|
-
// The peer may have unsubscribed after this message was queued.
|
|
20097
|
-
// In-lane gate: lifecycle -> subscription-epoch -> receive-epoch
|
|
20098
|
-
// -> blocked, term for term as before the session migration.
|
|
20099
|
-
if (
|
|
20100
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20101
|
-
receiveReplicationLifecycleController,
|
|
20102
|
-
) ||
|
|
20103
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
20104
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20105
|
-
fromHash,
|
|
20106
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20107
|
-
) ||
|
|
20108
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20109
|
-
) {
|
|
20110
|
-
return;
|
|
20111
|
-
}
|
|
20112
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20113
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20114
|
-
peerHash: fromHash,
|
|
20115
|
-
peerSession: receiveSession,
|
|
20116
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20117
|
-
senderTransportSession: context.message.header.session,
|
|
20118
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20119
|
-
message: msg,
|
|
20120
|
-
});
|
|
20121
|
-
return;
|
|
20122
|
-
}
|
|
20123
|
-
|
|
20124
|
-
// Process in-order to avoid races where repeated reset messages arrive
|
|
20125
|
-
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
20126
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
20127
|
-
if (prev && prev > messageTimestamp) {
|
|
20128
|
-
return;
|
|
20129
|
-
}
|
|
20130
|
-
|
|
20131
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
20132
|
-
|
|
20133
|
-
if (this.closed) {
|
|
20134
|
-
return;
|
|
20135
|
-
}
|
|
20136
|
-
|
|
20137
|
-
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
20138
|
-
const result = await this.addReplicationRange(
|
|
20139
|
-
replicationInfoMessage.segments.map((x) =>
|
|
20140
|
-
x.toReplicationRangeIndexable(from),
|
|
20141
|
-
),
|
|
20142
|
-
from,
|
|
20143
|
-
{
|
|
20144
|
-
reset,
|
|
20145
|
-
checkDuplicates: true,
|
|
20146
|
-
timestamp: Number(messageTimestamp),
|
|
20147
|
-
allowLegacyOrderedReplacementPairs:
|
|
20148
|
-
msg instanceof AddedReplicationSegmentMessage,
|
|
20149
|
-
},
|
|
20150
|
-
);
|
|
20151
|
-
if (result === undefined) {
|
|
20152
|
-
return;
|
|
20153
|
-
}
|
|
20154
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
20155
|
-
|
|
20156
|
-
// If the peer reports any replication segments, stop re-requesting.
|
|
20157
|
-
// (Empty reports can be transient during startup.)
|
|
20158
|
-
if (replicationInfoMessage.segments.length > 0) {
|
|
20159
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
20160
|
-
}
|
|
20161
|
-
} catch (e) {
|
|
20162
|
-
if (isNotStartedError(e as Error)) {
|
|
20163
|
-
return;
|
|
20164
|
-
}
|
|
20165
|
-
logger.error(
|
|
20166
|
-
`Failed to apply replication settings from '${fromHash}': ${
|
|
20167
|
-
(e as any)?.message ?? e
|
|
20168
|
-
}`,
|
|
20169
|
-
);
|
|
20170
|
-
}
|
|
20171
|
-
});
|
|
20172
|
-
}
|
|
20173
|
-
|
|
20174
|
-
private async handleStoppedReplicating(
|
|
20175
|
-
msg: StoppedReplicating,
|
|
20176
|
-
context: ReceiveRequestContext,
|
|
20177
|
-
lane: ReceiveLaneContext,
|
|
20178
|
-
): Promise<void> {
|
|
20179
|
-
const receiveFromHash = lane.fromHash;
|
|
20180
|
-
const receiveSession = lane.session;
|
|
20181
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
20182
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
20183
|
-
const peerReceiveLease = lane.lease;
|
|
20184
|
-
const from = context.from!;
|
|
20185
|
-
const segmentIds = msg.segmentIds;
|
|
20186
|
-
if (from.equals(this.node.identity.publicKey)) {
|
|
20187
|
-
return;
|
|
20188
|
-
}
|
|
20189
|
-
const fromHash = from.hashcode();
|
|
20190
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20191
|
-
if (receiveSession) {
|
|
20192
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20193
|
-
peerHash: fromHash,
|
|
20194
|
-
peerSession: receiveSession,
|
|
20195
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20196
|
-
senderTransportSession: context.message.header.session,
|
|
20197
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20198
|
-
message: msg,
|
|
20199
|
-
});
|
|
20200
|
-
}
|
|
20201
|
-
return;
|
|
20202
|
-
}
|
|
20203
|
-
// Same pre-lane gate shape as Added/All above (and the same
|
|
20204
|
-
// intentional absence of a subscription-epoch term).
|
|
20205
|
-
if (
|
|
20206
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20207
|
-
receiveReplicationLifecycleController,
|
|
20208
|
-
) ||
|
|
20209
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20210
|
-
receiveFromHash,
|
|
20211
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20212
|
-
) ||
|
|
20213
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20214
|
-
) {
|
|
20215
|
-
return;
|
|
20216
|
-
}
|
|
20217
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
20218
|
-
peerReceiveLease.release();
|
|
20219
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
20220
|
-
if (
|
|
20221
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20222
|
-
receiveReplicationLifecycleController,
|
|
20223
|
-
) ||
|
|
20224
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
20225
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20226
|
-
fromHash,
|
|
20227
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20228
|
-
) ||
|
|
20229
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20230
|
-
) {
|
|
20231
|
-
return;
|
|
20232
|
-
}
|
|
20233
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20234
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20235
|
-
peerHash: fromHash,
|
|
20236
|
-
peerSession: receiveSession,
|
|
20237
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20238
|
-
senderTransportSession: context.message.header.session,
|
|
20239
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20240
|
-
message: msg,
|
|
20241
|
-
});
|
|
20242
|
-
return;
|
|
20243
|
-
}
|
|
20244
|
-
|
|
20245
|
-
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
20246
|
-
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
20247
|
-
return;
|
|
20248
|
-
}
|
|
20249
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
20250
|
-
if (this.closed) {
|
|
20251
|
-
return;
|
|
20252
|
-
}
|
|
20253
|
-
|
|
20254
|
-
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(
|
|
20255
|
-
segmentIds,
|
|
20256
|
-
from,
|
|
20257
|
-
);
|
|
20258
|
-
|
|
20259
|
-
await this.removeReplicationRanges(rangesToRemove, from);
|
|
20260
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
20261
|
-
const timestamp = BigInt(+new Date());
|
|
20262
|
-
for (const range of rangesToRemove) {
|
|
20263
|
-
this.replicationChangeDebounceFn.add({
|
|
20264
|
-
range,
|
|
20265
|
-
type: "removed",
|
|
20266
|
-
timestamp,
|
|
20267
|
-
});
|
|
20268
|
-
}
|
|
20269
|
-
});
|
|
20270
|
-
}
|
|
20271
19764
|
|
|
20272
19765
|
async calculateTotalParticipation(options?: { sum?: boolean }) {
|
|
20273
19766
|
if (options?.sum) {
|
|
@@ -20556,7 +20049,7 @@ export class SharedLog<
|
|
|
20556
20049
|
throwIfInactive();
|
|
20557
20050
|
|
|
20558
20051
|
if (options?.replicate) {
|
|
20559
|
-
let messageToSend:
|
|
20052
|
+
let messageToSend: AddedReplicationInfoMutation | undefined = undefined;
|
|
20560
20053
|
|
|
20561
20054
|
if (assumeSynced) {
|
|
20562
20055
|
throwIfInactive();
|
|
@@ -20584,14 +20077,14 @@ export class SharedLog<
|
|
|
20584
20077
|
// in one large message instead
|
|
20585
20078
|
announce: (msg) => {
|
|
20586
20079
|
throwIfInactive();
|
|
20587
|
-
if (
|
|
20080
|
+
if (!("added" in msg)) {
|
|
20588
20081
|
throw new Error("Unexpected");
|
|
20589
20082
|
}
|
|
20590
20083
|
|
|
20591
20084
|
if (messageToSend) {
|
|
20592
20085
|
// merge segments to make it into one messages
|
|
20593
|
-
for (const segment of msg.segments) {
|
|
20594
|
-
messageToSend.segments.push(segment);
|
|
20086
|
+
for (const segment of msg.added.segments) {
|
|
20087
|
+
messageToSend.added.segments.push(segment);
|
|
20595
20088
|
}
|
|
20596
20089
|
} else {
|
|
20597
20090
|
messageToSend = msg;
|
|
@@ -20730,34 +20223,20 @@ export class SharedLog<
|
|
|
20730
20223
|
|
|
20731
20224
|
requestAttempts++;
|
|
20732
20225
|
|
|
20733
|
-
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20740
|
-
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20747
|
-
const peerSession = this._peerSessions.current(peerHash);
|
|
20748
|
-
if (peerSession?.phase === "open") {
|
|
20749
|
-
this._v2Receive.resumeParkedRequest({
|
|
20750
|
-
peerHash,
|
|
20751
|
-
peerSession,
|
|
20752
|
-
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
20753
|
-
});
|
|
20754
|
-
} else if (peerSession === null || peerSession.phase === "departing") {
|
|
20755
|
-
// A peer can be known to routing before its SharedLog topic
|
|
20756
|
-
// subscription has been observed. Legacy requests used to bootstrap
|
|
20757
|
-
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
20758
|
-
// before it can create a fenced PeerSession and request a Full.
|
|
20759
|
-
requestSubscriberSnapshot();
|
|
20760
|
-
}
|
|
20226
|
+
const peerHash = key.hashcode();
|
|
20227
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
20228
|
+
if (peerSession?.phase === "open") {
|
|
20229
|
+
this._v2Receive.resumeParkedRequest({
|
|
20230
|
+
peerHash,
|
|
20231
|
+
peerSession,
|
|
20232
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
20233
|
+
});
|
|
20234
|
+
} else if (peerSession === null || peerSession.phase === "departing") {
|
|
20235
|
+
// A peer can be known to routing before its SharedLog topic
|
|
20236
|
+
// subscription has been observed. Legacy requests used to bootstrap
|
|
20237
|
+
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
20238
|
+
// before it can create a fenced PeerSession and request a Full.
|
|
20239
|
+
requestSubscriberSnapshot();
|
|
20761
20240
|
}
|
|
20762
20241
|
|
|
20763
20242
|
if (requestAttempts < maxRequestAttempts) {
|
|
@@ -23501,7 +22980,7 @@ export class SharedLog<
|
|
|
23501
22980
|
*/
|
|
23502
22981
|
private resetReplicationInfoV2RecoveryEscalation(peerHash: string) {
|
|
23503
22982
|
const state = this._replicationInfoRequestByPeer.get(peerHash);
|
|
23504
|
-
if (!state
|
|
22983
|
+
if (!state) {
|
|
23505
22984
|
return;
|
|
23506
22985
|
}
|
|
23507
22986
|
state.attempts = 0;
|
|
@@ -23618,6 +23097,13 @@ export class SharedLog<
|
|
|
23618
23097
|
tick();
|
|
23619
23098
|
}
|
|
23620
23099
|
|
|
23100
|
+
/**
|
|
23101
|
+
* Collapsed B12 shell: the legacy request-polling body (bounded
|
|
23102
|
+
* RequestReplicationInfoMessage ticks) is deleted; V2 recovery is the
|
|
23103
|
+
* only scheduler. Retained as a named seam rather than inlined at the
|
|
23104
|
+
* callers because the liveness monitor wiring and several suites
|
|
23105
|
+
* stub/spy it by name.
|
|
23106
|
+
*/
|
|
23621
23107
|
private scheduleReplicationInfoRequests(
|
|
23622
23108
|
peer: PublicSignKey,
|
|
23623
23109
|
replicationLifecycleController = this._instanceLifecycle
|
|
@@ -23629,75 +23115,10 @@ export class SharedLog<
|
|
|
23629
23115
|
) {
|
|
23630
23116
|
return;
|
|
23631
23117
|
}
|
|
23632
|
-
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
);
|
|
23637
|
-
return;
|
|
23638
|
-
}
|
|
23639
|
-
const peerHash = peer.hashcode();
|
|
23640
|
-
const requestStates = this._replicationInfoRequestByPeer;
|
|
23641
|
-
if (requestStates.has(peerHash)) {
|
|
23642
|
-
return;
|
|
23643
|
-
}
|
|
23644
|
-
|
|
23645
|
-
const state: { attempts: number; timer?: ReturnType<typeof setTimeout> } = {
|
|
23646
|
-
attempts: 0,
|
|
23647
|
-
};
|
|
23648
|
-
requestStates.set(peerHash, state);
|
|
23649
|
-
const cancel = () => {
|
|
23650
|
-
if (requestStates.get(peerHash) !== state) {
|
|
23651
|
-
return;
|
|
23652
|
-
}
|
|
23653
|
-
if (state.timer) {
|
|
23654
|
-
clearTimeout(state.timer);
|
|
23655
|
-
}
|
|
23656
|
-
requestStates.delete(peerHash);
|
|
23657
|
-
};
|
|
23658
|
-
|
|
23659
|
-
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
23660
|
-
const maxAttempts =
|
|
23661
|
-
this.waitForReplicatorRequestMaxAttempts ??
|
|
23662
|
-
Math.max(
|
|
23663
|
-
WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS,
|
|
23664
|
-
Math.ceil(this.waitForReplicatorTimeout / intervalMs),
|
|
23665
|
-
);
|
|
23666
|
-
|
|
23667
|
-
const tick = () => {
|
|
23668
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
23669
|
-
cancel();
|
|
23670
|
-
return;
|
|
23671
|
-
}
|
|
23672
|
-
state.attempts++;
|
|
23673
|
-
|
|
23674
|
-
this.rpc
|
|
23675
|
-
.send(new RequestReplicationInfoMessage(), {
|
|
23676
|
-
mode: new AcknowledgeDelivery({ redundancy: 1, to: [peer] }),
|
|
23677
|
-
signal: replicationLifecycleController.signal,
|
|
23678
|
-
})
|
|
23679
|
-
.catch((e) => {
|
|
23680
|
-
// Best-effort: missing peers / unopened RPC should not fail join flows.
|
|
23681
|
-
if (
|
|
23682
|
-
isNotStartedError(e as Error) ||
|
|
23683
|
-
(replicationLifecycleController.signal.aborted &&
|
|
23684
|
-
e instanceof AbortError)
|
|
23685
|
-
) {
|
|
23686
|
-
return;
|
|
23687
|
-
}
|
|
23688
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
23689
|
-
});
|
|
23690
|
-
|
|
23691
|
-
if (state.attempts >= maxAttempts) {
|
|
23692
|
-
cancel();
|
|
23693
|
-
return;
|
|
23694
|
-
}
|
|
23695
|
-
|
|
23696
|
-
state.timer = setTimeout(tick, intervalMs);
|
|
23697
|
-
state.timer.unref?.();
|
|
23698
|
-
};
|
|
23699
|
-
|
|
23700
|
-
tick();
|
|
23118
|
+
this.scheduleReplicationInfoV2Recovery(
|
|
23119
|
+
peer,
|
|
23120
|
+
replicationLifecycleController,
|
|
23121
|
+
);
|
|
23701
23122
|
}
|
|
23702
23123
|
|
|
23703
23124
|
async handleSubscriptionChange(
|
|
@@ -23780,10 +23201,6 @@ export class SharedLog<
|
|
|
23780
23201
|
) {
|
|
23781
23202
|
return;
|
|
23782
23203
|
}
|
|
23783
|
-
// The timestamp watermark belongs to the previous subscription epoch.
|
|
23784
|
-
// Sender clocks are not synchronized, so carrying a local unsubscribe
|
|
23785
|
-
// timestamp forward could reject every valid announcement after reconnect.
|
|
23786
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
23787
23204
|
this._pendingReplicatorLeaveByPeer.delete(peerHash);
|
|
23788
23205
|
const openingCapabilities =
|
|
23789
23206
|
this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
@@ -23837,12 +23254,6 @@ export class SharedLog<
|
|
|
23837
23254
|
this.joinWarmup._warmupSessionsByTarget.get(peerHash) ?? null;
|
|
23838
23255
|
this.joinWarmup.cancelJoinWarmupTarget(peerHash);
|
|
23839
23256
|
|
|
23840
|
-
const now = BigInt(+new Date());
|
|
23841
|
-
const previous = this.latestReplicationInfoMessage.get(peerHash);
|
|
23842
|
-
if (!previous || previous < now) {
|
|
23843
|
-
this.latestReplicationInfoMessage.set(peerHash, now);
|
|
23844
|
-
}
|
|
23845
|
-
|
|
23846
23257
|
let removed = false;
|
|
23847
23258
|
try {
|
|
23848
23259
|
// Unsubscribe can race with the peer's final replication reset message.
|
|
@@ -23893,116 +23304,23 @@ export class SharedLog<
|
|
|
23893
23304
|
|
|
23894
23305
|
// Decode, sender and authenticated apply readiness are separate bits. An
|
|
23895
23306
|
// ACKed capability advert is the local half of receiver-led negotiation;
|
|
23896
|
-
// readiness is promoted
|
|
23897
|
-
// This
|
|
23898
|
-
//
|
|
23307
|
+
// readiness is promoted through the coordinator once the ACK arrives.
|
|
23308
|
+
// This keeps capability ACK latency off the subscription callback's
|
|
23309
|
+
// critical path.
|
|
23899
23310
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
23900
|
-
|
|
23901
|
-
|
|
23902
|
-
|
|
23903
|
-
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
23909
|
-
|
|
23910
|
-
|
|
23911
|
-
|
|
23912
|
-
|
|
23913
|
-
publicKey,
|
|
23914
|
-
replicationLifecycleController,
|
|
23915
|
-
);
|
|
23916
|
-
return;
|
|
23917
|
-
}
|
|
23918
|
-
|
|
23919
|
-
try {
|
|
23920
|
-
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
23921
|
-
try {
|
|
23922
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
23923
|
-
} catch (error) {
|
|
23924
|
-
if (
|
|
23925
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
23926
|
-
isNotStartedError(error as Error)
|
|
23927
|
-
) {
|
|
23928
|
-
return;
|
|
23929
|
-
}
|
|
23930
|
-
throw error;
|
|
23931
|
-
}
|
|
23932
|
-
if (
|
|
23933
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
23934
|
-
!ownsSubscriptionEpoch()
|
|
23935
|
-
) {
|
|
23936
|
-
return;
|
|
23937
|
-
}
|
|
23938
|
-
if (replicationSegments.length > 0) {
|
|
23939
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
23940
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
23941
|
-
await this.rpc
|
|
23942
|
-
.send(
|
|
23943
|
-
new AllReplicatingSegmentsMessage({
|
|
23944
|
-
segments,
|
|
23945
|
-
}),
|
|
23946
|
-
{
|
|
23947
|
-
mode: new AcknowledgeDelivery({
|
|
23948
|
-
redundancy: 1,
|
|
23949
|
-
to: [publicKey],
|
|
23950
|
-
}),
|
|
23951
|
-
signal: replicationLifecycleController.signal,
|
|
23952
|
-
},
|
|
23953
|
-
)
|
|
23954
|
-
.catch((error) =>
|
|
23955
|
-
this.handleReplicationLifecycleSendError(
|
|
23956
|
-
error,
|
|
23957
|
-
replicationLifecycleController,
|
|
23958
|
-
),
|
|
23959
|
-
);
|
|
23960
|
-
if (
|
|
23961
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
23962
|
-
!ownsSubscriptionEpoch()
|
|
23963
|
-
) {
|
|
23964
|
-
return;
|
|
23965
|
-
}
|
|
23966
|
-
|
|
23967
|
-
if (this.v8Behaviour) {
|
|
23968
|
-
// for backwards compatibility
|
|
23969
|
-
await this.rpc
|
|
23970
|
-
.send(
|
|
23971
|
-
new ResponseRoleMessage({
|
|
23972
|
-
role: this.getRoleFromReplicationSegments(replicationSegments),
|
|
23973
|
-
}),
|
|
23974
|
-
{
|
|
23975
|
-
mode: new AcknowledgeDelivery({
|
|
23976
|
-
redundancy: 1,
|
|
23977
|
-
to: [publicKey],
|
|
23978
|
-
}),
|
|
23979
|
-
signal: replicationLifecycleController.signal,
|
|
23980
|
-
},
|
|
23981
|
-
)
|
|
23982
|
-
.catch((error) =>
|
|
23983
|
-
this.handleReplicationLifecycleSendError(
|
|
23984
|
-
error,
|
|
23985
|
-
replicationLifecycleController,
|
|
23986
|
-
),
|
|
23987
|
-
);
|
|
23988
|
-
}
|
|
23989
|
-
}
|
|
23990
|
-
|
|
23991
|
-
// Keep legacy request-based discovery independent of the capability ACK.
|
|
23992
|
-
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
23993
|
-
// issues where we may miss the remote peer's initial announcement.
|
|
23994
|
-
if (
|
|
23995
|
-
this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
23996
|
-
ownsSubscriptionEpoch()
|
|
23997
|
-
) {
|
|
23998
|
-
this.scheduleReplicationInfoRequests(
|
|
23999
|
-
publicKey,
|
|
24000
|
-
replicationLifecycleController,
|
|
24001
|
-
);
|
|
24002
|
-
}
|
|
24003
|
-
} finally {
|
|
24004
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
24005
|
-
}
|
|
23311
|
+
// B12: there is no legacy startup work to order ahead of RequestV2, so
|
|
23312
|
+
// the two-phase legacy barrier is gone — an ACKed advert promotes
|
|
23313
|
+
// readiness through the coordinator as soon as it lands.
|
|
23314
|
+
this._v2Receive.advertiseLocalCapability({
|
|
23315
|
+
target: publicKey,
|
|
23316
|
+
peerSession: expectedSubscriptionEpoch,
|
|
23317
|
+
receiveEpoch,
|
|
23318
|
+
signal: replicationLifecycleController.signal,
|
|
23319
|
+
});
|
|
23320
|
+
this.scheduleReplicationInfoV2Recovery(
|
|
23321
|
+
publicKey,
|
|
23322
|
+
replicationLifecycleController,
|
|
23323
|
+
);
|
|
24006
23324
|
}
|
|
24007
23325
|
|
|
24008
23326
|
private getClampedReplicas(customValue?: MinReplicas) {
|
|
@@ -25403,15 +24721,6 @@ export class SharedLog<
|
|
|
25403
24721
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
25404
24722
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
25405
24723
|
this._recentRepairDispatch.delete(fromHash);
|
|
25406
|
-
|
|
25407
|
-
// Keep a per-peer timestamp watermark when we observe an unsubscribe. This
|
|
25408
|
-
// prevents late/out-of-order replication-info messages from re-introducing
|
|
25409
|
-
// stale segments for a peer that has already left the topic.
|
|
25410
|
-
const now = BigInt(+new Date());
|
|
25411
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
25412
|
-
if (!prev || prev < now) {
|
|
25413
|
-
this.latestReplicationInfoMessage.set(fromHash, now);
|
|
25414
|
-
}
|
|
25415
24724
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
25416
24725
|
|
|
25417
24726
|
return this.handleSubscriptionChange(
|
|
@@ -25575,26 +24884,16 @@ export class SharedLog<
|
|
|
25575
24884
|
return false;
|
|
25576
24885
|
}
|
|
25577
24886
|
|
|
25578
|
-
|
|
25579
|
-
|
|
25580
|
-
|
|
25581
|
-
|
|
25582
|
-
|
|
25583
|
-
|
|
25584
|
-
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
|
|
25588
|
-
if (!isCurrent()) return false;
|
|
25589
|
-
} catch (error) {
|
|
25590
|
-
if (
|
|
25591
|
-
isTransientReplicationAnnouncementError(error) &&
|
|
25592
|
-
this._announcements._replicationAnnouncementRetryPending
|
|
25593
|
-
) {
|
|
25594
|
-
return false;
|
|
25595
|
-
}
|
|
25596
|
-
throw error;
|
|
25597
|
-
}
|
|
24887
|
+
await this.startAnnounceReplicating(
|
|
24888
|
+
[dynamicRange],
|
|
24889
|
+
{
|
|
24890
|
+
checkDuplicates: false,
|
|
24891
|
+
reset: false,
|
|
24892
|
+
shouldApply: isCurrent,
|
|
24893
|
+
},
|
|
24894
|
+
ownershipLifecycleController,
|
|
24895
|
+
);
|
|
24896
|
+
if (!isCurrent()) return false;
|
|
25598
24897
|
|
|
25599
24898
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
25600
24899
|
if (isCurrent()) {
|