@peerbit/shared-log 13.2.31 → 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/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 +11 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +727 -85
- 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-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 +95 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -0
- package/dist/src/replication-info-v2-send.js +398 -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 +11 -11
- package/src/exchange-heads.ts +15 -2
- package/src/index.ts +1040 -201
- package/src/replication-announcement.ts +10 -4
- 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 +540 -0
- package/src/replication.ts +39 -5
package/src/index.ts
CHANGED
|
@@ -130,6 +130,14 @@ import {
|
|
|
130
130
|
type CheckedPruneRetryIdentity,
|
|
131
131
|
type CheckedPruneRetryState,
|
|
132
132
|
} from "./checked-prune.js";
|
|
133
|
+
import {
|
|
134
|
+
CoordinatePersistenceCoordinator,
|
|
135
|
+
type MaybePromise,
|
|
136
|
+
combineCoordinateDeleteHashes,
|
|
137
|
+
isPromiseLike,
|
|
138
|
+
mapMaybePromise,
|
|
139
|
+
normalizedHashValues,
|
|
140
|
+
} from "./coordinate-persistence.js";
|
|
133
141
|
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
134
142
|
import {
|
|
135
143
|
type DebouncedAccumulatorMap,
|
|
@@ -154,7 +162,9 @@ import {
|
|
|
154
162
|
ResponseIPrune,
|
|
155
163
|
ResponseIPruneV2,
|
|
156
164
|
SYNC_CAPABILITY_RAW_EXCHANGE_HEADS,
|
|
165
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
157
166
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
167
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
158
168
|
StashBackedRawExchangeHeadsMessage,
|
|
159
169
|
SyncCapabilitiesMessage,
|
|
160
170
|
collectRawExchangeHeadSendPlan,
|
|
@@ -179,14 +189,6 @@ import {
|
|
|
179
189
|
materializeVerifiedRawExchangeHeadsMessage,
|
|
180
190
|
} from "./exchange-heads.js";
|
|
181
191
|
import { FanoutEnvelope } from "./fanout-envelope.js";
|
|
182
|
-
import {
|
|
183
|
-
CoordinatePersistenceCoordinator,
|
|
184
|
-
type MaybePromise,
|
|
185
|
-
combineCoordinateDeleteHashes,
|
|
186
|
-
isPromiseLike,
|
|
187
|
-
mapMaybePromise,
|
|
188
|
-
normalizedHashValues,
|
|
189
|
-
} from "./coordinate-persistence.js";
|
|
190
192
|
import { InstanceLifecycle } from "./instance-lifecycle.js";
|
|
191
193
|
import {
|
|
192
194
|
MAX_U32,
|
|
@@ -199,10 +201,7 @@ import { JoinWarmupCoordinator, type WarmupSession } from "./join-warmup.js";
|
|
|
199
201
|
import { LeaderPlanCache } from "./leader-plan-cache.js";
|
|
200
202
|
import { TransportMessage } from "./message.js";
|
|
201
203
|
import { NativeBackboneWriteThroughBlockStore } from "./native-write-through-block-store.js";
|
|
202
|
-
import {
|
|
203
|
-
type PeerSession,
|
|
204
|
-
PeerSessionRegistry,
|
|
205
|
-
} from "./peer-session.js";
|
|
204
|
+
import { type PeerSession, PeerSessionRegistry } from "./peer-session.js";
|
|
206
205
|
import { PIDReplicationController } from "./pid.js";
|
|
207
206
|
import {
|
|
208
207
|
type EntryReplicated,
|
|
@@ -249,17 +248,24 @@ import {
|
|
|
249
248
|
type ReplicationDomain,
|
|
250
249
|
type ReplicationDomainConstructor,
|
|
251
250
|
} from "./replication-domain.js";
|
|
251
|
+
import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-receive.js";
|
|
252
|
+
import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
|
|
252
253
|
import {
|
|
253
254
|
AbsoluteReplicas,
|
|
255
|
+
AddedReplicationInfoV2Message,
|
|
254
256
|
AddedReplicationSegmentMessage,
|
|
255
257
|
AllReplicatingSegmentsMessage,
|
|
258
|
+
FullReplicationInfoV2Message,
|
|
256
259
|
MinReplicas,
|
|
257
260
|
ReplicationError,
|
|
261
|
+
type ReplicationInfoV2Message,
|
|
258
262
|
type ReplicationLimits,
|
|
259
263
|
ReplicationPingMessage,
|
|
260
264
|
RequestReplicationInfoMessage,
|
|
265
|
+
RequestReplicationInfoV2Message,
|
|
261
266
|
ResponseRoleMessage,
|
|
262
267
|
StoppedReplicating,
|
|
268
|
+
StoppedReplicationInfoV2Message,
|
|
263
269
|
decodeReplicas,
|
|
264
270
|
encodeReplicas,
|
|
265
271
|
isReplicationInfoV2Message,
|
|
@@ -350,6 +356,7 @@ type ReceiveLaneContext = {
|
|
|
350
356
|
fromHash: string;
|
|
351
357
|
session: PeerSession | null;
|
|
352
358
|
lifecycleController: AbortController | undefined;
|
|
359
|
+
ownershipLifecycleController: AbortController;
|
|
353
360
|
receiveEpoch: object | null;
|
|
354
361
|
syncProfile: SyncProfileFn | undefined;
|
|
355
362
|
lease: PeerReceiveLease;
|
|
@@ -556,6 +563,7 @@ type ReplicationRangeDeletionOutcome<R extends "u32" | "u64"> = {
|
|
|
556
563
|
removed: ReplicationRangeIndexable<R>[];
|
|
557
564
|
retained: ReplicationRangeIndexable<R>[];
|
|
558
565
|
ownerHasRanges: boolean;
|
|
566
|
+
rollback: () => Promise<void>;
|
|
559
567
|
error?: unknown;
|
|
560
568
|
};
|
|
561
569
|
|
|
@@ -628,7 +636,8 @@ export type NativeBackboneCoordinateRollback<R extends "u32" | "u64"> = {
|
|
|
628
636
|
generations: Map<string, number>;
|
|
629
637
|
};
|
|
630
638
|
|
|
631
|
-
export type RepairDispatchEntry<R extends "u32" | "u64"> =
|
|
639
|
+
export type RepairDispatchEntry<R extends "u32" | "u64"> =
|
|
640
|
+
ResidentCoordinateEntry<R>;
|
|
632
641
|
|
|
633
642
|
type PreparedLocalAppendCommit<R extends "u32" | "u64"> = {
|
|
634
643
|
hash: string;
|
|
@@ -1087,7 +1096,6 @@ const isReplicationOptionsDependentOnPreviousState = async (
|
|
|
1087
1096
|
return false;
|
|
1088
1097
|
};
|
|
1089
1098
|
|
|
1090
|
-
|
|
1091
1099
|
export interface IndexableDomain<R extends "u32" | "u64"> {
|
|
1092
1100
|
numbers: Numbers<R>;
|
|
1093
1101
|
constructorEntry: new (properties: {
|
|
@@ -1986,7 +1994,8 @@ export class SharedLog<
|
|
|
1986
1994
|
}
|
|
1987
1995
|
|
|
1988
1996
|
get _nativeBackboneCoordinateJournalLastFlushMs(): number {
|
|
1989
|
-
return this._coordinates
|
|
1997
|
+
return this._coordinates
|
|
1998
|
+
?._nativeBackboneCoordinateJournalLastFlushMs as number;
|
|
1990
1999
|
}
|
|
1991
2000
|
|
|
1992
2001
|
set _nativeBackboneCoordinateJournalLastFlushMs(value: number) {
|
|
@@ -2024,7 +2033,12 @@ export class SharedLog<
|
|
|
2024
2033
|
// PeerSession the advert was staged under; promote/delete key off it.
|
|
2025
2034
|
private _openingSyncCapabilitiesByPeer!: Map<
|
|
2026
2035
|
string,
|
|
2027
|
-
{
|
|
2036
|
+
{
|
|
2037
|
+
epoch: object;
|
|
2038
|
+
capabilities: number;
|
|
2039
|
+
transportSession?: bigint;
|
|
2040
|
+
timestamp?: bigint;
|
|
2041
|
+
}
|
|
2028
2042
|
>;
|
|
2029
2043
|
private _onFanoutDataFn?: (arg: any) => void;
|
|
2030
2044
|
private _onFanoutUnicastFn?: (arg: any) => void;
|
|
@@ -3068,7 +3082,8 @@ export class SharedLog<
|
|
|
3068
3082
|
if (!lowerMarkerCommitted) {
|
|
3069
3083
|
if (intent.coordinates.length > 0) {
|
|
3070
3084
|
const mutationGenerations =
|
|
3071
|
-
(this._coordinates._nativeCoordinateMutationGenerations ??=
|
|
3085
|
+
(this._coordinates._nativeCoordinateMutationGenerations ??=
|
|
3086
|
+
new Map());
|
|
3072
3087
|
const rollback: NativeBackboneCoordinateRollback<R> = {
|
|
3073
3088
|
hashes: new Set(),
|
|
3074
3089
|
entries: new Map(),
|
|
@@ -3100,7 +3115,10 @@ export class SharedLog<
|
|
|
3100
3115
|
);
|
|
3101
3116
|
}
|
|
3102
3117
|
}
|
|
3103
|
-
await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably(
|
|
3118
|
+
await this._coordinates.rollbackNativeBackboneCoordinateAppendDurably(
|
|
3119
|
+
"",
|
|
3120
|
+
rollback,
|
|
3121
|
+
);
|
|
3104
3122
|
}
|
|
3105
3123
|
for (const document of intent.documents) {
|
|
3106
3124
|
this.restoreNativeBackboneDocument({
|
|
@@ -3225,6 +3243,8 @@ export class SharedLog<
|
|
|
3225
3243
|
| ReturnType<typeof debounceFixedInterval>
|
|
3226
3244
|
| undefined;
|
|
3227
3245
|
private _announcements!: ReplicationAnnouncementCoordinator<R>;
|
|
3246
|
+
private _v2Receive!: ReplicationInfoV2ReceiveCoordinator;
|
|
3247
|
+
private _v2Send!: ReplicationInfoV2SendCoordinator<R>;
|
|
3228
3248
|
|
|
3229
3249
|
// A fn for debouncing the calls for pruning
|
|
3230
3250
|
pruneDebouncedFn!: DebouncedAccumulatorMap<{
|
|
@@ -3286,6 +3306,10 @@ export class SharedLog<
|
|
|
3286
3306
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
3287
3307
|
// by public key hash. Entries are dropped on unsubscribe/disconnect.
|
|
3288
3308
|
private _peerSyncCapabilities!: Map<string, number>;
|
|
3309
|
+
// Signed transport session carried by the capability envelope. Kept in a
|
|
3310
|
+
// parallel map so existing capability-number consumers remain unchanged.
|
|
3311
|
+
private _peerSyncCapabilitySessions!: Map<string, bigint>;
|
|
3312
|
+
private _peerSyncCapabilityTimestamps!: Map<string, bigint>;
|
|
3289
3313
|
// Pending live raw exchange-head gossip, coalesced per recipient set and
|
|
3290
3314
|
// flushed at the end of the current event-loop turn (or when a batch cap
|
|
3291
3315
|
// is hit). Only used when every recipient advertised raw capability.
|
|
@@ -3354,15 +3378,20 @@ export class SharedLog<
|
|
|
3354
3378
|
queueCurrentReplicationStateAnnouncementRepair: () =>
|
|
3355
3379
|
this._announcements.queueCurrentReplicationStateAnnouncementRepair(),
|
|
3356
3380
|
queueCurrentReplicationStateAnnouncementRetry: (error: unknown) =>
|
|
3357
|
-
this._announcements.queueCurrentReplicationStateAnnouncementRetry(
|
|
3381
|
+
this._announcements.queueCurrentReplicationStateAnnouncementRetry(
|
|
3382
|
+
error,
|
|
3383
|
+
),
|
|
3384
|
+
enqueueReplicationInfoV2: (message) => this._v2Send.enqueue(message),
|
|
3358
3385
|
isClosed: () => this.closed,
|
|
3359
3386
|
getCloseSignal: () => this._closeController.signal,
|
|
3360
3387
|
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
3361
3388
|
validatePersistedReplicationRangeSnapshot: (ranges) =>
|
|
3362
3389
|
this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
3363
|
-
getSubscribers: () =>
|
|
3390
|
+
getSubscribers: () =>
|
|
3391
|
+
this.node.services.pubsub.getSubscribers(this.topic),
|
|
3364
3392
|
getSelfHash: () => this.node.identity.publicKey.hashcode(),
|
|
3365
|
-
isBlockedPeer: (hash) =>
|
|
3393
|
+
isBlockedPeer: (hash) =>
|
|
3394
|
+
this._peerSessions.isReplicationInfoBlocked(hash),
|
|
3366
3395
|
getRpc: () => this.rpc,
|
|
3367
3396
|
captureReplicationOwnershipLifecycle: () =>
|
|
3368
3397
|
this.captureReplicationOwnershipLifecycle(),
|
|
@@ -3374,6 +3403,138 @@ export class SharedLog<
|
|
|
3374
3403
|
});
|
|
3375
3404
|
}
|
|
3376
3405
|
|
|
3406
|
+
private createReplicationInfoV2SendCoordinator(): ReplicationInfoV2SendCoordinator<R> {
|
|
3407
|
+
return new ReplicationInfoV2SendCoordinator<R>({
|
|
3408
|
+
getRpc: () => this.rpc,
|
|
3409
|
+
getSelfKey: () => this.node.identity.publicKey,
|
|
3410
|
+
getSenderTransportSession: () =>
|
|
3411
|
+
BigInt(
|
|
3412
|
+
(this.node.services.pubsub as unknown as { session: number }).session,
|
|
3413
|
+
),
|
|
3414
|
+
getMyReplicationSegments: () => this.getMyReplicationSegments(),
|
|
3415
|
+
validatePersistedReplicationRangeSnapshot: (ranges) =>
|
|
3416
|
+
this.validatePersistedReplicationRangeSnapshot(ranges),
|
|
3417
|
+
isClosed: () => this.closed,
|
|
3418
|
+
isPeerSessionOpen: (peerHash, peerSession) =>
|
|
3419
|
+
this._peerSessions.isCurrent(peerHash, peerSession) &&
|
|
3420
|
+
(peerSession as PeerSession).phase === "open" &&
|
|
3421
|
+
!this._peerSessions.isReplicationInfoBlocked(peerHash) &&
|
|
3422
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash),
|
|
3423
|
+
captureReplicationOwnershipLifecycle: () =>
|
|
3424
|
+
this.captureReplicationOwnershipLifecycle(),
|
|
3425
|
+
isReplicationOwnershipLifecycleActive: (controller) =>
|
|
3426
|
+
this.isRepairLifecycleActive(controller),
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
private replicationInfoV2ReceiveCapabilities(): number {
|
|
3431
|
+
return (
|
|
3432
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
3433
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
3434
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY |
|
|
3435
|
+
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
3436
|
+
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
3437
|
+
: 0)
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
private async advertiseReplicationInfoV2ReceiveCapability(properties: {
|
|
3442
|
+
target: PublicSignKey;
|
|
3443
|
+
peerSession: PeerSession;
|
|
3444
|
+
receiveEpoch: object | null;
|
|
3445
|
+
signal: AbortSignal;
|
|
3446
|
+
}): Promise<
|
|
3447
|
+
{ receiverTransportSession: bigint; requestNotBeforeMs: number } | undefined
|
|
3448
|
+
> {
|
|
3449
|
+
const peerHash = properties.target.hashcode();
|
|
3450
|
+
const receiverTransportSession = BigInt(
|
|
3451
|
+
(this.node.services.pubsub as unknown as { session: number }).session,
|
|
3452
|
+
);
|
|
3453
|
+
await this.rpc.send(
|
|
3454
|
+
new SyncCapabilitiesMessage({
|
|
3455
|
+
capabilities: this.replicationInfoV2ReceiveCapabilities(),
|
|
3456
|
+
}),
|
|
3457
|
+
{
|
|
3458
|
+
mode: new AcknowledgeDelivery({
|
|
3459
|
+
redundancy: 1,
|
|
3460
|
+
to: [properties.target],
|
|
3461
|
+
}),
|
|
3462
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3463
|
+
signal: properties.signal,
|
|
3464
|
+
},
|
|
3465
|
+
);
|
|
3466
|
+
if (
|
|
3467
|
+
properties.signal.aborted ||
|
|
3468
|
+
this.closed ||
|
|
3469
|
+
!this._peerSessions.isCurrent(peerHash, properties.peerSession) ||
|
|
3470
|
+
properties.peerSession.phase !== "open" ||
|
|
3471
|
+
!properties.peerSession.isActive() ||
|
|
3472
|
+
!this._peerSessions.isReceiveEpochCurrent(
|
|
3473
|
+
peerHash,
|
|
3474
|
+
properties.receiveEpoch,
|
|
3475
|
+
) ||
|
|
3476
|
+
this._peerSessions.isReplicationInfoBlocked(peerHash) ||
|
|
3477
|
+
!this._peerSessions.isReceiveCleanupGateOpen(peerHash) ||
|
|
3478
|
+
BigInt(
|
|
3479
|
+
(this.node.services.pubsub as unknown as { session: number }).session,
|
|
3480
|
+
) !== receiverTransportSession
|
|
3481
|
+
) {
|
|
3482
|
+
return undefined;
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
receiverTransportSession,
|
|
3486
|
+
requestNotBeforeMs: Date.now(),
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
private createReplicationInfoV2ReceiveCoordinator(): ReplicationInfoV2ReceiveCoordinator {
|
|
3491
|
+
return new ReplicationInfoV2ReceiveCoordinator({
|
|
3492
|
+
getSelfKey: () => this.node.identity.publicKey,
|
|
3493
|
+
getReceiverTransportSession: () =>
|
|
3494
|
+
BigInt(
|
|
3495
|
+
(this.node.services.pubsub as unknown as { session: number }).session,
|
|
3496
|
+
),
|
|
3497
|
+
isClosed: () => this.closed,
|
|
3498
|
+
isPeerStateCurrent: (peerHash, peerSession, receiveEpoch) =>
|
|
3499
|
+
this._peerSessions.isCurrent(peerHash, peerSession) &&
|
|
3500
|
+
(peerSession as PeerSession).phase === "open" &&
|
|
3501
|
+
(peerSession as PeerSession).isActive() &&
|
|
3502
|
+
this._peerSessions.isReceiveEpochCurrent(peerHash, receiveEpoch) &&
|
|
3503
|
+
!this._peerSessions.isReplicationInfoBlocked(peerHash) &&
|
|
3504
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash),
|
|
3505
|
+
isSenderTransportSessionCurrent: (peerHash, senderTransportSession) =>
|
|
3506
|
+
this._peerSyncCapabilitySessions.get(peerHash) ===
|
|
3507
|
+
senderTransportSession,
|
|
3508
|
+
sendRequest: async (request, target, signal) => {
|
|
3509
|
+
await this.rpc.send(request, {
|
|
3510
|
+
mode: new AcknowledgeDelivery({ redundancy: 1, to: [target] }),
|
|
3511
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
3512
|
+
signal,
|
|
3513
|
+
});
|
|
3514
|
+
},
|
|
3515
|
+
refreshLocalCapability: (properties) =>
|
|
3516
|
+
this.advertiseReplicationInfoV2ReceiveCapability({
|
|
3517
|
+
target: properties.target,
|
|
3518
|
+
peerSession: properties.peerSession as PeerSession,
|
|
3519
|
+
receiveEpoch: properties.receiveEpoch,
|
|
3520
|
+
signal: properties.signal,
|
|
3521
|
+
}),
|
|
3522
|
+
onRequestError: (error) => {
|
|
3523
|
+
if (
|
|
3524
|
+
isNotStartedError(error as Error) ||
|
|
3525
|
+
(this.closed && error instanceof AbortError)
|
|
3526
|
+
) {
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
logger.trace(
|
|
3530
|
+
`Replication-info V2 recovery request failed: ${
|
|
3531
|
+
(error as Error)?.message ?? String(error)
|
|
3532
|
+
}`,
|
|
3533
|
+
);
|
|
3534
|
+
},
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3377
3538
|
private createReplicatorLivenessMonitor(): ReplicatorLivenessMonitor {
|
|
3378
3539
|
return new ReplicatorLivenessMonitor({
|
|
3379
3540
|
// Sweep-driven probes and activity marks dispatch through the owner so
|
|
@@ -3404,7 +3565,8 @@ export class SharedLog<
|
|
|
3404
3565
|
}),
|
|
3405
3566
|
);
|
|
3406
3567
|
},
|
|
3407
|
-
isBlockedPeer: (hash) =>
|
|
3568
|
+
isBlockedPeer: (hash) =>
|
|
3569
|
+
this._peerSessions.isReplicationInfoBlocked(hash),
|
|
3408
3570
|
scheduleReplicationInfoRequests: (peer, replicationLifecycleController) =>
|
|
3409
3571
|
this.scheduleReplicationInfoRequests(
|
|
3410
3572
|
peer,
|
|
@@ -3488,6 +3650,8 @@ export class SharedLog<
|
|
|
3488
3650
|
this._appendBackfillPendingByTarget = new Map();
|
|
3489
3651
|
this._topicSubscribersCache = new Map();
|
|
3490
3652
|
this._peerSyncCapabilities = new Map();
|
|
3653
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
3654
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
3491
3655
|
this._liveRawGossipBatches = new Map();
|
|
3492
3656
|
this._liveRawGossipFlushScheduled = false;
|
|
3493
3657
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -3496,6 +3660,8 @@ export class SharedLog<
|
|
|
3496
3660
|
this._replicatorJoinEmitted = new Set();
|
|
3497
3661
|
this._replicatorsReconciled = false;
|
|
3498
3662
|
this._liveness = this.createReplicatorLivenessMonitor();
|
|
3663
|
+
this._v2Receive = this.createReplicationInfoV2ReceiveCoordinator();
|
|
3664
|
+
this._v2Send = this.createReplicationInfoV2SendCoordinator();
|
|
3499
3665
|
this._announcements = this.createReplicationAnnouncementCoordinator();
|
|
3500
3666
|
this.pendingMaturity = new Map();
|
|
3501
3667
|
this._closeController = new AbortController();
|
|
@@ -3991,6 +4157,138 @@ export class SharedLog<
|
|
|
3991
4157
|
);
|
|
3992
4158
|
}
|
|
3993
4159
|
|
|
4160
|
+
private observePeerSyncCapabilities(properties: {
|
|
4161
|
+
peerHash: string;
|
|
4162
|
+
capabilities: number;
|
|
4163
|
+
transportSession?: bigint;
|
|
4164
|
+
timestamp?: bigint;
|
|
4165
|
+
openingSession?: PeerSession;
|
|
4166
|
+
}): boolean {
|
|
4167
|
+
const {
|
|
4168
|
+
peerHash,
|
|
4169
|
+
capabilities,
|
|
4170
|
+
transportSession,
|
|
4171
|
+
timestamp,
|
|
4172
|
+
openingSession,
|
|
4173
|
+
} = properties;
|
|
4174
|
+
if (openingSession) {
|
|
4175
|
+
const previous = this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
4176
|
+
if (
|
|
4177
|
+
transportSession !== undefined &&
|
|
4178
|
+
timestamp !== undefined &&
|
|
4179
|
+
previous?.epoch === openingSession &&
|
|
4180
|
+
previous.timestamp !== undefined &&
|
|
4181
|
+
previous.transportSession !== transportSession &&
|
|
4182
|
+
timestamp <= previous.timestamp
|
|
4183
|
+
) {
|
|
4184
|
+
return false;
|
|
4185
|
+
}
|
|
4186
|
+
if (
|
|
4187
|
+
transportSession !== undefined &&
|
|
4188
|
+
timestamp !== undefined &&
|
|
4189
|
+
previous?.epoch === openingSession &&
|
|
4190
|
+
previous.transportSession === transportSession
|
|
4191
|
+
) {
|
|
4192
|
+
if (
|
|
4193
|
+
previous.timestamp !== undefined &&
|
|
4194
|
+
timestamp < previous.timestamp
|
|
4195
|
+
) {
|
|
4196
|
+
return false;
|
|
4197
|
+
}
|
|
4198
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
4199
|
+
epoch: openingSession,
|
|
4200
|
+
capabilities: previous.capabilities | capabilities,
|
|
4201
|
+
transportSession,
|
|
4202
|
+
timestamp:
|
|
4203
|
+
previous.timestamp === undefined || timestamp > previous.timestamp
|
|
4204
|
+
? timestamp
|
|
4205
|
+
: previous.timestamp,
|
|
4206
|
+
});
|
|
4207
|
+
return true;
|
|
4208
|
+
}
|
|
4209
|
+
this._openingSyncCapabilitiesByPeer.set(peerHash, {
|
|
4210
|
+
epoch: openingSession,
|
|
4211
|
+
capabilities,
|
|
4212
|
+
transportSession,
|
|
4213
|
+
timestamp,
|
|
4214
|
+
});
|
|
4215
|
+
return true;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
if (transportSession === undefined || timestamp === undefined) {
|
|
4219
|
+
// Test/in-process synthetic contexts predate signed envelope captures.
|
|
4220
|
+
// They may exercise capability-number behavior, but can never authorize V2.
|
|
4221
|
+
this._peerSyncCapabilities.set(peerHash, capabilities);
|
|
4222
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
4223
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
4224
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
4225
|
+
this._v2Receive.revokePeerCapability(peerHash);
|
|
4226
|
+
return true;
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
const previousSession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
4230
|
+
const previousTimestamp = this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
4231
|
+
const sameTransportSession = previousSession === transportSession;
|
|
4232
|
+
if (
|
|
4233
|
+
previousTimestamp !== undefined &&
|
|
4234
|
+
((sameTransportSession && timestamp < previousTimestamp) ||
|
|
4235
|
+
(!sameTransportSession && timestamp <= previousTimestamp))
|
|
4236
|
+
) {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
const previousCapabilities = this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
4240
|
+
const nextCapabilities = sameTransportSession
|
|
4241
|
+
? previousCapabilities | capabilities
|
|
4242
|
+
: capabilities;
|
|
4243
|
+
const senderGrantCapabilityMask =
|
|
4244
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
4245
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY;
|
|
4246
|
+
const generationAdvanced =
|
|
4247
|
+
!sameTransportSession ||
|
|
4248
|
+
(previousCapabilities & senderGrantCapabilityMask) !==
|
|
4249
|
+
(nextCapabilities & senderGrantCapabilityMask);
|
|
4250
|
+
this._peerSyncCapabilities.set(peerHash, nextCapabilities);
|
|
4251
|
+
this._peerSyncCapabilitySessions.set(peerHash, transportSession);
|
|
4252
|
+
this._peerSyncCapabilityTimestamps.set(
|
|
4253
|
+
peerHash,
|
|
4254
|
+
previousTimestamp === undefined ||
|
|
4255
|
+
!sameTransportSession ||
|
|
4256
|
+
timestamp > previousTimestamp
|
|
4257
|
+
? timestamp
|
|
4258
|
+
: previousTimestamp,
|
|
4259
|
+
);
|
|
4260
|
+
if (generationAdvanced) {
|
|
4261
|
+
this._v2Send.advancePeerCapability(peerHash);
|
|
4262
|
+
}
|
|
4263
|
+
return true;
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
private promoteReplicationInfoV2ReceiveCapability(
|
|
4267
|
+
target: PublicSignKey,
|
|
4268
|
+
peerSession: PeerSession,
|
|
4269
|
+
): boolean {
|
|
4270
|
+
const peerHash = target.hashcode();
|
|
4271
|
+
const senderTransportSession =
|
|
4272
|
+
this._peerSyncCapabilitySessions.get(peerHash);
|
|
4273
|
+
const capabilityTimestamp =
|
|
4274
|
+
this._peerSyncCapabilityTimestamps.get(peerHash);
|
|
4275
|
+
if (
|
|
4276
|
+
senderTransportSession === undefined ||
|
|
4277
|
+
capabilityTimestamp === undefined
|
|
4278
|
+
) {
|
|
4279
|
+
return false;
|
|
4280
|
+
}
|
|
4281
|
+
return this._v2Receive.observeCapability({
|
|
4282
|
+
peerHash,
|
|
4283
|
+
target,
|
|
4284
|
+
peerSession,
|
|
4285
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
4286
|
+
capabilities: this._peerSyncCapabilities.get(peerHash) ?? 0,
|
|
4287
|
+
senderTransportSession,
|
|
4288
|
+
capabilityTimestamp,
|
|
4289
|
+
});
|
|
4290
|
+
}
|
|
4291
|
+
|
|
3994
4292
|
/**
|
|
3995
4293
|
* Live append gossip may use the raw exchange-heads path only when we
|
|
3996
4294
|
* opted into raw sync and every remote recipient advertised raw capability
|
|
@@ -4868,7 +5166,8 @@ export class SharedLog<
|
|
|
4868
5166
|
|
|
4869
5167
|
private isReceiveOwnershipSnapshotStable(revision: number): boolean {
|
|
4870
5168
|
return (
|
|
4871
|
-
(this._instanceLifecycle?._receiveOwnershipMutationAdmissions ?? 0) ===
|
|
5169
|
+
(this._instanceLifecycle?._receiveOwnershipMutationAdmissions ?? 0) ===
|
|
5170
|
+
0 &&
|
|
4872
5171
|
(this._instanceLifecycle?._receiveOwnershipRevision ?? 0) === revision
|
|
4873
5172
|
);
|
|
4874
5173
|
}
|
|
@@ -5116,7 +5415,8 @@ export class SharedLog<
|
|
|
5116
5415
|
pending: PendingIHave<T>,
|
|
5117
5416
|
entry: Entry<T>,
|
|
5118
5417
|
): void {
|
|
5119
|
-
const replicationLifecycleController =
|
|
5418
|
+
const replicationLifecycleController =
|
|
5419
|
+
this._instanceLifecycle?.membershipLifecycleController;
|
|
5120
5420
|
if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
|
|
5121
5421
|
if (this._pendingIHave.get(entry.hash) === pending) {
|
|
5122
5422
|
pending.clear();
|
|
@@ -6024,6 +6324,7 @@ export class SharedLog<
|
|
|
6024
6324
|
};
|
|
6025
6325
|
let removed = false;
|
|
6026
6326
|
let removalCallCompleted = false;
|
|
6327
|
+
let replicationInfoRecoveryEpochAdvanced = false;
|
|
6027
6328
|
|
|
6028
6329
|
// Replication-info updates already serialize per peer. Put the hash-wide
|
|
6029
6330
|
// removal on the same queue so a newer reset cannot be deleted underneath
|
|
@@ -6113,6 +6414,13 @@ export class SharedLog<
|
|
|
6113
6414
|
checkedPruneCoordinator,
|
|
6114
6415
|
);
|
|
6115
6416
|
}
|
|
6417
|
+
if (!isMe) {
|
|
6418
|
+
// This is the last synchronous current-generation boundary
|
|
6419
|
+
// before destructive work. Fence parked receives now because
|
|
6420
|
+
// the coherent delete can durably mutate and then fail or poison.
|
|
6421
|
+
this.advanceReplicationInfoRecoveryEpoch(keyHash);
|
|
6422
|
+
replicationInfoRecoveryEpochAdvanced = true;
|
|
6423
|
+
}
|
|
6116
6424
|
const deletion = await this.deleteReplicationRangesCoherently(
|
|
6117
6425
|
deleted,
|
|
6118
6426
|
keyHash,
|
|
@@ -6136,7 +6444,6 @@ export class SharedLog<
|
|
|
6136
6444
|
}
|
|
6137
6445
|
return;
|
|
6138
6446
|
}
|
|
6139
|
-
|
|
6140
6447
|
if (options?.noEvent !== true && deleted.length > 0) {
|
|
6141
6448
|
const publicKey = toLocalPublicSignKey(key);
|
|
6142
6449
|
if (publicKey) {
|
|
@@ -6172,11 +6479,6 @@ export class SharedLog<
|
|
|
6172
6479
|
await cleanupDisconnectedPeer();
|
|
6173
6480
|
|
|
6174
6481
|
if (!isMe) {
|
|
6175
|
-
// Replication-info handlers release their receive lease before joining
|
|
6176
|
-
// this lane. Fence every handler queued behind this successful removal,
|
|
6177
|
-
// regardless of whether it came from liveness, startup pruning, or an
|
|
6178
|
-
// unsubscribe transition.
|
|
6179
|
-
this.advanceReplicationInfoRecoveryEpoch(keyHash);
|
|
6180
6482
|
this.rebalanceParticipationDebounced?.call();
|
|
6181
6483
|
}
|
|
6182
6484
|
removed = true;
|
|
@@ -6214,6 +6516,24 @@ export class SharedLog<
|
|
|
6214
6516
|
removalCallCompleted = true;
|
|
6215
6517
|
} finally {
|
|
6216
6518
|
releaseReceiveCleanupGate?.();
|
|
6519
|
+
if (
|
|
6520
|
+
replicationInfoRecoveryEpochAdvanced &&
|
|
6521
|
+
ownsReplicationOwnershipLifecycle() &&
|
|
6522
|
+
ownsReplicationLifecycle() &&
|
|
6523
|
+
ownsSubscriptionEpoch()
|
|
6524
|
+
) {
|
|
6525
|
+
// The first recovery arm ran while receive admission was fenced.
|
|
6526
|
+
// Re-arm after releasing the cleanup gate so a still-open peer can
|
|
6527
|
+
// deliver the authoritative Full that completes recovery.
|
|
6528
|
+
const peerSession = this._peerSessions.current(keyHash);
|
|
6529
|
+
if (peerSession?.phase === "open") {
|
|
6530
|
+
this._v2Receive.advanceRecovery({
|
|
6531
|
+
peerHash: keyHash,
|
|
6532
|
+
peerSession,
|
|
6533
|
+
receiveEpoch: this._peerSessions.receiveEpoch(keyHash),
|
|
6534
|
+
});
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6217
6537
|
if (isSpeculativePeerRemoval) {
|
|
6218
6538
|
const cancelledByFreshActivity =
|
|
6219
6539
|
removalCallCompleted &&
|
|
@@ -6289,6 +6609,7 @@ export class SharedLog<
|
|
|
6289
6609
|
options?: {
|
|
6290
6610
|
onRemoved?: (ranges: ReplicationRangeIndexable<R>[]) => void;
|
|
6291
6611
|
shouldRemove?: () => boolean;
|
|
6612
|
+
onDurableRemoveCommitted?: () => boolean | void;
|
|
6292
6613
|
},
|
|
6293
6614
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
6294
6615
|
): Promise<boolean> {
|
|
@@ -6307,6 +6628,7 @@ export class SharedLog<
|
|
|
6307
6628
|
options?: {
|
|
6308
6629
|
onRemoved?: (ranges: ReplicationRangeIndexable<R>[]) => void;
|
|
6309
6630
|
shouldRemove?: () => boolean;
|
|
6631
|
+
onDurableRemoveCommitted?: () => boolean | void;
|
|
6310
6632
|
},
|
|
6311
6633
|
): Promise<boolean> {
|
|
6312
6634
|
if (ranges.length === 0) {
|
|
@@ -6363,6 +6685,13 @@ export class SharedLog<
|
|
|
6363
6685
|
ownerHash,
|
|
6364
6686
|
);
|
|
6365
6687
|
ranges = deletion.removed;
|
|
6688
|
+
if (
|
|
6689
|
+
(options?.shouldRemove && !options.shouldRemove()) ||
|
|
6690
|
+
options?.onDurableRemoveCommitted?.() === false
|
|
6691
|
+
) {
|
|
6692
|
+
await deletion.rollback();
|
|
6693
|
+
return false;
|
|
6694
|
+
}
|
|
6366
6695
|
options?.onRemoved?.(ranges);
|
|
6367
6696
|
|
|
6368
6697
|
if (ranges.length > 0) {
|
|
@@ -6448,6 +6777,7 @@ export class SharedLog<
|
|
|
6448
6777
|
timestamp?: number;
|
|
6449
6778
|
allowLegacyOrderedReplacementPairs?: boolean;
|
|
6450
6779
|
onConfirmedDurableStateChanged?: () => void;
|
|
6780
|
+
onDurableApplyCommitted?: () => boolean | void;
|
|
6451
6781
|
shouldApply?: () => boolean;
|
|
6452
6782
|
} = {},
|
|
6453
6783
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
@@ -6489,6 +6819,7 @@ export class SharedLog<
|
|
|
6489
6819
|
rebalance,
|
|
6490
6820
|
allowLegacyOrderedReplacementPairs,
|
|
6491
6821
|
onConfirmedDurableStateChanged,
|
|
6822
|
+
onDurableApplyCommitted,
|
|
6492
6823
|
shouldApply,
|
|
6493
6824
|
}: {
|
|
6494
6825
|
reset?: boolean;
|
|
@@ -6497,6 +6828,7 @@ export class SharedLog<
|
|
|
6497
6828
|
timestamp?: number;
|
|
6498
6829
|
allowLegacyOrderedReplacementPairs?: boolean;
|
|
6499
6830
|
onConfirmedDurableStateChanged?: () => void;
|
|
6831
|
+
onDurableApplyCommitted?: () => boolean | void;
|
|
6500
6832
|
shouldApply?: () => boolean;
|
|
6501
6833
|
} = {},
|
|
6502
6834
|
replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
@@ -6511,6 +6843,12 @@ export class SharedLog<
|
|
|
6511
6843
|
if (shouldApply && !shouldApply()) {
|
|
6512
6844
|
return [];
|
|
6513
6845
|
}
|
|
6846
|
+
const applySuperseded = Symbol("replication-range-apply-superseded");
|
|
6847
|
+
const throwIfApplySuperseded = () => {
|
|
6848
|
+
if (shouldApply && !shouldApply()) {
|
|
6849
|
+
throw applySuperseded;
|
|
6850
|
+
}
|
|
6851
|
+
};
|
|
6514
6852
|
const fromHash = from.hashcode();
|
|
6515
6853
|
const incomingRangesById = new Map<
|
|
6516
6854
|
string,
|
|
@@ -6600,6 +6938,7 @@ export class SharedLog<
|
|
|
6600
6938
|
let isStoppedReplicating = false;
|
|
6601
6939
|
let wasReplicatorBeforeDestructiveReset = false;
|
|
6602
6940
|
let resetFailureLeaveEmitted = false;
|
|
6941
|
+
let resetDeletionRollback: (() => Promise<void>) | undefined;
|
|
6603
6942
|
const publishConfirmedResetStop = (ownerHasRanges: boolean) => {
|
|
6604
6943
|
if (ownerHasRanges || resetFailureLeaveEmitted) {
|
|
6605
6944
|
return;
|
|
@@ -6652,12 +6991,18 @@ export class SharedLog<
|
|
|
6652
6991
|
} else {
|
|
6653
6992
|
wasReplicatorBeforeDestructiveReset =
|
|
6654
6993
|
this.uniqueReplicators.has(fromHash);
|
|
6994
|
+
throwIfApplySuperseded();
|
|
6655
6995
|
const deletion = await this.deleteReplicationRangesCoherently(
|
|
6656
6996
|
deleted,
|
|
6657
6997
|
fromHash,
|
|
6658
6998
|
{ preserveOwnerMembership: ranges.length > 0 },
|
|
6659
6999
|
);
|
|
6660
7000
|
deleted = deletion.removed;
|
|
7001
|
+
resetDeletionRollback = deletion.rollback;
|
|
7002
|
+
if (shouldApply && !shouldApply()) {
|
|
7003
|
+
await deletion.rollback();
|
|
7004
|
+
return [];
|
|
7005
|
+
}
|
|
6661
7006
|
|
|
6662
7007
|
diffs = [
|
|
6663
7008
|
...deleted.map((x) => {
|
|
@@ -6853,18 +7198,26 @@ export class SharedLog<
|
|
|
6853
7198
|
};
|
|
6854
7199
|
|
|
6855
7200
|
try {
|
|
7201
|
+
throwIfApplySuperseded();
|
|
6856
7202
|
for (const diff of diffs) {
|
|
6857
7203
|
if (diff.type !== "added") {
|
|
6858
7204
|
continue;
|
|
6859
7205
|
}
|
|
7206
|
+
throwIfApplySuperseded();
|
|
6860
7207
|
appliedPositiveRanges.push(diff);
|
|
6861
7208
|
await this.replicationIndex.put(diff.range);
|
|
6862
7209
|
this.putNativeReplicationRange(diff.range);
|
|
7210
|
+
throwIfApplySuperseded();
|
|
6863
7211
|
}
|
|
6864
7212
|
if (reset && diffs.length > 0) {
|
|
6865
7213
|
await this.updateOldestTimestampFromIndex();
|
|
7214
|
+
throwIfApplySuperseded();
|
|
7215
|
+
}
|
|
7216
|
+
if (onDurableApplyCommitted?.() === false) {
|
|
7217
|
+
throw applySuperseded;
|
|
6866
7218
|
}
|
|
6867
7219
|
} catch (error) {
|
|
7220
|
+
const superseded = error === applySuperseded;
|
|
6868
7221
|
let outcomeError = error;
|
|
6869
7222
|
if (appliedPositiveRanges.length > 0) {
|
|
6870
7223
|
try {
|
|
@@ -6873,6 +7226,19 @@ export class SharedLog<
|
|
|
6873
7226
|
outcomeError = poisonFromPositiveRollback(rollbackError, error);
|
|
6874
7227
|
}
|
|
6875
7228
|
}
|
|
7229
|
+
if (superseded) {
|
|
7230
|
+
if (resetDeletionRollback) {
|
|
7231
|
+
try {
|
|
7232
|
+
await resetDeletionRollback();
|
|
7233
|
+
} catch (rollbackError) {
|
|
7234
|
+
outcomeError = rollbackError;
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
if (outcomeError === applySuperseded) {
|
|
7238
|
+
return [];
|
|
7239
|
+
}
|
|
7240
|
+
throw outcomeError;
|
|
7241
|
+
}
|
|
6876
7242
|
if (reset) {
|
|
6877
7243
|
const negativeDiffs = diffs.filter((diff) => diff.type !== "added");
|
|
6878
7244
|
if (negativeDiffs.length > 0) {
|
|
@@ -8479,7 +8845,8 @@ export class SharedLog<
|
|
|
8479
8845
|
}
|
|
8480
8846
|
};
|
|
8481
8847
|
|
|
8482
|
-
const residentEntriesByHash =
|
|
8848
|
+
const residentEntriesByHash =
|
|
8849
|
+
this._coordinates._residentEntryCoordinatesByHash;
|
|
8483
8850
|
if (
|
|
8484
8851
|
(this._nativeBackbone ?? this._nativeSharedLogState) &&
|
|
8485
8852
|
residentEntriesByHash &&
|
|
@@ -8791,7 +9158,8 @@ export class SharedLog<
|
|
|
8791
9158
|
const localLeaderHashes = new Set<string>();
|
|
8792
9159
|
const unresolvedHashes = new Set(hashes);
|
|
8793
9160
|
const nativePlanner = this._nativeBackbone ?? this._nativeRangePlanner;
|
|
8794
|
-
const nativeEntryMetadata =
|
|
9161
|
+
const nativeEntryMetadata =
|
|
9162
|
+
this._coordinates.getNativeLogEntryMetadataBatch(hashes);
|
|
8795
9163
|
|
|
8796
9164
|
if (nativePlanner && !this.hasCustomFindLeaders() && nativeEntryMetadata) {
|
|
8797
9165
|
const nativeItems: Array<{
|
|
@@ -10292,7 +10660,8 @@ export class SharedLog<
|
|
|
10292
10660
|
) {
|
|
10293
10661
|
try {
|
|
10294
10662
|
this.restoreNativeBackboneDocument(nativeDocumentRollback);
|
|
10295
|
-
const flushed =
|
|
10663
|
+
const flushed =
|
|
10664
|
+
this._coordinates.flushNativeBackboneCoordinateJournal();
|
|
10296
10665
|
if (isPromiseLike(flushed)) {
|
|
10297
10666
|
await flushed;
|
|
10298
10667
|
}
|
|
@@ -10382,11 +10751,12 @@ export class SharedLog<
|
|
|
10382
10751
|
prepared.trimmedEntries as Array<{ hash?: string }> | undefined
|
|
10383
10752
|
)?.flatMap((entry) => (entry.hash ? [entry.hash] : [])) ??
|
|
10384
10753
|
[];
|
|
10385
|
-
const coordinateRollback =
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10754
|
+
const coordinateRollback =
|
|
10755
|
+
this._coordinates.snapshotResidentCoordinateEntries([
|
|
10756
|
+
...(preparedHash ? [preparedHash] : []),
|
|
10757
|
+
...preparedNext,
|
|
10758
|
+
...nativeTrimmedHashes,
|
|
10759
|
+
]);
|
|
10390
10760
|
lowerPublicationRollback = {
|
|
10391
10761
|
committedHashes: preparedHash ? [preparedHash] : [],
|
|
10392
10762
|
trimmedEntries: prepared.trimmedEntries,
|
|
@@ -10602,7 +10972,8 @@ export class SharedLog<
|
|
|
10602
10972
|
for (const document of lowerPublicationRollback?.documents ?? []) {
|
|
10603
10973
|
this.restoreNativeBackboneDocument(document);
|
|
10604
10974
|
}
|
|
10605
|
-
const flushed =
|
|
10975
|
+
const flushed =
|
|
10976
|
+
this._coordinates.flushNativeBackboneCoordinateJournal();
|
|
10606
10977
|
if (isPromiseLike(flushed)) {
|
|
10607
10978
|
await flushed;
|
|
10608
10979
|
}
|
|
@@ -10697,7 +11068,10 @@ export class SharedLog<
|
|
|
10697
11068
|
ownershipLifecycleController,
|
|
10698
11069
|
);
|
|
10699
11070
|
const backbone = this._nativeBackbone;
|
|
10700
|
-
if (
|
|
11071
|
+
if (
|
|
11072
|
+
!backbone ||
|
|
11073
|
+
!this._coordinates.canUseNativeBackboneResidentCoordinateState()
|
|
11074
|
+
) {
|
|
10701
11075
|
return undefined;
|
|
10702
11076
|
}
|
|
10703
11077
|
if (
|
|
@@ -10902,11 +11276,12 @@ export class SharedLog<
|
|
|
10902
11276
|
const committedHash =
|
|
10903
11277
|
backboneAppend.entry.cid ?? backboneAppend.entry.hash;
|
|
10904
11278
|
const committedNext = backboneAppend.entry.next ?? next;
|
|
10905
|
-
nativeCoordinateRollback =
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
11279
|
+
nativeCoordinateRollback =
|
|
11280
|
+
this._coordinates.snapshotResidentCoordinateEntries([
|
|
11281
|
+
...(committedHash ? [committedHash] : []),
|
|
11282
|
+
...committedNext,
|
|
11283
|
+
...nativeTrimmedHashes,
|
|
11284
|
+
]);
|
|
10910
11285
|
await this.setNativeStrictDurableTransactionOperation(
|
|
10911
11286
|
nativeStrictTransaction,
|
|
10912
11287
|
committedHash ? [committedHash] : [],
|
|
@@ -11217,7 +11592,8 @@ export class SharedLog<
|
|
|
11217
11592
|
if (nativeDocumentRollback) {
|
|
11218
11593
|
try {
|
|
11219
11594
|
this.restoreNativeBackboneDocument(nativeDocumentRollback);
|
|
11220
|
-
const flushed =
|
|
11595
|
+
const flushed =
|
|
11596
|
+
this._coordinates.flushNativeBackboneCoordinateJournal();
|
|
11221
11597
|
if (isPromiseLike(flushed)) {
|
|
11222
11598
|
await flushed;
|
|
11223
11599
|
}
|
|
@@ -11249,15 +11625,17 @@ export class SharedLog<
|
|
|
11249
11625
|
coordinateIndex.putSharedLogCoordinateFieldsEncodedAndDeleteHashesNoReturn ||
|
|
11250
11626
|
coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesNoReturn;
|
|
11251
11627
|
const persisted = hasNativeCoordinatePut
|
|
11252
|
-
? this._coordinates.persistBackboneCoordinateFieldsNativeTransaction(
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
|
|
11628
|
+
? this._coordinates.persistBackboneCoordinateFieldsNativeTransaction(
|
|
11629
|
+
{
|
|
11630
|
+
coordinateIndex,
|
|
11631
|
+
fields: coordinateFields,
|
|
11632
|
+
hash: prepared.appendFacts.hash,
|
|
11633
|
+
deleteHashes: [],
|
|
11634
|
+
coordinates: backboneAppend.coordinate
|
|
11635
|
+
.coordinates as NumberFromType<R>[],
|
|
11636
|
+
skipGenericTransientCoordinateIndex: runtimeOnlyCoordinates,
|
|
11637
|
+
},
|
|
11638
|
+
)
|
|
11261
11639
|
: this._coordinates.persistPreparedCoordinate({
|
|
11262
11640
|
prepared: getPreparedCoordinate(),
|
|
11263
11641
|
hash: prepared.appendFacts.hash,
|
|
@@ -11311,9 +11689,10 @@ export class SharedLog<
|
|
|
11311
11689
|
if (!backboneAppend.isLeader && !delayAdaptiveRebalance) {
|
|
11312
11690
|
const leaders = backboneAppend.leaders;
|
|
11313
11691
|
if (leaders) {
|
|
11314
|
-
const pruneEntry =
|
|
11315
|
-
|
|
11316
|
-
|
|
11692
|
+
const pruneEntry =
|
|
11693
|
+
this._coordinates.materializePreparedCoordinateEntry(
|
|
11694
|
+
getPreparedCoordinate(),
|
|
11695
|
+
);
|
|
11317
11696
|
this.pruneDebouncedFnAddIfNotKeeping({
|
|
11318
11697
|
key: pruneEntry.hash,
|
|
11319
11698
|
value: { entry: pruneEntry, leaders },
|
|
@@ -11984,16 +12363,17 @@ export class SharedLog<
|
|
|
11984
12363
|
),
|
|
11985
12364
|
);
|
|
11986
12365
|
const nativeTrimmedHashes = [...nativeTrimmedHashSet];
|
|
11987
|
-
batchCoordinateRollback =
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
append.
|
|
11995
|
-
|
|
11996
|
-
|
|
12366
|
+
batchCoordinateRollback =
|
|
12367
|
+
this._coordinates.snapshotResidentCoordinateEntries(
|
|
12368
|
+
committedAppends.flatMap((append) => [
|
|
12369
|
+
...((append.entry.cid ?? append.entry.hash)
|
|
12370
|
+
? [append.entry.cid ?? append.entry.hash!]
|
|
12371
|
+
: []),
|
|
12372
|
+
...append.entry.next,
|
|
12373
|
+
...(append.trimmedHashes ??
|
|
12374
|
+
append.trimmed.map((entry) => entry.hash)),
|
|
12375
|
+
]),
|
|
12376
|
+
);
|
|
11997
12377
|
await this.setNativeStrictDurableTransactionOperation(
|
|
11998
12378
|
nativeStrictTransaction,
|
|
11999
12379
|
committedCids,
|
|
@@ -12245,7 +12625,8 @@ export class SharedLog<
|
|
|
12245
12625
|
for (const document of batchDocumentRollbacks) {
|
|
12246
12626
|
this.restoreNativeBackboneDocument(document);
|
|
12247
12627
|
}
|
|
12248
|
-
const flushed =
|
|
12628
|
+
const flushed =
|
|
12629
|
+
this._coordinates.flushNativeBackboneCoordinateJournal();
|
|
12249
12630
|
if (isPromiseLike(flushed)) {
|
|
12250
12631
|
await flushed;
|
|
12251
12632
|
}
|
|
@@ -12325,20 +12706,21 @@ export class SharedLog<
|
|
|
12325
12706
|
coordinateFields,
|
|
12326
12707
|
plannedCoordinateDeleteHashes,
|
|
12327
12708
|
});
|
|
12328
|
-
const persisted =
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12709
|
+
const persisted =
|
|
12710
|
+
this._coordinates.persistBackboneCoordinateFieldsNativeTransaction(
|
|
12711
|
+
{
|
|
12712
|
+
coordinateIndex: this.entryCoordinatesIndex as PutAndDeleteIndex<
|
|
12713
|
+
EntryReplicated<R>
|
|
12714
|
+
>,
|
|
12715
|
+
fields: coordinateFields,
|
|
12716
|
+
hash: facts.hash,
|
|
12717
|
+
deleteHashes: [],
|
|
12718
|
+
coordinates: backboneAppend.coordinate
|
|
12719
|
+
.coordinates as NumberFromType<R>[],
|
|
12720
|
+
skipGenericTransientCoordinateIndex: runtimeOnlyCoordinates,
|
|
12721
|
+
},
|
|
12722
|
+
ownershipLifecycleController,
|
|
12723
|
+
);
|
|
12342
12724
|
if (isPromiseLike(persisted)) {
|
|
12343
12725
|
await persisted;
|
|
12344
12726
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
@@ -13831,6 +14213,8 @@ export class SharedLog<
|
|
|
13831
14213
|
ttl: LEADER_PLAN_CACHE_TTL_MS,
|
|
13832
14214
|
});
|
|
13833
14215
|
this._peerSyncCapabilities = new Map();
|
|
14216
|
+
this._peerSyncCapabilitySessions = new Map();
|
|
14217
|
+
this._peerSyncCapabilityTimestamps = new Map();
|
|
13834
14218
|
this._liveRawGossipBatches = new Map();
|
|
13835
14219
|
this._liveRawGossipFlushScheduled = false;
|
|
13836
14220
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
@@ -13847,6 +14231,10 @@ export class SharedLog<
|
|
|
13847
14231
|
this._lastLocalAppendAt = 0;
|
|
13848
14232
|
this._announcements ??= this.createReplicationAnnouncementCoordinator();
|
|
13849
14233
|
this._announcements.resetForOpen();
|
|
14234
|
+
this._v2Receive ??= this.createReplicationInfoV2ReceiveCoordinator();
|
|
14235
|
+
this._v2Receive.resetForOpen();
|
|
14236
|
+
this._v2Send ??= this.createReplicationInfoV2SendCoordinator();
|
|
14237
|
+
this._v2Send.resetForOpen();
|
|
13850
14238
|
const adaptiveReplicateOptions =
|
|
13851
14239
|
options?.replicate && isAdaptiveReplicatorOption(options.replicate)
|
|
13852
14240
|
? options.replicate
|
|
@@ -14588,8 +14976,11 @@ export class SharedLog<
|
|
|
14588
14976
|
// tombstone was written. Complete that erase before the adapter can expose
|
|
14589
14977
|
// any stale coordinate or document state to this backbone.
|
|
14590
14978
|
await this._coordinates._nativeBackboneCoordinatePersistence.resumeDrop?.();
|
|
14591
|
-
await this._coordinates._nativeBackboneCoordinatePersistence.hydrate(
|
|
14592
|
-
|
|
14979
|
+
await this._coordinates._nativeBackboneCoordinatePersistence.hydrate(
|
|
14980
|
+
backbone,
|
|
14981
|
+
);
|
|
14982
|
+
this._coordinates._nativeBackboneCoordinateJournalLastFlushMs =
|
|
14983
|
+
Date.now();
|
|
14593
14984
|
this.hydrateNativeCoordinateStateFromBackbone(backbone);
|
|
14594
14985
|
return;
|
|
14595
14986
|
}
|
|
@@ -14629,7 +15020,9 @@ export class SharedLog<
|
|
|
14629
15020
|
if (!this._nativeBackbone) {
|
|
14630
15021
|
return;
|
|
14631
15022
|
}
|
|
14632
|
-
const hashes = new Set(
|
|
15023
|
+
const hashes = new Set(
|
|
15024
|
+
this._coordinates._residentEntryCoordinatesByHash?.keys() ?? [],
|
|
15025
|
+
);
|
|
14633
15026
|
const iterator = this.entryCoordinatesIndex.iterate({});
|
|
14634
15027
|
try {
|
|
14635
15028
|
for (;;) {
|
|
@@ -14699,7 +15092,10 @@ export class SharedLog<
|
|
|
14699
15092
|
coordinate.requestedReplicas,
|
|
14700
15093
|
sharedFields.hashNumber,
|
|
14701
15094
|
);
|
|
14702
|
-
this._coordinates._residentEntryCoordinatesByHash.set(
|
|
15095
|
+
this._coordinates._residentEntryCoordinatesByHash.set(
|
|
15096
|
+
sharedFields.hash,
|
|
15097
|
+
sharedFields,
|
|
15098
|
+
);
|
|
14703
15099
|
for (const value of sharedFields.coordinates) {
|
|
14704
15100
|
this.coordinateToHash.add(value, sharedFields.hash);
|
|
14705
15101
|
}
|
|
@@ -14858,7 +15254,8 @@ export class SharedLog<
|
|
|
14858
15254
|
this._coordinates._nativeBackboneCoordinatePersistence
|
|
14859
15255
|
) {
|
|
14860
15256
|
if (
|
|
14861
|
-
this._coordinates._nativeBackboneCoordinatePersistence
|
|
15257
|
+
this._coordinates._nativeBackboneCoordinatePersistence
|
|
15258
|
+
.durableBarrier !== true ||
|
|
14862
15259
|
typeof this._nativeBackboneCoordinatePersistenceStore
|
|
14863
15260
|
?.durableBarrier !== "function"
|
|
14864
15261
|
) {
|
|
@@ -14869,11 +15266,12 @@ export class SharedLog<
|
|
|
14869
15266
|
}
|
|
14870
15267
|
if (
|
|
14871
15268
|
this._coordinates._nativeBackboneCoordinatePersistence &&
|
|
14872
|
-
(this._coordinates._nativeBackboneCoordinatePersistence
|
|
14873
|
-
null ||
|
|
14874
|
-
this._coordinates._nativeBackboneCoordinatePersistence
|
|
14875
|
-
null) &&
|
|
14876
|
-
this._coordinates._nativeBackboneCoordinatePersistence
|
|
15269
|
+
(this._coordinates._nativeBackboneCoordinatePersistence
|
|
15270
|
+
.compactMaxJournalBytes != null ||
|
|
15271
|
+
this._coordinates._nativeBackboneCoordinatePersistence
|
|
15272
|
+
.compactMaxJournalRecords != null) &&
|
|
15273
|
+
this._coordinates._nativeBackboneCoordinatePersistence
|
|
15274
|
+
.crashSafeCompaction !== true
|
|
14877
15275
|
) {
|
|
14878
15276
|
// Durable custom adapters must explicitly advertise an atomic generation
|
|
14879
15277
|
// protocol before SharedLog permits automatic WAL compaction.
|
|
@@ -15099,7 +15497,8 @@ export class SharedLog<
|
|
|
15099
15497
|
async afterOpen(): Promise<void> {
|
|
15100
15498
|
await super.afterOpen();
|
|
15101
15499
|
const existingSubscribersPromise = this._getTopicSubscribers(this.topic);
|
|
15102
|
-
const replicationLifecycleController =
|
|
15500
|
+
const replicationLifecycleController =
|
|
15501
|
+
this._instanceLifecycle?.membershipLifecycleController;
|
|
15103
15502
|
|
|
15104
15503
|
// We do this here, because these calls requires this.closed == false
|
|
15105
15504
|
void this.pruneOfflineReplicators()
|
|
@@ -15140,7 +15539,8 @@ export class SharedLog<
|
|
|
15140
15539
|
async pruneOfflineReplicators() {
|
|
15141
15540
|
// Go through all segments and wait for replicators to become reachable;
|
|
15142
15541
|
// otherwise prune them away from the local membership view.
|
|
15143
|
-
const replicationLifecycleController =
|
|
15542
|
+
const replicationLifecycleController =
|
|
15543
|
+
this._instanceLifecycle?.membershipLifecycleController;
|
|
15144
15544
|
try {
|
|
15145
15545
|
if (
|
|
15146
15546
|
!replicationLifecycleController ||
|
|
@@ -15335,10 +15735,19 @@ export class SharedLog<
|
|
|
15335
15735
|
peerHash: string,
|
|
15336
15736
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
15337
15737
|
) {
|
|
15738
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
15739
|
+
const preserveV2Session =
|
|
15740
|
+
peerSession?.phase === "open" && peerSession.isActive();
|
|
15338
15741
|
this.cancelReplicationInfoRequests(peerHash);
|
|
15339
15742
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
15340
15743
|
this._liveness._replicatorLastActivityAt.delete(peerHash);
|
|
15341
|
-
|
|
15744
|
+
if (!preserveV2Session) {
|
|
15745
|
+
this._peerSyncCapabilities.delete(peerHash);
|
|
15746
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
15747
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
15748
|
+
this._v2Receive.clearPeer(peerHash);
|
|
15749
|
+
this._v2Send.clearPeer(peerHash);
|
|
15750
|
+
}
|
|
15342
15751
|
this.cleanupPendingIHavePeer(peerHash);
|
|
15343
15752
|
this.cleanupCheckedPrunePeer(
|
|
15344
15753
|
peerHash,
|
|
@@ -15362,8 +15771,16 @@ export class SharedLog<
|
|
|
15362
15771
|
// when they eventually reach the apply lane. Reset the sender's
|
|
15363
15772
|
// ordering watermark with the local epoch so a later arrival can be
|
|
15364
15773
|
// accepted without comparing its clock to this receiver's clock.
|
|
15365
|
-
this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
15774
|
+
const receiveEpoch = this._peerSessions.advanceReceiveEpoch(peerHash);
|
|
15366
15775
|
this.latestReplicationInfoMessage.delete(peerHash);
|
|
15776
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
15777
|
+
if (peerSession?.phase === "open") {
|
|
15778
|
+
this._v2Receive.advanceRecovery({
|
|
15779
|
+
peerHash,
|
|
15780
|
+
peerSession,
|
|
15781
|
+
receiveEpoch,
|
|
15782
|
+
});
|
|
15783
|
+
}
|
|
15367
15784
|
}
|
|
15368
15785
|
|
|
15369
15786
|
private async resolveCandidatePeersForHash(
|
|
@@ -15644,7 +16061,9 @@ export class SharedLog<
|
|
|
15644
16061
|
deferredCoordinateDeleteHashes,
|
|
15645
16062
|
);
|
|
15646
16063
|
} else {
|
|
15647
|
-
this._coordinates.forgetCoordinateStateForHashes(
|
|
16064
|
+
this._coordinates.forgetCoordinateStateForHashes(
|
|
16065
|
+
deferredCoordinateDeleteHashes,
|
|
16066
|
+
);
|
|
15648
16067
|
}
|
|
15649
16068
|
return deferredCoordinateDeleteHashes;
|
|
15650
16069
|
}
|
|
@@ -15692,7 +16111,9 @@ export class SharedLog<
|
|
|
15692
16111
|
deferredCoordinateDeleteHashes,
|
|
15693
16112
|
);
|
|
15694
16113
|
} else {
|
|
15695
|
-
this._coordinates.forgetCoordinateStateForHashes(
|
|
16114
|
+
this._coordinates.forgetCoordinateStateForHashes(
|
|
16115
|
+
deferredCoordinateDeleteHashes,
|
|
16116
|
+
);
|
|
15696
16117
|
}
|
|
15697
16118
|
return deferredCoordinateDeleteHashes;
|
|
15698
16119
|
}
|
|
@@ -16255,7 +16676,9 @@ export class SharedLog<
|
|
|
16255
16676
|
this._wireSyncSession = undefined;
|
|
16256
16677
|
}
|
|
16257
16678
|
});
|
|
16258
|
-
await capture(() =>
|
|
16679
|
+
await capture(() =>
|
|
16680
|
+
this._coordinates.closeNativeBackboneCoordinatePersistence(),
|
|
16681
|
+
);
|
|
16259
16682
|
await capture(() => this.syncronizer?.close());
|
|
16260
16683
|
|
|
16261
16684
|
captureSync(() => {
|
|
@@ -16348,6 +16771,10 @@ export class SharedLog<
|
|
|
16348
16771
|
this._openingSyncCapabilitiesByPeer?.clear();
|
|
16349
16772
|
this._gidPeersHistory?.clear();
|
|
16350
16773
|
this._peerSyncCapabilities?.clear();
|
|
16774
|
+
this._peerSyncCapabilitySessions?.clear();
|
|
16775
|
+
this._peerSyncCapabilityTimestamps?.clear();
|
|
16776
|
+
this._v2Receive?.clearForClose();
|
|
16777
|
+
this._v2Send?.clearForClose();
|
|
16351
16778
|
this._liveRawGossipBatches?.clear();
|
|
16352
16779
|
this._nativeSharedLogState?.clearGidPeers();
|
|
16353
16780
|
this._nativeBackbone?.clearGidPeers();
|
|
@@ -16420,6 +16847,7 @@ export class SharedLog<
|
|
|
16420
16847
|
this.preventParentAttachments();
|
|
16421
16848
|
this.stopRepairLifecycle();
|
|
16422
16849
|
this._instanceLifecycle?.beginTerminal("close");
|
|
16850
|
+
this._v2Receive?.clearForClose();
|
|
16423
16851
|
const replicationRangeTerminalFence =
|
|
16424
16852
|
this.acquireReplicationRangeMutationTerminalFence();
|
|
16425
16853
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
@@ -16480,12 +16908,16 @@ export class SharedLog<
|
|
|
16480
16908
|
}
|
|
16481
16909
|
}, 2_000);
|
|
16482
16910
|
try {
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16911
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
16912
|
+
await Promise.all([
|
|
16913
|
+
this.rpc
|
|
16914
|
+
.send(reset, {
|
|
16915
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
16916
|
+
signal: abort.signal,
|
|
16917
|
+
})
|
|
16918
|
+
.catch(() => {}),
|
|
16919
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
16920
|
+
]);
|
|
16489
16921
|
} finally {
|
|
16490
16922
|
clearTimeout(abortTimer);
|
|
16491
16923
|
}
|
|
@@ -16543,7 +16975,8 @@ export class SharedLog<
|
|
|
16543
16975
|
}
|
|
16544
16976
|
this.throwIfCheckedPruneRemoveBlocksLocalOperation("drop");
|
|
16545
16977
|
this.ensureNativeDurabilityRuntimeState();
|
|
16546
|
-
const nativePersistence =
|
|
16978
|
+
const nativePersistence =
|
|
16979
|
+
this._coordinates._nativeBackboneCoordinatePersistence;
|
|
16547
16980
|
if (
|
|
16548
16981
|
nativePersistence &&
|
|
16549
16982
|
(typeof nativePersistence.drop !== "function" ||
|
|
@@ -16561,6 +16994,7 @@ export class SharedLog<
|
|
|
16561
16994
|
this.preventParentAttachments();
|
|
16562
16995
|
this.stopRepairLifecycle();
|
|
16563
16996
|
this._instanceLifecycle?.beginTerminal("drop");
|
|
16997
|
+
this._v2Receive?.clearForClose();
|
|
16564
16998
|
const replicationRangeTerminalFence =
|
|
16565
16999
|
this.acquireReplicationRangeMutationTerminalFence();
|
|
16566
17000
|
const pruneRemoveTerminalFence = this.acquirePruneRemoveTerminalFence();
|
|
@@ -16604,12 +17038,16 @@ export class SharedLog<
|
|
|
16604
17038
|
}
|
|
16605
17039
|
}, 2_000);
|
|
16606
17040
|
try {
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
17041
|
+
const reset = new AllReplicatingSegmentsMessage({ segments: [] });
|
|
17042
|
+
await Promise.all([
|
|
17043
|
+
this.rpc
|
|
17044
|
+
.send(reset, {
|
|
17045
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
17046
|
+
signal: abort.signal,
|
|
17047
|
+
})
|
|
17048
|
+
.catch(() => {}),
|
|
17049
|
+
this._v2Send.sendTerminalReset(abort.signal),
|
|
17050
|
+
]);
|
|
16613
17051
|
} finally {
|
|
16614
17052
|
clearTimeout(abortTimer);
|
|
16615
17053
|
}
|
|
@@ -17126,13 +17564,6 @@ export class SharedLog<
|
|
|
17126
17564
|
msg: TransportMessage,
|
|
17127
17565
|
context: RequestContext,
|
|
17128
17566
|
): Promise<void> {
|
|
17129
|
-
// Decode-first only: capability advertisement is not permission to use
|
|
17130
|
-
// V2. Drop every unsolicited V2 envelope before even entering the shared
|
|
17131
|
-
// receive envelope, so durable poison checks, leases, liveness, ordering
|
|
17132
|
-
// watermarks, replication ranges and cleanup side effects cannot observe it.
|
|
17133
|
-
if (isReplicationInfoV2Message(msg)) {
|
|
17134
|
-
return;
|
|
17135
|
-
}
|
|
17136
17567
|
const stashBackedRawMessage = isStashBackedRawExchangeHeadsMessage(msg)
|
|
17137
17568
|
? msg
|
|
17138
17569
|
: undefined;
|
|
@@ -17185,17 +17616,29 @@ export class SharedLog<
|
|
|
17185
17616
|
}
|
|
17186
17617
|
if (
|
|
17187
17618
|
msg instanceof AllReplicatingSegmentsMessage ||
|
|
17188
|
-
msg instanceof AddedReplicationSegmentMessage
|
|
17619
|
+
msg instanceof AddedReplicationSegmentMessage ||
|
|
17620
|
+
msg instanceof FullReplicationInfoV2Message ||
|
|
17621
|
+
msg instanceof AddedReplicationInfoV2Message
|
|
17189
17622
|
) {
|
|
17190
17623
|
// Bound decoded untrusted vectors before per-peer/global mutation
|
|
17191
17624
|
// queues, trusted-replicator authorization, or liveness side effects.
|
|
17192
17625
|
this.validateReplicationRangeAnnouncement(msg.segments);
|
|
17193
|
-
} else if (
|
|
17626
|
+
} else if (
|
|
17627
|
+
msg instanceof StoppedReplicating ||
|
|
17628
|
+
msg instanceof StoppedReplicationInfoV2Message
|
|
17629
|
+
) {
|
|
17194
17630
|
// Bound the raw decoded vector before deduplication can hide the
|
|
17195
17631
|
// allocation cost, and before liveness or apply-queue side effects.
|
|
17196
17632
|
this.validateStoppedReplicationAnnouncement(msg.segmentIds);
|
|
17197
17633
|
}
|
|
17198
|
-
if (
|
|
17634
|
+
if (
|
|
17635
|
+
!context.from.equals(this.node.identity.publicKey) &&
|
|
17636
|
+
!(msg instanceof RequestReplicationInfoV2Message) &&
|
|
17637
|
+
!isReplicationInfoV2Message(msg) &&
|
|
17638
|
+
!(msg instanceof AllReplicatingSegmentsMessage) &&
|
|
17639
|
+
!(msg instanceof AddedReplicationSegmentMessage) &&
|
|
17640
|
+
!(msg instanceof StoppedReplicating)
|
|
17641
|
+
) {
|
|
17199
17642
|
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
17200
17643
|
}
|
|
17201
17644
|
|
|
@@ -18240,11 +18683,14 @@ export class SharedLog<
|
|
|
18240
18683
|
);
|
|
18241
18684
|
}
|
|
18242
18685
|
const reusableCoordinatePlans =
|
|
18243
|
-
this._coordinates.createReusableReceiveCoordinatePlans(
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
18686
|
+
this._coordinates.createReusableReceiveCoordinatePlans(
|
|
18687
|
+
receiveGroups,
|
|
18688
|
+
{
|
|
18689
|
+
decodedReplicaCounts: receiveReplicaCounts,
|
|
18690
|
+
allowRoleAgeZeroPlans:
|
|
18691
|
+
immediateReplicatingLeaderPlans !== undefined,
|
|
18692
|
+
},
|
|
18693
|
+
) as Map<string, ReusableReceiveCoordinatePlan<R>>;
|
|
18248
18694
|
if (syncProfile) {
|
|
18249
18695
|
emitSyncProfileDuration(syncProfile, joinPlanStartedAt, {
|
|
18250
18696
|
name: "sharedLog.receive.joinPlan",
|
|
@@ -18639,9 +19085,7 @@ export class SharedLog<
|
|
|
18639
19085
|
}
|
|
18640
19086
|
const checkedPruneStartedAt = syncProfileStart(syncProfile);
|
|
18641
19087
|
const ownershipChangedDuringReceive =
|
|
18642
|
-
!this.isReceiveOwnershipSnapshotStable(
|
|
18643
|
-
receiveOwnershipRevision,
|
|
18644
|
-
);
|
|
19088
|
+
!this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
|
|
18645
19089
|
if (ownershipChangedDuringReceive) {
|
|
18646
19090
|
const freshAuditRevision =
|
|
18647
19091
|
this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
@@ -18799,6 +19243,7 @@ export class SharedLog<
|
|
|
18799
19243
|
fromHash: receiveFromHash,
|
|
18800
19244
|
session: receiveSession,
|
|
18801
19245
|
lifecycleController: receiveReplicationLifecycleController,
|
|
19246
|
+
ownershipLifecycleController: receiveOwnershipLifecycleController,
|
|
18802
19247
|
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
18803
19248
|
syncProfile,
|
|
18804
19249
|
lease: peerReceiveLease,
|
|
@@ -18822,8 +19267,16 @@ export class SharedLog<
|
|
|
18822
19267
|
this.markEntriesKnownByPeer(msg.hashes, context.from.hashcode());
|
|
18823
19268
|
this.clearRepairFrontierHashes(context.from.hashcode(), msg.hashes);
|
|
18824
19269
|
return;
|
|
19270
|
+
} else if (isReplicationInfoV2Message(msg)) {
|
|
19271
|
+
await this.handleReplicationInfoV2Announcement(
|
|
19272
|
+
msg,
|
|
19273
|
+
laneRequestContext,
|
|
19274
|
+
lane,
|
|
19275
|
+
);
|
|
18825
19276
|
} else if (msg instanceof SyncCapabilitiesMessage) {
|
|
18826
19277
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
19278
|
+
const capabilityTransportSession = context.message?.header?.session;
|
|
19279
|
+
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
18827
19280
|
// No await separates this from the capture above, so the captured
|
|
18828
19281
|
// window state is exact: the legacy re-read of the opening map here
|
|
18829
19282
|
// could never observe a different value.
|
|
@@ -18834,15 +19287,55 @@ export class SharedLog<
|
|
|
18834
19287
|
// A prior unsubscribe cleanup may still be ahead of this reconnect
|
|
18835
19288
|
// barrier. Stage the new generation's one-shot advertisement so that
|
|
18836
19289
|
// cleanup cannot erase it before the opening transition commits.
|
|
18837
|
-
this.
|
|
18838
|
-
|
|
19290
|
+
this.observePeerSyncCapabilities({
|
|
19291
|
+
peerHash: receiveFromHash,
|
|
18839
19292
|
capabilities: msg.capabilities,
|
|
19293
|
+
transportSession: capabilityTransportSession,
|
|
19294
|
+
timestamp: capabilityTimestamp,
|
|
19295
|
+
openingSession: receiveSession!,
|
|
18840
19296
|
});
|
|
18841
|
-
} else
|
|
18842
|
-
this.
|
|
19297
|
+
} else if (
|
|
19298
|
+
this.observePeerSyncCapabilities({
|
|
19299
|
+
peerHash: receiveFromHash,
|
|
19300
|
+
capabilities: msg.capabilities,
|
|
19301
|
+
transportSession: capabilityTransportSession,
|
|
19302
|
+
timestamp: capabilityTimestamp,
|
|
19303
|
+
}) &&
|
|
19304
|
+
receiveSession?.phase === "open"
|
|
19305
|
+
) {
|
|
19306
|
+
this.promoteReplicationInfoV2ReceiveCapability(
|
|
19307
|
+
context.from,
|
|
19308
|
+
receiveSession,
|
|
19309
|
+
);
|
|
18843
19310
|
}
|
|
18844
19311
|
}
|
|
18845
19312
|
return;
|
|
19313
|
+
} else if (msg instanceof RequestReplicationInfoV2Message) {
|
|
19314
|
+
const requiredCapabilities =
|
|
19315
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
19316
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY;
|
|
19317
|
+
if (
|
|
19318
|
+
receiveSession &&
|
|
19319
|
+
((this._peerSyncCapabilities.get(receiveFromHash) ?? 0) &
|
|
19320
|
+
requiredCapabilities) ===
|
|
19321
|
+
requiredCapabilities &&
|
|
19322
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash) ===
|
|
19323
|
+
context.message.header.session &&
|
|
19324
|
+
this._peerSyncCapabilityTimestamps.has(receiveFromHash) &&
|
|
19325
|
+
context.message.header.timestamp >
|
|
19326
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash)! &&
|
|
19327
|
+
this._v2Send.acceptRequest(msg, {
|
|
19328
|
+
from: context.from,
|
|
19329
|
+
peerSession: receiveSession,
|
|
19330
|
+
receiverTransportSession: context.message.header.session,
|
|
19331
|
+
capabilityTimestamp:
|
|
19332
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash)!,
|
|
19333
|
+
requestTimestamp: context.message.header.timestamp,
|
|
19334
|
+
})
|
|
19335
|
+
) {
|
|
19336
|
+
this._liveness.markReplicatorActivity(receiveFromHash);
|
|
19337
|
+
}
|
|
19338
|
+
return;
|
|
18846
19339
|
} else if (await this.syncronizer.onMessage(msg, context)) {
|
|
18847
19340
|
return; // the syncronizer has handled the message
|
|
18848
19341
|
} else if (msg instanceof BlocksMessage) {
|
|
@@ -18980,9 +19473,7 @@ export class SharedLog<
|
|
|
18980
19473
|
msg.requests,
|
|
18981
19474
|
);
|
|
18982
19475
|
if (
|
|
18983
|
-
!this.isReplicationLifecycleActive(
|
|
18984
|
-
receiveReplicationLifecycleController,
|
|
18985
|
-
)
|
|
19476
|
+
!this.isReplicationLifecycleActive(receiveReplicationLifecycleController)
|
|
18986
19477
|
) {
|
|
18987
19478
|
return;
|
|
18988
19479
|
}
|
|
@@ -19041,9 +19532,7 @@ export class SharedLog<
|
|
|
19041
19532
|
let entry: Entry<T> | undefined;
|
|
19042
19533
|
try {
|
|
19043
19534
|
if (await this.log.blocks.has(request.hash)) {
|
|
19044
|
-
entry = (await this.log.get(request.hash)) as
|
|
19045
|
-
| Entry<T>
|
|
19046
|
-
| undefined;
|
|
19535
|
+
entry = (await this.log.get(request.hash)) as Entry<T> | undefined;
|
|
19047
19536
|
}
|
|
19048
19537
|
} catch {
|
|
19049
19538
|
// The normal entry-admission hook will retry this path.
|
|
@@ -19084,16 +19573,11 @@ export class SharedLog<
|
|
|
19084
19573
|
}
|
|
19085
19574
|
const responseTasks: Promise<void>[] = [];
|
|
19086
19575
|
for (const request of msg.requests) {
|
|
19087
|
-
const pendingDelete = this._checkedPrune.getPendingDelete(
|
|
19088
|
-
request.hash,
|
|
19089
|
-
);
|
|
19576
|
+
const pendingDelete = this._checkedPrune.getPendingDelete(request.hash);
|
|
19090
19577
|
if (pendingDelete) {
|
|
19091
19578
|
responseTasks.push(
|
|
19092
19579
|
Promise.resolve(
|
|
19093
|
-
pendingDelete.resolve(
|
|
19094
|
-
context.from.hashcode(),
|
|
19095
|
-
request.requestId,
|
|
19096
|
-
),
|
|
19580
|
+
pendingDelete.resolve(context.from.hashcode(), request.requestId),
|
|
19097
19581
|
),
|
|
19098
19582
|
);
|
|
19099
19583
|
}
|
|
@@ -19157,6 +19641,7 @@ export class SharedLog<
|
|
|
19157
19641
|
}
|
|
19158
19642
|
const segments = replicationSegments.map((x) => x.toReplicationRange());
|
|
19159
19643
|
this.validatePersistedReplicationRangeSnapshot(segments);
|
|
19644
|
+
this._v2Send.enqueueSnapshotForPeer(receiveFromHash);
|
|
19160
19645
|
|
|
19161
19646
|
await this.rpc
|
|
19162
19647
|
.send(new AllReplicatingSegmentsMessage({ segments }), {
|
|
@@ -19212,6 +19697,191 @@ export class SharedLog<
|
|
|
19212
19697
|
}
|
|
19213
19698
|
}
|
|
19214
19699
|
|
|
19700
|
+
private async handleReplicationInfoV2Announcement(
|
|
19701
|
+
msg: ReplicationInfoV2Message,
|
|
19702
|
+
context: ReceiveRequestContext,
|
|
19703
|
+
lane: ReceiveLaneContext,
|
|
19704
|
+
): Promise<void> {
|
|
19705
|
+
const from = context.from;
|
|
19706
|
+
const fromHash = lane.fromHash;
|
|
19707
|
+
const receiveSession = lane.session;
|
|
19708
|
+
if (
|
|
19709
|
+
from.equals(this.node.identity.publicKey) ||
|
|
19710
|
+
receiveSession === null ||
|
|
19711
|
+
receiveSession.phase !== "open"
|
|
19712
|
+
) {
|
|
19713
|
+
return;
|
|
19714
|
+
}
|
|
19715
|
+
|
|
19716
|
+
// Authenticate and reserve before the per-peer lane. One reservation per
|
|
19717
|
+
// peer bounds decoded-frame retention while another mutation is parked.
|
|
19718
|
+
const receiveState = this._v2Receive._receiveStates.get(fromHash);
|
|
19719
|
+
if (
|
|
19720
|
+
!receiveState ||
|
|
19721
|
+
receiveState.peerSession !== receiveSession ||
|
|
19722
|
+
receiveState.senderTransportSession !== context.message.header.session
|
|
19723
|
+
) {
|
|
19724
|
+
return;
|
|
19725
|
+
}
|
|
19726
|
+
const admission = this._v2Receive.reserve(msg, {
|
|
19727
|
+
from,
|
|
19728
|
+
peerSession: receiveSession,
|
|
19729
|
+
receiveEpoch: lane.receiveEpoch,
|
|
19730
|
+
senderTransportSession: context.message.header.session,
|
|
19731
|
+
transportTimestamp: context.message.header.timestamp,
|
|
19732
|
+
});
|
|
19733
|
+
if (!admission) {
|
|
19734
|
+
return;
|
|
19735
|
+
}
|
|
19736
|
+
|
|
19737
|
+
lane.lease.release();
|
|
19738
|
+
try {
|
|
19739
|
+
await this.withReplicationInfoApplyQueue(fromHash, async () => {
|
|
19740
|
+
const hostGate = () =>
|
|
19741
|
+
this._instanceLifecycle!.isMembershipActiveFor(
|
|
19742
|
+
lane.lifecycleController,
|
|
19743
|
+
) &&
|
|
19744
|
+
this.isRepairLifecycleActive(lane.ownershipLifecycleController) &&
|
|
19745
|
+
this._peerSessions.isCurrent(fromHash, receiveSession) &&
|
|
19746
|
+
receiveSession.phase === "open" &&
|
|
19747
|
+
this._peerSessions.isReceiveEpochCurrent(
|
|
19748
|
+
fromHash,
|
|
19749
|
+
lane.receiveEpoch,
|
|
19750
|
+
) &&
|
|
19751
|
+
!this._peerSessions.isReplicationInfoBlocked(fromHash) &&
|
|
19752
|
+
this._peerSessions.isReceiveCleanupGateOpen(fromHash);
|
|
19753
|
+
if (!hostGate()) {
|
|
19754
|
+
return;
|
|
19755
|
+
}
|
|
19756
|
+
const exactGate = () =>
|
|
19757
|
+
hostGate() && this._v2Receive.isAdmissionCurrent(admission);
|
|
19758
|
+
let durableCommitted = false;
|
|
19759
|
+
|
|
19760
|
+
try {
|
|
19761
|
+
if (
|
|
19762
|
+
msg instanceof FullReplicationInfoV2Message ||
|
|
19763
|
+
msg instanceof AddedReplicationInfoV2Message
|
|
19764
|
+
) {
|
|
19765
|
+
let mutationGateChecked = false;
|
|
19766
|
+
let mutationGateAdmitted = false;
|
|
19767
|
+
const result = await this.addReplicationRange(
|
|
19768
|
+
msg.segments.map((segment) =>
|
|
19769
|
+
segment.toReplicationRangeIndexable(from),
|
|
19770
|
+
),
|
|
19771
|
+
from,
|
|
19772
|
+
{
|
|
19773
|
+
reset: msg instanceof FullReplicationInfoV2Message,
|
|
19774
|
+
checkDuplicates: true,
|
|
19775
|
+
timestamp: Number(context.message.header.timestamp),
|
|
19776
|
+
allowLegacyOrderedReplacementPairs:
|
|
19777
|
+
msg instanceof AddedReplicationInfoV2Message,
|
|
19778
|
+
shouldApply: () => {
|
|
19779
|
+
mutationGateChecked = true;
|
|
19780
|
+
mutationGateAdmitted = exactGate();
|
|
19781
|
+
return mutationGateAdmitted;
|
|
19782
|
+
},
|
|
19783
|
+
onDurableApplyCommitted: () => {
|
|
19784
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
19785
|
+
return false;
|
|
19786
|
+
}
|
|
19787
|
+
durableCommitted = true;
|
|
19788
|
+
return true;
|
|
19789
|
+
},
|
|
19790
|
+
},
|
|
19791
|
+
lane.ownershipLifecycleController,
|
|
19792
|
+
);
|
|
19793
|
+
if (
|
|
19794
|
+
result === undefined ||
|
|
19795
|
+
!mutationGateChecked ||
|
|
19796
|
+
!mutationGateAdmitted ||
|
|
19797
|
+
!durableCommitted ||
|
|
19798
|
+
!exactGate()
|
|
19799
|
+
) {
|
|
19800
|
+
return;
|
|
19801
|
+
}
|
|
19802
|
+
} else {
|
|
19803
|
+
if (!exactGate()) {
|
|
19804
|
+
return;
|
|
19805
|
+
}
|
|
19806
|
+
const rangesToRemove =
|
|
19807
|
+
await this.resolveReplicationRangesFromIdsAndKey(
|
|
19808
|
+
msg.segmentIds,
|
|
19809
|
+
from,
|
|
19810
|
+
);
|
|
19811
|
+
if (!exactGate()) {
|
|
19812
|
+
return;
|
|
19813
|
+
}
|
|
19814
|
+
let mutationGateAdmitted = true;
|
|
19815
|
+
const removedRanges: ReplicationRangeIndexable<R>[] = [];
|
|
19816
|
+
const removed = await this.removeReplicationRanges(
|
|
19817
|
+
rangesToRemove,
|
|
19818
|
+
from,
|
|
19819
|
+
{
|
|
19820
|
+
shouldRemove: () => {
|
|
19821
|
+
mutationGateAdmitted = exactGate();
|
|
19822
|
+
return mutationGateAdmitted;
|
|
19823
|
+
},
|
|
19824
|
+
onDurableRemoveCommitted: () => {
|
|
19825
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
19826
|
+
return false;
|
|
19827
|
+
}
|
|
19828
|
+
durableCommitted = true;
|
|
19829
|
+
return true;
|
|
19830
|
+
},
|
|
19831
|
+
onRemoved: (ranges) => removedRanges.push(...ranges),
|
|
19832
|
+
},
|
|
19833
|
+
lane.ownershipLifecycleController,
|
|
19834
|
+
);
|
|
19835
|
+
if (!durableCommitted) {
|
|
19836
|
+
if (
|
|
19837
|
+
!mutationGateAdmitted ||
|
|
19838
|
+
!exactGate() ||
|
|
19839
|
+
removed ||
|
|
19840
|
+
!this._v2Receive.commit(admission)
|
|
19841
|
+
) {
|
|
19842
|
+
return;
|
|
19843
|
+
}
|
|
19844
|
+
durableCommitted = true;
|
|
19845
|
+
}
|
|
19846
|
+
if (
|
|
19847
|
+
this._instanceLifecycle!.isMembershipActiveFor(
|
|
19848
|
+
lane.lifecycleController,
|
|
19849
|
+
) &&
|
|
19850
|
+
this.isRepairLifecycleActive(lane.ownershipLifecycleController)
|
|
19851
|
+
) {
|
|
19852
|
+
const timestamp = BigInt(Date.now());
|
|
19853
|
+
for (const range of removedRanges) {
|
|
19854
|
+
this.replicationChangeDebounceFn.add({
|
|
19855
|
+
range,
|
|
19856
|
+
type: "removed",
|
|
19857
|
+
timestamp,
|
|
19858
|
+
});
|
|
19859
|
+
}
|
|
19860
|
+
}
|
|
19861
|
+
}
|
|
19862
|
+
} catch (error) {
|
|
19863
|
+
this._v2Receive.requireFullAfterFailure(admission);
|
|
19864
|
+
throw error;
|
|
19865
|
+
}
|
|
19866
|
+
|
|
19867
|
+
if (!durableCommitted) {
|
|
19868
|
+
if (!exactGate() || !this._v2Receive.commit(admission)) {
|
|
19869
|
+
return;
|
|
19870
|
+
}
|
|
19871
|
+
}
|
|
19872
|
+
if (!hostGate()) {
|
|
19873
|
+
return;
|
|
19874
|
+
}
|
|
19875
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
19876
|
+
if (msg instanceof FullReplicationInfoV2Message) {
|
|
19877
|
+
this.cancelReplicationInfoRequests(fromHash);
|
|
19878
|
+
}
|
|
19879
|
+
});
|
|
19880
|
+
} finally {
|
|
19881
|
+
this._v2Receive.release(admission);
|
|
19882
|
+
}
|
|
19883
|
+
}
|
|
19884
|
+
|
|
19215
19885
|
private async handleReplicationInfoAnnouncement(
|
|
19216
19886
|
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
19217
19887
|
context: ReceiveRequestContext,
|
|
@@ -19235,6 +19905,19 @@ export class SharedLog<
|
|
|
19235
19905
|
// (and downstream `waitForReplicator()` timeouts) under timing-sensitive joins.
|
|
19236
19906
|
const from = context.from!;
|
|
19237
19907
|
const fromHash = from.hashcode();
|
|
19908
|
+
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
19909
|
+
if (receiveSession) {
|
|
19910
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
19911
|
+
peerHash: fromHash,
|
|
19912
|
+
peerSession: receiveSession,
|
|
19913
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
19914
|
+
senderTransportSession: context.message.header.session,
|
|
19915
|
+
transportTimestamp: context.message.header.timestamp,
|
|
19916
|
+
message: msg,
|
|
19917
|
+
});
|
|
19918
|
+
}
|
|
19919
|
+
return;
|
|
19920
|
+
}
|
|
19238
19921
|
// Pre-lane gate: lifecycle -> receive-epoch -> blocked, exactly the
|
|
19239
19922
|
// legacy order. isMembershipActiveFor is the unit-pinned fold of
|
|
19240
19923
|
// isReplicationLifecycleActive; isReceiveEpochCurrent is the
|
|
@@ -19273,6 +19956,17 @@ export class SharedLog<
|
|
|
19273
19956
|
) {
|
|
19274
19957
|
return;
|
|
19275
19958
|
}
|
|
19959
|
+
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
19960
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
19961
|
+
peerHash: fromHash,
|
|
19962
|
+
peerSession: receiveSession,
|
|
19963
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
19964
|
+
senderTransportSession: context.message.header.session,
|
|
19965
|
+
transportTimestamp: context.message.header.timestamp,
|
|
19966
|
+
message: msg,
|
|
19967
|
+
});
|
|
19968
|
+
return;
|
|
19969
|
+
}
|
|
19276
19970
|
|
|
19277
19971
|
// Process in-order to avoid races where repeated reset messages arrive
|
|
19278
19972
|
// concurrently and trigger spurious "added" diffs / rebalancing.
|
|
@@ -19282,14 +19976,13 @@ export class SharedLog<
|
|
|
19282
19976
|
}
|
|
19283
19977
|
|
|
19284
19978
|
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
19285
|
-
this._liveness._replicatorLivenessFailures.delete(fromHash);
|
|
19286
19979
|
|
|
19287
19980
|
if (this.closed) {
|
|
19288
19981
|
return;
|
|
19289
19982
|
}
|
|
19290
19983
|
|
|
19291
19984
|
const reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
19292
|
-
await this.addReplicationRange(
|
|
19985
|
+
const result = await this.addReplicationRange(
|
|
19293
19986
|
replicationInfoMessage.segments.map((x) =>
|
|
19294
19987
|
x.toReplicationRangeIndexable(from),
|
|
19295
19988
|
),
|
|
@@ -19302,6 +19995,10 @@ export class SharedLog<
|
|
|
19302
19995
|
msg instanceof AddedReplicationSegmentMessage,
|
|
19303
19996
|
},
|
|
19304
19997
|
);
|
|
19998
|
+
if (result === undefined) {
|
|
19999
|
+
return;
|
|
20000
|
+
}
|
|
20001
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
19305
20002
|
|
|
19306
20003
|
// If the peer reports any replication segments, stop re-requesting.
|
|
19307
20004
|
// (Empty reports can be transient during startup.)
|
|
@@ -19337,6 +20034,19 @@ export class SharedLog<
|
|
|
19337
20034
|
return;
|
|
19338
20035
|
}
|
|
19339
20036
|
const fromHash = from.hashcode();
|
|
20037
|
+
if (this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20038
|
+
if (receiveSession) {
|
|
20039
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
20040
|
+
peerHash: fromHash,
|
|
20041
|
+
peerSession: receiveSession,
|
|
20042
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20043
|
+
senderTransportSession: context.message.header.session,
|
|
20044
|
+
transportTimestamp: context.message.header.timestamp,
|
|
20045
|
+
message: msg,
|
|
20046
|
+
});
|
|
20047
|
+
}
|
|
20048
|
+
return;
|
|
20049
|
+
}
|
|
19340
20050
|
// Same pre-lane gate shape as Added/All above (and the same
|
|
19341
20051
|
// intentional absence of a subscription-epoch term).
|
|
19342
20052
|
if (
|
|
@@ -19367,22 +20077,34 @@ export class SharedLog<
|
|
|
19367
20077
|
) {
|
|
19368
20078
|
return;
|
|
19369
20079
|
}
|
|
20080
|
+
if (receiveSession && this._v2Receive.isLegacyCutover(receiveSession)) {
|
|
20081
|
+
this._v2Receive.noteLegacyAnnouncement({
|
|
20082
|
+
peerHash: fromHash,
|
|
20083
|
+
peerSession: receiveSession,
|
|
20084
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20085
|
+
senderTransportSession: context.message.header.session,
|
|
20086
|
+
transportTimestamp: context.message.header.timestamp,
|
|
20087
|
+
message: msg,
|
|
20088
|
+
});
|
|
20089
|
+
return;
|
|
20090
|
+
}
|
|
19370
20091
|
|
|
19371
|
-
const previousTimestamp =
|
|
19372
|
-
this.latestReplicationInfoMessage.get(fromHash);
|
|
20092
|
+
const previousTimestamp = this.latestReplicationInfoMessage.get(fromHash);
|
|
19373
20093
|
if (previousTimestamp && previousTimestamp > messageTimestamp) {
|
|
19374
20094
|
return;
|
|
19375
20095
|
}
|
|
19376
20096
|
this.latestReplicationInfoMessage.set(fromHash, messageTimestamp);
|
|
19377
|
-
this._liveness._replicatorLivenessFailures.delete(fromHash);
|
|
19378
20097
|
if (this.closed) {
|
|
19379
20098
|
return;
|
|
19380
20099
|
}
|
|
19381
20100
|
|
|
19382
|
-
const rangesToRemove =
|
|
19383
|
-
|
|
20101
|
+
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(
|
|
20102
|
+
segmentIds,
|
|
20103
|
+
from,
|
|
20104
|
+
);
|
|
19384
20105
|
|
|
19385
20106
|
await this.removeReplicationRanges(rangesToRemove, from);
|
|
20107
|
+
this._liveness.markReplicatorActivity(fromHash);
|
|
19386
20108
|
const timestamp = BigInt(+new Date());
|
|
19387
20109
|
for (const range of rangesToRemove) {
|
|
19388
20110
|
this.replicationChangeDebounceFn.add({
|
|
@@ -20184,10 +20906,9 @@ export class SharedLog<
|
|
|
20184
20906
|
entry: ShallowOrFullEntry<any> | EntryReplicated<R> | NumberFromType<R>,
|
|
20185
20907
|
minReplicas: number,
|
|
20186
20908
|
): Promise<NumberFromType<R>[]> {
|
|
20187
|
-
return this._coordinates.createCoordinates(
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
) as Promise<NumberFromType<R>[]>;
|
|
20909
|
+
return this._coordinates.createCoordinates(entry, minReplicas) as Promise<
|
|
20910
|
+
NumberFromType<R>[]
|
|
20911
|
+
>;
|
|
20191
20912
|
}
|
|
20192
20913
|
|
|
20193
20914
|
async getDefaultMinRoleAge(): Promise<number> {
|
|
@@ -20551,8 +21272,9 @@ export class SharedLog<
|
|
|
20551
21272
|
this.createNativeLeaderOptions(context, firstItem.options),
|
|
20552
21273
|
);
|
|
20553
21274
|
const plans: EntryLeaderPlan<R>[] = [];
|
|
20554
|
-
const persistItems: Parameters<
|
|
20555
|
-
|
|
21275
|
+
const persistItems: Parameters<
|
|
21276
|
+
typeof this._coordinates.persistCoordinatesBatch
|
|
21277
|
+
>[0] = [];
|
|
20556
21278
|
for (let i = 0; i < itemArray.length; i++) {
|
|
20557
21279
|
const item = itemArray[i]!;
|
|
20558
21280
|
const nativePlan = nativePlans[i]!;
|
|
@@ -20614,8 +21336,9 @@ export class SharedLog<
|
|
|
20614
21336
|
);
|
|
20615
21337
|
const selfHash = this.node.identity.publicKey.hashcode();
|
|
20616
21338
|
const plans: EntryLeaderPlan<R>[] = [];
|
|
20617
|
-
const persistItems: Parameters<
|
|
20618
|
-
|
|
21339
|
+
const persistItems: Parameters<
|
|
21340
|
+
typeof this._coordinates.persistCoordinatesBatch
|
|
21341
|
+
>[0] = [];
|
|
20619
21342
|
for (let i = 0; i < itemArray.length; i++) {
|
|
20620
21343
|
const item = itemArray[i]!;
|
|
20621
21344
|
const nativePlan = nativePlans[i]!;
|
|
@@ -22226,6 +22949,8 @@ export class SharedLog<
|
|
|
22226
22949
|
ownerHash: string,
|
|
22227
22950
|
options?: { preserveOwnerMembership?: boolean },
|
|
22228
22951
|
): Promise<ReplicationRangeDeletionOutcome<R>> {
|
|
22952
|
+
const wasReplicator = this.uniqueReplicators.has(ownerHash);
|
|
22953
|
+
const joinWasEmitted = this._replicatorJoinEmitted.has(ownerHash);
|
|
22229
22954
|
if (ranges.length === 0) {
|
|
22230
22955
|
const ownerHasRanges =
|
|
22231
22956
|
(await this.replicationIndex.count({ query: { hash: ownerHash } })) > 0;
|
|
@@ -22237,14 +22962,24 @@ export class SharedLog<
|
|
|
22237
22962
|
removed: [],
|
|
22238
22963
|
retained: [],
|
|
22239
22964
|
ownerHasRanges,
|
|
22965
|
+
rollback: async () => {
|
|
22966
|
+
if (wasReplicator) {
|
|
22967
|
+
this.uniqueReplicators.add(ownerHash);
|
|
22968
|
+
} else {
|
|
22969
|
+
this.uniqueReplicators.delete(ownerHash);
|
|
22970
|
+
}
|
|
22971
|
+
if (joinWasEmitted) {
|
|
22972
|
+
this._replicatorJoinEmitted.add(ownerHash);
|
|
22973
|
+
} else {
|
|
22974
|
+
this._replicatorJoinEmitted.delete(ownerHash);
|
|
22975
|
+
}
|
|
22976
|
+
},
|
|
22240
22977
|
};
|
|
22241
22978
|
}
|
|
22242
22979
|
|
|
22243
22980
|
const uniqueRanges = [
|
|
22244
22981
|
...new Map(ranges.map((range) => [range.idString, range])).values(),
|
|
22245
22982
|
];
|
|
22246
|
-
const wasReplicator = this.uniqueReplicators.has(ownerHash);
|
|
22247
|
-
const joinWasEmitted = this._replicatorJoinEmitted.has(ownerHash);
|
|
22248
22983
|
type Snapshot = {
|
|
22249
22984
|
range: ReplicationRangeIndexable<R>;
|
|
22250
22985
|
pending?: PendingMaturityRecord<R>;
|
|
@@ -22478,10 +23213,80 @@ export class SharedLog<
|
|
|
22478
23213
|
);
|
|
22479
23214
|
this.poisonReplicationOwnership(outcomeError);
|
|
22480
23215
|
}
|
|
23216
|
+
let rollbackStarted = false;
|
|
23217
|
+
const rollback = async () => {
|
|
23218
|
+
if (rollbackStarted) {
|
|
23219
|
+
return;
|
|
23220
|
+
}
|
|
23221
|
+
rollbackStarted = true;
|
|
23222
|
+
const rollbackErrors: unknown[] = [];
|
|
23223
|
+
const removedIds = new Set(removed.map((range) => range.idString));
|
|
23224
|
+
for (const snapshot of snapshots) {
|
|
23225
|
+
if (!removedIds.has(snapshot.range.idString)) {
|
|
23226
|
+
continue;
|
|
23227
|
+
}
|
|
23228
|
+
try {
|
|
23229
|
+
await this.replicationIndex.put(snapshot.range);
|
|
23230
|
+
this.putNativeReplicationRange(snapshot.range);
|
|
23231
|
+
} catch (error) {
|
|
23232
|
+
rollbackErrors.push(error);
|
|
23233
|
+
}
|
|
23234
|
+
}
|
|
23235
|
+
for (const snapshot of snapshots) {
|
|
23236
|
+
if (
|
|
23237
|
+
!removedIds.has(snapshot.range.idString) ||
|
|
23238
|
+
!snapshot.pending ||
|
|
23239
|
+
this.pendingMaturity.get(ownerHash)?.has(snapshot.range.idString)
|
|
23240
|
+
) {
|
|
23241
|
+
continue;
|
|
23242
|
+
}
|
|
23243
|
+
try {
|
|
23244
|
+
this.schedulePendingMaturity(
|
|
23245
|
+
snapshot.pending.range,
|
|
23246
|
+
snapshot.pending.from,
|
|
23247
|
+
{
|
|
23248
|
+
rebalance: snapshot.pending.rebalance,
|
|
23249
|
+
waitMs: Math.max(0, snapshot.pending.expiresAt - Date.now()),
|
|
23250
|
+
},
|
|
23251
|
+
snapshot.pending.ownershipLifecycleController,
|
|
23252
|
+
);
|
|
23253
|
+
} catch (error) {
|
|
23254
|
+
rollbackErrors.push(error);
|
|
23255
|
+
}
|
|
23256
|
+
}
|
|
23257
|
+
try {
|
|
23258
|
+
if (wasReplicator) {
|
|
23259
|
+
this.uniqueReplicators.add(ownerHash);
|
|
23260
|
+
} else {
|
|
23261
|
+
this.uniqueReplicators.delete(ownerHash);
|
|
23262
|
+
}
|
|
23263
|
+
if (joinWasEmitted) {
|
|
23264
|
+
this._replicatorJoinEmitted.add(ownerHash);
|
|
23265
|
+
} else {
|
|
23266
|
+
this._replicatorJoinEmitted.delete(ownerHash);
|
|
23267
|
+
}
|
|
23268
|
+
} catch (error) {
|
|
23269
|
+
rollbackErrors.push(error);
|
|
23270
|
+
}
|
|
23271
|
+
try {
|
|
23272
|
+
await this.updateOldestTimestampFromIndex();
|
|
23273
|
+
} catch (error) {
|
|
23274
|
+
rollbackErrors.push(error);
|
|
23275
|
+
}
|
|
23276
|
+
if (rollbackErrors.length > 0) {
|
|
23277
|
+
const failure = new AggregateError(
|
|
23278
|
+
rollbackErrors,
|
|
23279
|
+
"Replication-range deletion rollback failed",
|
|
23280
|
+
);
|
|
23281
|
+
this.poisonReplicationOwnership(failure);
|
|
23282
|
+
throw failure;
|
|
23283
|
+
}
|
|
23284
|
+
};
|
|
22481
23285
|
return {
|
|
22482
23286
|
removed,
|
|
22483
23287
|
retained,
|
|
22484
23288
|
ownerHasRanges,
|
|
23289
|
+
rollback,
|
|
22485
23290
|
error: outcomeError,
|
|
22486
23291
|
};
|
|
22487
23292
|
}
|
|
@@ -22497,7 +23302,8 @@ export class SharedLog<
|
|
|
22497
23302
|
|
|
22498
23303
|
private scheduleReplicationInfoRequests(
|
|
22499
23304
|
peer: PublicSignKey,
|
|
22500
|
-
replicationLifecycleController = this._instanceLifecycle
|
|
23305
|
+
replicationLifecycleController = this._instanceLifecycle
|
|
23306
|
+
?.membershipLifecycleController,
|
|
22501
23307
|
) {
|
|
22502
23308
|
if (
|
|
22503
23309
|
!replicationLifecycleController ||
|
|
@@ -22579,7 +23385,8 @@ export class SharedLog<
|
|
|
22579
23385
|
if (!topics.includes(this.topic)) {
|
|
22580
23386
|
return;
|
|
22581
23387
|
}
|
|
22582
|
-
const replicationLifecycleController =
|
|
23388
|
+
const replicationLifecycleController =
|
|
23389
|
+
this._instanceLifecycle?.membershipLifecycleController;
|
|
22583
23390
|
if (
|
|
22584
23391
|
!replicationLifecycleController ||
|
|
22585
23392
|
!this.isReplicationLifecycleActive(replicationLifecycleController)
|
|
@@ -22590,15 +23397,19 @@ export class SharedLog<
|
|
|
22590
23397
|
const peerHash = publicKey.hashcode();
|
|
22591
23398
|
const expectedSubscriptionEpoch =
|
|
22592
23399
|
subscriptionEpoch ??
|
|
22593
|
-
this._peerSessions.rotate(
|
|
22594
|
-
peerHash,
|
|
22595
|
-
subscribed ? "opening" : "departing",
|
|
22596
|
-
);
|
|
23400
|
+
this._peerSessions.rotate(peerHash, subscribed ? "opening" : "departing");
|
|
22597
23401
|
const ownsSubscriptionEpoch = () =>
|
|
22598
23402
|
this._peerSessions.isCurrent(peerHash, expectedSubscriptionEpoch);
|
|
22599
23403
|
if (!ownsSubscriptionEpoch()) {
|
|
22600
23404
|
return;
|
|
22601
23405
|
}
|
|
23406
|
+
// A destination stream is scoped to exactly one topic-subscription
|
|
23407
|
+
// session. Abort the predecessor synchronously before either barrier can
|
|
23408
|
+
// yield; a late queue completion must never enter the new session.
|
|
23409
|
+
this._v2Receive.clearPeer(peerHash);
|
|
23410
|
+
this._v2Send.clearPeer(peerHash);
|
|
23411
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
23412
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
22602
23413
|
if (subscribed) {
|
|
22603
23414
|
const pendingOpeningCapabilities =
|
|
22604
23415
|
this._openingSyncCapabilitiesByPeer.get(peerHash);
|
|
@@ -22638,6 +23449,22 @@ export class SharedLog<
|
|
|
22638
23449
|
peerHash,
|
|
22639
23450
|
openingCapabilities.capabilities,
|
|
22640
23451
|
);
|
|
23452
|
+
if (openingCapabilities.transportSession === undefined) {
|
|
23453
|
+
this._peerSyncCapabilitySessions.delete(peerHash);
|
|
23454
|
+
} else {
|
|
23455
|
+
this._peerSyncCapabilitySessions.set(
|
|
23456
|
+
peerHash,
|
|
23457
|
+
openingCapabilities.transportSession,
|
|
23458
|
+
);
|
|
23459
|
+
}
|
|
23460
|
+
if (openingCapabilities.timestamp === undefined) {
|
|
23461
|
+
this._peerSyncCapabilityTimestamps.delete(peerHash);
|
|
23462
|
+
} else {
|
|
23463
|
+
this._peerSyncCapabilityTimestamps.set(
|
|
23464
|
+
peerHash,
|
|
23465
|
+
openingCapabilities.timestamp,
|
|
23466
|
+
);
|
|
23467
|
+
}
|
|
22641
23468
|
this._openingSyncCapabilitiesByPeer.delete(peerHash);
|
|
22642
23469
|
}
|
|
22643
23470
|
this._peerSessions.unblockReplicationInfo(peerHash);
|
|
@@ -22716,31 +23543,30 @@ export class SharedLog<
|
|
|
22716
23543
|
this._liveness._replicatorLivenessFailures.delete(peerHash);
|
|
22717
23544
|
this._liveness.markReplicatorActivity(peerHash);
|
|
22718
23545
|
this._peerSessions.markOpen(peerHash, expectedSubscriptionEpoch);
|
|
23546
|
+
this.promoteReplicationInfoV2ReceiveCapability(
|
|
23547
|
+
publicKey,
|
|
23548
|
+
expectedSubscriptionEpoch,
|
|
23549
|
+
);
|
|
22719
23550
|
|
|
22720
|
-
// Decode
|
|
22721
|
-
//
|
|
22722
|
-
//
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
{
|
|
22734
|
-
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
22735
|
-
signal: replicationLifecycleController.signal,
|
|
22736
|
-
},
|
|
22737
|
-
)
|
|
22738
|
-
.catch((error) =>
|
|
23551
|
+
// Decode, sender and authenticated apply readiness are separate bits. Start
|
|
23552
|
+
// the ACKed receiver-led negotiation before the legacy snapshot, but do not
|
|
23553
|
+
// hold mixed-version replication discovery behind that round trip. Attach
|
|
23554
|
+
// the rejection handler now because a lifecycle fence below may return before
|
|
23555
|
+
// awaiting the advert. RequestV2 is armed only after both sends settle.
|
|
23556
|
+
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
23557
|
+
const localCapabilityAdvertisement =
|
|
23558
|
+
this.advertiseReplicationInfoV2ReceiveCapability({
|
|
23559
|
+
target: publicKey,
|
|
23560
|
+
peerSession: expectedSubscriptionEpoch,
|
|
23561
|
+
receiveEpoch,
|
|
23562
|
+
signal: replicationLifecycleController.signal,
|
|
23563
|
+
}).catch((error) => {
|
|
22739
23564
|
this.handleReplicationLifecycleSendError(
|
|
22740
23565
|
error,
|
|
22741
23566
|
replicationLifecycleController,
|
|
22742
|
-
)
|
|
22743
|
-
|
|
23567
|
+
);
|
|
23568
|
+
return undefined;
|
|
23569
|
+
});
|
|
22744
23570
|
|
|
22745
23571
|
let replicationSegments: ReplicationRangeIndexable<R>[];
|
|
22746
23572
|
try {
|
|
@@ -22810,9 +23636,9 @@ export class SharedLog<
|
|
|
22810
23636
|
}
|
|
22811
23637
|
}
|
|
22812
23638
|
|
|
22813
|
-
//
|
|
22814
|
-
// timing-sensitive delivery/order
|
|
22815
|
-
//
|
|
23639
|
+
// Keep legacy request-based discovery independent of the capability ACK too.
|
|
23640
|
+
// This makes mixed-version joins resilient to timing-sensitive delivery/order
|
|
23641
|
+
// issues where we may miss the remote peer's initial announcement.
|
|
22816
23642
|
if (
|
|
22817
23643
|
this.isReplicationLifecycleActive(replicationLifecycleController) &&
|
|
22818
23644
|
ownsSubscriptionEpoch()
|
|
@@ -22822,6 +23648,16 @@ export class SharedLog<
|
|
|
22822
23648
|
replicationLifecycleController,
|
|
22823
23649
|
);
|
|
22824
23650
|
}
|
|
23651
|
+
|
|
23652
|
+
const localCapabilityReady = await localCapabilityAdvertisement;
|
|
23653
|
+
if (localCapabilityReady) {
|
|
23654
|
+
this._v2Receive.markLocalCapabilityReady({
|
|
23655
|
+
peerHash,
|
|
23656
|
+
peerSession: expectedSubscriptionEpoch,
|
|
23657
|
+
receiveEpoch,
|
|
23658
|
+
...localCapabilityReady,
|
|
23659
|
+
});
|
|
23660
|
+
}
|
|
22825
23661
|
}
|
|
22826
23662
|
|
|
22827
23663
|
private getClampedReplicas(customValue?: MinReplicas) {
|
|
@@ -23944,7 +24780,10 @@ export class SharedLog<
|
|
|
23944
24780
|
if (oldPeersSet) {
|
|
23945
24781
|
for (const oldPeer of oldPeersSet) {
|
|
23946
24782
|
if (!currentPeers.has(oldPeer)) {
|
|
23947
|
-
this._checkedPrune.removeRequestSent(
|
|
24783
|
+
this._checkedPrune.removeRequestSent(
|
|
24784
|
+
entryReplicated.hash,
|
|
24785
|
+
oldPeer,
|
|
24786
|
+
);
|
|
23948
24787
|
}
|
|
23949
24788
|
}
|
|
23950
24789
|
}
|
|
@@ -24033,7 +24872,10 @@ export class SharedLog<
|
|
|
24033
24872
|
if (oldPeersSet) {
|
|
24034
24873
|
for (const oldPeer of oldPeersSet) {
|
|
24035
24874
|
if (!currentPeers.has(oldPeer)) {
|
|
24036
|
-
this._checkedPrune.removeRequestSent(
|
|
24875
|
+
this._checkedPrune.removeRequestSent(
|
|
24876
|
+
entryReplicated.hash,
|
|
24877
|
+
oldPeer,
|
|
24878
|
+
);
|
|
24037
24879
|
}
|
|
24038
24880
|
}
|
|
24039
24881
|
}
|
|
@@ -24213,10 +25055,7 @@ export class SharedLog<
|
|
|
24213
25055
|
}
|
|
24214
25056
|
|
|
24215
25057
|
const fromHash = evt.detail.from.hashcode();
|
|
24216
|
-
const subscriptionEpoch = this._peerSessions.rotate(
|
|
24217
|
-
fromHash,
|
|
24218
|
-
"departing",
|
|
24219
|
-
);
|
|
25058
|
+
const subscriptionEpoch = this._peerSessions.rotate(fromHash, "departing");
|
|
24220
25059
|
this._peerSessions.blockReplicationInfo(fromHash);
|
|
24221
25060
|
this._recentRepairDispatch.delete(fromHash);
|
|
24222
25061
|
|