@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/dist/src/index.js
CHANGED
|
@@ -54,7 +54,7 @@ import { CheckedPruneCoordinator, } from "./checked-prune.js";
|
|
|
54
54
|
import { CPUUsageIntervalLag } from "./cpu.js";
|
|
55
55
|
import { debouncedAccumulatorMap, } from "./debounce.js";
|
|
56
56
|
import { NativeDurableCommitError, NoPeersError, isNotStartedError, } from "./errors.js";
|
|
57
|
-
import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
|
|
57
|
+
import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY, SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE, SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
|
|
58
58
|
import { FanoutEnvelope } from "./fanout-envelope.js";
|
|
59
59
|
import { CoordinatePersistenceCoordinator, combineCoordinateDeleteHashes, isPromiseLike, mapMaybePromise, normalizedHashValues, } from "./coordinate-persistence.js";
|
|
60
60
|
import { InstanceLifecycle } from "./instance-lifecycle.js";
|
|
@@ -67,10 +67,11 @@ import { PeerSessionRegistry, } from "./peer-session.js";
|
|
|
67
67
|
import { PIDReplicationController } from "./pid.js";
|
|
68
68
|
import { EntryReplicatedU32, EntryReplicatedU64, ReplicationIntent, ReplicationRangeIndexableU32, ReplicationRangeIndexableU64, ReplicationRangeMessage, appromixateCoverage, calculateCoverage, countCoveringRangesSameOwner, createAssignedRangesQuery, debounceAggregationChanges, getAllMergeCandiates, getCoverSet, getSamples, isEntryReplicated, isMatured, isReplicationRangeMessage, mergeRanges, minimumWidthToCover, shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary, toRebalance, } from "./ranges.js";
|
|
69
69
|
import { ReplicationAnnouncementCoordinator, isTransientReplicationAnnouncementError, } from "./replication-announcement.js";
|
|
70
|
+
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
70
71
|
import { createReplicationDomainHash, } from "./replication-domain-hash.js";
|
|
71
72
|
import { createReplicationDomainTime, } from "./replication-domain-time.js";
|
|
72
73
|
import {} from "./replication-domain.js";
|
|
73
|
-
import { AbsoluteReplicas, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, MinReplicas, ReplicationError, ReplicationPingMessage, RequestReplicationInfoMessage, ResponseRoleMessage, StoppedReplicating, decodeReplicas, encodeReplicas, maxReplicas, } from "./replication.js";
|
|
74
|
+
import { AbsoluteReplicas, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, MinReplicas, ReplicationError, ReplicationPingMessage, RequestReplicationInfoMessage, RequestReplicationInfoV2Message, ResponseRoleMessage, StoppedReplicating, decodeReplicas, encodeReplicas, isReplicationInfoV2Message, maxReplicas, } from "./replication.js";
|
|
74
75
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
75
76
|
import { Observer, Replicator } from "./role.js";
|
|
76
77
|
import { createSyncronizer } from "./sync/factory.js";
|
|
@@ -1608,6 +1609,7 @@ let SharedLog = (() => {
|
|
|
1608
1609
|
// A fn that we can call many times that recalculates the participation role
|
|
1609
1610
|
rebalanceParticipationDebounced;
|
|
1610
1611
|
_announcements;
|
|
1612
|
+
_v2Send;
|
|
1611
1613
|
// A fn for debouncing the calls for pruning
|
|
1612
1614
|
pruneDebouncedFn;
|
|
1613
1615
|
_checkedPruneAuditTimer;
|
|
@@ -1637,6 +1639,10 @@ let SharedLog = (() => {
|
|
|
1637
1639
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
1638
1640
|
// by public key hash. Entries are dropped on unsubscribe/disconnect.
|
|
1639
1641
|
_peerSyncCapabilities;
|
|
1642
|
+
// Signed transport session carried by the capability envelope. Kept in a
|
|
1643
|
+
// parallel map so existing capability-number consumers remain unchanged.
|
|
1644
|
+
_peerSyncCapabilitySessions;
|
|
1645
|
+
_peerSyncCapabilityTimestamps;
|
|
1640
1646
|
// Pending live raw exchange-head gossip, coalesced per recipient set and
|
|
1641
1647
|
// flushed at the end of the current event-loop turn (or when a batch cap
|
|
1642
1648
|
// is hit). Only used when every recipient advertised raw capability.
|
|
@@ -1690,6 +1696,7 @@ let SharedLog = (() => {
|
|
|
1690
1696
|
// this).
|
|
1691
1697
|
queueCurrentReplicationStateAnnouncementRepair: () => this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
1692
1698
|
queueCurrentReplicationStateAnnouncementRetry: (error) => this._announcements.queueCurrentReplicationStateAnnouncementRetry(error),
|
|
1699
|
+
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
1693
1700
|
isClosed: () => this.closed,
|
|
1694
1701
|
getCloseSignal: () => this._closeController.signal,
|
|
1695
1702
|
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
@@ -1704,6 +1711,22 @@ let SharedLog = (() => {
|
|
|
1704
1711
|
callRebalanceParticipationDebounced: () => this.rebalanceParticipationDebounced?.call(),
|
|
1705
1712
|
});
|
|
1706
1713
|
}
|
|
1714
|
+
createReplicationInfoV2SendCoordinator() {
|
|
1715
|
+
return new ReplicationInfoV2SendCoordinator({
|
|
1716
|
+
getRpc: () => this.rpc,
|
|
1717
|
+
getSelfKey: () => this.node.identity.publicKey,
|
|
1718
|
+
getSenderTransportSession: () => BigInt(this.node.services.pubsub.session),
|
|
1719
|
+
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
1720
|
+
validatePersistedReplicationRangeSnapshot: (ranges) => this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
1721
|
+
isClosed: () => this.closed,
|
|
1722
|
+
isPeerSessionOpen: (peerHash, peerSession) => this._peerSessions.isCurrent(peerHash, peerSession) &&
|
|
1723
|
+
peerSession.phase === "open" &&
|
|
1724
|
+
!this._peerSessions.isReplicationInfoBlocked(peerHash) &&
|
|
1725
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash),
|
|
1726
|
+
captureReplicationOwnershipLifecycle: () => this.captureReplicationOwnershipLifecycle(),
|
|
1727
|
+
isReplicationOwnershipLifecycleActive: (controller) => this.isRepairLifecycleActive(controller),
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1707
1730
|
createReplicatorLivenessMonitor() {
|
|
1708
1731
|
return new ReplicatorLivenessMonitor({
|
|
1709
1732
|
// Sweep-driven probes and activity marks dispatch through the owner so
|
|
@@ -1798,6 +1821,8 @@ let SharedLog = (() => {
|
|
|
1798
1821
|
this._appendBackfillPendingByTarget = new Map();
|
|
1799
1822
|
this._topicSubscribersCache = new Map();
|
|
1800
1823
|
this._peerSyncCapabilities = new Map();
|
|
1824
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
1825
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
1801
1826
|
this._liveRawGossipBatches = new Map();
|
|
1802
1827
|
this._liveRawGossipFlushScheduled = false;
|
|
1803
1828
|
this.coordinateToHash = new Cache({ max: 1e6, ttl: 1e4 });
|
|
@@ -1806,6 +1831,7 @@ let SharedLog = (() => {
|
|
|
1806
1831
|
this._replicatorJoinEmitted = new Set();
|
|
1807
1832
|
this._replicatorsReconciled = false;
|
|
1808
1833
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
1834
|
+
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
1809
1835
|
this._announcements = this.createReplicationAnnouncementCoordinator();
|
|
1810
1836
|
this.pendingMaturity = new Map();
|
|
1811
1837
|
this._closeController = new AbortController();
|
|
@@ -2196,6 +2222,72 @@ let SharedLog = (() => {
|
|
|
2196
2222
|
SYNC_CAPABILITY_RAW_EXCHANGE_HEADS) !==
|
|
2197
2223
|
0);
|
|
2198
2224
|
}
|
|
2225
|
+
observePeerSyncCapabilities(properties) {
|
|
2226
|
+
const { peerHash, capabilities, transportSession, timestamp, openingSession, } = properties;
|
|
2227
|
+
if (openingSession) {
|
|
2228
|
+
const previous = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
2229
|
+
if (transportSession !== undefined &&
|
|
2230
|
+
timestamp !== undefined &&
|
|
2231
|
+
previous?.epoch === openingSession &&
|
|
2232
|
+
previous.transportSession === transportSession) {
|
|
2233
|
+
if (previous.timestamp !== undefined && timestamp < previous.timestamp) {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
2237
|
+
epoch: openingSession,
|
|
2238
|
+
capabilities: previous.capabilities | capabilities,
|
|
2239
|
+
transportSession,
|
|
2240
|
+
timestamp: previous.timestamp === undefined || timestamp > previous.timestamp
|
|
2241
|
+
? timestamp
|
|
2242
|
+
: previous.timestamp,
|
|
2243
|
+
});
|
|
2244
|
+
return true;
|
|
2245
|
+
}
|
|
2246
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
2247
|
+
epoch: openingSession,
|
|
2248
|
+
capabilities,
|
|
2249
|
+
transportSession,
|
|
2250
|
+
timestamp,
|
|
2251
|
+
});
|
|
2252
|
+
return true;
|
|
2253
|
+
}
|
|
2254
|
+
if (transportSession === undefined || timestamp === undefined) {
|
|
2255
|
+
// Test/in-process synthetic contexts predate signed envelope captures.
|
|
2256
|
+
// They may exercise capability-number behavior, but can never authorize V2.
|
|
2257
|
+
this._peerSyncCapabilities.set(peerHash, capabilities);
|
|
2258
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
2259
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
2260
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
2261
|
+
return true;
|
|
2262
|
+
}
|
|
2263
|
+
const previousSession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
2264
|
+
const previousTimestamp = this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
2265
|
+
const sameTransportSession = previousSession === transportSession;
|
|
2266
|
+
if (sameTransportSession &&
|
|
2267
|
+
previousTimestamp !== undefined &&
|
|
2268
|
+
timestamp < previousTimestamp) {
|
|
2269
|
+
return false;
|
|
2270
|
+
}
|
|
2271
|
+
const previousCapabilities = this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
2272
|
+
const nextCapabilities = sameTransportSession
|
|
2273
|
+
? previousCapabilities | capabilities
|
|
2274
|
+
: capabilities;
|
|
2275
|
+
const generationAdvanced = !sameTransportSession ||
|
|
2276
|
+
previousTimestamp === undefined ||
|
|
2277
|
+
timestamp > previousTimestamp ||
|
|
2278
|
+
nextCapabilities !== previousCapabilities;
|
|
2279
|
+
this._peerSyncCapabilities.set(peerHash, nextCapabilities);
|
|
2280
|
+
this._peerSyncCapabilitySessions.set(peerHash, transportSession);
|
|
2281
|
+
this._peerSyncCapabilityTimestamps.set(peerHash, previousTimestamp === undefined ||
|
|
2282
|
+
!sameTransportSession ||
|
|
2283
|
+
timestamp > previousTimestamp
|
|
2284
|
+
? timestamp
|
|
2285
|
+
: previousTimestamp);
|
|
2286
|
+
if (generationAdvanced) {
|
|
2287
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
2288
|
+
}
|
|
2289
|
+
return true;
|
|
2290
|
+
}
|
|
2199
2291
|
/**
|
|
2200
2292
|
* Live append gossip may use the raw exchange-heads path only when we
|
|
2201
2293
|
* opted into raw sync and every remote recipient advertised raw capability
|
|
@@ -8895,6 +8987,8 @@ let SharedLog = (() => {
|
|
|
8895
8987
|
ttl: LEADER_PLAN_CACHE_TTL_MS,
|
|
8896
8988
|
});
|
|
8897
8989
|
this._peerSyncCapabilities = new Map();
|
|
8990
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
8991
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
8898
8992
|
this._liveRawGossipBatches = new Map();
|
|
8899
8993
|
this._liveRawGossipFlushScheduled = false;
|
|
8900
8994
|
this.coordinateToHash = new Cache({ max: 1e6, ttl: 1e4 });
|
|
@@ -8910,6 +9004,8 @@ let SharedLog = (() => {
|
|
|
8910
9004
|
this._lastLocalAppendAt = 0;
|
|
8911
9005
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
8912
9006
|
this._announcements.resetForOpen();
|
|
9007
|
+
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
9008
|
+
this._v2Send.resetForOpen();
|
|
8913
9009
|
const adaptiveReplicateOptions = options?.replicate && isAdaptiveReplicatorOption(options.replicate)
|
|
8914
9010
|
? options.replicate
|
|
8915
9011
|
: undefined;
|
|
@@ -10029,6 +10125,9 @@ let SharedLog = (() => {
|
|
|
10029
10125
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
10030
10126
|
this._liveness._replicatorLastActivityAt.delete(peerHash);
|
|
10031
10127
|
this._peerSyncCapabilities.delete(peerHash);
|
|
10128
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
10129
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
10130
|
+
this._v2Send.clearPeer(peerHash);
|
|
10032
10131
|
this.cleanupPendingIHavePeer(peerHash);
|
|
10033
10132
|
this.cleanupCheckedPrunePeer(peerHash, ownershipLifecycleController, this._checkedPrune);
|
|
10034
10133
|
}
|
|
@@ -10845,6 +10944,9 @@ let SharedLog = (() => {
|
|
|
10845
10944
|
this._openingSyncCapabilitiesByPeer?.clear();
|
|
10846
10945
|
this._gidPeersHistory?.clear();
|
|
10847
10946
|
this._peerSyncCapabilities?.clear();
|
|
10947
|
+
this._peerSyncCapabilitySessions?.clear();
|
|
10948
|
+
this._peerSyncCapabilityTimestamps?.clear();
|
|
10949
|
+
this._v2Send?.clearForClose();
|
|
10848
10950
|
this._liveRawGossipBatches?.clear();
|
|
10849
10951
|
this._nativeSharedLogState?.clearGidPeers();
|
|
10850
10952
|
this._nativeBackbone?.clearGidPeers();
|
|
@@ -10965,12 +11067,14 @@ let SharedLog = (() => {
|
|
|
10965
11067
|
}
|
|
10966
11068
|
}, 2_000);
|
|
10967
11069
|
try {
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
.catch(() => { })
|
|
11070
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
11071
|
+
await Promise.all([
|
|
11072
|
+
this.rpc.send(reset, {
|
|
11073
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11074
|
+
signal: abort.signal,
|
|
11075
|
+
}).catch(() => { }),
|
|
11076
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
11077
|
+
]);
|
|
10974
11078
|
}
|
|
10975
11079
|
finally {
|
|
10976
11080
|
clearTimeout(abortTimer);
|
|
@@ -11088,12 +11192,14 @@ let SharedLog = (() => {
|
|
|
11088
11192
|
}
|
|
11089
11193
|
}, 2_000);
|
|
11090
11194
|
try {
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
.catch(() => { })
|
|
11195
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
11196
|
+
await Promise.all([
|
|
11197
|
+
this.rpc.send(reset, {
|
|
11198
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11199
|
+
signal: abort.signal,
|
|
11200
|
+
}).catch(() => { }),
|
|
11201
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
11202
|
+
]);
|
|
11097
11203
|
}
|
|
11098
11204
|
finally {
|
|
11099
11205
|
clearTimeout(abortTimer);
|
|
@@ -11508,6 +11614,13 @@ let SharedLog = (() => {
|
|
|
11508
11614
|
}
|
|
11509
11615
|
// Callback for receiving a message from the network
|
|
11510
11616
|
async onMessage(msg, context) {
|
|
11617
|
+
// Decode-first only: capability advertisement is not permission to use
|
|
11618
|
+
// V2. Drop every unsolicited V2 envelope before even entering the shared
|
|
11619
|
+
// receive envelope, so durable poison checks, leases, liveness, ordering
|
|
11620
|
+
// watermarks, replication ranges and cleanup side effects cannot observe it.
|
|
11621
|
+
if (isReplicationInfoV2Message(msg)) {
|
|
11622
|
+
return;
|
|
11623
|
+
}
|
|
11511
11624
|
const stashBackedRawMessage = isStashBackedRawExchangeHeadsMessage(msg)
|
|
11512
11625
|
? msg
|
|
11513
11626
|
: undefined;
|
|
@@ -11558,7 +11671,8 @@ let SharedLog = (() => {
|
|
|
11558
11671
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
11559
11672
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
11560
11673
|
}
|
|
11561
|
-
if (!context.from.equals(this.node.identity.publicKey)
|
|
11674
|
+
if (!context.from.equals(this.node.identity.publicKey) &&
|
|
11675
|
+
!(msg instanceof RequestReplicationInfoV2Message)) {
|
|
11562
11676
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
11563
11677
|
}
|
|
11564
11678
|
const syncProfile = this._logProperties?.sync?.profile;
|
|
@@ -12901,6 +13015,8 @@ let SharedLog = (() => {
|
|
|
12901
13015
|
}
|
|
12902
13016
|
else if (msg instanceof SyncCapabilitiesMessage) {
|
|
12903
13017
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
13018
|
+
const capabilityTransportSession = context.message?.header?.session;
|
|
13019
|
+
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
12904
13020
|
// No await separates this from the capture above, so the captured
|
|
12905
13021
|
// window state is exact: the legacy re-read of the opening map here
|
|
12906
13022
|
// could never observe a different value.
|
|
@@ -12909,17 +13025,47 @@ let SharedLog = (() => {
|
|
|
12909
13025
|
// A prior unsubscribe cleanup may still be ahead of this reconnect
|
|
12910
13026
|
// barrier. Stage the new generation's one-shot advertisement so that
|
|
12911
13027
|
// cleanup cannot erase it before the opening transition commits.
|
|
12912
|
-
this.
|
|
12913
|
-
|
|
13028
|
+
this.observePeerSyncCapabilities({
|
|
13029
|
+
peerHash: receiveFromHash,
|
|
12914
13030
|
capabilities: msg.capabilities,
|
|
13031
|
+
transportSession: capabilityTransportSession,
|
|
13032
|
+
timestamp: capabilityTimestamp,
|
|
13033
|
+
openingSession: receiveSession,
|
|
12915
13034
|
});
|
|
12916
13035
|
}
|
|
12917
13036
|
else {
|
|
12918
|
-
this.
|
|
13037
|
+
this.observePeerSyncCapabilities({
|
|
13038
|
+
peerHash: receiveFromHash,
|
|
13039
|
+
capabilities: msg.capabilities,
|
|
13040
|
+
transportSession: capabilityTransportSession,
|
|
13041
|
+
timestamp: capabilityTimestamp,
|
|
13042
|
+
});
|
|
12919
13043
|
}
|
|
12920
13044
|
}
|
|
12921
13045
|
return;
|
|
12922
13046
|
}
|
|
13047
|
+
else if (msg instanceof RequestReplicationInfoV2Message) {
|
|
13048
|
+
const requiredCapabilities = SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
13049
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY;
|
|
13050
|
+
if (receiveSession &&
|
|
13051
|
+
((this._peerSyncCapabilities.get(receiveFromHash) ?? 0) &
|
|
13052
|
+
requiredCapabilities) ===
|
|
13053
|
+
requiredCapabilities &&
|
|
13054
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash) ===
|
|
13055
|
+
context.message.header.session &&
|
|
13056
|
+
this._peerSyncCapabilityTimestamps.has(receiveFromHash) &&
|
|
13057
|
+
context.message.header.timestamp >
|
|
13058
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash) &&
|
|
13059
|
+
this._v2Send.acceptRequest(msg, {
|
|
13060
|
+
from: context.from,
|
|
13061
|
+
peerSession: receiveSession,
|
|
13062
|
+
receiverTransportSession: context.message.header.session,
|
|
13063
|
+
requestTimestamp: context.message.header.timestamp,
|
|
13064
|
+
})) {
|
|
13065
|
+
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
13066
|
+
}
|
|
13067
|
+
return;
|
|
13068
|
+
}
|
|
12923
13069
|
else if (await this.syncronizer.onMessage(msg, context)) {
|
|
12924
13070
|
return; // the syncronizer has handled the message
|
|
12925
13071
|
}
|
|
@@ -13155,6 +13301,7 @@ let SharedLog = (() => {
|
|
|
13155
13301
|
}
|
|
13156
13302
|
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
13157
13303
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
13304
|
+
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
13158
13305
|
await this.rpc
|
|
13159
13306
|
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
13160
13307
|
mode: new AcknowledgeDelivery({
|
|
@@ -15456,6 +15603,12 @@ let SharedLog = (() => {
|
|
|
15456
15603
|
if (!ownsSubscriptionEpoch()) {
|
|
15457
15604
|
return;
|
|
15458
15605
|
}
|
|
15606
|
+
// A destination stream is scoped to exactly one topic-subscription
|
|
15607
|
+
// session. Abort the predecessor synchronously before either barrier can
|
|
15608
|
+
// yield; a late queue completion must never enter the new session.
|
|
15609
|
+
this._v2Send.clearPeer(peerHash);
|
|
15610
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15611
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
15459
15612
|
if (subscribed) {
|
|
15460
15613
|
const pendingOpeningCapabilities = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
15461
15614
|
if (pendingOpeningCapabilities &&
|
|
@@ -15486,6 +15639,18 @@ let SharedLog = (() => {
|
|
|
15486
15639
|
const openingCapabilities = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
15487
15640
|
if (openingCapabilities?.epoch === expectedSubscriptionEpoch) {
|
|
15488
15641
|
this._peerSyncCapabilities.set(peerHash, openingCapabilities.capabilities);
|
|
15642
|
+
if (openingCapabilities.transportSession === undefined) {
|
|
15643
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15644
|
+
}
|
|
15645
|
+
else {
|
|
15646
|
+
this._peerSyncCapabilitySessions.set(peerHash, openingCapabilities.transportSession);
|
|
15647
|
+
}
|
|
15648
|
+
if (openingCapabilities.timestamp === undefined) {
|
|
15649
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
15650
|
+
}
|
|
15651
|
+
else {
|
|
15652
|
+
this._peerSyncCapabilityTimestamps.set(peerHash, openingCapabilities.timestamp);
|
|
15653
|
+
}
|
|
15489
15654
|
this._openingSyncCapabilitiesByPeer.delete(peerHash);
|
|
15490
15655
|
}
|
|
15491
15656
|
this._peerSessions.unblockReplicationInfo(peerHash);
|
|
@@ -15555,19 +15720,23 @@ let SharedLog = (() => {
|
|
|
15555
15720
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
15556
15721
|
this._liveness.markReplicatorActivity(peerHash);
|
|
15557
15722
|
this._peerSessions.markOpen(peerHash, expectedSubscriptionEpoch);
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
}
|
|
15723
|
+
// Decode support and receiver-led negotiation readiness are separate bits.
|
|
15724
|
+
// This node never initiates a request in this rollout; normal replication
|
|
15725
|
+
// announcements remain legacy unless a peer explicitly grants one signed,
|
|
15726
|
+
// session-bound V2 destination stream.
|
|
15727
|
+
const receiveCapabilities = SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
15728
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
15729
|
+
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
15730
|
+
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
15731
|
+
: 0);
|
|
15732
|
+
this.rpc
|
|
15733
|
+
.send(new SyncCapabilitiesMessage({
|
|
15734
|
+
capabilities: receiveCapabilities,
|
|
15735
|
+
}), {
|
|
15736
|
+
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
15737
|
+
signal: replicationLifecycleController.signal,
|
|
15738
|
+
})
|
|
15739
|
+
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
15571
15740
|
let replicationSegments;
|
|
15572
15741
|
try {
|
|
15573
15742
|
replicationSegments = await this.getMyReplicationSegments();
|