@peerbit/shared-log 13.2.31 → 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 +13 -2
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +13 -2
- 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 +181 -21
- 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 +26 -4
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +60 -5
- package/dist/src/replication.js.map +1 -1
- package/package.json +8 -8
- package/src/exchange-heads.ts +15 -2
- package/src/index.ts +225 -16
- package/src/replication-announcement.ts +10 -4
- package/src/replication-info-v2-send.ts +568 -0
- package/src/replication.ts +39 -5
package/src/index.ts
CHANGED
|
@@ -154,7 +154,9 @@ import {
|
|
|
154
154
|
ResponseIPrune,
|
|
155
155
|
ResponseIPruneV2,
|
|
156
156
|
SYNC_CAPABILITY_RAW_EXCHANGE_HEADS,
|
|
157
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
157
158
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
159
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
158
160
|
StashBackedRawExchangeHeadsMessage,
|
|
159
161
|
SyncCapabilitiesMessage,
|
|
160
162
|
collectRawExchangeHeadSendPlan,
|
|
@@ -235,6 +237,7 @@ import {
|
|
|
235
237
|
ReplicationAnnouncementCoordinator,
|
|
236
238
|
isTransientReplicationAnnouncementError,
|
|
237
239
|
} from "./replication-announcement.js";
|
|
240
|
+
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
238
241
|
import {
|
|
239
242
|
type ReplicationDomainHash,
|
|
240
243
|
createReplicationDomainHash,
|
|
@@ -258,6 +261,7 @@ import {
|
|
|
258
261
|
type ReplicationLimits,
|
|
259
262
|
ReplicationPingMessage,
|
|
260
263
|
RequestReplicationInfoMessage,
|
|
264
|
+
RequestReplicationInfoV2Message,
|
|
261
265
|
ResponseRoleMessage,
|
|
262
266
|
StoppedReplicating,
|
|
263
267
|
decodeReplicas,
|
|
@@ -2024,7 +2028,12 @@ export class SharedLog<
|
|
|
2024
2028
|
// PeerSession the advert was staged under; promote/delete key off it.
|
|
2025
2029
|
private _openingSyncCapabilitiesByPeer!: Map<
|
|
2026
2030
|
string,
|
|
2027
|
-
{
|
|
2031
|
+
{
|
|
2032
|
+
epoch: object;
|
|
2033
|
+
capabilities: number;
|
|
2034
|
+
transportSession?: bigint;
|
|
2035
|
+
timestamp?: bigint;
|
|
2036
|
+
}
|
|
2028
2037
|
>;
|
|
2029
2038
|
private _onFanoutDataFn?: (arg: any) => void;
|
|
2030
2039
|
private _onFanoutUnicastFn?: (arg: any) => void;
|
|
@@ -3225,6 +3234,7 @@ export class SharedLog<
|
|
|
3225
3234
|
| ReturnType<typeof debounceFixedInterval>
|
|
3226
3235
|
| undefined;
|
|
3227
3236
|
private _announcements!: ReplicationAnnouncementCoordinator<R>;
|
|
3237
|
+
private _v2Send!: ReplicationInfoV2SendCoordinator<R>;
|
|
3228
3238
|
|
|
3229
3239
|
// A fn for debouncing the calls for pruning
|
|
3230
3240
|
pruneDebouncedFn!: DebouncedAccumulatorMap<{
|
|
@@ -3286,6 +3296,10 @@ export class SharedLog<
|
|
|
3286
3296
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
3287
3297
|
// by public key hash. Entries are dropped on unsubscribe/disconnect.
|
|
3288
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>;
|
|
3289
3303
|
// Pending live raw exchange-head gossip, coalesced per recipient set and
|
|
3290
3304
|
// flushed at the end of the current event-loop turn (or when a batch cap
|
|
3291
3305
|
// is hit). Only used when every recipient advertised raw capability.
|
|
@@ -3355,6 +3369,7 @@ export class SharedLog<
|
|
|
3355
3369
|
this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
3356
3370
|
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) =>
|
|
3357
3371
|
this._announcements.queueCurrentReplicationStateAnnouncementRetry(error),
|
|
3372
|
+
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
3358
3373
|
isClosed: () => this.closed,
|
|
3359
3374
|
getCloseSignal: () => this._closeController.signal,
|
|
3360
3375
|
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
@@ -3374,6 +3389,30 @@ export class SharedLog<
|
|
|
3374
3389
|
});
|
|
3375
3390
|
}
|
|
3376
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
|
+
|
|
3377
3416
|
private createReplicatorLivenessMonitor(): ReplicatorLivenessMonitor {
|
|
3378
3417
|
return new ReplicatorLivenessMonitor({
|
|
3379
3418
|
// Sweep-driven probes and activity marks dispatch through the owner so
|
|
@@ -3488,6 +3527,8 @@ export class SharedLog<
|
|
|
3488
3527
|
this._appendBackfillPendingByTarget = new Map();
|
|
3489
3528
|
this._topicSubscribersCache = new Map();
|
|
3490
3529
|
this._peerSyncCapabilities = new Map();
|
|
3530
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
3531
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
3491
3532
|
this._liveRawGossipBatches = new Map();
|
|
3492
3533
|
this._liveRawGossipFlushScheduled = false;
|
|
3493
3534
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -3496,6 +3537,7 @@ export class SharedLog<
|
|
|
3496
3537
|
this._replicatorJoinEmitted = new Set();
|
|
3497
3538
|
this._replicatorsReconciled = false;
|
|
3498
3539
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
3540
|
+
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
3499
3541
|
this._announcements = this.createReplicationAnnouncementCoordinator();
|
|
3500
3542
|
this.pendingMaturity = new Map();
|
|
3501
3543
|
this._closeController = new AbortController();
|
|
@@ -3991,6 +4033,97 @@ export class SharedLog<
|
|
|
3991
4033
|
);
|
|
3992
4034
|
}
|
|
3993
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
|
+
|
|
3994
4127
|
/**
|
|
3995
4128
|
* Live append gossip may use the raw exchange-heads path only when we
|
|
3996
4129
|
* opted into raw sync and every remote recipient advertised raw capability
|
|
@@ -13831,6 +13964,8 @@ export class SharedLog<
|
|
|
13831
13964
|
ttl: LEADER_PLAN_CACHE_TTL_MS,
|
|
13832
13965
|
});
|
|
13833
13966
|
this._peerSyncCapabilities = new Map();
|
|
13967
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
13968
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
13834
13969
|
this._liveRawGossipBatches = new Map();
|
|
13835
13970
|
this._liveRawGossipFlushScheduled = false;
|
|
13836
13971
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -13847,6 +13982,8 @@ export class SharedLog<
|
|
|
13847
13982
|
this._lastLocalAppendAt = 0;
|
|
13848
13983
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
13849
13984
|
this._announcements.resetForOpen();
|
|
13985
|
+
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
13986
|
+
this._v2Send.resetForOpen();
|
|
13850
13987
|
const adaptiveReplicateOptions =
|
|
13851
13988
|
options?.replicate && isAdaptiveReplicatorOption(options.replicate)
|
|
13852
13989
|
? options.replicate
|
|
@@ -15339,6 +15476,9 @@ export class SharedLog<
|
|
|
15339
15476
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
15340
15477
|
this._liveness._replicatorLastActivityAt.delete(peerHash);
|
|
15341
15478
|
this._peerSyncCapabilities.delete(peerHash);
|
|
15479
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15480
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
15481
|
+
this._v2Send.clearPeer(peerHash);
|
|
15342
15482
|
this.cleanupPendingIHavePeer(peerHash);
|
|
15343
15483
|
this.cleanupCheckedPrunePeer(
|
|
15344
15484
|
peerHash,
|
|
@@ -16348,6 +16488,9 @@ export class SharedLog<
|
|
|
16348
16488
|
this._openingSyncCapabilitiesByPeer?.clear();
|
|
16349
16489
|
this._gidPeersHistory?.clear();
|
|
16350
16490
|
this._peerSyncCapabilities?.clear();
|
|
16491
|
+
this._peerSyncCapabilitySessions?.clear();
|
|
16492
|
+
this._peerSyncCapabilityTimestamps?.clear();
|
|
16493
|
+
this._v2Send?.clearForClose();
|
|
16351
16494
|
this._liveRawGossipBatches?.clear();
|
|
16352
16495
|
this._nativeSharedLogState?.clearGidPeers();
|
|
16353
16496
|
this._nativeBackbone?.clearGidPeers();
|
|
@@ -16480,12 +16623,14 @@ export class SharedLog<
|
|
|
16480
16623
|
}
|
|
16481
16624
|
}, 2_000);
|
|
16482
16625
|
try {
|
|
16483
|
-
|
|
16484
|
-
|
|
16626
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
16627
|
+
await Promise.all([
|
|
16628
|
+
this.rpc.send(reset, {
|
|
16485
16629
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
16486
16630
|
signal: abort.signal,
|
|
16487
|
-
})
|
|
16488
|
-
.
|
|
16631
|
+
}).catch(() => {}),
|
|
16632
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
16633
|
+
]);
|
|
16489
16634
|
} finally {
|
|
16490
16635
|
clearTimeout(abortTimer);
|
|
16491
16636
|
}
|
|
@@ -16604,12 +16749,14 @@ export class SharedLog<
|
|
|
16604
16749
|
}
|
|
16605
16750
|
}, 2_000);
|
|
16606
16751
|
try {
|
|
16607
|
-
|
|
16608
|
-
|
|
16752
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
16753
|
+
await Promise.all([
|
|
16754
|
+
this.rpc.send(reset, {
|
|
16609
16755
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
16610
16756
|
signal: abort.signal,
|
|
16611
|
-
})
|
|
16612
|
-
.
|
|
16757
|
+
}).catch(() => {}),
|
|
16758
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
16759
|
+
]);
|
|
16613
16760
|
} finally {
|
|
16614
16761
|
clearTimeout(abortTimer);
|
|
16615
16762
|
}
|
|
@@ -17195,7 +17342,10 @@ export class SharedLog<
|
|
|
17195
17342
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
17196
17343
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
17197
17344
|
}
|
|
17198
|
-
if (
|
|
17345
|
+
if (
|
|
17346
|
+
!context.from.equals(this.node.identity.publicKey) &&
|
|
17347
|
+
!(msg instanceof RequestReplicationInfoV2Message)
|
|
17348
|
+
) {
|
|
17199
17349
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
17200
17350
|
}
|
|
17201
17351
|
|
|
@@ -18824,6 +18974,8 @@ export class SharedLog<
|
|
|
18824
18974
|
return;
|
|
18825
18975
|
} else if (msg instanceof SyncCapabilitiesMessage) {
|
|
18826
18976
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
18977
|
+
const capabilityTransportSession = context.message?.header?.session;
|
|
18978
|
+
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
18827
18979
|
// No await separates this from the capture above, so the captured
|
|
18828
18980
|
// window state is exact: the legacy re-read of the opening map here
|
|
18829
18981
|
// could never observe a different value.
|
|
@@ -18834,15 +18986,47 @@ export class SharedLog<
|
|
|
18834
18986
|
// A prior unsubscribe cleanup may still be ahead of this reconnect
|
|
18835
18987
|
// barrier. Stage the new generation's one-shot advertisement so that
|
|
18836
18988
|
// cleanup cannot erase it before the opening transition commits.
|
|
18837
|
-
this.
|
|
18838
|
-
|
|
18989
|
+
this.observePeerSyncCapabilities({
|
|
18990
|
+
peerHash: receiveFromHash,
|
|
18839
18991
|
capabilities: msg.capabilities,
|
|
18992
|
+
transportSession: capabilityTransportSession,
|
|
18993
|
+
timestamp: capabilityTimestamp,
|
|
18994
|
+
openingSession: receiveSession!,
|
|
18840
18995
|
});
|
|
18841
18996
|
} else {
|
|
18842
|
-
this.
|
|
18997
|
+
this.observePeerSyncCapabilities({
|
|
18998
|
+
peerHash: receiveFromHash,
|
|
18999
|
+
capabilities: msg.capabilities,
|
|
19000
|
+
transportSession: capabilityTransportSession,
|
|
19001
|
+
timestamp: capabilityTimestamp,
|
|
19002
|
+
});
|
|
18843
19003
|
}
|
|
18844
19004
|
}
|
|
18845
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;
|
|
18846
19030
|
} else if (await this.syncronizer.onMessage(msg, context)) {
|
|
18847
19031
|
return; // the syncronizer has handled the message
|
|
18848
19032
|
} else if (msg instanceof BlocksMessage) {
|
|
@@ -19157,6 +19341,7 @@ export class SharedLog<
|
|
|
19157
19341
|
}
|
|
19158
19342
|
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
19159
19343
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
19344
|
+
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
19160
19345
|
|
|
19161
19346
|
await this.rpc
|
|
19162
19347
|
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
@@ -22599,6 +22784,12 @@ export class SharedLog<
|
|
|
22599
22784
|
if (!ownsSubscriptionEpoch()) {
|
|
22600
22785
|
return;
|
|
22601
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);
|
|
22602
22793
|
if (subscribed) {
|
|
22603
22794
|
const pendingOpeningCapabilities =
|
|
22604
22795
|
this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
@@ -22638,6 +22829,22 @@ export class SharedLog<
|
|
|
22638
22829
|
peerHash,
|
|
22639
22830
|
openingCapabilities.capabilities,
|
|
22640
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
|
+
}
|
|
22641
22848
|
this._openingSyncCapabilitiesByPeer.delete(peerHash);
|
|
22642
22849
|
}
|
|
22643
22850
|
this._peerSessions.unblockReplicationInfo(peerHash);
|
|
@@ -22717,11 +22924,13 @@ export class SharedLog<
|
|
|
22717
22924
|
this._liveness.markReplicatorActivity(peerHash);
|
|
22718
22925
|
this._peerSessions.markOpen(peerHash, expectedSubscriptionEpoch);
|
|
22719
22926
|
|
|
22720
|
-
// Decode support
|
|
22721
|
-
//
|
|
22722
|
-
//
|
|
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.
|
|
22723
22931
|
const receiveCapabilities =
|
|
22724
22932
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
22933
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
22725
22934
|
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
22726
22935
|
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
22727
22936
|
: 0);
|
|
@@ -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,
|