@peerbit/shared-log 13.2.32 → 13.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +574 -92
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-binding.d.ts +16 -0
- package/dist/src/replication-info-v2-binding.d.ts.map +1 -0
- package/dist/src/replication-info-v2-binding.js +30 -0
- package/dist/src/replication-info-v2-binding.js.map +1 -0
- package/dist/src/replication-info-v2-receive.d.ts +181 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -0
- package/dist/src/replication-info-v2-receive.js +725 -0
- package/dist/src/replication-info-v2-receive.js.map +1 -0
- package/dist/src/replication-info-v2-send.d.ts +5 -17
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +15 -32
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +838 -208
- package/src/replication-info-v2-binding.ts +44 -0
- package/src/replication-info-v2-receive.ts +1045 -0
- package/src/replication-info-v2-send.ts +17 -45
package/dist/src/index.js
CHANGED
|
@@ -51,27 +51,28 @@ import PQueue from "p-queue";
|
|
|
51
51
|
import { concat, fromString } from "uint8arrays";
|
|
52
52
|
import { BlocksMessage } from "./blocks.js";
|
|
53
53
|
import { CheckedPruneCoordinator, } from "./checked-prune.js";
|
|
54
|
+
import { CoordinatePersistenceCoordinator, combineCoordinateDeleteHashes, isPromiseLike, mapMaybePromise, normalizedHashValues, } from "./coordinate-persistence.js";
|
|
54
55
|
import { CPUUsageIntervalLag } from "./cpu.js";
|
|
55
56
|
import { debouncedAccumulatorMap, } from "./debounce.js";
|
|
56
57
|
import { NativeDurableCommitError, NoPeersError, isNotStartedError, } from "./errors.js";
|
|
57
58
|
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
59
|
import { FanoutEnvelope } from "./fanout-envelope.js";
|
|
59
|
-
import { CoordinatePersistenceCoordinator, combineCoordinateDeleteHashes, isPromiseLike, mapMaybePromise, normalizedHashValues, } from "./coordinate-persistence.js";
|
|
60
60
|
import { InstanceLifecycle } from "./instance-lifecycle.js";
|
|
61
61
|
import { MAX_U32, MAX_U64, createNumbers, } from "./integers.js";
|
|
62
62
|
import { JoinWarmupCoordinator } from "./join-warmup.js";
|
|
63
63
|
import { LeaderPlanCache } from "./leader-plan-cache.js";
|
|
64
64
|
import { TransportMessage } from "./message.js";
|
|
65
65
|
import { NativeBackboneWriteThroughBlockStore } from "./native-write-through-block-store.js";
|
|
66
|
-
import { PeerSessionRegistry
|
|
66
|
+
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";
|
|
71
70
|
import { createReplicationDomainHash, } from "./replication-domain-hash.js";
|
|
72
71
|
import { createReplicationDomainTime, } from "./replication-domain-time.js";
|
|
73
72
|
import {} from "./replication-domain.js";
|
|
74
|
-
import {
|
|
73
|
+
import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-receive.js";
|
|
74
|
+
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
75
|
+
import { AbsoluteReplicas, AddedReplicationInfoV2Message, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, FullReplicationInfoV2Message, MinReplicas, ReplicationError, ReplicationPingMessage, RequestReplicationInfoMessage, RequestReplicationInfoV2Message, ResponseRoleMessage, StoppedReplicating, StoppedReplicationInfoV2Message, decodeReplicas, encodeReplicas, isReplicationInfoV2Message, maxReplicas, } from "./replication.js";
|
|
75
76
|
import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
|
|
76
77
|
import { Observer, Replicator } from "./role.js";
|
|
77
78
|
import { createSyncronizer } from "./sync/factory.js";
|
|
@@ -625,7 +626,8 @@ let SharedLog = (() => {
|
|
|
625
626
|
value;
|
|
626
627
|
}
|
|
627
628
|
get _nativeBackboneCoordinateJournalLastFlushMs() {
|
|
628
|
-
return this._coordinates
|
|
629
|
+
return this._coordinates
|
|
630
|
+
?._nativeBackboneCoordinateJournalLastFlushMs;
|
|
629
631
|
}
|
|
630
632
|
set _nativeBackboneCoordinateJournalLastFlushMs(value) {
|
|
631
633
|
(this._coordinates ??=
|
|
@@ -1498,7 +1500,8 @@ let SharedLog = (() => {
|
|
|
1498
1500
|
}
|
|
1499
1501
|
if (!lowerMarkerCommitted) {
|
|
1500
1502
|
if (intent.coordinates.length > 0) {
|
|
1501
|
-
const mutationGenerations = (this._coordinates._nativeCoordinateMutationGenerations ??=
|
|
1503
|
+
const mutationGenerations = (this._coordinates._nativeCoordinateMutationGenerations ??=
|
|
1504
|
+
new Map());
|
|
1502
1505
|
const rollback = {
|
|
1503
1506
|
hashes: new Set(),
|
|
1504
1507
|
entries: new Map(),
|
|
@@ -1609,6 +1612,7 @@ let SharedLog = (() => {
|
|
|
1609
1612
|
// A fn that we can call many times that recalculates the participation role
|
|
1610
1613
|
rebalanceParticipationDebounced;
|
|
1611
1614
|
_announcements;
|
|
1615
|
+
_v2Receive;
|
|
1612
1616
|
_v2Send;
|
|
1613
1617
|
// A fn for debouncing the calls for pruning
|
|
1614
1618
|
pruneDebouncedFn;
|
|
@@ -1727,6 +1731,78 @@ let SharedLog = (() => {
|
|
|
1727
1731
|
isReplicationOwnershipLifecycleActive: (controller) => this.isRepairLifecycleActive(controller),
|
|
1728
1732
|
});
|
|
1729
1733
|
}
|
|
1734
|
+
replicationInfoV2ReceiveCapabilities() {
|
|
1735
|
+
return (SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
1736
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
1737
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY |
|
|
1738
|
+
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
1739
|
+
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
1740
|
+
: 0));
|
|
1741
|
+
}
|
|
1742
|
+
async advertiseReplicationInfoV2ReceiveCapability(properties) {
|
|
1743
|
+
const peerHash = properties.target.hashcode();
|
|
1744
|
+
const receiverTransportSession = BigInt(this.node.services.pubsub.session);
|
|
1745
|
+
await this.rpc.send(new SyncCapabilitiesMessage({
|
|
1746
|
+
capabilities: this.replicationInfoV2ReceiveCapabilities(),
|
|
1747
|
+
}), {
|
|
1748
|
+
mode: new AcknowledgeDelivery({
|
|
1749
|
+
redundancy: 1,
|
|
1750
|
+
to: [properties.target],
|
|
1751
|
+
}),
|
|
1752
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1753
|
+
signal: properties.signal,
|
|
1754
|
+
});
|
|
1755
|
+
if (properties.signal.aborted ||
|
|
1756
|
+
this.closed ||
|
|
1757
|
+
!this._peerSessions.isCurrent(peerHash, properties.peerSession) ||
|
|
1758
|
+
properties.peerSession.phase !== "open" ||
|
|
1759
|
+
!properties.peerSession.isActive() ||
|
|
1760
|
+
!this._peerSessions.isReceiveEpochCurrent(peerHash, properties.receiveEpoch) ||
|
|
1761
|
+
this._peerSessions.isReplicationInfoBlocked(peerHash) ||
|
|
1762
|
+
!this._peerSessions.isReceiveCleanupGateOpen(peerHash) ||
|
|
1763
|
+
BigInt(this.node.services.pubsub.session) !== receiverTransportSession) {
|
|
1764
|
+
return undefined;
|
|
1765
|
+
}
|
|
1766
|
+
return {
|
|
1767
|
+
receiverTransportSession,
|
|
1768
|
+
requestNotBeforeMs: Date.now(),
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
createReplicationInfoV2ReceiveCoordinator() {
|
|
1772
|
+
return new ReplicationInfoV2ReceiveCoordinator({
|
|
1773
|
+
getSelfKey: () => this.node.identity.publicKey,
|
|
1774
|
+
getReceiverTransportSession: () => BigInt(this.node.services.pubsub.session),
|
|
1775
|
+
isClosed: () => this.closed,
|
|
1776
|
+
isPeerStateCurrent: (peerHash, peerSession, receiveEpoch) => this._peerSessions.isCurrent(peerHash, peerSession) &&
|
|
1777
|
+
peerSession.phase === "open" &&
|
|
1778
|
+
peerSession.isActive() &&
|
|
1779
|
+
this._peerSessions.isReceiveEpochCurrent(peerHash, receiveEpoch) &&
|
|
1780
|
+
!this._peerSessions.isReplicationInfoBlocked(peerHash) &&
|
|
1781
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash),
|
|
1782
|
+
isSenderTransportSessionCurrent: (peerHash, senderTransportSession) => this._peerSyncCapabilitySessions.get(peerHash) ===
|
|
1783
|
+
senderTransportSession,
|
|
1784
|
+
sendRequest: async (request, target, signal) => {
|
|
1785
|
+
await this.rpc.send(request, {
|
|
1786
|
+
mode: new AcknowledgeDelivery({ redundancy: 1, to: [target] }),
|
|
1787
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
1788
|
+
signal,
|
|
1789
|
+
});
|
|
1790
|
+
},
|
|
1791
|
+
refreshLocalCapability: (properties) => this.advertiseReplicationInfoV2ReceiveCapability({
|
|
1792
|
+
target: properties.target,
|
|
1793
|
+
peerSession: properties.peerSession,
|
|
1794
|
+
receiveEpoch: properties.receiveEpoch,
|
|
1795
|
+
signal: properties.signal,
|
|
1796
|
+
}),
|
|
1797
|
+
onRequestError: (error) => {
|
|
1798
|
+
if (isNotStartedError(error) ||
|
|
1799
|
+
(this.closed && error instanceof AbortError)) {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
logger.trace(`Replication-info V2 recovery request failed: ${error?.message ?? String(error)}`);
|
|
1803
|
+
},
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1730
1806
|
createReplicatorLivenessMonitor() {
|
|
1731
1807
|
return new ReplicatorLivenessMonitor({
|
|
1732
1808
|
// Sweep-driven probes and activity marks dispatch through the owner so
|
|
@@ -1831,6 +1907,7 @@ let SharedLog = (() => {
|
|
|
1831
1907
|
this._replicatorJoinEmitted = new Set();
|
|
1832
1908
|
this._replicatorsReconciled = false;
|
|
1833
1909
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
1910
|
+
this._v2Receive = this.createReplicationInfoV2ReceiveCoordinator();
|
|
1834
1911
|
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
1835
1912
|
this._announcements = this.createReplicationAnnouncementCoordinator();
|
|
1836
1913
|
this.pendingMaturity = new Map();
|
|
@@ -2226,11 +2303,20 @@ let SharedLog = (() => {
|
|
|
2226
2303
|
const { peerHash, capabilities, transportSession, timestamp, openingSession, } = properties;
|
|
2227
2304
|
if (openingSession) {
|
|
2228
2305
|
const previous = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
2306
|
+
if (transportSession !== undefined &&
|
|
2307
|
+
timestamp !== undefined &&
|
|
2308
|
+
previous?.epoch === openingSession &&
|
|
2309
|
+
previous.timestamp !== undefined &&
|
|
2310
|
+
previous.transportSession !== transportSession &&
|
|
2311
|
+
timestamp <= previous.timestamp) {
|
|
2312
|
+
return false;
|
|
2313
|
+
}
|
|
2229
2314
|
if (transportSession !== undefined &&
|
|
2230
2315
|
timestamp !== undefined &&
|
|
2231
2316
|
previous?.epoch === openingSession &&
|
|
2232
2317
|
previous.transportSession === transportSession) {
|
|
2233
|
-
if (previous.timestamp !== undefined &&
|
|
2318
|
+
if (previous.timestamp !== undefined &&
|
|
2319
|
+
timestamp < previous.timestamp) {
|
|
2234
2320
|
return false;
|
|
2235
2321
|
}
|
|
2236
2322
|
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
@@ -2258,24 +2344,26 @@ let SharedLog = (() => {
|
|
|
2258
2344
|
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
2259
2345
|
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
2260
2346
|
this._v2Send.advancePeerCapability(peerHash);
|
|
2347
|
+
this._v2Receive.revokePeerCapability(peerHash);
|
|
2261
2348
|
return true;
|
|
2262
2349
|
}
|
|
2263
2350
|
const previousSession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
2264
2351
|
const previousTimestamp = this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
2265
2352
|
const sameTransportSession = previousSession === transportSession;
|
|
2266
|
-
if (
|
|
2267
|
-
|
|
2268
|
-
|
|
2353
|
+
if (previousTimestamp !== undefined &&
|
|
2354
|
+
((sameTransportSession && timestamp < previousTimestamp) ||
|
|
2355
|
+
(!sameTransportSession && timestamp <= previousTimestamp))) {
|
|
2269
2356
|
return false;
|
|
2270
2357
|
}
|
|
2271
2358
|
const previousCapabilities = this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
2272
2359
|
const nextCapabilities = sameTransportSession
|
|
2273
2360
|
? previousCapabilities | capabilities
|
|
2274
2361
|
: capabilities;
|
|
2362
|
+
const senderGrantCapabilityMask = SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
2363
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY;
|
|
2275
2364
|
const generationAdvanced = !sameTransportSession ||
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
nextCapabilities !== previousCapabilities;
|
|
2365
|
+
(previousCapabilities & senderGrantCapabilityMask) !==
|
|
2366
|
+
(nextCapabilities & senderGrantCapabilityMask);
|
|
2279
2367
|
this._peerSyncCapabilities.set(peerHash, nextCapabilities);
|
|
2280
2368
|
this._peerSyncCapabilitySessions.set(peerHash, transportSession);
|
|
2281
2369
|
this._peerSyncCapabilityTimestamps.set(peerHash, previousTimestamp === undefined ||
|
|
@@ -2288,6 +2376,24 @@ let SharedLog = (() => {
|
|
|
2288
2376
|
}
|
|
2289
2377
|
return true;
|
|
2290
2378
|
}
|
|
2379
|
+
promoteReplicationInfoV2ReceiveCapability(target, peerSession) {
|
|
2380
|
+
const peerHash = target.hashcode();
|
|
2381
|
+
const senderTransportSession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
2382
|
+
const capabilityTimestamp = this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
2383
|
+
if (senderTransportSession === undefined ||
|
|
2384
|
+
capabilityTimestamp === undefined) {
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
return this._v2Receive.observeCapability({
|
|
2388
|
+
peerHash,
|
|
2389
|
+
target,
|
|
2390
|
+
peerSession,
|
|
2391
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
2392
|
+
capabilities: this._peerSyncCapabilities.get(peerHash) ?? 0,
|
|
2393
|
+
senderTransportSession,
|
|
2394
|
+
capabilityTimestamp,
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2291
2397
|
/**
|
|
2292
2398
|
* Live append gossip may use the raw exchange-heads path only when we
|
|
2293
2399
|
* opted into raw sync and every remote recipient advertised raw capability
|
|
@@ -2984,7 +3090,8 @@ let SharedLog = (() => {
|
|
|
2984
3090
|
return undefined;
|
|
2985
3091
|
}
|
|
2986
3092
|
isReceiveOwnershipSnapshotStable(revision) {
|
|
2987
|
-
return ((this._instanceLifecycle?._receiveOwnershipMutationAdmissions ?? 0) ===
|
|
3093
|
+
return ((this._instanceLifecycle?._receiveOwnershipMutationAdmissions ?? 0) ===
|
|
3094
|
+
0 &&
|
|
2988
3095
|
(this._instanceLifecycle?._receiveOwnershipRevision ?? 0) === revision);
|
|
2989
3096
|
}
|
|
2990
3097
|
canCacheLeaderSelectionContext(options) {
|
|
@@ -3771,6 +3878,7 @@ let SharedLog = (() => {
|
|
|
3771
3878
|
};
|
|
3772
3879
|
let removed = false;
|
|
3773
3880
|
let removalCallCompleted = false;
|
|
3881
|
+
let replicationInfoRecoveryEpochAdvanced = false;
|
|
3774
3882
|
// Replication-info updates already serialize per peer. Put the hash-wide
|
|
3775
3883
|
// removal on the same queue so a newer reset cannot be deleted underneath
|
|
3776
3884
|
// itself by an older unsubscribe callback.
|
|
@@ -3840,6 +3948,13 @@ let SharedLog = (() => {
|
|
|
3840
3948
|
// before mutating ownership.
|
|
3841
3949
|
this.cleanupCheckedPrunePeer(keyHash, replicationOwnershipLifecycleController, checkedPruneCoordinator);
|
|
3842
3950
|
}
|
|
3951
|
+
if (!isMe) {
|
|
3952
|
+
// This is the last synchronous current-generation boundary
|
|
3953
|
+
// before destructive work. Fence parked receives now because
|
|
3954
|
+
// the coherent delete can durably mutate and then fail or poison.
|
|
3955
|
+
this.advanceReplicationInfoRecoveryEpoch(keyHash);
|
|
3956
|
+
replicationInfoRecoveryEpochAdvanced = true;
|
|
3957
|
+
}
|
|
3843
3958
|
const deletion = await this.deleteReplicationRangesCoherently(deleted, keyHash);
|
|
3844
3959
|
deleted = deletion.removed;
|
|
3845
3960
|
ownerHasRanges = deletion.ownerHasRanges;
|
|
@@ -3885,11 +4000,6 @@ let SharedLog = (() => {
|
|
|
3885
4000
|
// through replication-info updates without a topic unsubscribe event.
|
|
3886
4001
|
await cleanupDisconnectedPeer();
|
|
3887
4002
|
if (!isMe) {
|
|
3888
|
-
// Replication-info handlers release their receive lease before joining
|
|
3889
|
-
// this lane. Fence every handler queued behind this successful removal,
|
|
3890
|
-
// regardless of whether it came from liveness, startup pruning, or an
|
|
3891
|
-
// unsubscribe transition.
|
|
3892
|
-
this.advanceReplicationInfoRecoveryEpoch(keyHash);
|
|
3893
4003
|
this.rebalanceParticipationDebounced?.call();
|
|
3894
4004
|
}
|
|
3895
4005
|
removed = true;
|
|
@@ -3920,6 +4030,22 @@ let SharedLog = (() => {
|
|
|
3920
4030
|
}
|
|
3921
4031
|
finally {
|
|
3922
4032
|
releaseReceiveCleanupGate?.();
|
|
4033
|
+
if (replicationInfoRecoveryEpochAdvanced &&
|
|
4034
|
+
ownsReplicationOwnershipLifecycle() &&
|
|
4035
|
+
ownsReplicationLifecycle() &&
|
|
4036
|
+
ownsSubscriptionEpoch()) {
|
|
4037
|
+
// The first recovery arm ran while receive admission was fenced.
|
|
4038
|
+
// Re-arm after releasing the cleanup gate so a still-open peer can
|
|
4039
|
+
// deliver the authoritative Full that completes recovery.
|
|
4040
|
+
const peerSession = this._peerSessions.current(keyHash);
|
|
4041
|
+
if (peerSession?.phase === "open") {
|
|
4042
|
+
this._v2Receive.advanceRecovery({
|
|
4043
|
+
peerHash: keyHash,
|
|
4044
|
+
peerSession,
|
|
4045
|
+
receiveEpoch: this._peerSessions.receiveEpoch(keyHash),
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
3923
4049
|
if (isSpeculativePeerRemoval) {
|
|
3924
4050
|
const cancelledByFreshActivity = removalCallCompleted &&
|
|
3925
4051
|
!removed &&
|
|
@@ -4012,6 +4138,11 @@ let SharedLog = (() => {
|
|
|
4012
4138
|
}
|
|
4013
4139
|
const deletion = await this.deleteReplicationRangesCoherently(ranges, ownerHash);
|
|
4014
4140
|
ranges = deletion.removed;
|
|
4141
|
+
if ((options?.shouldRemove && !options.shouldRemove()) ||
|
|
4142
|
+
options?.onDurableRemoveCommitted?.() === false) {
|
|
4143
|
+
await deletion.rollback();
|
|
4144
|
+
return false;
|
|
4145
|
+
}
|
|
4015
4146
|
options?.onRemoved?.(ranges);
|
|
4016
4147
|
if (ranges.length > 0) {
|
|
4017
4148
|
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
@@ -4075,7 +4206,7 @@ let SharedLog = (() => {
|
|
|
4075
4206
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
4076
4207
|
return this.withReceiveOwnershipMutationQueue(() => this.addReplicationRangeUnlocked(ranges, from, options, replicationOwnershipLifecycleController), replicationOwnershipLifecycleController);
|
|
4077
4208
|
}
|
|
4078
|
-
async addReplicationRangeUnlocked(ranges, from, { reset, checkDuplicates, timestamp: ts, rebalance, allowLegacyOrderedReplacementPairs, onConfirmedDurableStateChanged, shouldApply, } = {}, replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
4209
|
+
async addReplicationRangeUnlocked(ranges, from, { reset, checkDuplicates, timestamp: ts, rebalance, allowLegacyOrderedReplacementPairs, onConfirmedDurableStateChanged, onDurableApplyCommitted, shouldApply, } = {}, replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
4079
4210
|
this.validateReplicationRangeAnnouncement(ranges);
|
|
4080
4211
|
this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
|
|
4081
4212
|
// This predicate is intentionally checked inside the global ownership lane.
|
|
@@ -4084,6 +4215,12 @@ let SharedLog = (() => {
|
|
|
4084
4215
|
if (shouldApply && !shouldApply()) {
|
|
4085
4216
|
return [];
|
|
4086
4217
|
}
|
|
4218
|
+
const applySuperseded = Symbol("replication-range-apply-superseded");
|
|
4219
|
+
const throwIfApplySuperseded = () => {
|
|
4220
|
+
if (shouldApply && !shouldApply()) {
|
|
4221
|
+
throw applySuperseded;
|
|
4222
|
+
}
|
|
4223
|
+
};
|
|
4087
4224
|
const fromHash = from.hashcode();
|
|
4088
4225
|
const incomingRangesById = new Map();
|
|
4089
4226
|
const incomingRangeCountsById = new Map();
|
|
@@ -4142,6 +4279,7 @@ let SharedLog = (() => {
|
|
|
4142
4279
|
let isStoppedReplicating = false;
|
|
4143
4280
|
let wasReplicatorBeforeDestructiveReset = false;
|
|
4144
4281
|
let resetFailureLeaveEmitted = false;
|
|
4282
|
+
let resetDeletionRollback;
|
|
4145
4283
|
const publishConfirmedResetStop = (ownerHasRanges) => {
|
|
4146
4284
|
if (ownerHasRanges || resetFailureLeaveEmitted) {
|
|
4147
4285
|
return;
|
|
@@ -4182,8 +4320,14 @@ let SharedLog = (() => {
|
|
|
4182
4320
|
else {
|
|
4183
4321
|
wasReplicatorBeforeDestructiveReset =
|
|
4184
4322
|
this.uniqueReplicators.has(fromHash);
|
|
4323
|
+
throwIfApplySuperseded();
|
|
4185
4324
|
const deletion = await this.deleteReplicationRangesCoherently(deleted, fromHash, { preserveOwnerMembership: ranges.length > 0 });
|
|
4186
4325
|
deleted = deletion.removed;
|
|
4326
|
+
resetDeletionRollback = deletion.rollback;
|
|
4327
|
+
if (shouldApply && !shouldApply()) {
|
|
4328
|
+
await deletion.rollback();
|
|
4329
|
+
return [];
|
|
4330
|
+
}
|
|
4187
4331
|
diffs = [
|
|
4188
4332
|
...deleted.map((x) => {
|
|
4189
4333
|
return { range: x, type: "removed", timestamp };
|
|
@@ -4339,19 +4483,27 @@ let SharedLog = (() => {
|
|
|
4339
4483
|
return failure;
|
|
4340
4484
|
};
|
|
4341
4485
|
try {
|
|
4486
|
+
throwIfApplySuperseded();
|
|
4342
4487
|
for (const diff of diffs) {
|
|
4343
4488
|
if (diff.type !== "added") {
|
|
4344
4489
|
continue;
|
|
4345
4490
|
}
|
|
4491
|
+
throwIfApplySuperseded();
|
|
4346
4492
|
appliedPositiveRanges.push(diff);
|
|
4347
4493
|
await this.replicationIndex.put(diff.range);
|
|
4348
4494
|
this.putNativeReplicationRange(diff.range);
|
|
4495
|
+
throwIfApplySuperseded();
|
|
4349
4496
|
}
|
|
4350
4497
|
if (reset && diffs.length > 0) {
|
|
4351
4498
|
await this.updateOldestTimestampFromIndex();
|
|
4499
|
+
throwIfApplySuperseded();
|
|
4500
|
+
}
|
|
4501
|
+
if (onDurableApplyCommitted?.() === false) {
|
|
4502
|
+
throw applySuperseded;
|
|
4352
4503
|
}
|
|
4353
4504
|
}
|
|
4354
4505
|
catch (error) {
|
|
4506
|
+
const superseded = error === applySuperseded;
|
|
4355
4507
|
let outcomeError = error;
|
|
4356
4508
|
if (appliedPositiveRanges.length > 0) {
|
|
4357
4509
|
try {
|
|
@@ -4361,6 +4513,20 @@ let SharedLog = (() => {
|
|
|
4361
4513
|
outcomeError = poisonFromPositiveRollback(rollbackError, error);
|
|
4362
4514
|
}
|
|
4363
4515
|
}
|
|
4516
|
+
if (superseded) {
|
|
4517
|
+
if (resetDeletionRollback) {
|
|
4518
|
+
try {
|
|
4519
|
+
await resetDeletionRollback();
|
|
4520
|
+
}
|
|
4521
|
+
catch (rollbackError) {
|
|
4522
|
+
outcomeError = rollbackError;
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
if (outcomeError === applySuperseded) {
|
|
4526
|
+
return [];
|
|
4527
|
+
}
|
|
4528
|
+
throw outcomeError;
|
|
4529
|
+
}
|
|
4364
4530
|
if (reset) {
|
|
4365
4531
|
const negativeDiffs = diffs.filter((diff) => diff.type !== "added");
|
|
4366
4532
|
if (negativeDiffs.length > 0) {
|
|
@@ -6967,7 +7133,8 @@ let SharedLog = (() => {
|
|
|
6967
7133
|
appendLocallyPreparedPayloadNativeBackboneStorageTransaction(payloadData, appendOptions, properties, minReplicasValue, runtimeOnlyCoordinates, ownershipLifecycleController) {
|
|
6968
7134
|
this.throwIfReplicationOwnershipLifecycleInactive(ownershipLifecycleController);
|
|
6969
7135
|
const backbone = this._nativeBackbone;
|
|
6970
|
-
if (!backbone ||
|
|
7136
|
+
if (!backbone ||
|
|
7137
|
+
!this._coordinates.canUseNativeBackboneResidentCoordinateState()) {
|
|
6971
7138
|
return undefined;
|
|
6972
7139
|
}
|
|
6973
7140
|
if (properties?.nativeBackboneDocumentDeleteKey &&
|
|
@@ -7098,11 +7265,12 @@ let SharedLog = (() => {
|
|
|
7098
7265
|
backboneAppend.trimmed.map((entry) => entry.hash);
|
|
7099
7266
|
const committedHash = backboneAppend.entry.cid ?? backboneAppend.entry.hash;
|
|
7100
7267
|
const committedNext = backboneAppend.entry.next ?? next;
|
|
7101
|
-
nativeCoordinateRollback =
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7268
|
+
nativeCoordinateRollback =
|
|
7269
|
+
this._coordinates.snapshotResidentCoordinateEntries([
|
|
7270
|
+
...(committedHash ? [committedHash] : []),
|
|
7271
|
+
...committedNext,
|
|
7272
|
+
...nativeTrimmedHashes,
|
|
7273
|
+
]);
|
|
7106
7274
|
await this.setNativeStrictDurableTransactionOperation(nativeStrictTransaction, committedHash ? [committedHash] : [], nativeTrimmedHashes, nativeCoordinateRollback, combineCoordinateDeleteHashes(committedNext, nativeTrimmedHashes));
|
|
7107
7275
|
const rollbackCommitted = async (cause, committedCids) => {
|
|
7108
7276
|
durableWrapper?.cancelNativeDeleteCleanup?.(nativeDeleteCleanupToken);
|
|
@@ -7770,14 +7938,15 @@ let SharedLog = (() => {
|
|
|
7770
7938
|
const nativeTrimmedHashSet = new Set(committedAppends.flatMap((append) => append.trimmedHashes ??
|
|
7771
7939
|
append.trimmed.map((entry) => entry.hash)));
|
|
7772
7940
|
const nativeTrimmedHashes = [...nativeTrimmedHashSet];
|
|
7773
|
-
batchCoordinateRollback =
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
append.
|
|
7780
|
-
|
|
7941
|
+
batchCoordinateRollback =
|
|
7942
|
+
this._coordinates.snapshotResidentCoordinateEntries(committedAppends.flatMap((append) => [
|
|
7943
|
+
...((append.entry.cid ?? append.entry.hash)
|
|
7944
|
+
? [append.entry.cid ?? append.entry.hash]
|
|
7945
|
+
: []),
|
|
7946
|
+
...append.entry.next,
|
|
7947
|
+
...(append.trimmedHashes ??
|
|
7948
|
+
append.trimmed.map((entry) => entry.hash)),
|
|
7949
|
+
]));
|
|
7781
7950
|
await this.setNativeStrictDurableTransactionOperation(nativeStrictTransaction, committedCids, nativeTrimmedHashes, batchCoordinateRollback, committedAppends.flatMap((append) => [
|
|
7782
7951
|
...append.entry.next,
|
|
7783
7952
|
...(append.trimmedHashes ??
|
|
@@ -9004,6 +9173,8 @@ let SharedLog = (() => {
|
|
|
9004
9173
|
this._lastLocalAppendAt = 0;
|
|
9005
9174
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
9006
9175
|
this._announcements.resetForOpen();
|
|
9176
|
+
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
9177
|
+
this._v2Receive.resetForOpen();
|
|
9007
9178
|
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
9008
9179
|
this._v2Send.resetForOpen();
|
|
9009
9180
|
const adaptiveReplicateOptions = options?.replicate && isAdaptiveReplicatorOption(options.replicate)
|
|
@@ -9575,7 +9746,8 @@ let SharedLog = (() => {
|
|
|
9575
9746
|
// any stale coordinate or document state to this backbone.
|
|
9576
9747
|
await this._coordinates._nativeBackboneCoordinatePersistence.resumeDrop?.();
|
|
9577
9748
|
await this._coordinates._nativeBackboneCoordinatePersistence.hydrate(backbone);
|
|
9578
|
-
this._coordinates._nativeBackboneCoordinateJournalLastFlushMs =
|
|
9749
|
+
this._coordinates._nativeBackboneCoordinateJournalLastFlushMs =
|
|
9750
|
+
Date.now();
|
|
9579
9751
|
this.hydrateNativeCoordinateStateFromBackbone(backbone);
|
|
9580
9752
|
return;
|
|
9581
9753
|
}
|
|
@@ -9787,18 +9959,20 @@ let SharedLog = (() => {
|
|
|
9787
9959
|
}
|
|
9788
9960
|
if (this.node.directory != null &&
|
|
9789
9961
|
this._coordinates._nativeBackboneCoordinatePersistence) {
|
|
9790
|
-
if (this._coordinates._nativeBackboneCoordinatePersistence
|
|
9962
|
+
if (this._coordinates._nativeBackboneCoordinatePersistence
|
|
9963
|
+
.durableBarrier !== true ||
|
|
9791
9964
|
typeof this._nativeBackboneCoordinatePersistenceStore
|
|
9792
9965
|
?.durableBarrier !== "function") {
|
|
9793
9966
|
throw new Error("Durable nativeBackbone coordinate persistence requires an explicit physical durability barrier");
|
|
9794
9967
|
}
|
|
9795
9968
|
}
|
|
9796
9969
|
if (this._coordinates._nativeBackboneCoordinatePersistence &&
|
|
9797
|
-
(this._coordinates._nativeBackboneCoordinatePersistence
|
|
9798
|
-
null ||
|
|
9799
|
-
this._coordinates._nativeBackboneCoordinatePersistence
|
|
9800
|
-
null) &&
|
|
9801
|
-
this._coordinates._nativeBackboneCoordinatePersistence
|
|
9970
|
+
(this._coordinates._nativeBackboneCoordinatePersistence
|
|
9971
|
+
.compactMaxJournalBytes != null ||
|
|
9972
|
+
this._coordinates._nativeBackboneCoordinatePersistence
|
|
9973
|
+
.compactMaxJournalRecords != null) &&
|
|
9974
|
+
this._coordinates._nativeBackboneCoordinatePersistence
|
|
9975
|
+
.crashSafeCompaction !== true) {
|
|
9802
9976
|
// Durable custom adapters must explicitly advertise an atomic generation
|
|
9803
9977
|
// protocol before SharedLog permits automatic WAL compaction.
|
|
9804
9978
|
throw new Error("Durable native coordinate persistence compaction thresholds require crashSafeCompaction");
|
|
@@ -10121,13 +10295,18 @@ let SharedLog = (() => {
|
|
|
10121
10295
|
}
|
|
10122
10296
|
}
|
|
10123
10297
|
cleanupPeerDisconnectTracking(peerHash, ownershipLifecycleController = this.captureReplicationOwnershipLifecycle()) {
|
|
10298
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
10299
|
+
const preserveV2Session = peerSession?.phase === "open" && peerSession.isActive();
|
|
10124
10300
|
this.cancelReplicationInfoRequests(peerHash);
|
|
10125
10301
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
10126
10302
|
this._liveness._replicatorLastActivityAt.delete(peerHash);
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10303
|
+
if (!preserveV2Session) {
|
|
10304
|
+
this._peerSyncCapabilities.delete(peerHash);
|
|
10305
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
10306
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
10307
|
+
this._v2Receive.clearPeer(peerHash);
|
|
10308
|
+
this._v2Send.clearPeer(peerHash);
|
|
10309
|
+
}
|
|
10131
10310
|
this.cleanupPendingIHavePeer(peerHash);
|
|
10132
10311
|
this.cleanupCheckedPrunePeer(peerHash, ownershipLifecycleController, this._checkedPrune);
|
|
10133
10312
|
}
|
|
@@ -10145,8 +10324,16 @@ let SharedLog = (() => {
|
|
|
10145
10324
|
// when they eventually reach the apply lane. Reset the sender's
|
|
10146
10325
|
// ordering watermark with the local epoch so a later arrival can be
|
|
10147
10326
|
// accepted without comparing its clock to this receiver's clock.
|
|
10148
|
-
this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
10327
|
+
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
10149
10328
|
this.latestReplicationInfoMessage.delete(peerHash);
|
|
10329
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
10330
|
+
if (peerSession?.phase === "open") {
|
|
10331
|
+
this._v2Receive.advanceRecovery({
|
|
10332
|
+
peerHash,
|
|
10333
|
+
peerSession,
|
|
10334
|
+
receiveEpoch,
|
|
10335
|
+
});
|
|
10336
|
+
}
|
|
10150
10337
|
}
|
|
10151
10338
|
async resolveCandidatePeersForHash(hash, options) {
|
|
10152
10339
|
if (options?.signal?.aborted)
|
|
@@ -10946,6 +11133,7 @@ let SharedLog = (() => {
|
|
|
10946
11133
|
this._peerSyncCapabilities?.clear();
|
|
10947
11134
|
this._peerSyncCapabilitySessions?.clear();
|
|
10948
11135
|
this._peerSyncCapabilityTimestamps?.clear();
|
|
11136
|
+
this._v2Receive?.clearForClose();
|
|
10949
11137
|
this._v2Send?.clearForClose();
|
|
10950
11138
|
this._liveRawGossipBatches?.clear();
|
|
10951
11139
|
this._nativeSharedLogState?.clearGidPeers();
|
|
@@ -11009,6 +11197,7 @@ let SharedLog = (() => {
|
|
|
11009
11197
|
this.preventParentAttachments();
|
|
11010
11198
|
this.stopRepairLifecycle();
|
|
11011
11199
|
this._instanceLifecycle?.beginTerminal("close");
|
|
11200
|
+
this._v2Receive?.clearForClose();
|
|
11012
11201
|
const replicationRangeTerminalFence = this.acquireReplicationRangeMutationTerminalFence();
|
|
11013
11202
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
11014
11203
|
try {
|
|
@@ -11069,10 +11258,12 @@ let SharedLog = (() => {
|
|
|
11069
11258
|
try {
|
|
11070
11259
|
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
11071
11260
|
await Promise.all([
|
|
11072
|
-
this.rpc
|
|
11261
|
+
this.rpc
|
|
11262
|
+
.send(reset, {
|
|
11073
11263
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11074
11264
|
signal: abort.signal,
|
|
11075
|
-
})
|
|
11265
|
+
})
|
|
11266
|
+
.catch(() => { }),
|
|
11076
11267
|
this._v2Send.sendTerminalReset(abort.signal),
|
|
11077
11268
|
]);
|
|
11078
11269
|
}
|
|
@@ -11151,6 +11342,7 @@ let SharedLog = (() => {
|
|
|
11151
11342
|
this.preventParentAttachments();
|
|
11152
11343
|
this.stopRepairLifecycle();
|
|
11153
11344
|
this._instanceLifecycle?.beginTerminal("drop");
|
|
11345
|
+
this._v2Receive?.clearForClose();
|
|
11154
11346
|
const replicationRangeTerminalFence = this.acquireReplicationRangeMutationTerminalFence();
|
|
11155
11347
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
11156
11348
|
try {
|
|
@@ -11194,10 +11386,12 @@ let SharedLog = (() => {
|
|
|
11194
11386
|
try {
|
|
11195
11387
|
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
11196
11388
|
await Promise.all([
|
|
11197
|
-
this.rpc
|
|
11389
|
+
this.rpc
|
|
11390
|
+
.send(reset, {
|
|
11198
11391
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
11199
11392
|
signal: abort.signal,
|
|
11200
|
-
})
|
|
11393
|
+
})
|
|
11394
|
+
.catch(() => { }),
|
|
11201
11395
|
this._v2Send.sendTerminalReset(abort.signal),
|
|
11202
11396
|
]);
|
|
11203
11397
|
}
|
|
@@ -11614,13 +11808,6 @@ let SharedLog = (() => {
|
|
|
11614
11808
|
}
|
|
11615
11809
|
// Callback for receiving a message from the network
|
|
11616
11810
|
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
|
-
}
|
|
11624
11811
|
const stashBackedRawMessage = isStashBackedRawExchangeHeadsMessage(msg)
|
|
11625
11812
|
? msg
|
|
11626
11813
|
: undefined;
|
|
@@ -11661,18 +11848,25 @@ let SharedLog = (() => {
|
|
|
11661
11848
|
msg = msg.toReplicationInfoMessage(); // migration
|
|
11662
11849
|
}
|
|
11663
11850
|
if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
11664
|
-
msg instanceof AddedReplicationSegmentMessage
|
|
11851
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
11852
|
+
msg instanceof FullReplicationInfoV2Message ||
|
|
11853
|
+
msg instanceof AddedReplicationInfoV2Message) {
|
|
11665
11854
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
11666
11855
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
11667
11856
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
11668
11857
|
}
|
|
11669
|
-
else if (msg instanceof StoppedReplicating
|
|
11858
|
+
else if (msg instanceof StoppedReplicating ||
|
|
11859
|
+
msg instanceof StoppedReplicationInfoV2Message) {
|
|
11670
11860
|
// Bound the raw decoded vector before deduplication can hide the
|
|
11671
11861
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
11672
11862
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
11673
11863
|
}
|
|
11674
11864
|
if (!context.from.equals(this.node.identity.publicKey) &&
|
|
11675
|
-
!(msg instanceof RequestReplicationInfoV2Message)
|
|
11865
|
+
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
11866
|
+
!isReplicationInfoV2Message(msg) &&
|
|
11867
|
+
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
11868
|
+
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
11869
|
+
!(msg instanceof StoppedReplicating)) {
|
|
11676
11870
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
11677
11871
|
}
|
|
11678
11872
|
const syncProfile = this._logProperties?.sync?.profile;
|
|
@@ -12988,6 +13182,7 @@ let SharedLog = (() => {
|
|
|
12988
13182
|
fromHash: receiveFromHash,
|
|
12989
13183
|
session: receiveSession,
|
|
12990
13184
|
lifecycleController: receiveReplicationLifecycleController,
|
|
13185
|
+
ownershipLifecycleController: receiveOwnershipLifecycleController,
|
|
12991
13186
|
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
12992
13187
|
syncProfile,
|
|
12993
13188
|
lease: peerReceiveLease,
|
|
@@ -13013,6 +13208,9 @@ let SharedLog = (() => {
|
|
|
13013
13208
|
this.clearRepairFrontierHashes(context.from.hashcode(), msg.hashes);
|
|
13014
13209
|
return;
|
|
13015
13210
|
}
|
|
13211
|
+
else if (isReplicationInfoV2Message(msg)) {
|
|
13212
|
+
await this.handleReplicationInfoV2Announcement(msg, laneRequestContext, lane);
|
|
13213
|
+
}
|
|
13016
13214
|
else if (msg instanceof SyncCapabilitiesMessage) {
|
|
13017
13215
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
13018
13216
|
const capabilityTransportSession = context.message?.header?.session;
|
|
@@ -13033,13 +13231,14 @@ let SharedLog = (() => {
|
|
|
13033
13231
|
openingSession: receiveSession,
|
|
13034
13232
|
});
|
|
13035
13233
|
}
|
|
13036
|
-
else {
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13234
|
+
else if (this.observePeerSyncCapabilities({
|
|
13235
|
+
peerHash: receiveFromHash,
|
|
13236
|
+
capabilities: msg.capabilities,
|
|
13237
|
+
transportSession: capabilityTransportSession,
|
|
13238
|
+
timestamp: capabilityTimestamp,
|
|
13239
|
+
}) &&
|
|
13240
|
+
receiveSession?.phase === "open") {
|
|
13241
|
+
this.promoteReplicationInfoV2ReceiveCapability(context.from, receiveSession);
|
|
13043
13242
|
}
|
|
13044
13243
|
}
|
|
13045
13244
|
return;
|
|
@@ -13060,6 +13259,7 @@ let SharedLog = (() => {
|
|
|
13060
13259
|
from: context.from,
|
|
13061
13260
|
peerSession: receiveSession,
|
|
13062
13261
|
receiverTransportSession: context.message.header.session,
|
|
13262
|
+
capabilityTimestamp: this._peerSyncCapabilityTimestamps.get(receiveFromHash),
|
|
13063
13263
|
requestTimestamp: context.message.header.timestamp,
|
|
13064
13264
|
})) {
|
|
13065
13265
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
@@ -13335,6 +13535,147 @@ let SharedLog = (() => {
|
|
|
13335
13535
|
}
|
|
13336
13536
|
}
|
|
13337
13537
|
}
|
|
13538
|
+
async handleReplicationInfoV2Announcement(msg, context, lane) {
|
|
13539
|
+
const from = context.from;
|
|
13540
|
+
const fromHash = lane.fromHash;
|
|
13541
|
+
const receiveSession = lane.session;
|
|
13542
|
+
if (from.equals(this.node.identity.publicKey) ||
|
|
13543
|
+
receiveSession === null ||
|
|
13544
|
+
receiveSession.phase !== "open") {
|
|
13545
|
+
return;
|
|
13546
|
+
}
|
|
13547
|
+
// Authenticate and reserve before the per-peer lane. One reservation per
|
|
13548
|
+
// peer bounds decoded-frame retention while another mutation is parked.
|
|
13549
|
+
const receiveState = this._v2Receive._receiveStates.get(fromHash);
|
|
13550
|
+
if (!receiveState ||
|
|
13551
|
+
receiveState.peerSession !== receiveSession ||
|
|
13552
|
+
receiveState.senderTransportSession !== context.message.header.session) {
|
|
13553
|
+
return;
|
|
13554
|
+
}
|
|
13555
|
+
const admission = this._v2Receive.reserve(msg, {
|
|
13556
|
+
from,
|
|
13557
|
+
peerSession: receiveSession,
|
|
13558
|
+
receiveEpoch: lane.receiveEpoch,
|
|
13559
|
+
senderTransportSession: context.message.header.session,
|
|
13560
|
+
transportTimestamp: context.message.header.timestamp,
|
|
13561
|
+
});
|
|
13562
|
+
if (!admission) {
|
|
13563
|
+
return;
|
|
13564
|
+
}
|
|
13565
|
+
lane.lease.release();
|
|
13566
|
+
try {
|
|
13567
|
+
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
13568
|
+
const hostGate = () => this._instanceLifecycle.isMembershipActiveFor(lane.lifecycleController) &&
|
|
13569
|
+
this.isRepairLifecycleActive(lane.ownershipLifecycleController) &&
|
|
13570
|
+
this._peerSessions.isCurrent(fromHash, receiveSession) &&
|
|
13571
|
+
receiveSession.phase === "open" &&
|
|
13572
|
+
this._peerSessions.isReceiveEpochCurrent(fromHash, lane.receiveEpoch) &&
|
|
13573
|
+
!this._peerSessions.isReplicationInfoBlocked(fromHash) &&
|
|
13574
|
+
this._peerSessions.isReceiveCleanupGateOpen(fromHash);
|
|
13575
|
+
if (!hostGate()) {
|
|
13576
|
+
return;
|
|
13577
|
+
}
|
|
13578
|
+
const exactGate = () => hostGate() && this._v2Receive.isAdmissionCurrent(admission);
|
|
13579
|
+
let durableCommitted = false;
|
|
13580
|
+
try {
|
|
13581
|
+
if (msg instanceof FullReplicationInfoV2Message ||
|
|
13582
|
+
msg instanceof AddedReplicationInfoV2Message) {
|
|
13583
|
+
let mutationGateChecked = false;
|
|
13584
|
+
let mutationGateAdmitted = false;
|
|
13585
|
+
const result = await this.addReplicationRange(msg.segments.map((segment) => segment.toReplicationRangeIndexable(from)), from, {
|
|
13586
|
+
reset: msg instanceof FullReplicationInfoV2Message,
|
|
13587
|
+
checkDuplicates: true,
|
|
13588
|
+
timestamp: Number(context.message.header.timestamp),
|
|
13589
|
+
allowLegacyOrderedReplacementPairs: msg instanceof AddedReplicationInfoV2Message,
|
|
13590
|
+
shouldApply: () => {
|
|
13591
|
+
mutationGateChecked = true;
|
|
13592
|
+
mutationGateAdmitted = exactGate();
|
|
13593
|
+
return mutationGateAdmitted;
|
|
13594
|
+
},
|
|
13595
|
+
onDurableApplyCommitted: () => {
|
|
13596
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
13597
|
+
return false;
|
|
13598
|
+
}
|
|
13599
|
+
durableCommitted = true;
|
|
13600
|
+
return true;
|
|
13601
|
+
},
|
|
13602
|
+
}, lane.ownershipLifecycleController);
|
|
13603
|
+
if (result === undefined ||
|
|
13604
|
+
!mutationGateChecked ||
|
|
13605
|
+
!mutationGateAdmitted ||
|
|
13606
|
+
!durableCommitted ||
|
|
13607
|
+
!exactGate()) {
|
|
13608
|
+
return;
|
|
13609
|
+
}
|
|
13610
|
+
}
|
|
13611
|
+
else {
|
|
13612
|
+
if (!exactGate()) {
|
|
13613
|
+
return;
|
|
13614
|
+
}
|
|
13615
|
+
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(msg.segmentIds, from);
|
|
13616
|
+
if (!exactGate()) {
|
|
13617
|
+
return;
|
|
13618
|
+
}
|
|
13619
|
+
let mutationGateAdmitted = true;
|
|
13620
|
+
const removedRanges = [];
|
|
13621
|
+
const removed = await this.removeReplicationRanges(rangesToRemove, from, {
|
|
13622
|
+
shouldRemove: () => {
|
|
13623
|
+
mutationGateAdmitted = exactGate();
|
|
13624
|
+
return mutationGateAdmitted;
|
|
13625
|
+
},
|
|
13626
|
+
onDurableRemoveCommitted: () => {
|
|
13627
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
13628
|
+
return false;
|
|
13629
|
+
}
|
|
13630
|
+
durableCommitted = true;
|
|
13631
|
+
return true;
|
|
13632
|
+
},
|
|
13633
|
+
onRemoved: (ranges) => removedRanges.push(...ranges),
|
|
13634
|
+
}, lane.ownershipLifecycleController);
|
|
13635
|
+
if (!durableCommitted) {
|
|
13636
|
+
if (!mutationGateAdmitted ||
|
|
13637
|
+
!exactGate() ||
|
|
13638
|
+
removed ||
|
|
13639
|
+
!this._v2Receive.commit(admission)) {
|
|
13640
|
+
return;
|
|
13641
|
+
}
|
|
13642
|
+
durableCommitted = true;
|
|
13643
|
+
}
|
|
13644
|
+
if (this._instanceLifecycle.isMembershipActiveFor(lane.lifecycleController) &&
|
|
13645
|
+
this.isRepairLifecycleActive(lane.ownershipLifecycleController)) {
|
|
13646
|
+
const timestamp = BigInt(Date.now());
|
|
13647
|
+
for (const range of removedRanges) {
|
|
13648
|
+
this.replicationChangeDebounceFn.add({
|
|
13649
|
+
range,
|
|
13650
|
+
type: "removed",
|
|
13651
|
+
timestamp,
|
|
13652
|
+
});
|
|
13653
|
+
}
|
|
13654
|
+
}
|
|
13655
|
+
}
|
|
13656
|
+
}
|
|
13657
|
+
catch (error) {
|
|
13658
|
+
this._v2Receive.requireFullAfterFailure(admission);
|
|
13659
|
+
throw error;
|
|
13660
|
+
}
|
|
13661
|
+
if (!durableCommitted) {
|
|
13662
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
13663
|
+
return;
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
if (!hostGate()) {
|
|
13667
|
+
return;
|
|
13668
|
+
}
|
|
13669
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
13670
|
+
if (msg instanceof FullReplicationInfoV2Message) {
|
|
13671
|
+
this.cancelReplicationInfoRequests(fromHash);
|
|
13672
|
+
}
|
|
13673
|
+
});
|
|
13674
|
+
}
|
|
13675
|
+
finally {
|
|
13676
|
+
this._v2Receive.release(admission);
|
|
13677
|
+
}
|
|
13678
|
+
}
|
|
13338
13679
|
async handleReplicationInfoAnnouncement(msg, context, lane) {
|
|
13339
13680
|
const receiveFromHash = lane.fromHash;
|
|
13340
13681
|
const receiveSession = lane.session;
|
|
@@ -13350,6 +13691,19 @@ let SharedLog = (() => {
|
|
|
13350
13691
|
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
13351
13692
|
const from = context.from;
|
|
13352
13693
|
const fromHash = from.hashcode();
|
|
13694
|
+
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13695
|
+
if (receiveSession) {
|
|
13696
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
13697
|
+
peerHash: fromHash,
|
|
13698
|
+
peerSession: receiveSession,
|
|
13699
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13700
|
+
senderTransportSession: context.message.header.session,
|
|
13701
|
+
transportTimestamp: context.message.header.timestamp,
|
|
13702
|
+
message: msg,
|
|
13703
|
+
});
|
|
13704
|
+
}
|
|
13705
|
+
return;
|
|
13706
|
+
}
|
|
13353
13707
|
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
13354
13708
|
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
13355
13709
|
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
@@ -13374,6 +13728,17 @@ let SharedLog = (() => {
|
|
|
13374
13728
|
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13375
13729
|
return;
|
|
13376
13730
|
}
|
|
13731
|
+
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13732
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
13733
|
+
peerHash: fromHash,
|
|
13734
|
+
peerSession: receiveSession,
|
|
13735
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13736
|
+
senderTransportSession: context.message.header.session,
|
|
13737
|
+
transportTimestamp: context.message.header.timestamp,
|
|
13738
|
+
message: msg,
|
|
13739
|
+
});
|
|
13740
|
+
return;
|
|
13741
|
+
}
|
|
13377
13742
|
// Process in-order to avoid races where repeated reset messages arrive
|
|
13378
13743
|
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
13379
13744
|
const prev = this.latestReplicationInfoMessage.get(fromHash);
|
|
@@ -13381,17 +13746,20 @@ let SharedLog = (() => {
|
|
|
13381
13746
|
return;
|
|
13382
13747
|
}
|
|
13383
13748
|
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13384
|
-
this._liveness._replicatorLivenessFailures.delete(fromHash);
|
|
13385
13749
|
if (this.closed) {
|
|
13386
13750
|
return;
|
|
13387
13751
|
}
|
|
13388
13752
|
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
13389
|
-
await this.addReplicationRange(replicationInfoMessage.segments.map((x) => x.toReplicationRangeIndexable(from)), from, {
|
|
13753
|
+
const result = await this.addReplicationRange(replicationInfoMessage.segments.map((x) => x.toReplicationRangeIndexable(from)), from, {
|
|
13390
13754
|
reset,
|
|
13391
13755
|
checkDuplicates: true,
|
|
13392
13756
|
timestamp: Number(messageTimestamp),
|
|
13393
13757
|
allowLegacyOrderedReplacementPairs: msg instanceof AddedReplicationSegmentMessage,
|
|
13394
13758
|
});
|
|
13759
|
+
if (result === undefined) {
|
|
13760
|
+
return;
|
|
13761
|
+
}
|
|
13762
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
13395
13763
|
// If the peer reports any replication segments, stop re-requesting.
|
|
13396
13764
|
// (Empty reports can be transient during startup.)
|
|
13397
13765
|
if (replicationInfoMessage.segments.length > 0) {
|
|
@@ -13418,6 +13786,19 @@ let SharedLog = (() => {
|
|
|
13418
13786
|
return;
|
|
13419
13787
|
}
|
|
13420
13788
|
const fromHash = from.hashcode();
|
|
13789
|
+
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13790
|
+
if (receiveSession) {
|
|
13791
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
13792
|
+
peerHash: fromHash,
|
|
13793
|
+
peerSession: receiveSession,
|
|
13794
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13795
|
+
senderTransportSession: context.message.header.session,
|
|
13796
|
+
transportTimestamp: context.message.header.timestamp,
|
|
13797
|
+
message: msg,
|
|
13798
|
+
});
|
|
13799
|
+
}
|
|
13800
|
+
return;
|
|
13801
|
+
}
|
|
13421
13802
|
// Same pre-lane gate shape as Added/All above (and the same
|
|
13422
13803
|
// intentional absence of a subscription-epoch term).
|
|
13423
13804
|
if (!this._instanceLifecycle.isMembershipActiveFor(receiveReplicationLifecycleController) ||
|
|
@@ -13434,17 +13815,28 @@ let SharedLog = (() => {
|
|
|
13434
13815
|
this._peerSessions.isReplicationInfoBlocked(fromHash)) {
|
|
13435
13816
|
return;
|
|
13436
13817
|
}
|
|
13818
|
+
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
13819
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
13820
|
+
peerHash: fromHash,
|
|
13821
|
+
peerSession: receiveSession,
|
|
13822
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
13823
|
+
senderTransportSession: context.message.header.session,
|
|
13824
|
+
transportTimestamp: context.message.header.timestamp,
|
|
13825
|
+
message: msg,
|
|
13826
|
+
});
|
|
13827
|
+
return;
|
|
13828
|
+
}
|
|
13437
13829
|
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
13438
13830
|
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
13439
13831
|
return;
|
|
13440
13832
|
}
|
|
13441
13833
|
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
13442
|
-
this._liveness._replicatorLivenessFailures.delete(fromHash);
|
|
13443
13834
|
if (this.closed) {
|
|
13444
13835
|
return;
|
|
13445
13836
|
}
|
|
13446
13837
|
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(segmentIds, from);
|
|
13447
13838
|
await this.removeReplicationRanges(rangesToRemove, from);
|
|
13839
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
13448
13840
|
const timestamp = BigInt(+new Date());
|
|
13449
13841
|
for (const range of rangesToRemove) {
|
|
13450
13842
|
this.replicationChangeDebounceFn.add({
|
|
@@ -15313,6 +15705,8 @@ let SharedLog = (() => {
|
|
|
15313
15705
|
* This method must run inside the global replication-range mutation lane.
|
|
15314
15706
|
*/
|
|
15315
15707
|
async deleteReplicationRangesCoherently(ranges, ownerHash, options) {
|
|
15708
|
+
const wasReplicator = this.uniqueReplicators.has(ownerHash);
|
|
15709
|
+
const joinWasEmitted = this._replicatorJoinEmitted.has(ownerHash);
|
|
15316
15710
|
if (ranges.length === 0) {
|
|
15317
15711
|
const ownerHasRanges = (await this.replicationIndex.count({ query: { hash: ownerHash } })) > 0;
|
|
15318
15712
|
if (!ownerHasRanges) {
|
|
@@ -15323,13 +15717,25 @@ let SharedLog = (() => {
|
|
|
15323
15717
|
removed: [],
|
|
15324
15718
|
retained: [],
|
|
15325
15719
|
ownerHasRanges,
|
|
15720
|
+
rollback: async () => {
|
|
15721
|
+
if (wasReplicator) {
|
|
15722
|
+
this.uniqueReplicators.add(ownerHash);
|
|
15723
|
+
}
|
|
15724
|
+
else {
|
|
15725
|
+
this.uniqueReplicators.delete(ownerHash);
|
|
15726
|
+
}
|
|
15727
|
+
if (joinWasEmitted) {
|
|
15728
|
+
this._replicatorJoinEmitted.add(ownerHash);
|
|
15729
|
+
}
|
|
15730
|
+
else {
|
|
15731
|
+
this._replicatorJoinEmitted.delete(ownerHash);
|
|
15732
|
+
}
|
|
15733
|
+
},
|
|
15326
15734
|
};
|
|
15327
15735
|
}
|
|
15328
15736
|
const uniqueRanges = [
|
|
15329
15737
|
...new Map(ranges.map((range) => [range.idString, range])).values(),
|
|
15330
15738
|
];
|
|
15331
|
-
const wasReplicator = this.uniqueReplicators.has(ownerHash);
|
|
15332
|
-
const joinWasEmitted = this._replicatorJoinEmitted.has(ownerHash);
|
|
15333
15739
|
const snapshots = uniqueRanges.map((range) => {
|
|
15334
15740
|
const pending = this.pendingMaturity.get(ownerHash)?.get(range.idString);
|
|
15335
15741
|
return {
|
|
@@ -15516,10 +15922,76 @@ let SharedLog = (() => {
|
|
|
15516
15922
|
outcomeError = new AggregateError(errors, "Replication-range deletion and post-state reconciliation failed");
|
|
15517
15923
|
this.poisonReplicationOwnership(outcomeError);
|
|
15518
15924
|
}
|
|
15925
|
+
let rollbackStarted = false;
|
|
15926
|
+
const rollback = async () => {
|
|
15927
|
+
if (rollbackStarted) {
|
|
15928
|
+
return;
|
|
15929
|
+
}
|
|
15930
|
+
rollbackStarted = true;
|
|
15931
|
+
const rollbackErrors = [];
|
|
15932
|
+
const removedIds = new Set(removed.map((range) => range.idString));
|
|
15933
|
+
for (const snapshot of snapshots) {
|
|
15934
|
+
if (!removedIds.has(snapshot.range.idString)) {
|
|
15935
|
+
continue;
|
|
15936
|
+
}
|
|
15937
|
+
try {
|
|
15938
|
+
await this.replicationIndex.put(snapshot.range);
|
|
15939
|
+
this.putNativeReplicationRange(snapshot.range);
|
|
15940
|
+
}
|
|
15941
|
+
catch (error) {
|
|
15942
|
+
rollbackErrors.push(error);
|
|
15943
|
+
}
|
|
15944
|
+
}
|
|
15945
|
+
for (const snapshot of snapshots) {
|
|
15946
|
+
if (!removedIds.has(snapshot.range.idString) ||
|
|
15947
|
+
!snapshot.pending ||
|
|
15948
|
+
this.pendingMaturity.get(ownerHash)?.has(snapshot.range.idString)) {
|
|
15949
|
+
continue;
|
|
15950
|
+
}
|
|
15951
|
+
try {
|
|
15952
|
+
this.schedulePendingMaturity(snapshot.pending.range, snapshot.pending.from, {
|
|
15953
|
+
rebalance: snapshot.pending.rebalance,
|
|
15954
|
+
waitMs: Math.max(0, snapshot.pending.expiresAt - Date.now()),
|
|
15955
|
+
}, snapshot.pending.ownershipLifecycleController);
|
|
15956
|
+
}
|
|
15957
|
+
catch (error) {
|
|
15958
|
+
rollbackErrors.push(error);
|
|
15959
|
+
}
|
|
15960
|
+
}
|
|
15961
|
+
try {
|
|
15962
|
+
if (wasReplicator) {
|
|
15963
|
+
this.uniqueReplicators.add(ownerHash);
|
|
15964
|
+
}
|
|
15965
|
+
else {
|
|
15966
|
+
this.uniqueReplicators.delete(ownerHash);
|
|
15967
|
+
}
|
|
15968
|
+
if (joinWasEmitted) {
|
|
15969
|
+
this._replicatorJoinEmitted.add(ownerHash);
|
|
15970
|
+
}
|
|
15971
|
+
else {
|
|
15972
|
+
this._replicatorJoinEmitted.delete(ownerHash);
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
catch (error) {
|
|
15976
|
+
rollbackErrors.push(error);
|
|
15977
|
+
}
|
|
15978
|
+
try {
|
|
15979
|
+
await this.updateOldestTimestampFromIndex();
|
|
15980
|
+
}
|
|
15981
|
+
catch (error) {
|
|
15982
|
+
rollbackErrors.push(error);
|
|
15983
|
+
}
|
|
15984
|
+
if (rollbackErrors.length > 0) {
|
|
15985
|
+
const failure = new AggregateError(rollbackErrors, "Replication-range deletion rollback failed");
|
|
15986
|
+
this.poisonReplicationOwnership(failure);
|
|
15987
|
+
throw failure;
|
|
15988
|
+
}
|
|
15989
|
+
};
|
|
15519
15990
|
return {
|
|
15520
15991
|
removed,
|
|
15521
15992
|
retained,
|
|
15522
15993
|
ownerHasRanges,
|
|
15994
|
+
rollback,
|
|
15523
15995
|
error: outcomeError,
|
|
15524
15996
|
};
|
|
15525
15997
|
}
|
|
@@ -15532,7 +16004,8 @@ let SharedLog = (() => {
|
|
|
15532
16004
|
}
|
|
15533
16005
|
this._replicationInfoRequestByPeer.delete(peerHash);
|
|
15534
16006
|
}
|
|
15535
|
-
scheduleReplicationInfoRequests(peer, replicationLifecycleController = this._instanceLifecycle
|
|
16007
|
+
scheduleReplicationInfoRequests(peer, replicationLifecycleController = this._instanceLifecycle
|
|
16008
|
+
?.membershipLifecycleController) {
|
|
15536
16009
|
if (!replicationLifecycleController ||
|
|
15537
16010
|
!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
15538
16011
|
return;
|
|
@@ -15606,6 +16079,7 @@ let SharedLog = (() => {
|
|
|
15606
16079
|
// A destination stream is scoped to exactly one topic-subscription
|
|
15607
16080
|
// session. Abort the predecessor synchronously before either barrier can
|
|
15608
16081
|
// yield; a late queue completion must never enter the new session.
|
|
16082
|
+
this._v2Receive.clearPeer(peerHash);
|
|
15609
16083
|
this._v2Send.clearPeer(peerHash);
|
|
15610
16084
|
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15611
16085
|
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
@@ -15720,23 +16194,22 @@ let SharedLog = (() => {
|
|
|
15720
16194
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
15721
16195
|
this._liveness.markReplicatorActivity(peerHash);
|
|
15722
16196
|
this._peerSessions.markOpen(peerHash, expectedSubscriptionEpoch);
|
|
15723
|
-
|
|
15724
|
-
//
|
|
15725
|
-
//
|
|
15726
|
-
//
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
capabilities: receiveCapabilities,
|
|
15735
|
-
}), {
|
|
15736
|
-
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
16197
|
+
this.promoteReplicationInfoV2ReceiveCapability(publicKey, expectedSubscriptionEpoch);
|
|
16198
|
+
// Decode, sender and authenticated apply readiness are separate bits. Start
|
|
16199
|
+
// the ACKed receiver-led negotiation before the legacy snapshot, but do not
|
|
16200
|
+
// hold mixed-version replication discovery behind that round trip. Attach
|
|
16201
|
+
// the rejection handler now because a lifecycle fence below may return before
|
|
16202
|
+
// awaiting the advert. RequestV2 is armed only after both sends settle.
|
|
16203
|
+
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
16204
|
+
const localCapabilityAdvertisement = this.advertiseReplicationInfoV2ReceiveCapability({
|
|
16205
|
+
target: publicKey,
|
|
16206
|
+
peerSession: expectedSubscriptionEpoch,
|
|
16207
|
+
receiveEpoch,
|
|
15737
16208
|
signal: replicationLifecycleController.signal,
|
|
15738
|
-
})
|
|
15739
|
-
|
|
16209
|
+
}).catch((error) => {
|
|
16210
|
+
this.handleReplicationLifecycleSendError(error, replicationLifecycleController);
|
|
16211
|
+
return undefined;
|
|
16212
|
+
});
|
|
15740
16213
|
let replicationSegments;
|
|
15741
16214
|
try {
|
|
15742
16215
|
replicationSegments = await this.getMyReplicationSegments();
|
|
@@ -15782,13 +16255,22 @@ let SharedLog = (() => {
|
|
|
15782
16255
|
.catch((error) => this.handleReplicationLifecycleSendError(error, replicationLifecycleController));
|
|
15783
16256
|
}
|
|
15784
16257
|
}
|
|
15785
|
-
//
|
|
15786
|
-
// timing-sensitive delivery/order
|
|
15787
|
-
//
|
|
16258
|
+
// Keep legacy request-based discovery independent of the capability ACK too.
|
|
16259
|
+
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
16260
|
+
// issues where we may miss the remote peer's initial announcement.
|
|
15788
16261
|
if (this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
15789
16262
|
ownsSubscriptionEpoch()) {
|
|
15790
16263
|
this.scheduleReplicationInfoRequests(publicKey, replicationLifecycleController);
|
|
15791
16264
|
}
|
|
16265
|
+
const localCapabilityReady = await localCapabilityAdvertisement;
|
|
16266
|
+
if (localCapabilityReady) {
|
|
16267
|
+
this._v2Receive.markLocalCapabilityReady({
|
|
16268
|
+
peerHash,
|
|
16269
|
+
peerSession: expectedSubscriptionEpoch,
|
|
16270
|
+
receiveEpoch,
|
|
16271
|
+
...localCapabilityReady,
|
|
16272
|
+
});
|
|
16273
|
+
}
|
|
15792
16274
|
}
|
|
15793
16275
|
getClampedReplicas(customValue) {
|
|
15794
16276
|
if (!customValue) {
|