@peerbit/shared-log 16.0.20 → 16.0.22
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 +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +218 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +23 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +44 -10
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +15 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +89 -30
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +13 -13
- package/src/index.ts +314 -20
- package/src/replication-info-v2-receive.ts +66 -14
- package/src/replication-info-v2-send.ts +150 -38
|
@@ -46,9 +46,16 @@ type ApplicationConfirmationRequest = {
|
|
|
46
46
|
controller: AbortController;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
type ApplicationConfirmationTarget = {
|
|
50
|
+
peerHash: string;
|
|
51
|
+
peerSession: object;
|
|
52
|
+
receiverTransportSession: bigint;
|
|
53
|
+
};
|
|
54
|
+
|
|
49
55
|
type ApplicationConfirmationWaiter = {
|
|
50
56
|
revision: bigint;
|
|
51
57
|
minPeers: number;
|
|
58
|
+
target?: ApplicationConfirmationTarget;
|
|
52
59
|
resolve: () => void;
|
|
53
60
|
reject: (error: unknown) => void;
|
|
54
61
|
timer?: ReturnType<typeof setTimeout>;
|
|
@@ -193,6 +200,10 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
clearPeer(peerHash: string, expectedSession?: object): void {
|
|
203
|
+
this.rejectTargetConfirmations(
|
|
204
|
+
{ peerHash, peerSession: expectedSession },
|
|
205
|
+
new AbortError("Replication confirmation destination changed"),
|
|
206
|
+
);
|
|
196
207
|
const state = this._sendStates.get(peerHash);
|
|
197
208
|
if (!state || (expectedSession && state.peerSession !== expectedSession)) {
|
|
198
209
|
return;
|
|
@@ -201,6 +212,10 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
advancePeerCapability(peerHash: string): void {
|
|
215
|
+
this.rejectTargetConfirmations(
|
|
216
|
+
{ peerHash },
|
|
217
|
+
new AbortError("Replication confirmation destination changed"),
|
|
218
|
+
);
|
|
204
219
|
const state = this._sendStates.get(peerHash);
|
|
205
220
|
if (state) {
|
|
206
221
|
this.clearState(state);
|
|
@@ -209,6 +224,14 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
209
224
|
|
|
210
225
|
private clearState(state: ReplicationInfoV2SendState): void {
|
|
211
226
|
this.trackRetiringWorker(state);
|
|
227
|
+
this.rejectTargetConfirmations(
|
|
228
|
+
{
|
|
229
|
+
peerHash: state.peerHash,
|
|
230
|
+
peerSession: state.peerSession,
|
|
231
|
+
receiverTransportSession: state.receiverTransportSession,
|
|
232
|
+
},
|
|
233
|
+
new AbortError("Replication confirmation destination changed"),
|
|
234
|
+
);
|
|
212
235
|
if (state.retryTimer) {
|
|
213
236
|
clearTimeout(state.retryTimer);
|
|
214
237
|
state.retryTimer = undefined;
|
|
@@ -251,7 +274,7 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
251
274
|
waiter.onAbort = undefined;
|
|
252
275
|
for (const state of this._sendStates.values()) {
|
|
253
276
|
const requested = state.applicationConfirmationRequest;
|
|
254
|
-
if (requested && !this.hasConfirmationFor(requested.revision)) {
|
|
277
|
+
if (requested && !this.hasConfirmationFor(state, requested.revision)) {
|
|
255
278
|
state.applicationConfirmationRequest = undefined;
|
|
256
279
|
requested.controller.abort(
|
|
257
280
|
new AbortError("Replication confirmation no longer pending"),
|
|
@@ -275,12 +298,50 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
275
298
|
}
|
|
276
299
|
}
|
|
277
300
|
|
|
278
|
-
private
|
|
301
|
+
private rejectTargetConfirmations(
|
|
302
|
+
target: {
|
|
303
|
+
peerHash: string;
|
|
304
|
+
peerSession?: object;
|
|
305
|
+
receiverTransportSession?: bigint;
|
|
306
|
+
},
|
|
307
|
+
error: unknown,
|
|
308
|
+
): void {
|
|
309
|
+
for (const waiter of [...this._confirmations]) {
|
|
310
|
+
if (
|
|
311
|
+
waiter.target === undefined ||
|
|
312
|
+
waiter.target.peerHash !== target.peerHash ||
|
|
313
|
+
(target.peerSession !== undefined &&
|
|
314
|
+
waiter.target.peerSession !== target.peerSession) ||
|
|
315
|
+
(target.receiverTransportSession !== undefined &&
|
|
316
|
+
waiter.target.receiverTransportSession !==
|
|
317
|
+
target.receiverTransportSession)
|
|
318
|
+
) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
this.clearConfirmationWaiter(waiter);
|
|
322
|
+
waiter.reject(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private matchesConfirmationTarget(
|
|
327
|
+
state: ReplicationInfoV2SendState,
|
|
328
|
+
target: ApplicationConfirmationTarget | undefined,
|
|
329
|
+
): boolean {
|
|
330
|
+
return (
|
|
331
|
+
target === undefined ||
|
|
332
|
+
(state.peerHash === target.peerHash &&
|
|
333
|
+
state.peerSession === target.peerSession &&
|
|
334
|
+
state.receiverTransportSession === target.receiverTransportSession)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private countAppliedPeers(waiter: ApplicationConfirmationWaiter): number {
|
|
279
339
|
let applied = 0;
|
|
280
340
|
for (const state of this._sendStates.values()) {
|
|
281
341
|
if (
|
|
282
342
|
state.appliedRevision !== undefined &&
|
|
283
|
-
state.appliedRevision >= revision &&
|
|
343
|
+
state.appliedRevision >= waiter.revision &&
|
|
344
|
+
this.matchesConfirmationTarget(state, waiter.target) &&
|
|
284
345
|
this.isDestinationReady(state) &&
|
|
285
346
|
this.supportsApplicationConfirmation(state)
|
|
286
347
|
) {
|
|
@@ -292,16 +353,22 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
292
353
|
|
|
293
354
|
private settleConfirmations(): void {
|
|
294
355
|
for (const waiter of [...this._confirmations]) {
|
|
295
|
-
if (this.countAppliedPeers(waiter
|
|
356
|
+
if (this.countAppliedPeers(waiter) >= waiter.minPeers) {
|
|
296
357
|
this.clearConfirmationWaiter(waiter);
|
|
297
358
|
waiter.resolve();
|
|
298
359
|
}
|
|
299
360
|
}
|
|
300
361
|
}
|
|
301
362
|
|
|
302
|
-
private hasConfirmationFor(
|
|
363
|
+
private hasConfirmationFor(
|
|
364
|
+
state: ReplicationInfoV2SendState,
|
|
365
|
+
revision: bigint,
|
|
366
|
+
): boolean {
|
|
303
367
|
for (const waiter of this._confirmations) {
|
|
304
|
-
if (
|
|
368
|
+
if (
|
|
369
|
+
waiter.revision <= revision &&
|
|
370
|
+
this.matchesConfirmationTarget(state, waiter.target)
|
|
371
|
+
) {
|
|
305
372
|
return true;
|
|
306
373
|
}
|
|
307
374
|
}
|
|
@@ -327,7 +394,8 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
327
394
|
for (const state of [...this._sendStates.values()]) {
|
|
328
395
|
if (
|
|
329
396
|
this.isCurrent(state) &&
|
|
330
|
-
this.supportsApplicationConfirmation(state)
|
|
397
|
+
this.supportsApplicationConfirmation(state) &&
|
|
398
|
+
this.hasConfirmationFor(state, this._revision)
|
|
331
399
|
) {
|
|
332
400
|
const revision = this._revision;
|
|
333
401
|
if (
|
|
@@ -351,22 +419,14 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
351
419
|
this.scheduleConfirmationRetry();
|
|
352
420
|
}
|
|
353
421
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
): Promise<void> {
|
|
363
|
-
const minPeers = options.minPeers ?? 1;
|
|
364
|
-
const timeout = options.timeout ?? DEFAULT_CONFIRM_TIMEOUT_MS;
|
|
365
|
-
if (!Number.isSafeInteger(minPeers) || minPeers <= 0) {
|
|
366
|
-
return Promise.reject(
|
|
367
|
-
new RangeError("Replication confirmation minPeers must be positive"),
|
|
368
|
-
);
|
|
369
|
-
}
|
|
422
|
+
private waitForConfirmation(properties: {
|
|
423
|
+
minPeers: number;
|
|
424
|
+
target?: ApplicationConfirmationTarget;
|
|
425
|
+
timeout?: number;
|
|
426
|
+
signal?: AbortSignal;
|
|
427
|
+
timeoutMessage: (revision: bigint) => string;
|
|
428
|
+
}): Promise<void> {
|
|
429
|
+
const timeout = properties.timeout ?? DEFAULT_CONFIRM_TIMEOUT_MS;
|
|
370
430
|
if (
|
|
371
431
|
!Number.isSafeInteger(timeout) ||
|
|
372
432
|
timeout <= 0 ||
|
|
@@ -381,10 +441,10 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
381
441
|
if (this.deps.isClosed()) {
|
|
382
442
|
return Promise.reject(new AbortError("Replication-info sender closed"));
|
|
383
443
|
}
|
|
384
|
-
if (
|
|
444
|
+
if (properties.signal?.aborted) {
|
|
385
445
|
return Promise.reject(
|
|
386
|
-
|
|
387
|
-
?
|
|
446
|
+
properties.signal.reason instanceof Error
|
|
447
|
+
? properties.signal.reason
|
|
388
448
|
: new AbortError("Replication confirmation aborted"),
|
|
389
449
|
);
|
|
390
450
|
}
|
|
@@ -399,30 +459,27 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
399
459
|
return new Promise<void>((resolve, reject) => {
|
|
400
460
|
const waiter: ApplicationConfirmationWaiter = {
|
|
401
461
|
revision: this._revision,
|
|
402
|
-
minPeers,
|
|
462
|
+
minPeers: properties.minPeers,
|
|
463
|
+
target: properties.target ? { ...properties.target } : undefined,
|
|
403
464
|
resolve,
|
|
404
465
|
reject,
|
|
405
|
-
signal:
|
|
466
|
+
signal: properties.signal,
|
|
406
467
|
};
|
|
407
468
|
waiter.timer = setTimeout(() => {
|
|
408
469
|
this.clearConfirmationWaiter(waiter);
|
|
409
|
-
reject(
|
|
410
|
-
new TimeoutError(
|
|
411
|
-
`Timed out waiting for ${minPeers} peer${minPeers === 1 ? "" : "s"} to apply replication revision ${waiter.revision}`,
|
|
412
|
-
),
|
|
413
|
-
);
|
|
470
|
+
reject(new TimeoutError(properties.timeoutMessage(waiter.revision)));
|
|
414
471
|
}, timeout);
|
|
415
472
|
waiter.timer.unref?.();
|
|
416
|
-
if (
|
|
473
|
+
if (properties.signal) {
|
|
417
474
|
waiter.onAbort = () => {
|
|
418
475
|
this.clearConfirmationWaiter(waiter);
|
|
419
476
|
reject(
|
|
420
|
-
|
|
421
|
-
?
|
|
477
|
+
properties.signal!.reason instanceof Error
|
|
478
|
+
? properties.signal!.reason
|
|
422
479
|
: new AbortError("Replication confirmation aborted"),
|
|
423
480
|
);
|
|
424
481
|
};
|
|
425
|
-
|
|
482
|
+
properties.signal.addEventListener("abort", waiter.onAbort, {
|
|
426
483
|
once: true,
|
|
427
484
|
});
|
|
428
485
|
}
|
|
@@ -431,6 +488,61 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
431
488
|
});
|
|
432
489
|
}
|
|
433
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Wait until the latest committed local replication revision is reported as
|
|
493
|
+
* durably applied by the configured number of current peers. Revisions newer
|
|
494
|
+
* than this call satisfy it, so rapid updates coalesce to one authoritative
|
|
495
|
+
* snapshot without weakening older callers' guarantees.
|
|
496
|
+
*/
|
|
497
|
+
confirmLatest(
|
|
498
|
+
options: ReplicationInfoV2ConfirmationOptions = {},
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
const minPeers = options.minPeers ?? 1;
|
|
501
|
+
if (!Number.isSafeInteger(minPeers) || minPeers <= 0) {
|
|
502
|
+
return Promise.reject(
|
|
503
|
+
new RangeError("Replication confirmation minPeers must be positive"),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return this.waitForConfirmation({
|
|
507
|
+
minPeers,
|
|
508
|
+
timeout: options.timeout,
|
|
509
|
+
signal: options.signal,
|
|
510
|
+
timeoutMessage: (revision) =>
|
|
511
|
+
`Timed out waiting for ${minPeers} peer${minPeers === 1 ? "" : "s"} to apply replication revision ${revision}`,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Confirm the latest revision on one exact destination generation. Multiple
|
|
517
|
+
* callers for that peer share the state's single snapshot/query worker, so
|
|
518
|
+
* persisted batches remain bounded by peers rather than entries.
|
|
519
|
+
*/
|
|
520
|
+
confirmLatestForPeer(
|
|
521
|
+
target: ApplicationConfirmationTarget,
|
|
522
|
+
options: Omit<ReplicationInfoV2ConfirmationOptions, "minPeers"> = {},
|
|
523
|
+
): Promise<void> {
|
|
524
|
+
if (
|
|
525
|
+
!this.deps.isPeerSessionCurrent(target.peerHash, target.peerSession) ||
|
|
526
|
+
!this.deps.isPeerSessionOpen(target.peerHash, target.peerSession) ||
|
|
527
|
+
this.deps.supportsApplicationConfirmation?.(
|
|
528
|
+
target.peerHash,
|
|
529
|
+
target.receiverTransportSession,
|
|
530
|
+
) !== true
|
|
531
|
+
) {
|
|
532
|
+
return Promise.reject(
|
|
533
|
+
new AbortError("Replication confirmation destination is not current"),
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
return this.waitForConfirmation({
|
|
537
|
+
minPeers: 1,
|
|
538
|
+
target,
|
|
539
|
+
timeout: options.timeout,
|
|
540
|
+
signal: options.signal,
|
|
541
|
+
timeoutMessage: (revision) =>
|
|
542
|
+
`Timed out waiting for peer ${target.peerHash} to apply replication revision ${revision}`,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
434
546
|
private trackRetiringWorker(state: ReplicationInfoV2SendState): void {
|
|
435
547
|
const worker = state.worker;
|
|
436
548
|
if (!worker) {
|
|
@@ -973,7 +1085,7 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
973
1085
|
});
|
|
974
1086
|
state.inFlightSequence = undefined;
|
|
975
1087
|
if (
|
|
976
|
-
this.hasConfirmationFor(request.revision) &&
|
|
1088
|
+
this.hasConfirmationFor(state, request.revision) &&
|
|
977
1089
|
this.supportsApplicationConfirmation(state) &&
|
|
978
1090
|
this.isCurrent(state)
|
|
979
1091
|
) {
|