@peerbit/shared-log 15.0.0 → 16.0.1
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/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +12 -0
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/index.d.ts +7 -13
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +81 -598
- package/dist/src/index.js.map +1 -1
- package/dist/src/instance-lifecycle.d.ts.map +1 -1
- package/dist/src/instance-lifecycle.js +9 -0
- package/dist/src/instance-lifecycle.js.map +1 -1
- package/dist/src/peer-session.d.ts.map +1 -1
- package/dist/src/peer-session.js +9 -0
- package/dist/src/peer-session.js.map +1 -1
- package/dist/src/pid.d.ts +0 -2
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +0 -5
- package/dist/src/pid.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +3 -106
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +0 -521
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +14 -43
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +24 -199
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +0 -1
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +0 -6
- 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/role.d.ts +0 -4
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +0 -9
- package/dist/src/role.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 +8 -8
- package/src/checked-prune.ts +12 -0
- package/src/index.ts +104 -817
- package/src/instance-lifecycle.ts +9 -0
- package/src/peer-session.ts +9 -0
- package/src/pid.ts +0 -6
- package/src/replication-announcement.ts +8 -739
- package/src/replication-info-v2-receive.ts +25 -271
- package/src/replication-info-v2-send.ts +0 -7
- package/src/replication.ts +1 -20
- package/src/role.ts +0 -13
- 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,
|
|
@@ -277,7 +274,6 @@ import {
|
|
|
277
274
|
maxReplicas,
|
|
278
275
|
} from "./replication.js";
|
|
279
276
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
280
|
-
import { Observer, Replicator } from "./role.js";
|
|
281
277
|
import { createSyncronizer } from "./sync/factory.js";
|
|
282
278
|
import type {
|
|
283
279
|
SharedLogNativeWireSync,
|
|
@@ -2021,7 +2017,6 @@ export class SharedLog<
|
|
|
2021
2017
|
|
|
2022
2018
|
uniqueReplicators!: Set<string>;
|
|
2023
2019
|
private _replicatorJoinEmitted!: Set<string>;
|
|
2024
|
-
private _replicatorsReconciled!: boolean;
|
|
2025
2020
|
|
|
2026
2021
|
/* private _totalParticipation!: number; */
|
|
2027
2022
|
|
|
@@ -2078,34 +2073,26 @@ export class SharedLog<
|
|
|
2078
2073
|
// public key hash to range id to range
|
|
2079
2074
|
pendingMaturity!: Map<string, Map<string, PendingMaturityRecord<R>>>; // map of peerId to timeout
|
|
2080
2075
|
|
|
2081
|
-
//
|
|
2082
|
-
// FENCING role
|
|
2083
|
-
//
|
|
2084
|
-
// the
|
|
2085
|
-
//
|
|
2086
|
-
//
|
|
2087
|
-
// NOT subsumed: within one (lifecycle, session, epoch, unblocked) regime
|
|
2088
|
-
// the epoch token is constant across every message from the peer, so only
|
|
2089
|
-
// the two apply-lane timestamp comparisons can drop an older reset
|
|
2090
|
-
// delivered after a newer add (unordered pubsub / retransmits) — an
|
|
2091
|
-
// identity token carries no order. Deletion is blocked until
|
|
2092
|
-
// replication-info messages carry sender-authoritative sequence numbers
|
|
2093
|
-
// (stage-5 schema change); the `receive admission replication-info
|
|
2094
|
-
// ordering watermark` pins fail if the read sites are removed before then.
|
|
2095
|
-
private latestReplicationInfoMessage!: Map<string, bigint>;
|
|
2076
|
+
// The legacy replication-info ordering watermark (fence B8) is deleted:
|
|
2077
|
+
// its FENCING role was subsumed by the per-peer receive epoch, blocked set
|
|
2078
|
+
// and session identity, and its intra-epoch ORDERING role existed only for
|
|
2079
|
+
// the legacy apply lanes. The V2 lane orders by sender-authoritative
|
|
2080
|
+
// sequence numbers, and legacy frames are dropped unconditionally at the
|
|
2081
|
+
// B1 gate before any side effect.
|
|
2096
2082
|
// The replication-info blocked set (fence B5) lives on the peer-session
|
|
2097
2083
|
// registry: unsubscribed peers whose replication-info is ignored until a
|
|
2098
2084
|
// reconnect barrier commits. See PeerSessionRegistry._replicationInfoBlockedPeers.
|
|
2085
|
+
// V2 recovery scheduler state, one row per open peer session (B12: the
|
|
2086
|
+
// legacy request scheduler that shared this map is deleted).
|
|
2099
2087
|
private _replicationInfoRequestByPeer!: Map<
|
|
2100
2088
|
string,
|
|
2101
2089
|
{
|
|
2102
|
-
//
|
|
2103
|
-
//
|
|
2104
|
-
// for the next unpark delay, reset on any applied V2 progress.
|
|
2090
|
+
// Consecutive fruitless unparks — the escalation exponent for the
|
|
2091
|
+
// next unpark delay, reset on any applied V2 progress.
|
|
2105
2092
|
attempts: number;
|
|
2106
2093
|
timer?: ReturnType<typeof setTimeout>;
|
|
2107
|
-
peerSession
|
|
2108
|
-
//
|
|
2094
|
+
peerSession: PeerSession;
|
|
2095
|
+
// When the current park was first observed.
|
|
2109
2096
|
parkedSinceMs?: number;
|
|
2110
2097
|
}
|
|
2111
2098
|
>;
|
|
@@ -2266,7 +2253,6 @@ export class SharedLog<
|
|
|
2266
2253
|
for (const hash of this._checkedPrune?.retries.keys() ?? []) {
|
|
2267
2254
|
this._checkedPrune.clearRetry(hash);
|
|
2268
2255
|
}
|
|
2269
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
2270
2256
|
this.joinWarmup.cancelAllJoinWarmupTargets();
|
|
2271
2257
|
for (const timer of this._repairRetryTimers) {
|
|
2272
2258
|
clearTimeout(timer);
|
|
@@ -3268,7 +3254,7 @@ export class SharedLog<
|
|
|
3268
3254
|
private rebalanceParticipationDebounced:
|
|
3269
3255
|
| ReturnType<typeof debounceFixedInterval>
|
|
3270
3256
|
| undefined;
|
|
3271
|
-
private _announcements!: ReplicationAnnouncementCoordinator
|
|
3257
|
+
private _announcements!: ReplicationAnnouncementCoordinator;
|
|
3272
3258
|
private _v2Receive!: ReplicationInfoV2ReceiveCoordinator;
|
|
3273
3259
|
private _v2Send!: ReplicationInfoV2SendCoordinator<R>;
|
|
3274
3260
|
|
|
@@ -3396,37 +3382,13 @@ export class SharedLog<
|
|
|
3396
3382
|
});
|
|
3397
3383
|
}
|
|
3398
3384
|
|
|
3399
|
-
private createReplicationAnnouncementCoordinator(): ReplicationAnnouncementCoordinator
|
|
3400
|
-
return new ReplicationAnnouncementCoordinator
|
|
3401
|
-
// Route re-entrant queueing through the owner so coordinator spies keep
|
|
3402
|
-
// observing it (the poison guard assertions in events.spec.ts depend on
|
|
3403
|
-
// this).
|
|
3404
|
-
queueCurrentReplicationStateAnnouncementRepair: () =>
|
|
3405
|
-
this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
3406
|
-
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) =>
|
|
3407
|
-
this._announcements.queueCurrentReplicationStateAnnouncementRetry(
|
|
3408
|
-
error,
|
|
3409
|
-
),
|
|
3385
|
+
private createReplicationAnnouncementCoordinator(): ReplicationAnnouncementCoordinator {
|
|
3386
|
+
return new ReplicationAnnouncementCoordinator({
|
|
3410
3387
|
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
3411
|
-
isLegacyReplicationInfoEnabled: () => this.legacyReplicationInfoEnabled,
|
|
3412
|
-
isClosed: () => this.closed,
|
|
3413
|
-
getCloseSignal: () => this._closeController.signal,
|
|
3414
|
-
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
3415
|
-
validatePersistedReplicationRangeSnapshot: (ranges) =>
|
|
3416
|
-
this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
3417
|
-
getSubscribers: () =>
|
|
3418
|
-
this.node.services.pubsub.getSubscribers(this.topic),
|
|
3419
|
-
getSelfHash: () => this.node.identity.publicKey.hashcode(),
|
|
3420
|
-
isBlockedPeer: (hash) =>
|
|
3421
|
-
this._peerSessions.isReplicationInfoBlocked(hash),
|
|
3422
|
-
getRpc: () => this.rpc,
|
|
3423
3388
|
captureReplicationOwnershipLifecycle: () =>
|
|
3424
3389
|
this.captureReplicationOwnershipLifecycle(),
|
|
3425
3390
|
throwIfReplicationOwnershipLifecycleInactive: (controller) =>
|
|
3426
3391
|
this.throwIfReplicationOwnershipLifecycleInactive(controller),
|
|
3427
|
-
isAdaptiveReplicating: () => this._isAdaptiveReplicating,
|
|
3428
|
-
callRebalanceParticipationDebounced: () =>
|
|
3429
|
-
this.rebalanceParticipationDebounced?.call(),
|
|
3430
3392
|
});
|
|
3431
3393
|
}
|
|
3432
3394
|
|
|
@@ -3653,7 +3615,6 @@ export class SharedLog<
|
|
|
3653
3615
|
this._admittedPruneRemoves = new Set();
|
|
3654
3616
|
this._pendingIHave = new Map();
|
|
3655
3617
|
this._pendingIHaveCallbacks = new Set();
|
|
3656
|
-
this.latestReplicationInfoMessage = new Map();
|
|
3657
3618
|
this._replicationInfoRequestByPeer = new Map();
|
|
3658
3619
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
3659
3620
|
this._replicationInfoApplyQueueByPeer = new Map();
|
|
@@ -3696,7 +3657,6 @@ export class SharedLog<
|
|
|
3696
3657
|
this.recentlyRebalanced = new Cache<string>({ max: 1e4, ttl: 1e5 });
|
|
3697
3658
|
this.uniqueReplicators = new Set();
|
|
3698
3659
|
this._replicatorJoinEmitted = new Set();
|
|
3699
|
-
this._replicatorsReconciled = false;
|
|
3700
3660
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
3701
3661
|
this._v2Receive = this.createReplicationInfoV2ReceiveCoordinator();
|
|
3702
3662
|
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
@@ -3764,29 +3724,10 @@ export class SharedLog<
|
|
|
3764
3724
|
this._nativeStrictDurableTransactionsClosing ??= false;
|
|
3765
3725
|
}
|
|
3766
3726
|
|
|
3767
|
-
get compatibility(): number | undefined {
|
|
3768
|
-
// B12: the open option was removed and any defined value rejects at
|
|
3769
|
-
// open(); this is permanently undefined and dies with the residual
|
|
3770
|
-
// gates in a later cleanup stage.
|
|
3771
|
-
return (this._logProperties as any)?.compatibility;
|
|
3772
|
-
}
|
|
3773
|
-
|
|
3774
|
-
/**
|
|
3775
|
-
* Legacy replication-info is an explicit compatibility fallback. Current
|
|
3776
|
-
* logs never infer or re-enable it from a remote peer's capabilities.
|
|
3777
|
-
*/
|
|
3778
|
-
private get legacyReplicationInfoEnabled(): boolean {
|
|
3779
|
-
return this.compatibility !== undefined && this.compatibility < 10;
|
|
3780
|
-
}
|
|
3781
|
-
|
|
3782
3727
|
get isAdaptiveReplicating() {
|
|
3783
3728
|
return this._isAdaptiveReplicating;
|
|
3784
3729
|
}
|
|
3785
3730
|
|
|
3786
|
-
private get v8Behaviour() {
|
|
3787
|
-
return (this.compatibility ?? Number.MAX_VALUE) < 9;
|
|
3788
|
-
}
|
|
3789
|
-
|
|
3790
3731
|
private getFanoutChannelOptions(
|
|
3791
3732
|
options?: SharedLogFanoutOptions,
|
|
3792
3733
|
): Omit<FanoutTreeChannelOptions, "role"> {
|
|
@@ -4691,8 +4632,7 @@ export class SharedLog<
|
|
|
4691
4632
|
(leaders.size === 0 || (leaders.size === 1 && leaders.has(selfHash)))
|
|
4692
4633
|
) {
|
|
4693
4634
|
const allowSubscriberFallback =
|
|
4694
|
-
this.syncronizer instanceof SimpleSyncronizer
|
|
4695
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
4635
|
+
this.syncronizer instanceof SimpleSyncronizer;
|
|
4696
4636
|
if (!allowSubscriberFallback) {
|
|
4697
4637
|
return;
|
|
4698
4638
|
}
|
|
@@ -4841,8 +4781,7 @@ export class SharedLog<
|
|
|
4841
4781
|
const set = new Set(leaders.keys());
|
|
4842
4782
|
let hasRemotePeers = set.has(selfHash) ? set.size > 1 : set.size > 0;
|
|
4843
4783
|
const allowSubscriberFallback =
|
|
4844
|
-
this.syncronizer instanceof SimpleSyncronizer
|
|
4845
|
-
(this.compatibility ?? Number.MAX_VALUE) < 10;
|
|
4784
|
+
this.syncronizer instanceof SimpleSyncronizer;
|
|
4846
4785
|
if (!hasRemotePeers && allowSubscriberFallback) {
|
|
4847
4786
|
try {
|
|
4848
4787
|
const subscribers = await this._getTopicSubscribers(this.topic);
|
|
@@ -5305,28 +5244,6 @@ export class SharedLog<
|
|
|
5305
5244
|
};
|
|
5306
5245
|
}
|
|
5307
5246
|
|
|
5308
|
-
// @deprecated
|
|
5309
|
-
private getRoleFromReplicationSegments(
|
|
5310
|
-
segments: ReplicationRangeIndexable<R>[],
|
|
5311
|
-
) {
|
|
5312
|
-
if (segments.length > 1) {
|
|
5313
|
-
throw new Error(
|
|
5314
|
-
"More than one replication segment found. Can only use one segment for compatbility with v8",
|
|
5315
|
-
);
|
|
5316
|
-
}
|
|
5317
|
-
|
|
5318
|
-
if (segments.length > 0) {
|
|
5319
|
-
const segment = segments[0].toReplicationRange();
|
|
5320
|
-
return new Replicator({
|
|
5321
|
-
factor: (segment.factor as number) / MAX_U32,
|
|
5322
|
-
offset: (segment.offset as number) / MAX_U32,
|
|
5323
|
-
});
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
// TODO this is not accurate but might be good enough
|
|
5327
|
-
return new Observer();
|
|
5328
|
-
}
|
|
5329
|
-
|
|
5330
5247
|
private isTerminating() {
|
|
5331
5248
|
return (
|
|
5332
5249
|
this.acceptsParentAttachments === false ||
|
|
@@ -5604,12 +5521,7 @@ export class SharedLog<
|
|
|
5604
5521
|
}
|
|
5605
5522
|
|
|
5606
5523
|
private onRebalanceParticipationError(error: Error): void {
|
|
5607
|
-
if (
|
|
5608
|
-
this.closed ||
|
|
5609
|
-
isNotStartedError(error) ||
|
|
5610
|
-
(isTransientReplicationAnnouncementError(error) &&
|
|
5611
|
-
this._announcements._replicationAnnouncementRetryPending)
|
|
5612
|
-
) {
|
|
5524
|
+
if (this.closed || isNotStartedError(error)) {
|
|
5613
5525
|
return;
|
|
5614
5526
|
}
|
|
5615
5527
|
|
|
@@ -6865,7 +6777,7 @@ export class SharedLog<
|
|
|
6865
6777
|
rebalance?: boolean;
|
|
6866
6778
|
checkDuplicates?: boolean;
|
|
6867
6779
|
timestamp?: number;
|
|
6868
|
-
|
|
6780
|
+
allowOrderedReplacementPairs?: boolean;
|
|
6869
6781
|
onConfirmedDurableStateChanged?: () => void;
|
|
6870
6782
|
onDurableApplyCommitted?: () => boolean | void;
|
|
6871
6783
|
shouldApply?: () => boolean;
|
|
@@ -6907,7 +6819,7 @@ export class SharedLog<
|
|
|
6907
6819
|
checkDuplicates,
|
|
6908
6820
|
timestamp: ts,
|
|
6909
6821
|
rebalance,
|
|
6910
|
-
|
|
6822
|
+
allowOrderedReplacementPairs,
|
|
6911
6823
|
onConfirmedDurableStateChanged,
|
|
6912
6824
|
onDurableApplyCommitted,
|
|
6913
6825
|
shouldApply,
|
|
@@ -6916,7 +6828,7 @@ export class SharedLog<
|
|
|
6916
6828
|
rebalance?: boolean;
|
|
6917
6829
|
checkDuplicates?: boolean;
|
|
6918
6830
|
timestamp?: number;
|
|
6919
|
-
|
|
6831
|
+
allowOrderedReplacementPairs?: boolean;
|
|
6920
6832
|
onConfirmedDurableStateChanged?: () => void;
|
|
6921
6833
|
onDurableApplyCommitted?: () => boolean | void;
|
|
6922
6834
|
shouldApply?: () => boolean;
|
|
@@ -6955,17 +6867,19 @@ export class SharedLog<
|
|
|
6955
6867
|
incomingRangeCountsById.set(range.idString, count);
|
|
6956
6868
|
if (
|
|
6957
6869
|
count > 1 &&
|
|
6958
|
-
(!
|
|
6870
|
+
(!allowOrderedReplacementPairs || reset === true || count > 2)
|
|
6959
6871
|
) {
|
|
6960
6872
|
throw new Error(
|
|
6961
6873
|
`Duplicate replication range id in announcement: ${range.idString}`,
|
|
6962
6874
|
);
|
|
6963
6875
|
}
|
|
6964
|
-
//
|
|
6965
|
-
//
|
|
6966
|
-
//
|
|
6967
|
-
//
|
|
6968
|
-
//
|
|
6876
|
+
// Rolling-upgrade relaxation on the live V2 Added path: a peer may
|
|
6877
|
+
// still express a non-reset replacement as the retired geometry
|
|
6878
|
+
// followed by the current geometry under one id. The sender is already
|
|
6879
|
+
// authorized to replace that id with the final item, so collapsing an
|
|
6880
|
+
// EXACT two-item incremental pair to its last item accepts the older
|
|
6881
|
+
// wire shape without broadening authority. Deliberately narrow — reset
|
|
6882
|
+
// announcements and any run longer than two still fail as duplicates.
|
|
6969
6883
|
incomingRangesById.set(range.idString, range);
|
|
6970
6884
|
}
|
|
6971
6885
|
const incomingRanges = [...incomingRangesById.values()];
|
|
@@ -14247,12 +14161,7 @@ export class SharedLog<
|
|
|
14247
14161
|
|
|
14248
14162
|
this.domain = options?.domain
|
|
14249
14163
|
? (options.domain(this) as unknown as D)
|
|
14250
|
-
: (createReplicationDomainHash(
|
|
14251
|
-
(options as any)?.compatibility !== undefined &&
|
|
14252
|
-
(options as any).compatibility < 10
|
|
14253
|
-
? "u32"
|
|
14254
|
-
: "u64",
|
|
14255
|
-
)(this) as unknown as D);
|
|
14164
|
+
: (createReplicationDomainHash("u64")(this) as unknown as D);
|
|
14256
14165
|
this.indexableDomain = createIndexableDomainFromResolution(
|
|
14257
14166
|
this.domain.resolution,
|
|
14258
14167
|
);
|
|
@@ -14262,7 +14171,6 @@ export class SharedLog<
|
|
|
14262
14171
|
this._admittedPruneRemoves = new Set();
|
|
14263
14172
|
this._pendingIHave = new Map();
|
|
14264
14173
|
this._pendingIHaveCallbacks = new Set();
|
|
14265
|
-
this.latestReplicationInfoMessage = new Map();
|
|
14266
14174
|
this._replicationInfoRequestByPeer = new Map();
|
|
14267
14175
|
this._subscriberSnapshotRequestsByPeer = new Map();
|
|
14268
14176
|
// Terminal close/drop drains the previous lifecycle before another open can
|
|
@@ -14327,7 +14235,6 @@ export class SharedLog<
|
|
|
14327
14235
|
|
|
14328
14236
|
this.uniqueReplicators = new Set();
|
|
14329
14237
|
this._replicatorJoinEmitted = new Set();
|
|
14330
|
-
this._replicatorsReconciled = false;
|
|
14331
14238
|
// Deserialized instances never ran the constructor; create the monitor and
|
|
14332
14239
|
// coordinator lazily on first open. Reopens keep the SAME instances so
|
|
14333
14240
|
// stale async continuations observe resets via property lookup.
|
|
@@ -14335,7 +14242,6 @@ export class SharedLog<
|
|
|
14335
14242
|
this._liveness.resetForOpen();
|
|
14336
14243
|
this._lastLocalAppendAt = 0;
|
|
14337
14244
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
14338
|
-
this._announcements.resetForOpen();
|
|
14339
14245
|
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
14340
14246
|
this._v2Receive.resetForOpen();
|
|
14341
14247
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
@@ -14398,12 +14304,6 @@ export class SharedLog<
|
|
|
14398
14304
|
}
|
|
14399
14305
|
|
|
14400
14306
|
this._closeController = new AbortController();
|
|
14401
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
14402
|
-
this._announcements.setupReplicationAnnouncementRetryFunction();
|
|
14403
|
-
this._announcements.setupReplicationAnnouncementRepairFunction();
|
|
14404
|
-
} else {
|
|
14405
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
14406
|
-
}
|
|
14407
14307
|
this._closeController.signal.addEventListener("abort", () => {
|
|
14408
14308
|
for (const [_peer, state] of this._replicationInfoRequestByPeer) {
|
|
14409
14309
|
if (state.timer) clearTimeout(state.timer);
|
|
@@ -14859,7 +14759,6 @@ export class SharedLog<
|
|
|
14859
14759
|
sendOptions?: { priority?: number; signal?: AbortSignal },
|
|
14860
14760
|
) => this.trySendFusedRawExchangeHeads(hashes, to, sendOptions),
|
|
14861
14761
|
warn,
|
|
14862
|
-
compatibility: (this._logProperties as any)?.compatibility,
|
|
14863
14762
|
resolution: this.domain.resolution,
|
|
14864
14763
|
sync: options?.sync,
|
|
14865
14764
|
syncronizer: options?.syncronizer,
|
|
@@ -15613,16 +15512,8 @@ export class SharedLog<
|
|
|
15613
15512
|
const existingSubscribersPromise = this.node.services.pubsub.getSubscribers(
|
|
15614
15513
|
this.topic,
|
|
15615
15514
|
);
|
|
15616
|
-
const replicationLifecycleController =
|
|
15617
|
-
this._instanceLifecycle?.membershipLifecycleController;
|
|
15618
|
-
|
|
15619
15515
|
// We do this here, because these calls requires this.closed == false
|
|
15620
15516
|
void this.pruneOfflineReplicators()
|
|
15621
|
-
.then(() => {
|
|
15622
|
-
if (this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
15623
|
-
this._replicatorsReconciled = true;
|
|
15624
|
-
}
|
|
15625
|
-
})
|
|
15626
15517
|
.catch((error) => {
|
|
15627
15518
|
if (isNotStartedError(error as Error)) {
|
|
15628
15519
|
return;
|
|
@@ -15863,7 +15754,7 @@ export class SharedLog<
|
|
|
15863
15754
|
const peerSession = this._peerSessions.current(peerHash);
|
|
15864
15755
|
const preserveV2Session =
|
|
15865
15756
|
peerSession?.phase === "open" && peerSession.isActive();
|
|
15866
|
-
if (
|
|
15757
|
+
if (!preserveV2Session) {
|
|
15867
15758
|
this.cancelReplicationInfoRequests(peerHash);
|
|
15868
15759
|
}
|
|
15869
15760
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
@@ -15895,11 +15786,8 @@ export class SharedLog<
|
|
|
15895
15786
|
|
|
15896
15787
|
private advanceReplicationInfoRecoveryEpoch(peerHash: string) {
|
|
15897
15788
|
// Handlers admitted before a successful peer removal must not restore state
|
|
15898
|
-
// when they eventually reach the apply lane.
|
|
15899
|
-
// ordering watermark with the local epoch so a later arrival can be
|
|
15900
|
-
// accepted without comparing its clock to this receiver's clock.
|
|
15789
|
+
// when they eventually reach the apply lane.
|
|
15901
15790
|
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
15902
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
15903
15791
|
const peerSession = this._peerSessions.current(peerHash);
|
|
15904
15792
|
if (peerSession?.phase === "open") {
|
|
15905
15793
|
this._v2Receive.advanceRecovery({
|
|
@@ -16788,15 +16676,6 @@ export class SharedLog<
|
|
|
16788
16676
|
firstError ??= error;
|
|
16789
16677
|
}
|
|
16790
16678
|
};
|
|
16791
|
-
// A borsh-deserialized instance that is closed before ever being opened
|
|
16792
|
-
// has no coordinators (they are created in open()); the old inline code
|
|
16793
|
-
// only touched plain fields here and never threw.
|
|
16794
|
-
captureSync(() =>
|
|
16795
|
-
this._announcements?.cancelCurrentReplicationStateAnnouncementRetry(),
|
|
16796
|
-
);
|
|
16797
|
-
if (this._announcements) {
|
|
16798
|
-
this._announcements.replicationAnnouncementRetryDebounced = undefined;
|
|
16799
|
-
}
|
|
16800
16679
|
captureSync(() => {
|
|
16801
16680
|
if (this._wireSyncSession) {
|
|
16802
16681
|
this._wireSyncSession.unregisterTopic(this.topic);
|
|
@@ -16890,7 +16769,6 @@ export class SharedLog<
|
|
|
16890
16769
|
captureSync(() => {
|
|
16891
16770
|
this._pendingIHave?.clear();
|
|
16892
16771
|
this._pendingIHaveCallbacks?.clear();
|
|
16893
|
-
this.latestReplicationInfoMessage?.clear();
|
|
16894
16772
|
this._peerSessions?.clearReceiveEpochsForClose();
|
|
16895
16773
|
this._peerSessions?.clearCleanupGatesForClose();
|
|
16896
16774
|
this._activeReceiveHandlersByPeer?.clear();
|
|
@@ -16991,7 +16869,6 @@ export class SharedLog<
|
|
|
16991
16869
|
await pruneRemoveTerminalFence.drained;
|
|
16992
16870
|
await this.drainPendingIHaveCallbacks();
|
|
16993
16871
|
this.ensureNativeDurabilityRuntimeState();
|
|
16994
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
16995
16872
|
} catch (error) {
|
|
16996
16873
|
// The terminal preamble has already disabled parent attachments and the
|
|
16997
16874
|
// network lifecycle. Keep mutation admission fenced for an exact retry.
|
|
@@ -17035,19 +16912,7 @@ export class SharedLog<
|
|
|
17035
16912
|
}
|
|
17036
16913
|
}, 2_000);
|
|
17037
16914
|
try {
|
|
17038
|
-
|
|
17039
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
17040
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
17041
|
-
resets.push(
|
|
17042
|
-
this.rpc
|
|
17043
|
-
.send(reset, {
|
|
17044
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
17045
|
-
signal: abort.signal,
|
|
17046
|
-
})
|
|
17047
|
-
.catch(() => {}),
|
|
17048
|
-
);
|
|
17049
|
-
}
|
|
17050
|
-
await Promise.all(resets);
|
|
16915
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
17051
16916
|
} finally {
|
|
17052
16917
|
clearTimeout(abortTimer);
|
|
17053
16918
|
}
|
|
@@ -17140,7 +17005,6 @@ export class SharedLog<
|
|
|
17140
17005
|
await replicationRangeTerminalFence.drained;
|
|
17141
17006
|
await pruneRemoveTerminalFence.drained;
|
|
17142
17007
|
await this.drainPendingIHaveCallbacks();
|
|
17143
|
-
this._announcements.cancelCurrentReplicationStateAnnouncementRetry();
|
|
17144
17008
|
} catch (error) {
|
|
17145
17009
|
// The terminal preamble is not safely reversible. Preserve the fence until
|
|
17146
17010
|
// a retry finishes cleanup.
|
|
@@ -17168,19 +17032,7 @@ export class SharedLog<
|
|
|
17168
17032
|
}
|
|
17169
17033
|
}, 2_000);
|
|
17170
17034
|
try {
|
|
17171
|
-
|
|
17172
|
-
const resets = [this._v2Send.sendTerminalReset(abort.signal)];
|
|
17173
|
-
if (this.legacyReplicationInfoEnabled) {
|
|
17174
|
-
resets.push(
|
|
17175
|
-
this.rpc
|
|
17176
|
-
.send(reset, {
|
|
17177
|
-
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
17178
|
-
signal: abort.signal,
|
|
17179
|
-
})
|
|
17180
|
-
.catch(() => {}),
|
|
17181
|
-
);
|
|
17182
|
-
}
|
|
17183
|
-
await Promise.all(resets);
|
|
17035
|
+
await this._v2Send.sendTerminalReset(abort.signal);
|
|
17184
17036
|
} finally {
|
|
17185
17037
|
clearTimeout(abortTimer);
|
|
17186
17038
|
}
|
|
@@ -17707,16 +17559,16 @@ export class SharedLog<
|
|
|
17707
17559
|
throw new Error("Missing from in update role message");
|
|
17708
17560
|
}
|
|
17709
17561
|
if (
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
msg instanceof StoppedReplicating)
|
|
17562
|
+
msg instanceof RequestReplicationInfoMessage ||
|
|
17563
|
+
msg instanceof ResponseRoleMessage ||
|
|
17564
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
17565
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
17566
|
+
msg instanceof StoppedReplicating
|
|
17716
17567
|
) {
|
|
17717
|
-
// These variants remain registered decode tombstones, but
|
|
17718
|
-
//
|
|
17719
|
-
//
|
|
17568
|
+
// These variants remain registered decode tombstones, but logs fail
|
|
17569
|
+
// closed unconditionally before leases, synchronizer work, liveness,
|
|
17570
|
+
// watermarks or mutations (B12: the compatibility opens that once
|
|
17571
|
+
// admitted them reject at open()).
|
|
17720
17572
|
return;
|
|
17721
17573
|
}
|
|
17722
17574
|
// Snapshot receive ownership before any async handler gets a chance to
|
|
@@ -17757,22 +17609,14 @@ export class SharedLog<
|
|
|
17757
17609
|
this.captureReplicationOwnershipLifecycle();
|
|
17758
17610
|
const receiveReplicationInfoReceiveEpoch =
|
|
17759
17611
|
this._peerSessions.receiveEpoch(receiveFromHash);
|
|
17760
|
-
if (msg instanceof ResponseRoleMessage) {
|
|
17761
|
-
msg = msg.toReplicationInfoMessage(); // migration
|
|
17762
|
-
}
|
|
17763
17612
|
if (
|
|
17764
|
-
msg instanceof AllReplicatingSegmentsMessage ||
|
|
17765
|
-
msg instanceof AddedReplicationSegmentMessage ||
|
|
17766
17613
|
msg instanceof FullReplicationInfoV2Message ||
|
|
17767
17614
|
msg instanceof AddedReplicationInfoV2Message
|
|
17768
17615
|
) {
|
|
17769
17616
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
17770
17617
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
17771
17618
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
17772
|
-
} else if (
|
|
17773
|
-
msg instanceof StoppedReplicating ||
|
|
17774
|
-
msg instanceof StoppedReplicationInfoV2Message
|
|
17775
|
-
) {
|
|
17619
|
+
} else if (msg instanceof StoppedReplicationInfoV2Message) {
|
|
17776
17620
|
// Bound the raw decoded vector before deduplication can hide the
|
|
17777
17621
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
17778
17622
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
@@ -17780,10 +17624,7 @@ export class SharedLog<
|
|
|
17780
17624
|
if (
|
|
17781
17625
|
!context.from.equals(this.node.identity.publicKey) &&
|
|
17782
17626
|
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
17783
|
-
!isReplicationInfoV2Message(msg)
|
|
17784
|
-
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
17785
|
-
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
17786
|
-
!(msg instanceof StoppedReplicating)
|
|
17627
|
+
!isReplicationInfoV2Message(msg)
|
|
17787
17628
|
) {
|
|
17788
17629
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
17789
17630
|
}
|
|
@@ -19498,23 +19339,6 @@ export class SharedLog<
|
|
|
19498
19339
|
});
|
|
19499
19340
|
} else if (msg instanceof ReplicationPingMessage) {
|
|
19500
19341
|
// No-op: used as an ACKed unicast liveness probe.
|
|
19501
|
-
} else if (msg instanceof RequestReplicationInfoMessage) {
|
|
19502
|
-
await this.handleRequestReplicationInfo(
|
|
19503
|
-
msg,
|
|
19504
|
-
laneRequestContext,
|
|
19505
|
-
lane,
|
|
19506
|
-
);
|
|
19507
|
-
} else if (
|
|
19508
|
-
msg instanceof AllReplicatingSegmentsMessage ||
|
|
19509
|
-
msg instanceof AddedReplicationSegmentMessage
|
|
19510
|
-
) {
|
|
19511
|
-
await this.handleReplicationInfoAnnouncement(
|
|
19512
|
-
msg,
|
|
19513
|
-
laneRequestContext,
|
|
19514
|
-
lane,
|
|
19515
|
-
);
|
|
19516
|
-
} else if (msg instanceof StoppedReplicating) {
|
|
19517
|
-
await this.handleStoppedReplicating(msg, laneRequestContext, lane);
|
|
19518
19342
|
} else {
|
|
19519
19343
|
throw new Error("Unexpected message");
|
|
19520
19344
|
}
|
|
@@ -19743,113 +19567,6 @@ export class SharedLog<
|
|
|
19743
19567
|
}
|
|
19744
19568
|
}
|
|
19745
19569
|
|
|
19746
|
-
private async handleRequestReplicationInfo(
|
|
19747
|
-
_msg: RequestReplicationInfoMessage,
|
|
19748
|
-
context: ReceiveRequestContext,
|
|
19749
|
-
lane: ReceiveLaneContext,
|
|
19750
|
-
): Promise<void> {
|
|
19751
|
-
const receiveFromHash = lane.fromHash;
|
|
19752
|
-
const receiveSession = lane.session;
|
|
19753
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
19754
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
19755
|
-
return;
|
|
19756
|
-
}
|
|
19757
|
-
const replicationLifecycleController =
|
|
19758
|
-
receiveReplicationLifecycleController;
|
|
19759
|
-
if (
|
|
19760
|
-
!replicationLifecycleController ||
|
|
19761
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19762
|
-
receiveFromHash,
|
|
19763
|
-
receiveSession,
|
|
19764
|
-
replicationLifecycleController,
|
|
19765
|
-
)
|
|
19766
|
-
) {
|
|
19767
|
-
return;
|
|
19768
|
-
}
|
|
19769
|
-
|
|
19770
|
-
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
19771
|
-
try {
|
|
19772
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
19773
|
-
} catch (error) {
|
|
19774
|
-
if (
|
|
19775
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19776
|
-
receiveFromHash,
|
|
19777
|
-
receiveSession,
|
|
19778
|
-
replicationLifecycleController,
|
|
19779
|
-
) &&
|
|
19780
|
-
isNotStartedError(error as Error)
|
|
19781
|
-
) {
|
|
19782
|
-
return;
|
|
19783
|
-
}
|
|
19784
|
-
throw error;
|
|
19785
|
-
}
|
|
19786
|
-
if (
|
|
19787
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19788
|
-
receiveFromHash,
|
|
19789
|
-
receiveSession,
|
|
19790
|
-
replicationLifecycleController,
|
|
19791
|
-
)
|
|
19792
|
-
) {
|
|
19793
|
-
return;
|
|
19794
|
-
}
|
|
19795
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
19796
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
19797
|
-
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
19798
|
-
|
|
19799
|
-
await this.rpc
|
|
19800
|
-
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
19801
|
-
mode: new AcknowledgeDelivery({
|
|
19802
|
-
to: [context.from],
|
|
19803
|
-
redundancy: 1,
|
|
19804
|
-
}),
|
|
19805
|
-
signal: replicationLifecycleController.signal,
|
|
19806
|
-
})
|
|
19807
|
-
.catch((error) =>
|
|
19808
|
-
this.handleReplicationLifecycleSendError(
|
|
19809
|
-
error,
|
|
19810
|
-
replicationLifecycleController,
|
|
19811
|
-
),
|
|
19812
|
-
);
|
|
19813
|
-
if (
|
|
19814
|
-
!this._peerSessions.isReceiveAdmissionOpen(
|
|
19815
|
-
receiveFromHash,
|
|
19816
|
-
receiveSession,
|
|
19817
|
-
replicationLifecycleController,
|
|
19818
|
-
)
|
|
19819
|
-
) {
|
|
19820
|
-
return;
|
|
19821
|
-
}
|
|
19822
|
-
|
|
19823
|
-
// for backwards compatibility (v8) remove this when we are sure that all nodes are v9+
|
|
19824
|
-
if (this.v8Behaviour) {
|
|
19825
|
-
const role = this.getRoleFromReplicationSegments(replicationSegments);
|
|
19826
|
-
if (role instanceof Replicator) {
|
|
19827
|
-
const fixedSettings = !this._isAdaptiveReplicating;
|
|
19828
|
-
if (fixedSettings) {
|
|
19829
|
-
await this.rpc
|
|
19830
|
-
.send(
|
|
19831
|
-
new ResponseRoleMessage({
|
|
19832
|
-
role,
|
|
19833
|
-
}),
|
|
19834
|
-
{
|
|
19835
|
-
mode: new SilentDelivery({
|
|
19836
|
-
to: [context.from],
|
|
19837
|
-
redundancy: 1,
|
|
19838
|
-
}),
|
|
19839
|
-
signal: replicationLifecycleController.signal,
|
|
19840
|
-
},
|
|
19841
|
-
)
|
|
19842
|
-
.catch((error) =>
|
|
19843
|
-
this.handleReplicationLifecycleSendError(
|
|
19844
|
-
error,
|
|
19845
|
-
replicationLifecycleController,
|
|
19846
|
-
),
|
|
19847
|
-
);
|
|
19848
|
-
}
|
|
19849
|
-
}
|
|
19850
|
-
}
|
|
19851
|
-
}
|
|
19852
|
-
|
|
19853
19570
|
private async handleReplicationInfoV2Announcement(
|
|
19854
19571
|
msg: ReplicationInfoV2Message,
|
|
19855
19572
|
context: ReceiveRequestContext,
|
|
@@ -19926,7 +19643,7 @@ export class SharedLog<
|
|
|
19926
19643
|
reset: msg instanceof FullReplicationInfoV2Message,
|
|
19927
19644
|
checkDuplicates: true,
|
|
19928
19645
|
timestamp: Number(context.message.header.timestamp),
|
|
19929
|
-
|
|
19646
|
+
allowOrderedReplacementPairs:
|
|
19930
19647
|
msg instanceof AddedReplicationInfoV2Message,
|
|
19931
19648
|
shouldApply: () => {
|
|
19932
19649
|
mutationGateChecked = true;
|
|
@@ -20029,252 +19746,12 @@ export class SharedLog<
|
|
|
20029
19746
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
20030
19747
|
// so recovery re-solicitation may restart from the base interval.
|
|
20031
19748
|
this.resetReplicationInfoV2RecoveryEscalation(fromHash);
|
|
20032
|
-
if (
|
|
20033
|
-
msg instanceof FullReplicationInfoV2Message &&
|
|
20034
|
-
this.legacyReplicationInfoEnabled
|
|
20035
|
-
) {
|
|
20036
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
20037
|
-
}
|
|
20038
19749
|
});
|
|
20039
19750
|
} finally {
|
|
20040
19751
|
this._v2Receive.release(admission);
|
|
20041
19752
|
}
|
|
20042
19753
|
}
|
|
20043
19754
|
|
|
20044
|
-
private async handleReplicationInfoAnnouncement(
|
|
20045
|
-
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
20046
|
-
context: ReceiveRequestContext,
|
|
20047
|
-
lane: ReceiveLaneContext,
|
|
20048
|
-
): Promise<void> {
|
|
20049
|
-
const receiveFromHash = lane.fromHash;
|
|
20050
|
-
const receiveSession = lane.session;
|
|
20051
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
20052
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
20053
|
-
const peerReceiveLease = lane.lease;
|
|
20054
|
-
if (context.from.equals(this.node.identity.publicKey)) {
|
|
20055
|
-
return;
|
|
20056
|
-
}
|
|
20057
|
-
|
|
20058
|
-
const replicationInfoMessage = msg as
|
|
20059
|
-
| AllReplicatingSegmentsMessage
|
|
20060
|
-
| AddedReplicationSegmentMessage;
|
|
20061
|
-
|
|
20062
|
-
// Process replication updates even if the sender isn't yet considered "ready" by
|
|
20063
|
-
// `Program.waitFor()`. Dropping these messages can lead to missing replicator info
|
|
20064
|
-
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
20065
|
-
const from = context.from!;
|
|
20066
|
-
const fromHash = from.hashcode();
|
|
20067
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20068
|
-
if (receiveSession) {
|
|
20069
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20070
|
-
peerHash: fromHash,
|
|
20071
|
-
peerSession: receiveSession,
|
|
20072
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20073
|
-
senderTransportSession: context.message.header.session,
|
|
20074
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20075
|
-
message: msg,
|
|
20076
|
-
});
|
|
20077
|
-
}
|
|
20078
|
-
return;
|
|
20079
|
-
}
|
|
20080
|
-
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
20081
|
-
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
20082
|
-
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
20083
|
-
// relocated `===`-with-`?? null`. The DELIBERATE absence of a
|
|
20084
|
-
// subscription-epoch term is preserved: the lease already validated
|
|
20085
|
-
// it, and the in-lane recheck owns post-await staleness.
|
|
20086
|
-
if (
|
|
20087
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20088
|
-
receiveReplicationLifecycleController,
|
|
20089
|
-
) ||
|
|
20090
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20091
|
-
receiveFromHash,
|
|
20092
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20093
|
-
) ||
|
|
20094
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20095
|
-
) {
|
|
20096
|
-
return;
|
|
20097
|
-
}
|
|
20098
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
20099
|
-
peerReceiveLease.release();
|
|
20100
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
20101
|
-
try {
|
|
20102
|
-
// The peer may have unsubscribed after this message was queued.
|
|
20103
|
-
// In-lane gate: lifecycle -> subscription-epoch -> receive-epoch
|
|
20104
|
-
// -> blocked, term for term as before the session migration.
|
|
20105
|
-
if (
|
|
20106
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20107
|
-
receiveReplicationLifecycleController,
|
|
20108
|
-
) ||
|
|
20109
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
20110
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20111
|
-
fromHash,
|
|
20112
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20113
|
-
) ||
|
|
20114
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20115
|
-
) {
|
|
20116
|
-
return;
|
|
20117
|
-
}
|
|
20118
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20119
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20120
|
-
peerHash: fromHash,
|
|
20121
|
-
peerSession: receiveSession,
|
|
20122
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20123
|
-
senderTransportSession: context.message.header.session,
|
|
20124
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20125
|
-
message: msg,
|
|
20126
|
-
});
|
|
20127
|
-
return;
|
|
20128
|
-
}
|
|
20129
|
-
|
|
20130
|
-
// Process in-order to avoid races where repeated reset messages arrive
|
|
20131
|
-
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
20132
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
20133
|
-
if (prev && prev > messageTimestamp) {
|
|
20134
|
-
return;
|
|
20135
|
-
}
|
|
20136
|
-
|
|
20137
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
20138
|
-
|
|
20139
|
-
if (this.closed) {
|
|
20140
|
-
return;
|
|
20141
|
-
}
|
|
20142
|
-
|
|
20143
|
-
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
20144
|
-
const result = await this.addReplicationRange(
|
|
20145
|
-
replicationInfoMessage.segments.map((x) =>
|
|
20146
|
-
x.toReplicationRangeIndexable(from),
|
|
20147
|
-
),
|
|
20148
|
-
from,
|
|
20149
|
-
{
|
|
20150
|
-
reset,
|
|
20151
|
-
checkDuplicates: true,
|
|
20152
|
-
timestamp: Number(messageTimestamp),
|
|
20153
|
-
allowLegacyOrderedReplacementPairs:
|
|
20154
|
-
msg instanceof AddedReplicationSegmentMessage,
|
|
20155
|
-
},
|
|
20156
|
-
);
|
|
20157
|
-
if (result === undefined) {
|
|
20158
|
-
return;
|
|
20159
|
-
}
|
|
20160
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
20161
|
-
|
|
20162
|
-
// If the peer reports any replication segments, stop re-requesting.
|
|
20163
|
-
// (Empty reports can be transient during startup.)
|
|
20164
|
-
if (replicationInfoMessage.segments.length > 0) {
|
|
20165
|
-
this.cancelReplicationInfoRequests(fromHash);
|
|
20166
|
-
}
|
|
20167
|
-
} catch (e) {
|
|
20168
|
-
if (isNotStartedError(e as Error)) {
|
|
20169
|
-
return;
|
|
20170
|
-
}
|
|
20171
|
-
logger.error(
|
|
20172
|
-
`Failed to apply replication settings from '${fromHash}': ${
|
|
20173
|
-
(e as any)?.message ?? e
|
|
20174
|
-
}`,
|
|
20175
|
-
);
|
|
20176
|
-
}
|
|
20177
|
-
});
|
|
20178
|
-
}
|
|
20179
|
-
|
|
20180
|
-
private async handleStoppedReplicating(
|
|
20181
|
-
msg: StoppedReplicating,
|
|
20182
|
-
context: ReceiveRequestContext,
|
|
20183
|
-
lane: ReceiveLaneContext,
|
|
20184
|
-
): Promise<void> {
|
|
20185
|
-
const receiveFromHash = lane.fromHash;
|
|
20186
|
-
const receiveSession = lane.session;
|
|
20187
|
-
const receiveReplicationLifecycleController = lane.lifecycleController;
|
|
20188
|
-
const receiveReplicationInfoReceiveEpoch = lane.receiveEpoch;
|
|
20189
|
-
const peerReceiveLease = lane.lease;
|
|
20190
|
-
const from = context.from!;
|
|
20191
|
-
const segmentIds = msg.segmentIds;
|
|
20192
|
-
if (from.equals(this.node.identity.publicKey)) {
|
|
20193
|
-
return;
|
|
20194
|
-
}
|
|
20195
|
-
const fromHash = from.hashcode();
|
|
20196
|
-
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20197
|
-
if (receiveSession) {
|
|
20198
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20199
|
-
peerHash: fromHash,
|
|
20200
|
-
peerSession: receiveSession,
|
|
20201
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20202
|
-
senderTransportSession: context.message.header.session,
|
|
20203
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20204
|
-
message: msg,
|
|
20205
|
-
});
|
|
20206
|
-
}
|
|
20207
|
-
return;
|
|
20208
|
-
}
|
|
20209
|
-
// Same pre-lane gate shape as Added/All above (and the same
|
|
20210
|
-
// intentional absence of a subscription-epoch term).
|
|
20211
|
-
if (
|
|
20212
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20213
|
-
receiveReplicationLifecycleController,
|
|
20214
|
-
) ||
|
|
20215
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20216
|
-
receiveFromHash,
|
|
20217
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20218
|
-
) ||
|
|
20219
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20220
|
-
) {
|
|
20221
|
-
return;
|
|
20222
|
-
}
|
|
20223
|
-
const messageTimestamp = context.message.header.timestamp;
|
|
20224
|
-
peerReceiveLease.release();
|
|
20225
|
-
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
20226
|
-
if (
|
|
20227
|
-
!this._instanceLifecycle!.isMembershipActiveFor(
|
|
20228
|
-
receiveReplicationLifecycleController,
|
|
20229
|
-
) ||
|
|
20230
|
-
!this._peerSessions.isCurrent(fromHash, receiveSession) ||
|
|
20231
|
-
!this._peerSessions.isReceiveEpochCurrent(
|
|
20232
|
-
fromHash,
|
|
20233
|
-
receiveReplicationInfoReceiveEpoch,
|
|
20234
|
-
) ||
|
|
20235
|
-
this._peerSessions.isReplicationInfoBlocked(fromHash)
|
|
20236
|
-
) {
|
|
20237
|
-
return;
|
|
20238
|
-
}
|
|
20239
|
-
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20240
|
-
this._v2Receive.noteLegacyAnnouncement({
|
|
20241
|
-
peerHash: fromHash,
|
|
20242
|
-
peerSession: receiveSession,
|
|
20243
|
-
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20244
|
-
senderTransportSession: context.message.header.session,
|
|
20245
|
-
transportTimestamp: context.message.header.timestamp,
|
|
20246
|
-
message: msg,
|
|
20247
|
-
});
|
|
20248
|
-
return;
|
|
20249
|
-
}
|
|
20250
|
-
|
|
20251
|
-
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
20252
|
-
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
20253
|
-
return;
|
|
20254
|
-
}
|
|
20255
|
-
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
20256
|
-
if (this.closed) {
|
|
20257
|
-
return;
|
|
20258
|
-
}
|
|
20259
|
-
|
|
20260
|
-
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(
|
|
20261
|
-
segmentIds,
|
|
20262
|
-
from,
|
|
20263
|
-
);
|
|
20264
|
-
|
|
20265
|
-
await this.removeReplicationRanges(rangesToRemove, from);
|
|
20266
|
-
this._liveness.markReplicatorActivity(fromHash);
|
|
20267
|
-
const timestamp = BigInt(+new Date());
|
|
20268
|
-
for (const range of rangesToRemove) {
|
|
20269
|
-
this.replicationChangeDebounceFn.add({
|
|
20270
|
-
range,
|
|
20271
|
-
type: "removed",
|
|
20272
|
-
timestamp,
|
|
20273
|
-
});
|
|
20274
|
-
}
|
|
20275
|
-
});
|
|
20276
|
-
}
|
|
20277
|
-
|
|
20278
19755
|
async calculateTotalParticipation(options?: { sum?: boolean }) {
|
|
20279
19756
|
if (options?.sum) {
|
|
20280
19757
|
const ranges = await this.replicationIndex.iterate().all();
|
|
@@ -20736,34 +20213,20 @@ export class SharedLog<
|
|
|
20736
20213
|
|
|
20737
20214
|
requestAttempts++;
|
|
20738
20215
|
|
|
20739
|
-
|
|
20740
|
-
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20747
|
-
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20751
|
-
|
|
20752
|
-
|
|
20753
|
-
const peerSession = this._peerSessions.current(peerHash);
|
|
20754
|
-
if (peerSession?.phase === "open") {
|
|
20755
|
-
this._v2Receive.resumeParkedRequest({
|
|
20756
|
-
peerHash,
|
|
20757
|
-
peerSession,
|
|
20758
|
-
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
20759
|
-
});
|
|
20760
|
-
} else if (peerSession === null || peerSession.phase === "departing") {
|
|
20761
|
-
// A peer can be known to routing before its SharedLog topic
|
|
20762
|
-
// subscription has been observed. Legacy requests used to bootstrap
|
|
20763
|
-
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
20764
|
-
// before it can create a fenced PeerSession and request a Full.
|
|
20765
|
-
requestSubscriberSnapshot();
|
|
20766
|
-
}
|
|
20216
|
+
const peerHash = key.hashcode();
|
|
20217
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
20218
|
+
if (peerSession?.phase === "open") {
|
|
20219
|
+
this._v2Receive.resumeParkedRequest({
|
|
20220
|
+
peerHash,
|
|
20221
|
+
peerSession,
|
|
20222
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
20223
|
+
});
|
|
20224
|
+
} else if (peerSession === null || peerSession.phase === "departing") {
|
|
20225
|
+
// A peer can be known to routing before its SharedLog topic
|
|
20226
|
+
// subscription has been observed. Legacy requests used to bootstrap
|
|
20227
|
+
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
20228
|
+
// before it can create a fenced PeerSession and request a Full.
|
|
20229
|
+
requestSubscriberSnapshot();
|
|
20767
20230
|
}
|
|
20768
20231
|
|
|
20769
20232
|
if (requestAttempts < maxRequestAttempts) {
|
|
@@ -23507,7 +22970,7 @@ export class SharedLog<
|
|
|
23507
22970
|
*/
|
|
23508
22971
|
private resetReplicationInfoV2RecoveryEscalation(peerHash: string) {
|
|
23509
22972
|
const state = this._replicationInfoRequestByPeer.get(peerHash);
|
|
23510
|
-
if (!state
|
|
22973
|
+
if (!state) {
|
|
23511
22974
|
return;
|
|
23512
22975
|
}
|
|
23513
22976
|
state.attempts = 0;
|
|
@@ -23590,7 +23053,11 @@ export class SharedLog<
|
|
|
23590
23053
|
}
|
|
23591
23054
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
23592
23055
|
if (
|
|
23593
|
-
!this._v2Receive.isRequestParked({
|
|
23056
|
+
!this._v2Receive.isRequestParked({
|
|
23057
|
+
peerHash,
|
|
23058
|
+
peerSession,
|
|
23059
|
+
receiveEpoch,
|
|
23060
|
+
})
|
|
23594
23061
|
) {
|
|
23595
23062
|
// Active, or a bounded request cycle is still running its own
|
|
23596
23063
|
// exponential retries. Keep polling for the next park.
|
|
@@ -23624,6 +23091,13 @@ export class SharedLog<
|
|
|
23624
23091
|
tick();
|
|
23625
23092
|
}
|
|
23626
23093
|
|
|
23094
|
+
/**
|
|
23095
|
+
* Collapsed B12 shell: the legacy request-polling body (bounded
|
|
23096
|
+
* RequestReplicationInfoMessage ticks) is deleted; V2 recovery is the
|
|
23097
|
+
* only scheduler. Retained as a named seam rather than inlined at the
|
|
23098
|
+
* callers because the liveness monitor wiring and several suites
|
|
23099
|
+
* stub/spy it by name.
|
|
23100
|
+
*/
|
|
23627
23101
|
private scheduleReplicationInfoRequests(
|
|
23628
23102
|
peer: PublicSignKey,
|
|
23629
23103
|
replicationLifecycleController = this._instanceLifecycle
|
|
@@ -23635,75 +23109,10 @@ export class SharedLog<
|
|
|
23635
23109
|
) {
|
|
23636
23110
|
return;
|
|
23637
23111
|
}
|
|
23638
|
-
|
|
23639
|
-
|
|
23640
|
-
|
|
23641
|
-
|
|
23642
|
-
);
|
|
23643
|
-
return;
|
|
23644
|
-
}
|
|
23645
|
-
const peerHash = peer.hashcode();
|
|
23646
|
-
const requestStates = this._replicationInfoRequestByPeer;
|
|
23647
|
-
if (requestStates.has(peerHash)) {
|
|
23648
|
-
return;
|
|
23649
|
-
}
|
|
23650
|
-
|
|
23651
|
-
const state: { attempts: number; timer?: ReturnType<typeof setTimeout> } = {
|
|
23652
|
-
attempts: 0,
|
|
23653
|
-
};
|
|
23654
|
-
requestStates.set(peerHash, state);
|
|
23655
|
-
const cancel = () => {
|
|
23656
|
-
if (requestStates.get(peerHash) !== state) {
|
|
23657
|
-
return;
|
|
23658
|
-
}
|
|
23659
|
-
if (state.timer) {
|
|
23660
|
-
clearTimeout(state.timer);
|
|
23661
|
-
}
|
|
23662
|
-
requestStates.delete(peerHash);
|
|
23663
|
-
};
|
|
23664
|
-
|
|
23665
|
-
const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
|
|
23666
|
-
const maxAttempts =
|
|
23667
|
-
this.waitForReplicatorRequestMaxAttempts ??
|
|
23668
|
-
Math.max(
|
|
23669
|
-
WAIT_FOR_REPLICATOR_REQUEST_MIN_ATTEMPTS,
|
|
23670
|
-
Math.ceil(this.waitForReplicatorTimeout / intervalMs),
|
|
23671
|
-
);
|
|
23672
|
-
|
|
23673
|
-
const tick = () => {
|
|
23674
|
-
if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
23675
|
-
cancel();
|
|
23676
|
-
return;
|
|
23677
|
-
}
|
|
23678
|
-
state.attempts++;
|
|
23679
|
-
|
|
23680
|
-
this.rpc
|
|
23681
|
-
.send(new RequestReplicationInfoMessage(), {
|
|
23682
|
-
mode: new AcknowledgeDelivery({ redundancy: 1, to: [peer] }),
|
|
23683
|
-
signal: replicationLifecycleController.signal,
|
|
23684
|
-
})
|
|
23685
|
-
.catch((e) => {
|
|
23686
|
-
// Best-effort: missing peers / unopened RPC should not fail join flows.
|
|
23687
|
-
if (
|
|
23688
|
-
isNotStartedError(e as Error) ||
|
|
23689
|
-
(replicationLifecycleController.signal.aborted &&
|
|
23690
|
-
e instanceof AbortError)
|
|
23691
|
-
) {
|
|
23692
|
-
return;
|
|
23693
|
-
}
|
|
23694
|
-
logger.error(e?.toString?.() ?? String(e));
|
|
23695
|
-
});
|
|
23696
|
-
|
|
23697
|
-
if (state.attempts >= maxAttempts) {
|
|
23698
|
-
cancel();
|
|
23699
|
-
return;
|
|
23700
|
-
}
|
|
23701
|
-
|
|
23702
|
-
state.timer = setTimeout(tick, intervalMs);
|
|
23703
|
-
state.timer.unref?.();
|
|
23704
|
-
};
|
|
23705
|
-
|
|
23706
|
-
tick();
|
|
23112
|
+
this.scheduleReplicationInfoV2Recovery(
|
|
23113
|
+
peer,
|
|
23114
|
+
replicationLifecycleController,
|
|
23115
|
+
);
|
|
23707
23116
|
}
|
|
23708
23117
|
|
|
23709
23118
|
async handleSubscriptionChange(
|
|
@@ -23786,10 +23195,6 @@ export class SharedLog<
|
|
|
23786
23195
|
) {
|
|
23787
23196
|
return;
|
|
23788
23197
|
}
|
|
23789
|
-
// The timestamp watermark belongs to the previous subscription epoch.
|
|
23790
|
-
// Sender clocks are not synchronized, so carrying a local unsubscribe
|
|
23791
|
-
// timestamp forward could reject every valid announcement after reconnect.
|
|
23792
|
-
this.latestReplicationInfoMessage.delete(peerHash);
|
|
23793
23198
|
this._pendingReplicatorLeaveByPeer.delete(peerHash);
|
|
23794
23199
|
const openingCapabilities =
|
|
23795
23200
|
this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
@@ -23843,12 +23248,6 @@ export class SharedLog<
|
|
|
23843
23248
|
this.joinWarmup._warmupSessionsByTarget.get(peerHash) ?? null;
|
|
23844
23249
|
this.joinWarmup.cancelJoinWarmupTarget(peerHash);
|
|
23845
23250
|
|
|
23846
|
-
const now = BigInt(+new Date());
|
|
23847
|
-
const previous = this.latestReplicationInfoMessage.get(peerHash);
|
|
23848
|
-
if (!previous || previous < now) {
|
|
23849
|
-
this.latestReplicationInfoMessage.set(peerHash, now);
|
|
23850
|
-
}
|
|
23851
|
-
|
|
23852
23251
|
let removed = false;
|
|
23853
23252
|
try {
|
|
23854
23253
|
// Unsubscribe can race with the peer's final replication reset message.
|
|
@@ -23899,116 +23298,23 @@ export class SharedLog<
|
|
|
23899
23298
|
|
|
23900
23299
|
// Decode, sender and authenticated apply readiness are separate bits. An
|
|
23901
23300
|
// ACKed capability advert is the local half of receiver-led negotiation;
|
|
23902
|
-
// readiness is promoted
|
|
23903
|
-
// This
|
|
23904
|
-
//
|
|
23301
|
+
// readiness is promoted through the coordinator once the ACK arrives.
|
|
23302
|
+
// This keeps capability ACK latency off the subscription callback's
|
|
23303
|
+
// critical path.
|
|
23905
23304
|
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
23909
|
-
|
|
23910
|
-
|
|
23911
|
-
|
|
23912
|
-
|
|
23913
|
-
|
|
23914
|
-
|
|
23915
|
-
|
|
23916
|
-
|
|
23917
|
-
|
|
23918
|
-
|
|
23919
|
-
publicKey,
|
|
23920
|
-
replicationLifecycleController,
|
|
23921
|
-
);
|
|
23922
|
-
return;
|
|
23923
|
-
}
|
|
23924
|
-
|
|
23925
|
-
try {
|
|
23926
|
-
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
23927
|
-
try {
|
|
23928
|
-
replicationSegments = await this.getMyReplicationSegments();
|
|
23929
|
-
} catch (error) {
|
|
23930
|
-
if (
|
|
23931
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
23932
|
-
isNotStartedError(error as Error)
|
|
23933
|
-
) {
|
|
23934
|
-
return;
|
|
23935
|
-
}
|
|
23936
|
-
throw error;
|
|
23937
|
-
}
|
|
23938
|
-
if (
|
|
23939
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
23940
|
-
!ownsSubscriptionEpoch()
|
|
23941
|
-
) {
|
|
23942
|
-
return;
|
|
23943
|
-
}
|
|
23944
|
-
if (replicationSegments.length > 0) {
|
|
23945
|
-
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
23946
|
-
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
23947
|
-
await this.rpc
|
|
23948
|
-
.send(
|
|
23949
|
-
new AllReplicatingSegmentsMessage({
|
|
23950
|
-
segments,
|
|
23951
|
-
}),
|
|
23952
|
-
{
|
|
23953
|
-
mode: new AcknowledgeDelivery({
|
|
23954
|
-
redundancy: 1,
|
|
23955
|
-
to: [publicKey],
|
|
23956
|
-
}),
|
|
23957
|
-
signal: replicationLifecycleController.signal,
|
|
23958
|
-
},
|
|
23959
|
-
)
|
|
23960
|
-
.catch((error) =>
|
|
23961
|
-
this.handleReplicationLifecycleSendError(
|
|
23962
|
-
error,
|
|
23963
|
-
replicationLifecycleController,
|
|
23964
|
-
),
|
|
23965
|
-
);
|
|
23966
|
-
if (
|
|
23967
|
-
!this.isReplicationLifecycleActive(replicationLifecycleController) ||
|
|
23968
|
-
!ownsSubscriptionEpoch()
|
|
23969
|
-
) {
|
|
23970
|
-
return;
|
|
23971
|
-
}
|
|
23972
|
-
|
|
23973
|
-
if (this.v8Behaviour) {
|
|
23974
|
-
// for backwards compatibility
|
|
23975
|
-
await this.rpc
|
|
23976
|
-
.send(
|
|
23977
|
-
new ResponseRoleMessage({
|
|
23978
|
-
role: this.getRoleFromReplicationSegments(replicationSegments),
|
|
23979
|
-
}),
|
|
23980
|
-
{
|
|
23981
|
-
mode: new AcknowledgeDelivery({
|
|
23982
|
-
redundancy: 1,
|
|
23983
|
-
to: [publicKey],
|
|
23984
|
-
}),
|
|
23985
|
-
signal: replicationLifecycleController.signal,
|
|
23986
|
-
},
|
|
23987
|
-
)
|
|
23988
|
-
.catch((error) =>
|
|
23989
|
-
this.handleReplicationLifecycleSendError(
|
|
23990
|
-
error,
|
|
23991
|
-
replicationLifecycleController,
|
|
23992
|
-
),
|
|
23993
|
-
);
|
|
23994
|
-
}
|
|
23995
|
-
}
|
|
23996
|
-
|
|
23997
|
-
// Keep legacy request-based discovery independent of the capability ACK.
|
|
23998
|
-
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
23999
|
-
// issues where we may miss the remote peer's initial announcement.
|
|
24000
|
-
if (
|
|
24001
|
-
this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
24002
|
-
ownsSubscriptionEpoch()
|
|
24003
|
-
) {
|
|
24004
|
-
this.scheduleReplicationInfoRequests(
|
|
24005
|
-
publicKey,
|
|
24006
|
-
replicationLifecycleController,
|
|
24007
|
-
);
|
|
24008
|
-
}
|
|
24009
|
-
} finally {
|
|
24010
|
-
localCapabilityAdvertisement.releaseLegacyBarrier();
|
|
24011
|
-
}
|
|
23305
|
+
// B12: there is no legacy startup work to order ahead of RequestV2, so
|
|
23306
|
+
// the two-phase legacy barrier is gone — an ACKed advert promotes
|
|
23307
|
+
// readiness through the coordinator as soon as it lands.
|
|
23308
|
+
this._v2Receive.advertiseLocalCapability({
|
|
23309
|
+
target: publicKey,
|
|
23310
|
+
peerSession: expectedSubscriptionEpoch,
|
|
23311
|
+
receiveEpoch,
|
|
23312
|
+
signal: replicationLifecycleController.signal,
|
|
23313
|
+
});
|
|
23314
|
+
this.scheduleReplicationInfoV2Recovery(
|
|
23315
|
+
publicKey,
|
|
23316
|
+
replicationLifecycleController,
|
|
23317
|
+
);
|
|
24012
23318
|
}
|
|
24013
23319
|
|
|
24014
23320
|
private getClampedReplicas(customValue?: MinReplicas) {
|
|
@@ -25409,15 +24715,6 @@ export class SharedLog<
|
|
|
25409
24715
|
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
25410
24716
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
25411
24717
|
this._recentRepairDispatch.delete(fromHash);
|
|
25412
|
-
|
|
25413
|
-
// Keep a per-peer timestamp watermark when we observe an unsubscribe. This
|
|
25414
|
-
// prevents late/out-of-order replication-info messages from re-introducing
|
|
25415
|
-
// stale segments for a peer that has already left the topic.
|
|
25416
|
-
const now = BigInt(+new Date());
|
|
25417
|
-
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
25418
|
-
if (!prev || prev < now) {
|
|
25419
|
-
this.latestReplicationInfoMessage.set(fromHash, now);
|
|
25420
|
-
}
|
|
25421
24718
|
this.invalidateSharedLogTopicSubscribersCache();
|
|
25422
24719
|
|
|
25423
24720
|
return this.handleSubscriptionChange(
|
|
@@ -25581,26 +24878,16 @@ export class SharedLog<
|
|
|
25581
24878
|
return false;
|
|
25582
24879
|
}
|
|
25583
24880
|
|
|
25584
|
-
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
|
|
25588
|
-
|
|
25589
|
-
|
|
25590
|
-
|
|
25591
|
-
|
|
25592
|
-
|
|
25593
|
-
|
|
25594
|
-
if (!isCurrent()) return false;
|
|
25595
|
-
} catch (error) {
|
|
25596
|
-
if (
|
|
25597
|
-
isTransientReplicationAnnouncementError(error) &&
|
|
25598
|
-
this._announcements._replicationAnnouncementRetryPending
|
|
25599
|
-
) {
|
|
25600
|
-
return false;
|
|
25601
|
-
}
|
|
25602
|
-
throw error;
|
|
25603
|
-
}
|
|
24881
|
+
await this.startAnnounceReplicating(
|
|
24882
|
+
[dynamicRange],
|
|
24883
|
+
{
|
|
24884
|
+
checkDuplicates: false,
|
|
24885
|
+
reset: false,
|
|
24886
|
+
shouldApply: isCurrent,
|
|
24887
|
+
},
|
|
24888
|
+
ownershipLifecycleController,
|
|
24889
|
+
);
|
|
24890
|
+
if (!isCurrent()) return false;
|
|
25604
24891
|
|
|
25605
24892
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
25606
24893
|
if (isCurrent()) {
|