@peerbit/shared-log 13.2.30 → 13.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/exchange-heads.d.ts +20 -3
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +20 -3
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +200 -31
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +3 -1
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +1 -0
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +107 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -0
- package/dist/src/replication-info-v2-send.js +415 -0
- package/dist/src/replication-info-v2-send.js.map +1 -0
- package/dist/src/replication.d.ts +66 -0
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +219 -2
- package/dist/src/replication.js.map +1 -1
- package/package.json +9 -9
- package/src/exchange-heads.ts +23 -3
- package/src/index.ts +255 -34
- package/src/replication-announcement.ts +10 -4
- package/src/replication-info-v2-send.ts +568 -0
- package/src/replication.ts +138 -1
package/src/index.ts
CHANGED
|
@@ -154,6 +154,9 @@ import {
|
|
|
154
154
|
ResponseIPrune,
|
|
155
155
|
ResponseIPruneV2,
|
|
156
156
|
SYNC_CAPABILITY_RAW_EXCHANGE_HEADS,
|
|
157
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
158
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
159
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
157
160
|
StashBackedRawExchangeHeadsMessage,
|
|
158
161
|
SyncCapabilitiesMessage,
|
|
159
162
|
collectRawExchangeHeadSendPlan,
|
|
@@ -234,6 +237,7 @@ import {
|
|
|
234
237
|
ReplicationAnnouncementCoordinator,
|
|
235
238
|
isTransientReplicationAnnouncementError,
|
|
236
239
|
} from "./replication-announcement.js";
|
|
240
|
+
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
237
241
|
import {
|
|
238
242
|
type ReplicationDomainHash,
|
|
239
243
|
createReplicationDomainHash,
|
|
@@ -257,10 +261,12 @@ import {
|
|
|
257
261
|
type ReplicationLimits,
|
|
258
262
|
ReplicationPingMessage,
|
|
259
263
|
RequestReplicationInfoMessage,
|
|
264
|
+
RequestReplicationInfoV2Message,
|
|
260
265
|
ResponseRoleMessage,
|
|
261
266
|
StoppedReplicating,
|
|
262
267
|
decodeReplicas,
|
|
263
268
|
encodeReplicas,
|
|
269
|
+
isReplicationInfoV2Message,
|
|
264
270
|
maxReplicas,
|
|
265
271
|
} from "./replication.js";
|
|
266
272
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
@@ -2022,7 +2028,12 @@ export class SharedLog<
|
|
|
2022
2028
|
// PeerSession the advert was staged under; promote/delete key off it.
|
|
2023
2029
|
private _openingSyncCapabilitiesByPeer!: Map<
|
|
2024
2030
|
string,
|
|
2025
|
-
{
|
|
2031
|
+
{
|
|
2032
|
+
epoch: object;
|
|
2033
|
+
capabilities: number;
|
|
2034
|
+
transportSession?: bigint;
|
|
2035
|
+
timestamp?: bigint;
|
|
2036
|
+
}
|
|
2026
2037
|
>;
|
|
2027
2038
|
private _onFanoutDataFn?: (arg: any) => void;
|
|
2028
2039
|
private _onFanoutUnicastFn?: (arg: any) => void;
|
|
@@ -3223,6 +3234,7 @@ export class SharedLog<
|
|
|
3223
3234
|
| ReturnType<typeof debounceFixedInterval>
|
|
3224
3235
|
| undefined;
|
|
3225
3236
|
private _announcements!: ReplicationAnnouncementCoordinator<R>;
|
|
3237
|
+
private _v2Send!: ReplicationInfoV2SendCoordinator<R>;
|
|
3226
3238
|
|
|
3227
3239
|
// A fn for debouncing the calls for pruning
|
|
3228
3240
|
pruneDebouncedFn!: DebouncedAccumulatorMap<{
|
|
@@ -3284,6 +3296,10 @@ export class SharedLog<
|
|
|
3284
3296
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
3285
3297
|
// by public key hash. Entries are dropped on unsubscribe/disconnect.
|
|
3286
3298
|
private _peerSyncCapabilities!: Map<string, number>;
|
|
3299
|
+
// Signed transport session carried by the capability envelope. Kept in a
|
|
3300
|
+
// parallel map so existing capability-number consumers remain unchanged.
|
|
3301
|
+
private _peerSyncCapabilitySessions!: Map<string, bigint>;
|
|
3302
|
+
private _peerSyncCapabilityTimestamps!: Map<string, bigint>;
|
|
3287
3303
|
// Pending live raw exchange-head gossip, coalesced per recipient set and
|
|
3288
3304
|
// flushed at the end of the current event-loop turn (or when a batch cap
|
|
3289
3305
|
// is hit). Only used when every recipient advertised raw capability.
|
|
@@ -3353,6 +3369,7 @@ export class SharedLog<
|
|
|
3353
3369
|
this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
3354
3370
|
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) =>
|
|
3355
3371
|
this._announcements.queueCurrentReplicationStateAnnouncementRetry(error),
|
|
3372
|
+
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
3356
3373
|
isClosed: () => this.closed,
|
|
3357
3374
|
getCloseSignal: () => this._closeController.signal,
|
|
3358
3375
|
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
@@ -3372,6 +3389,30 @@ export class SharedLog<
|
|
|
3372
3389
|
});
|
|
3373
3390
|
}
|
|
3374
3391
|
|
|
3392
|
+
private createReplicationInfoV2SendCoordinator(): ReplicationInfoV2SendCoordinator<R> {
|
|
3393
|
+
return new ReplicationInfoV2SendCoordinator<R>({
|
|
3394
|
+
getRpc: () => this.rpc,
|
|
3395
|
+
getSelfKey: () => this.node.identity.publicKey,
|
|
3396
|
+
getSenderTransportSession: () =>
|
|
3397
|
+
BigInt(
|
|
3398
|
+
(this.node.services.pubsub as unknown as { session: number }).session,
|
|
3399
|
+
),
|
|
3400
|
+
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
3401
|
+
validatePersistedReplicationRangeSnapshot: (ranges) =>
|
|
3402
|
+
this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
3403
|
+
isClosed: () => this.closed,
|
|
3404
|
+
isPeerSessionOpen: (peerHash, peerSession) =>
|
|
3405
|
+
this._peerSessions.isCurrent(peerHash, peerSession) &&
|
|
3406
|
+
(peerSession as PeerSession).phase === "open" &&
|
|
3407
|
+
!this._peerSessions.isReplicationInfoBlocked(peerHash) &&
|
|
3408
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash),
|
|
3409
|
+
captureReplicationOwnershipLifecycle: () =>
|
|
3410
|
+
this.captureReplicationOwnershipLifecycle(),
|
|
3411
|
+
isReplicationOwnershipLifecycleActive: (controller) =>
|
|
3412
|
+
this.isRepairLifecycleActive(controller),
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3375
3416
|
private createReplicatorLivenessMonitor(): ReplicatorLivenessMonitor {
|
|
3376
3417
|
return new ReplicatorLivenessMonitor({
|
|
3377
3418
|
// Sweep-driven probes and activity marks dispatch through the owner so
|
|
@@ -3486,6 +3527,8 @@ export class SharedLog<
|
|
|
3486
3527
|
this._appendBackfillPendingByTarget = new Map();
|
|
3487
3528
|
this._topicSubscribersCache = new Map();
|
|
3488
3529
|
this._peerSyncCapabilities = new Map();
|
|
3530
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
3531
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
3489
3532
|
this._liveRawGossipBatches = new Map();
|
|
3490
3533
|
this._liveRawGossipFlushScheduled = false;
|
|
3491
3534
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -3494,6 +3537,7 @@ export class SharedLog<
|
|
|
3494
3537
|
this._replicatorJoinEmitted = new Set();
|
|
3495
3538
|
this._replicatorsReconciled = false;
|
|
3496
3539
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
3540
|
+
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
3497
3541
|
this._announcements = this.createReplicationAnnouncementCoordinator();
|
|
3498
3542
|
this.pendingMaturity = new Map();
|
|
3499
3543
|
this._closeController = new AbortController();
|
|
@@ -3989,6 +4033,97 @@ export class SharedLog<
|
|
|
3989
4033
|
);
|
|
3990
4034
|
}
|
|
3991
4035
|
|
|
4036
|
+
private observePeerSyncCapabilities(properties: {
|
|
4037
|
+
peerHash: string;
|
|
4038
|
+
capabilities: number;
|
|
4039
|
+
transportSession?: bigint;
|
|
4040
|
+
timestamp?: bigint;
|
|
4041
|
+
openingSession?: PeerSession;
|
|
4042
|
+
}): boolean {
|
|
4043
|
+
const {
|
|
4044
|
+
peerHash,
|
|
4045
|
+
capabilities,
|
|
4046
|
+
transportSession,
|
|
4047
|
+
timestamp,
|
|
4048
|
+
openingSession,
|
|
4049
|
+
} = properties;
|
|
4050
|
+
if (openingSession) {
|
|
4051
|
+
const previous = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
4052
|
+
if (
|
|
4053
|
+
transportSession !== undefined &&
|
|
4054
|
+
timestamp !== undefined &&
|
|
4055
|
+
previous?.epoch === openingSession &&
|
|
4056
|
+
previous.transportSession === transportSession
|
|
4057
|
+
) {
|
|
4058
|
+
if (previous.timestamp !== undefined && timestamp < previous.timestamp) {
|
|
4059
|
+
return false;
|
|
4060
|
+
}
|
|
4061
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
4062
|
+
epoch: openingSession,
|
|
4063
|
+
capabilities: previous.capabilities | capabilities,
|
|
4064
|
+
transportSession,
|
|
4065
|
+
timestamp:
|
|
4066
|
+
previous.timestamp === undefined || timestamp > previous.timestamp
|
|
4067
|
+
? timestamp
|
|
4068
|
+
: previous.timestamp,
|
|
4069
|
+
});
|
|
4070
|
+
return true;
|
|
4071
|
+
}
|
|
4072
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
4073
|
+
epoch: openingSession,
|
|
4074
|
+
capabilities,
|
|
4075
|
+
transportSession,
|
|
4076
|
+
timestamp,
|
|
4077
|
+
});
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
if (transportSession === undefined || timestamp === undefined) {
|
|
4082
|
+
// Test/in-process synthetic contexts predate signed envelope captures.
|
|
4083
|
+
// They may exercise capability-number behavior, but can never authorize V2.
|
|
4084
|
+
this._peerSyncCapabilities.set(peerHash, capabilities);
|
|
4085
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
4086
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
4087
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
4088
|
+
return true;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
const previousSession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
4092
|
+
const previousTimestamp = this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
4093
|
+
const sameTransportSession = previousSession === transportSession;
|
|
4094
|
+
if (
|
|
4095
|
+
sameTransportSession &&
|
|
4096
|
+
previousTimestamp !== undefined &&
|
|
4097
|
+
timestamp < previousTimestamp
|
|
4098
|
+
) {
|
|
4099
|
+
return false;
|
|
4100
|
+
}
|
|
4101
|
+
const previousCapabilities =
|
|
4102
|
+
this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
4103
|
+
const nextCapabilities = sameTransportSession
|
|
4104
|
+
? previousCapabilities | capabilities
|
|
4105
|
+
: capabilities;
|
|
4106
|
+
const generationAdvanced =
|
|
4107
|
+
!sameTransportSession ||
|
|
4108
|
+
previousTimestamp === undefined ||
|
|
4109
|
+
timestamp > previousTimestamp ||
|
|
4110
|
+
nextCapabilities !== previousCapabilities;
|
|
4111
|
+
this._peerSyncCapabilities.set(peerHash, nextCapabilities);
|
|
4112
|
+
this._peerSyncCapabilitySessions.set(peerHash, transportSession);
|
|
4113
|
+
this._peerSyncCapabilityTimestamps.set(
|
|
4114
|
+
peerHash,
|
|
4115
|
+
previousTimestamp === undefined ||
|
|
4116
|
+
!sameTransportSession ||
|
|
4117
|
+
timestamp > previousTimestamp
|
|
4118
|
+
? timestamp
|
|
4119
|
+
: previousTimestamp,
|
|
4120
|
+
);
|
|
4121
|
+
if (generationAdvanced) {
|
|
4122
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
4123
|
+
}
|
|
4124
|
+
return true;
|
|
4125
|
+
}
|
|
4126
|
+
|
|
3992
4127
|
/**
|
|
3993
4128
|
* Live append gossip may use the raw exchange-heads path only when we
|
|
3994
4129
|
* opted into raw sync and every remote recipient advertised raw capability
|
|
@@ -13829,6 +13964,8 @@ export class SharedLog<
|
|
|
13829
13964
|
ttl: LEADER_PLAN_CACHE_TTL_MS,
|
|
13830
13965
|
});
|
|
13831
13966
|
this._peerSyncCapabilities = new Map();
|
|
13967
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
13968
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
13832
13969
|
this._liveRawGossipBatches = new Map();
|
|
13833
13970
|
this._liveRawGossipFlushScheduled = false;
|
|
13834
13971
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -13845,6 +13982,8 @@ export class SharedLog<
|
|
|
13845
13982
|
this._lastLocalAppendAt = 0;
|
|
13846
13983
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
13847
13984
|
this._announcements.resetForOpen();
|
|
13985
|
+
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
13986
|
+
this._v2Send.resetForOpen();
|
|
13848
13987
|
const adaptiveReplicateOptions =
|
|
13849
13988
|
options?.replicate && isAdaptiveReplicatorOption(options.replicate)
|
|
13850
13989
|
? options.replicate
|
|
@@ -15337,6 +15476,9 @@ export class SharedLog<
|
|
|
15337
15476
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
15338
15477
|
this._liveness._replicatorLastActivityAt.delete(peerHash);
|
|
15339
15478
|
this._peerSyncCapabilities.delete(peerHash);
|
|
15479
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15480
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
15481
|
+
this._v2Send.clearPeer(peerHash);
|
|
15340
15482
|
this.cleanupPendingIHavePeer(peerHash);
|
|
15341
15483
|
this.cleanupCheckedPrunePeer(
|
|
15342
15484
|
peerHash,
|
|
@@ -16346,6 +16488,9 @@ export class SharedLog<
|
|
|
16346
16488
|
this._openingSyncCapabilitiesByPeer?.clear();
|
|
16347
16489
|
this._gidPeersHistory?.clear();
|
|
16348
16490
|
this._peerSyncCapabilities?.clear();
|
|
16491
|
+
this._peerSyncCapabilitySessions?.clear();
|
|
16492
|
+
this._peerSyncCapabilityTimestamps?.clear();
|
|
16493
|
+
this._v2Send?.clearForClose();
|
|
16349
16494
|
this._liveRawGossipBatches?.clear();
|
|
16350
16495
|
this._nativeSharedLogState?.clearGidPeers();
|
|
16351
16496
|
this._nativeBackbone?.clearGidPeers();
|
|
@@ -16478,12 +16623,14 @@ export class SharedLog<
|
|
|
16478
16623
|
}
|
|
16479
16624
|
}, 2_000);
|
|
16480
16625
|
try {
|
|
16481
|
-
|
|
16482
|
-
|
|
16626
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
16627
|
+
await Promise.all([
|
|
16628
|
+
this.rpc.send(reset, {
|
|
16483
16629
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
16484
16630
|
signal: abort.signal,
|
|
16485
|
-
})
|
|
16486
|
-
.
|
|
16631
|
+
}).catch(() => {}),
|
|
16632
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
16633
|
+
]);
|
|
16487
16634
|
} finally {
|
|
16488
16635
|
clearTimeout(abortTimer);
|
|
16489
16636
|
}
|
|
@@ -16602,12 +16749,14 @@ export class SharedLog<
|
|
|
16602
16749
|
}
|
|
16603
16750
|
}, 2_000);
|
|
16604
16751
|
try {
|
|
16605
|
-
|
|
16606
|
-
|
|
16752
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
16753
|
+
await Promise.all([
|
|
16754
|
+
this.rpc.send(reset, {
|
|
16607
16755
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
16608
16756
|
signal: abort.signal,
|
|
16609
|
-
})
|
|
16610
|
-
.
|
|
16757
|
+
}).catch(() => {}),
|
|
16758
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
16759
|
+
]);
|
|
16611
16760
|
} finally {
|
|
16612
16761
|
clearTimeout(abortTimer);
|
|
16613
16762
|
}
|
|
@@ -17124,6 +17273,13 @@ export class SharedLog<
|
|
|
17124
17273
|
msg: TransportMessage,
|
|
17125
17274
|
context: RequestContext,
|
|
17126
17275
|
): Promise<void> {
|
|
17276
|
+
// Decode-first only: capability advertisement is not permission to use
|
|
17277
|
+
// V2. Drop every unsolicited V2 envelope before even entering the shared
|
|
17278
|
+
// receive envelope, so durable poison checks, leases, liveness, ordering
|
|
17279
|
+
// watermarks, replication ranges and cleanup side effects cannot observe it.
|
|
17280
|
+
if (isReplicationInfoV2Message(msg)) {
|
|
17281
|
+
return;
|
|
17282
|
+
}
|
|
17127
17283
|
const stashBackedRawMessage = isStashBackedRawExchangeHeadsMessage(msg)
|
|
17128
17284
|
? msg
|
|
17129
17285
|
: undefined;
|
|
@@ -17186,7 +17342,10 @@ export class SharedLog<
|
|
|
17186
17342
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
17187
17343
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
17188
17344
|
}
|
|
17189
|
-
if (
|
|
17345
|
+
if (
|
|
17346
|
+
!context.from.equals(this.node.identity.publicKey) &&
|
|
17347
|
+
!(msg instanceof RequestReplicationInfoV2Message)
|
|
17348
|
+
) {
|
|
17190
17349
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
17191
17350
|
}
|
|
17192
17351
|
|
|
@@ -18815,6 +18974,8 @@ export class SharedLog<
|
|
|
18815
18974
|
return;
|
|
18816
18975
|
} else if (msg instanceof SyncCapabilitiesMessage) {
|
|
18817
18976
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
18977
|
+
const capabilityTransportSession = context.message?.header?.session;
|
|
18978
|
+
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
18818
18979
|
// No await separates this from the capture above, so the captured
|
|
18819
18980
|
// window state is exact: the legacy re-read of the opening map here
|
|
18820
18981
|
// could never observe a different value.
|
|
@@ -18825,15 +18986,47 @@ export class SharedLog<
|
|
|
18825
18986
|
// A prior unsubscribe cleanup may still be ahead of this reconnect
|
|
18826
18987
|
// barrier. Stage the new generation's one-shot advertisement so that
|
|
18827
18988
|
// cleanup cannot erase it before the opening transition commits.
|
|
18828
|
-
this.
|
|
18829
|
-
|
|
18989
|
+
this.observePeerSyncCapabilities({
|
|
18990
|
+
peerHash: receiveFromHash,
|
|
18830
18991
|
capabilities: msg.capabilities,
|
|
18992
|
+
transportSession: capabilityTransportSession,
|
|
18993
|
+
timestamp: capabilityTimestamp,
|
|
18994
|
+
openingSession: receiveSession!,
|
|
18831
18995
|
});
|
|
18832
18996
|
} else {
|
|
18833
|
-
this.
|
|
18997
|
+
this.observePeerSyncCapabilities({
|
|
18998
|
+
peerHash: receiveFromHash,
|
|
18999
|
+
capabilities: msg.capabilities,
|
|
19000
|
+
transportSession: capabilityTransportSession,
|
|
19001
|
+
timestamp: capabilityTimestamp,
|
|
19002
|
+
});
|
|
18834
19003
|
}
|
|
18835
19004
|
}
|
|
18836
19005
|
return;
|
|
19006
|
+
} else if (msg instanceof RequestReplicationInfoV2Message) {
|
|
19007
|
+
const requiredCapabilities =
|
|
19008
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
19009
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY;
|
|
19010
|
+
if (
|
|
19011
|
+
receiveSession &&
|
|
19012
|
+
((this._peerSyncCapabilities.get(receiveFromHash) ?? 0) &
|
|
19013
|
+
requiredCapabilities) ===
|
|
19014
|
+
requiredCapabilities &&
|
|
19015
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash) ===
|
|
19016
|
+
context.message.header.session &&
|
|
19017
|
+
this._peerSyncCapabilityTimestamps.has(receiveFromHash) &&
|
|
19018
|
+
context.message.header.timestamp >
|
|
19019
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash)! &&
|
|
19020
|
+
this._v2Send.acceptRequest(msg, {
|
|
19021
|
+
from: context.from,
|
|
19022
|
+
peerSession: receiveSession,
|
|
19023
|
+
receiverTransportSession: context.message.header.session,
|
|
19024
|
+
requestTimestamp: context.message.header.timestamp,
|
|
19025
|
+
})
|
|
19026
|
+
) {
|
|
19027
|
+
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
19028
|
+
}
|
|
19029
|
+
return;
|
|
18837
19030
|
} else if (await this.syncronizer.onMessage(msg, context)) {
|
|
18838
19031
|
return; // the syncronizer has handled the message
|
|
18839
19032
|
} else if (msg instanceof BlocksMessage) {
|
|
@@ -19148,6 +19341,7 @@ export class SharedLog<
|
|
|
19148
19341
|
}
|
|
19149
19342
|
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
19150
19343
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
19344
|
+
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
19151
19345
|
|
|
19152
19346
|
await this.rpc
|
|
19153
19347
|
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
@@ -22590,6 +22784,12 @@ export class SharedLog<
|
|
|
22590
22784
|
if (!ownsSubscriptionEpoch()) {
|
|
22591
22785
|
return;
|
|
22592
22786
|
}
|
|
22787
|
+
// A destination stream is scoped to exactly one topic-subscription
|
|
22788
|
+
// session. Abort the predecessor synchronously before either barrier can
|
|
22789
|
+
// yield; a late queue completion must never enter the new session.
|
|
22790
|
+
this._v2Send.clearPeer(peerHash);
|
|
22791
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
22792
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
22593
22793
|
if (subscribed) {
|
|
22594
22794
|
const pendingOpeningCapabilities =
|
|
22595
22795
|
this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
@@ -22629,6 +22829,22 @@ export class SharedLog<
|
|
|
22629
22829
|
peerHash,
|
|
22630
22830
|
openingCapabilities.capabilities,
|
|
22631
22831
|
);
|
|
22832
|
+
if (openingCapabilities.transportSession === undefined) {
|
|
22833
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
22834
|
+
} else {
|
|
22835
|
+
this._peerSyncCapabilitySessions.set(
|
|
22836
|
+
peerHash,
|
|
22837
|
+
openingCapabilities.transportSession,
|
|
22838
|
+
);
|
|
22839
|
+
}
|
|
22840
|
+
if (openingCapabilities.timestamp === undefined) {
|
|
22841
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
22842
|
+
} else {
|
|
22843
|
+
this._peerSyncCapabilityTimestamps.set(
|
|
22844
|
+
peerHash,
|
|
22845
|
+
openingCapabilities.timestamp,
|
|
22846
|
+
);
|
|
22847
|
+
}
|
|
22632
22848
|
this._openingSyncCapabilitiesByPeer.delete(peerHash);
|
|
22633
22849
|
}
|
|
22634
22850
|
this._peerSessions.unblockReplicationInfo(peerHash);
|
|
@@ -22708,27 +22924,32 @@ export class SharedLog<
|
|
|
22708
22924
|
this._liveness.markReplicatorActivity(peerHash);
|
|
22709
22925
|
this._peerSessions.markOpen(peerHash, expectedSubscriptionEpoch);
|
|
22710
22926
|
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22927
|
+
// Decode support and receiver-led negotiation readiness are separate bits.
|
|
22928
|
+
// This node never initiates a request in this rollout; normal replication
|
|
22929
|
+
// announcements remain legacy unless a peer explicitly grants one signed,
|
|
22930
|
+
// session-bound V2 destination stream.
|
|
22931
|
+
const receiveCapabilities =
|
|
22932
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
22933
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
22934
|
+
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
22935
|
+
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
22936
|
+
: 0);
|
|
22937
|
+
this.rpc
|
|
22938
|
+
.send(
|
|
22939
|
+
new SyncCapabilitiesMessage({
|
|
22940
|
+
capabilities: receiveCapabilities,
|
|
22941
|
+
}),
|
|
22942
|
+
{
|
|
22943
|
+
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
22944
|
+
signal: replicationLifecycleController.signal,
|
|
22945
|
+
},
|
|
22946
|
+
)
|
|
22947
|
+
.catch((error) =>
|
|
22948
|
+
this.handleReplicationLifecycleSendError(
|
|
22949
|
+
error,
|
|
22950
|
+
replicationLifecycleController,
|
|
22951
|
+
),
|
|
22952
|
+
);
|
|
22732
22953
|
|
|
22733
22954
|
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
22734
22955
|
try {
|
|
@@ -204,8 +204,16 @@ export type ReplicationAnnouncementDeps<R extends "u32" | "u64"> = {
|
|
|
204
204
|
// Owner-routed so coordinator spies keep observing re-entrant queueing.
|
|
205
205
|
queueCurrentReplicationStateAnnouncementRepair: () => void;
|
|
206
206
|
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) => boolean;
|
|
207
|
+
// Best-effort sidecar. The legacy publish remains the primary operation and
|
|
208
|
+
// retains its exact rejection/retry semantics during mixed-version rollout.
|
|
209
|
+
enqueueReplicationInfoV2: (message: LegacyReplicationInfoMessage) => void;
|
|
207
210
|
};
|
|
208
211
|
|
|
212
|
+
type LegacyReplicationInfoMessage =
|
|
213
|
+
| AllReplicatingSegmentsMessage
|
|
214
|
+
| AddedReplicationSegmentMessage
|
|
215
|
+
| StoppedReplicating;
|
|
216
|
+
|
|
209
217
|
export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
210
218
|
replicationAnnouncementRetryDebounced:
|
|
211
219
|
| ReturnType<typeof debounceFixedInterval>
|
|
@@ -611,10 +619,7 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
611
619
|
}
|
|
612
620
|
|
|
613
621
|
async sendReplicationAnnouncement(
|
|
614
|
-
message:
|
|
615
|
-
| AllReplicatingSegmentsMessage
|
|
616
|
-
| AddedReplicationSegmentMessage
|
|
617
|
-
| StoppedReplicating,
|
|
622
|
+
message: LegacyReplicationInfoMessage,
|
|
618
623
|
ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
|
|
619
624
|
options?: { shouldSend?: () => boolean },
|
|
620
625
|
): Promise<void> {
|
|
@@ -639,6 +644,7 @@ export class ReplicationAnnouncementCoordinator<R extends "u32" | "u64"> {
|
|
|
639
644
|
// after that stale send settles.
|
|
640
645
|
this.rotateAnnouncementSession();
|
|
641
646
|
this.advanceCurrentReplicationStateAnnouncementRepairGeneration();
|
|
647
|
+
this.deps.enqueueReplicationInfoV2(message);
|
|
642
648
|
try {
|
|
643
649
|
await this.deps.getRpc().send(message, {
|
|
644
650
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|