@peerbit/shared-log 16.0.27 → 16.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +112 -24
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +9 -2
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +9 -2
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +67 -32
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +12 -12
- package/src/index.ts +120 -25
- package/src/replication-info-v2-receive.ts +13 -2
- package/src/replication-info-v2-send.ts +95 -42
|
@@ -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?:
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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
|
-
|
|
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)) {
|