@peerbit/shared-log 16.0.28 → 16.0.30

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.
@@ -40,10 +40,14 @@ type SendRequest =
40
40
  revision: bigint;
41
41
  };
42
42
 
43
+ type PendingRequest = SendRequest | { kind: "confirmation"; revision: bigint };
44
+
43
45
  type ApplicationConfirmationRequest = {
44
46
  sequence: bigint;
45
47
  revision: bigint;
46
48
  controller: AbortController;
49
+ nextFullReassertAt: number;
50
+ fullReassertions: number;
47
51
  };
48
52
 
49
53
  type ApplicationConfirmationTarget = {
@@ -124,7 +128,7 @@ export type ReplicationInfoV2SendState = {
124
128
  retryTimer?: ReturnType<typeof setTimeout>;
125
129
  retryAttempts: number;
126
130
  controller: AbortController;
127
- pending?: SendRequest;
131
+ pending?: PendingRequest;
128
132
  worker?: Promise<void>;
129
133
  applicationConfirmationRequest?: ApplicationConfirmationRequest;
130
134
  appliedRevision?: bigint;
@@ -442,10 +446,42 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
442
446
  ) {
443
447
  continue;
444
448
  }
445
- this.enqueueState(state, {
446
- kind: "snapshot",
447
- revision,
448
- });
449
+ const requested = state.applicationConfirmationRequest;
450
+ // A Full reserves the receiver's apply lane, which also closes its
451
+ // confirmation gate. Leave increasingly long query-only intervals
452
+ // for that commit to finish, while retaining Full repair for a quiet
453
+ // stream whose update reached transport but missed host admission.
454
+ if (
455
+ forceReassert &&
456
+ requested &&
457
+ Date.now() < requested.nextFullReassertAt
458
+ ) {
459
+ this.enqueueState(state, {
460
+ kind: "confirmation",
461
+ revision: requested.revision,
462
+ });
463
+ } else {
464
+ if (requested) {
465
+ requested.fullReassertions = Math.min(
466
+ requested.fullReassertions + 1,
467
+ MAX_BACKOFF_EXPONENT,
468
+ );
469
+ requested.nextFullReassertAt =
470
+ Date.now() +
471
+ Math.min(
472
+ Math.max(
473
+ DEFAULT_MAX_SEND_RETRY_MS,
474
+ this.confirmationRetryMs * 2,
475
+ ),
476
+ this.confirmationRetryMs *
477
+ 2 ** (requested.fullReassertions + 1),
478
+ );
479
+ }
480
+ this.enqueueState(state, {
481
+ kind: "snapshot",
482
+ revision,
483
+ });
484
+ }
449
485
  }
450
486
  }
451
487
  this.scheduleConfirmationRetry();
@@ -1000,7 +1036,7 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
1000
1036
 
1001
1037
  private enqueueState(
1002
1038
  state: ReplicationInfoV2SendState,
1003
- request: SendRequest,
1039
+ request: PendingRequest,
1004
1040
  ): void {
1005
1041
  if (state.nextSequence > MAX_U64 || !this.isCurrent(state)) {
1006
1042
  this.parkSnapshotForRetry(state);
@@ -1207,7 +1243,7 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
1207
1243
  });
1208
1244
  }
1209
1245
 
1210
- /** Accept an exact, signed response to this destination's latest query. */
1246
+ /** Accept an exact, signed response to this destination's outstanding query. */
1211
1247
  acceptApplied(
1212
1248
  message: ReplicationInfoV2AppliedMessage,
1213
1249
  properties: {
@@ -1247,6 +1283,36 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
1247
1283
  return true;
1248
1284
  }
1249
1285
 
1286
+ private async queryApplication(
1287
+ state: ReplicationInfoV2SendState,
1288
+ requested: ApplicationConfirmationRequest,
1289
+ ): Promise<void> {
1290
+ const signal = AbortSignal.any([
1291
+ state.controller.signal,
1292
+ state.ownershipLifecycleController.signal,
1293
+ requested.controller.signal,
1294
+ ]);
1295
+ try {
1296
+ await this.sendConfirmationRequest(
1297
+ new RequestReplicationInfoV2AppliedMessage({
1298
+ receiverChallenge: state.receiverChallenge.slice(),
1299
+ senderEpoch: state.senderEpoch.slice(),
1300
+ sequence: requested.sequence,
1301
+ revision: requested.revision,
1302
+ }),
1303
+ state,
1304
+ signal,
1305
+ );
1306
+ } catch (error) {
1307
+ if (state.applicationConfirmationRequest === requested) {
1308
+ state.applicationConfirmationRequest = undefined;
1309
+ }
1310
+ if (!requested.controller.signal.aborted) {
1311
+ throw error;
1312
+ }
1313
+ }
1314
+ }
1315
+
1250
1316
  private async runWorker(state: ReplicationInfoV2SendState): Promise<void> {
1251
1317
  while (true) {
1252
1318
  if (state.nextSequence > MAX_U64 || !this.isCurrent(state)) {
@@ -1259,6 +1325,14 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
1259
1325
  }
1260
1326
  state.pending = undefined;
1261
1327
  state.inFlightRevision = request.revision;
1328
+ if (request.kind === "confirmation") {
1329
+ const requested = state.applicationConfirmationRequest;
1330
+ if (requested && this.supportsApplicationConfirmation(state)) {
1331
+ await this.queryApplication(state, requested);
1332
+ }
1333
+ state.inFlightRevision = undefined;
1334
+ continue;
1335
+ }
1262
1336
  const message = await this.createMessage(state, request);
1263
1337
  if (state.nextSequence > MAX_U64 || !this.isCurrent(state)) {
1264
1338
  this.parkSnapshotForRetry(state);
@@ -1287,44 +1361,23 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
1287
1361
  this.supportsApplicationConfirmation(state) &&
1288
1362
  this.isCurrent(state)
1289
1363
  ) {
1290
- const confirmationRequest: ApplicationConfirmationRequest = {
1291
- sequence: message.sequence,
1292
- revision: request.revision,
1293
- controller: new AbortController(),
1294
- };
1364
+ // Keep one outstanding query until answered or no longer needed. A
1365
+ // query can arrive before the receiver commits its Full, and a retry
1366
+ // must still ask about that sequence after the commit. Replacing it
1367
+ // with each newer Full can keep every query ahead of the apply cursor
1368
+ // and also discard a valid response already in flight.
1369
+ const confirmationRequest: ApplicationConfirmationRequest =
1370
+ state.applicationConfirmationRequest ?? {
1371
+ sequence: message.sequence,
1372
+ revision: request.revision,
1373
+ controller: new AbortController(),
1374
+ nextFullReassertAt: Date.now() + this.confirmationRetryMs * 2,
1375
+ fullReassertions: 0,
1376
+ };
1295
1377
  // Install before invoking transport: an in-process or very fast remote
1296
1378
  // response may arrive before this send promise resolves.
1297
- state.applicationConfirmationRequest?.controller.abort(
1298
- new AbortError("Replication confirmation superseded"),
1299
- );
1300
1379
  state.applicationConfirmationRequest = confirmationRequest;
1301
- const confirmationSignal = AbortSignal.any([
1302
- state.controller.signal,
1303
- state.ownershipLifecycleController.signal,
1304
- confirmationRequest.controller.signal,
1305
- ]);
1306
- try {
1307
- await this.sendConfirmationRequest(
1308
- new RequestReplicationInfoV2AppliedMessage({
1309
- receiverChallenge: state.receiverChallenge.slice(),
1310
- senderEpoch: state.senderEpoch.slice(),
1311
- sequence: message.sequence,
1312
- revision: request.revision,
1313
- }),
1314
- state,
1315
- confirmationSignal,
1316
- );
1317
- } catch (error) {
1318
- if (state.applicationConfirmationRequest === confirmationRequest) {
1319
- state.applicationConfirmationRequest = undefined;
1320
- }
1321
- if (confirmationRequest.controller.signal.aborted) {
1322
- if (!this.isCurrent(state)) return;
1323
- state.inFlightRevision = undefined;
1324
- continue;
1325
- }
1326
- throw error;
1327
- }
1380
+ await this.queryApplication(state, confirmationRequest);
1328
1381
  }
1329
1382
  state.inFlightRevision = undefined;
1330
1383
  if (state.nextSequence > MAX_U64 || !this.isCurrent(state)) {