@peerbit/shared-log 16.0.25 → 16.0.27
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 +45 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +167 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +49 -4
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +272 -25
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +27 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +112 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/package.json +16 -16
- package/src/index.ts +297 -34
- package/src/replication-info-v2-receive.ts +409 -31
- package/src/replication-info-v2-send.ts +164 -0
package/src/index.ts
CHANGED
|
@@ -260,10 +260,14 @@ import type {
|
|
|
260
260
|
AddedReplicationInfoMutation,
|
|
261
261
|
FullReplicationInfoMutation,
|
|
262
262
|
} from "./replication-info-mutation.js";
|
|
263
|
-
import {
|
|
263
|
+
import {
|
|
264
|
+
ReplicationInfoV2ReceiveCoordinator,
|
|
265
|
+
type ReplicationInfoV2ReceiveDiagnostic,
|
|
266
|
+
} from "./replication-info-v2-receive.js";
|
|
264
267
|
import {
|
|
265
268
|
type ReplicationInfoV2ConfirmationOptions,
|
|
266
269
|
ReplicationInfoV2SendCoordinator,
|
|
270
|
+
type ReplicationInfoV2SendDiagnostic,
|
|
267
271
|
} from "./replication-info-v2-send.js";
|
|
268
272
|
import {
|
|
269
273
|
type ReplicationStatus,
|
|
@@ -1860,6 +1864,39 @@ export type PersistedReceiptPeerReadinessUnsupportedReason =
|
|
|
1860
1864
|
| "persisted-receipts-unsupported"
|
|
1861
1865
|
| "replication-confirmation-unsupported";
|
|
1862
1866
|
|
|
1867
|
+
/**
|
|
1868
|
+
* Bounded, payload-free diagnostics for one persisted-receipt readiness
|
|
1869
|
+
* inspection. Session and transport identities remain opaque; revision and
|
|
1870
|
+
* sequence values are decimal strings so the snapshot is JSON-safe. `reason`
|
|
1871
|
+
* fields classify the current state; rejected-packet and disconnected-peer
|
|
1872
|
+
* history is deliberately not retained.
|
|
1873
|
+
*/
|
|
1874
|
+
export type PersistedReceiptPeerReadinessDiagnostic = Readonly<{
|
|
1875
|
+
version: 1;
|
|
1876
|
+
log: "open" | "closed";
|
|
1877
|
+
session: Readonly<{
|
|
1878
|
+
state: "absent" | "current";
|
|
1879
|
+
phase?: "opening" | "open" | "departing" | "superseded";
|
|
1880
|
+
established: boolean;
|
|
1881
|
+
suspended: boolean;
|
|
1882
|
+
openingBarrierActive: boolean;
|
|
1883
|
+
replicationInfoBlocked: boolean;
|
|
1884
|
+
receiveCleanupGateOpen: boolean;
|
|
1885
|
+
}>;
|
|
1886
|
+
capability: Readonly<{
|
|
1887
|
+
state: "absent" | "observed";
|
|
1888
|
+
persistedReceipts: boolean;
|
|
1889
|
+
replicationConfirmation: boolean;
|
|
1890
|
+
}>;
|
|
1891
|
+
sender: ReplicationInfoV2SendDiagnostic;
|
|
1892
|
+
receiver: ReplicationInfoV2ReceiveDiagnostic;
|
|
1893
|
+
}>;
|
|
1894
|
+
|
|
1895
|
+
type PersistedReceiptPeerReadinessDiagnosticAttachment = Readonly<{
|
|
1896
|
+
/** Present only when `diagnostics: true` was requested. */
|
|
1897
|
+
diagnostic?: PersistedReceiptPeerReadinessDiagnostic;
|
|
1898
|
+
}>;
|
|
1899
|
+
|
|
1863
1900
|
/**
|
|
1864
1901
|
* Detached view of one public key's current persisted-receipt generation.
|
|
1865
1902
|
* `generation` is opaque: callers may compare it for equality, but must not
|
|
@@ -1868,20 +1905,23 @@ export type PersistedReceiptPeerReadinessUnsupportedReason =
|
|
|
1868
1905
|
* leadership and outbound confirmation can still change within a generation.
|
|
1869
1906
|
*/
|
|
1870
1907
|
export type PersistedReceiptPeerReadiness =
|
|
1871
|
-
| Readonly<{
|
|
1908
|
+
| (Readonly<{
|
|
1872
1909
|
status: "ready";
|
|
1873
1910
|
generation: string;
|
|
1874
|
-
}>
|
|
1875
|
-
|
|
1911
|
+
}> &
|
|
1912
|
+
PersistedReceiptPeerReadinessDiagnosticAttachment)
|
|
1913
|
+
| (Readonly<{
|
|
1876
1914
|
status: "pending";
|
|
1877
1915
|
reason: PersistedReceiptPeerReadinessPendingReason;
|
|
1878
1916
|
generation?: string;
|
|
1879
|
-
}>
|
|
1880
|
-
|
|
1917
|
+
}> &
|
|
1918
|
+
PersistedReceiptPeerReadinessDiagnosticAttachment)
|
|
1919
|
+
| (Readonly<{
|
|
1881
1920
|
status: "unsupported";
|
|
1882
1921
|
reason: PersistedReceiptPeerReadinessUnsupportedReason;
|
|
1883
1922
|
generation: string;
|
|
1884
|
-
}
|
|
1923
|
+
}> &
|
|
1924
|
+
PersistedReceiptPeerReadinessDiagnosticAttachment);
|
|
1885
1925
|
|
|
1886
1926
|
export type PersistedReceiptPeerReady = Extract<
|
|
1887
1927
|
PersistedReceiptPeerReadiness,
|
|
@@ -1892,6 +1932,8 @@ export type PersistedReceiptPeerReadinessOptions<
|
|
|
1892
1932
|
T,
|
|
1893
1933
|
R extends "u32" | "u64",
|
|
1894
1934
|
> = Readonly<{
|
|
1935
|
+
/** Include a bounded, JSON-safe diagnostic snapshot in the result. */
|
|
1936
|
+
diagnostics?: boolean;
|
|
1895
1937
|
/** Require this peer to be a freshly planned leader for every entry. */
|
|
1896
1938
|
entries?: readonly (ShallowOrFullEntry<T> | EntryReplicated<R>)[];
|
|
1897
1939
|
/**
|
|
@@ -1952,6 +1994,7 @@ const MAX_PERSISTED_RECEIPT_ATTEMPT_MS = 2_000;
|
|
|
1952
1994
|
const MAX_PERSISTED_RECEIPT_REQUESTS_GLOBAL = 8;
|
|
1953
1995
|
const MAX_PERSISTED_RECEIPT_REQUESTS_PER_PEER = 2;
|
|
1954
1996
|
const MAX_PERSISTED_RECEIPT_READINESS_WAITERS = 1_024;
|
|
1997
|
+
const MAX_PERSISTED_RECEIPT_READINESS_DIAGNOSTIC_SUMMARY_CHARS = 768;
|
|
1955
1998
|
const PERSISTED_RECEIPT_INGRESS_PEER_REQUEST_CAPACITY = 16;
|
|
1956
1999
|
const PERSISTED_RECEIPT_INGRESS_PEER_HASH_CAPACITY = 8_192;
|
|
1957
2000
|
const PERSISTED_RECEIPT_INGRESS_PEER_REQUESTS_PER_SECOND = 8;
|
|
@@ -4087,10 +4130,15 @@ export class SharedLog<
|
|
|
4087
4130
|
: 0),
|
|
4088
4131
|
}),
|
|
4089
4132
|
{
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4133
|
+
// The transient rearm is only a bounded recovery hint. The ensuing
|
|
4134
|
+
// authenticated Full/Applied exchange is the readiness proof, so do not
|
|
4135
|
+
// let an ACK-delivery promise park the recovery scheduler itself.
|
|
4136
|
+
mode: properties.requestRemoteFullRearm
|
|
4137
|
+
? new SilentDelivery({ redundancy: 1, to: [properties.target] })
|
|
4138
|
+
: new AcknowledgeDelivery({
|
|
4139
|
+
redundancy: 1,
|
|
4140
|
+
to: [properties.target],
|
|
4141
|
+
}),
|
|
4094
4142
|
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
4095
4143
|
signal: properties.signal,
|
|
4096
4144
|
},
|
|
@@ -4442,8 +4490,7 @@ export class SharedLog<
|
|
|
4442
4490
|
|
|
4443
4491
|
const profile = this._logProperties?.sync?.profile;
|
|
4444
4492
|
const startedAt = syncProfileStart(profile);
|
|
4445
|
-
const mode =
|
|
4446
|
-
resolvedRoot === fanoutService.publicKeyHash ? "root" : "node";
|
|
4493
|
+
const mode = resolvedRoot === fanoutService.publicKeyHash ? "root" : "node";
|
|
4447
4494
|
const before = profile
|
|
4448
4495
|
? snapshotFanoutOpenMetrics(fanoutService, this.topic, resolvedRoot)
|
|
4449
4496
|
: undefined;
|
|
@@ -5765,6 +5812,117 @@ export class SharedLog<
|
|
|
5765
5812
|
});
|
|
5766
5813
|
}
|
|
5767
5814
|
|
|
5815
|
+
private persistedReceiptReadinessDiagnostic(
|
|
5816
|
+
peerHash: string,
|
|
5817
|
+
): PersistedReceiptPeerReadinessDiagnostic {
|
|
5818
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
5819
|
+
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
5820
|
+
const capabilitySession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
5821
|
+
const capabilities = this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
5822
|
+
const replicationInfoBlocked =
|
|
5823
|
+
this._peerSessions.isReplicationInfoBlocked(peerHash);
|
|
5824
|
+
const receiveCleanupGateOpen =
|
|
5825
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash);
|
|
5826
|
+
const active = peerSession?.isActive() ?? false;
|
|
5827
|
+
const established =
|
|
5828
|
+
!this.closed &&
|
|
5829
|
+
peerSession?.phase === "open" &&
|
|
5830
|
+
active &&
|
|
5831
|
+
!replicationInfoBlocked &&
|
|
5832
|
+
receiveCleanupGateOpen;
|
|
5833
|
+
const session = Object.freeze({
|
|
5834
|
+
state: peerSession ? ("current" as const) : ("absent" as const),
|
|
5835
|
+
...(peerSession ? { phase: peerSession.phase } : {}),
|
|
5836
|
+
established,
|
|
5837
|
+
suspended: !established,
|
|
5838
|
+
openingBarrierActive: peerSession?.openingBarrierActive ?? false,
|
|
5839
|
+
replicationInfoBlocked,
|
|
5840
|
+
receiveCleanupGateOpen,
|
|
5841
|
+
});
|
|
5842
|
+
const capabilityObserved =
|
|
5843
|
+
capabilitySession !== undefined &&
|
|
5844
|
+
this._peerSyncCapabilityTimestamps.has(peerHash);
|
|
5845
|
+
const capability = Object.freeze({
|
|
5846
|
+
state: capabilityObserved ? ("observed" as const) : ("absent" as const),
|
|
5847
|
+
persistedReceipts:
|
|
5848
|
+
(capabilities & SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS) !== 0,
|
|
5849
|
+
replicationConfirmation:
|
|
5850
|
+
(capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !== 0,
|
|
5851
|
+
});
|
|
5852
|
+
return Object.freeze({
|
|
5853
|
+
version: 1 as const,
|
|
5854
|
+
log: this.closed ? ("closed" as const) : ("open" as const),
|
|
5855
|
+
session,
|
|
5856
|
+
capability,
|
|
5857
|
+
sender: this._v2Send.diagnosePeer({
|
|
5858
|
+
peerHash,
|
|
5859
|
+
...(peerSession ? { peerSession } : {}),
|
|
5860
|
+
...(capabilitySession === undefined
|
|
5861
|
+
? {}
|
|
5862
|
+
: { receiverTransportSession: capabilitySession }),
|
|
5863
|
+
}),
|
|
5864
|
+
receiver: this._v2Receive.diagnosePeer({
|
|
5865
|
+
peerHash,
|
|
5866
|
+
...(peerSession ? { peerSession } : {}),
|
|
5867
|
+
receiveEpoch,
|
|
5868
|
+
...(capabilitySession === undefined
|
|
5869
|
+
? {}
|
|
5870
|
+
: { senderTransportSession: capabilitySession }),
|
|
5871
|
+
}),
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
private attachPersistedReceiptReadinessDiagnostic<
|
|
5876
|
+
Snapshot extends PersistedReceiptPeerReadiness,
|
|
5877
|
+
>(
|
|
5878
|
+
peerHash: string,
|
|
5879
|
+
snapshot: Snapshot,
|
|
5880
|
+
enabled: boolean | undefined,
|
|
5881
|
+
): Snapshot {
|
|
5882
|
+
if (enabled !== true) return snapshot;
|
|
5883
|
+
return Object.freeze({
|
|
5884
|
+
...snapshot,
|
|
5885
|
+
diagnostic: this.persistedReceiptReadinessDiagnostic(peerHash),
|
|
5886
|
+
}) as Snapshot;
|
|
5887
|
+
}
|
|
5888
|
+
|
|
5889
|
+
private formatPersistedReceiptReadinessDiagnostic(peerHash: string): string {
|
|
5890
|
+
const diagnostic = this.persistedReceiptReadinessDiagnostic(peerHash);
|
|
5891
|
+
const { session, capability, sender, receiver } = diagnostic;
|
|
5892
|
+
const summary = [
|
|
5893
|
+
`session=${session.state}/${session.phase ?? "none"}/${
|
|
5894
|
+
session.established ? "established" : "suspended"
|
|
5895
|
+
}`,
|
|
5896
|
+
`capability=${capability.state}/persisted:${
|
|
5897
|
+
capability.persistedReceipts ? 1 : 0
|
|
5898
|
+
}/confirm:${capability.replicationConfirmation ? 1 : 0}`,
|
|
5899
|
+
`sender=${sender.state}/${sender.reason}/revision:${
|
|
5900
|
+
sender.currentRevision
|
|
5901
|
+
}/pending:${sender.pendingRevision ?? "none"}/inflight:${
|
|
5902
|
+
sender.inFlightSequence ?? "none"
|
|
5903
|
+
}@${sender.inFlightRevision ?? "none"}/applied:${
|
|
5904
|
+
sender.appliedRevision ?? "none"
|
|
5905
|
+
}/confirmation:${sender.confirmationSequence ?? "none"}@${
|
|
5906
|
+
sender.confirmationRevision ?? "none"
|
|
5907
|
+
}/waiters:${sender.confirmationWaiters}/oldest-ms:${
|
|
5908
|
+
sender.oldestConfirmationAgeMs ?? "none"
|
|
5909
|
+
}/retries:${sender.retryAttempts ?? 0}`,
|
|
5910
|
+
`receiver=${receiver.state}/${receiver.reason}/phase:${
|
|
5911
|
+
receiver.phase ?? "none"
|
|
5912
|
+
}/request:${receiver.requestState ?? "none"}/applied-sequence:${
|
|
5913
|
+
receiver.lastAppliedSequence ?? "none"
|
|
5914
|
+
}/retries:${receiver.requestAttempts ?? 0}/rearm:${
|
|
5915
|
+
receiver.remoteFullRearm
|
|
5916
|
+
}/rearm-attempts:${receiver.remoteFullRearmAttempts}/rearm-outstanding:${
|
|
5917
|
+
receiver.remoteFullRearmOutstanding
|
|
5918
|
+
}/rearm-global:${receiver.remoteFullRearmGlobalOutstanding}`,
|
|
5919
|
+
].join(" ");
|
|
5920
|
+
return summary.slice(
|
|
5921
|
+
0,
|
|
5922
|
+
MAX_PERSISTED_RECEIPT_READINESS_DIAGNOSTIC_SUMMARY_CHARS,
|
|
5923
|
+
);
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5768
5926
|
private dispatchPersistedReceiptReadinessChange(peerHash: string): void {
|
|
5769
5927
|
this.events.dispatchEvent(
|
|
5770
5928
|
new CustomEvent<PersistedReceiptPeerReadinessEvent>(
|
|
@@ -5856,9 +6014,7 @@ export class SharedLog<
|
|
|
5856
6014
|
};
|
|
5857
6015
|
}
|
|
5858
6016
|
|
|
5859
|
-
private persistedReceiptPeerSession(
|
|
5860
|
-
peerHash: string,
|
|
5861
|
-
):
|
|
6017
|
+
private persistedReceiptPeerSession(peerHash: string):
|
|
5862
6018
|
| {
|
|
5863
6019
|
capabilitySession: bigint;
|
|
5864
6020
|
peerSession: PeerSession;
|
|
@@ -24381,6 +24537,11 @@ export class SharedLog<
|
|
|
24381
24537
|
private nudgePersistedReceiptPeerReadiness(
|
|
24382
24538
|
publicKey: PublicSignKey,
|
|
24383
24539
|
signal: AbortSignal,
|
|
24540
|
+
rearmCurrentUnconfirmed?: {
|
|
24541
|
+
peerSession: PeerSession;
|
|
24542
|
+
receiveEpoch: object | null;
|
|
24543
|
+
capabilitySession: bigint;
|
|
24544
|
+
},
|
|
24384
24545
|
): void {
|
|
24385
24546
|
if (this.closed || signal.aborted) return;
|
|
24386
24547
|
const peerHash = publicKey.hashcode();
|
|
@@ -24388,8 +24549,7 @@ export class SharedLog<
|
|
|
24388
24549
|
if (
|
|
24389
24550
|
!peerSession ||
|
|
24390
24551
|
peerSession.phase === "departing" ||
|
|
24391
|
-
(peerSession.phase === "opening" &&
|
|
24392
|
-
!peerSession.openingBarrierActive)
|
|
24552
|
+
(peerSession.phase === "opening" && !peerSession.openingBarrierActive)
|
|
24393
24553
|
) {
|
|
24394
24554
|
// A barrier rejection deliberately leaves the current session in its
|
|
24395
24555
|
// fail-closed opening phase after the barrier window has settled. Ask the
|
|
@@ -24416,11 +24576,20 @@ export class SharedLog<
|
|
|
24416
24576
|
const receiptTarget = this.persistedReceiptPeerSession(peerHash);
|
|
24417
24577
|
if (
|
|
24418
24578
|
receiptTarget &&
|
|
24419
|
-
!this._v2Send.hasCurrentStateForPeer({
|
|
24579
|
+
(!this._v2Send.hasCurrentStateForPeer({
|
|
24420
24580
|
peerHash,
|
|
24421
24581
|
peerSession: receiptTarget.peerSession,
|
|
24422
24582
|
receiverTransportSession: receiptTarget.capabilitySession,
|
|
24423
|
-
})
|
|
24583
|
+
}) ||
|
|
24584
|
+
(rearmCurrentUnconfirmed?.peerSession === receiptTarget.peerSession &&
|
|
24585
|
+
rearmCurrentUnconfirmed.receiveEpoch === receiptTarget.receiveEpoch &&
|
|
24586
|
+
rearmCurrentUnconfirmed.capabilitySession ===
|
|
24587
|
+
receiptTarget.capabilitySession &&
|
|
24588
|
+
!this._v2Send.isLatestConfirmedForPeer({
|
|
24589
|
+
peerHash,
|
|
24590
|
+
peerSession: receiptTarget.peerSession,
|
|
24591
|
+
receiverTransportSession: receiptTarget.capabilitySession,
|
|
24592
|
+
})))
|
|
24424
24593
|
) {
|
|
24425
24594
|
this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
|
|
24426
24595
|
peerHash,
|
|
@@ -24446,7 +24615,18 @@ export class SharedLog<
|
|
|
24446
24615
|
key: PublicSignKey,
|
|
24447
24616
|
options: PersistedReceiptPeerReadinessOptions<T, R> = {},
|
|
24448
24617
|
): Promise<PersistedReceiptPeerReadiness> {
|
|
24449
|
-
|
|
24618
|
+
if (options.diagnostics !== true) {
|
|
24619
|
+
return this.inspectPersistedReceiptPeerReadiness(key, options);
|
|
24620
|
+
}
|
|
24621
|
+
const snapshot = await this.inspectPersistedReceiptPeerReadiness(
|
|
24622
|
+
key,
|
|
24623
|
+
options,
|
|
24624
|
+
);
|
|
24625
|
+
return this.attachPersistedReceiptReadinessDiagnostic(
|
|
24626
|
+
key.hashcode(),
|
|
24627
|
+
snapshot,
|
|
24628
|
+
options.diagnostics,
|
|
24629
|
+
);
|
|
24450
24630
|
}
|
|
24451
24631
|
|
|
24452
24632
|
private async inspectPersistedReceiptPeerReadiness(
|
|
@@ -24563,7 +24743,9 @@ export class SharedLog<
|
|
|
24563
24743
|
* Wait for a public key's current (or replacement) connection generation to
|
|
24564
24744
|
* become persisted-receipt ready. Transition listeners are installed before
|
|
24565
24745
|
* the first asynchronous inspection, and a bounded recovery tick repairs
|
|
24566
|
-
* missed subscriber/capability wakes without retaining stale PeerSessions.
|
|
24746
|
+
* missed subscriber/capability wakes without retaining stale PeerSessions. A
|
|
24747
|
+
* current sender gets one normal confirmation interval before an exact-
|
|
24748
|
+
* generation watchdog requests a bounded Full rearm.
|
|
24567
24749
|
* This waiter is advisory only; the following persisted delivery remains the
|
|
24568
24750
|
* operation that proves the requested remote durability quorum.
|
|
24569
24751
|
*/
|
|
@@ -24621,6 +24803,14 @@ export class SharedLog<
|
|
|
24621
24803
|
let rerun = false;
|
|
24622
24804
|
let recoveryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
24623
24805
|
let confirmationController: AbortController | undefined;
|
|
24806
|
+
let confirmationRecoveryTarget:
|
|
24807
|
+
| {
|
|
24808
|
+
peerSession: PeerSession;
|
|
24809
|
+
receiveEpoch: object | null;
|
|
24810
|
+
capabilitySession: bigint;
|
|
24811
|
+
notBefore: number;
|
|
24812
|
+
}
|
|
24813
|
+
| undefined;
|
|
24624
24814
|
let lastSnapshot: PersistedReceiptPeerReadiness | undefined;
|
|
24625
24815
|
const createTimeoutError = () => {
|
|
24626
24816
|
const suffix = lastSnapshot
|
|
@@ -24628,8 +24818,10 @@ export class SharedLog<
|
|
|
24628
24818
|
"reason" in lastSnapshot ? `/${lastSnapshot.reason}` : ""
|
|
24629
24819
|
})`
|
|
24630
24820
|
: "";
|
|
24821
|
+
const diagnostic =
|
|
24822
|
+
this.formatPersistedReceiptReadinessDiagnostic(peerHash);
|
|
24631
24823
|
return new TimeoutError(
|
|
24632
|
-
`Timeout waiting for persisted-receipt readiness from ${peerHash}${suffix}`,
|
|
24824
|
+
`Timeout waiting for persisted-receipt readiness from ${peerHash}${suffix}; diagnostic: ${diagnostic}`,
|
|
24633
24825
|
);
|
|
24634
24826
|
};
|
|
24635
24827
|
|
|
@@ -24651,6 +24843,7 @@ export class SharedLog<
|
|
|
24651
24843
|
new AbortError("Persisted-receipt readiness generation changed"),
|
|
24652
24844
|
);
|
|
24653
24845
|
confirmationController = undefined;
|
|
24846
|
+
confirmationRecoveryTarget = undefined;
|
|
24654
24847
|
operationController.abort(
|
|
24655
24848
|
new AbortError("Persisted-receipt readiness wait settled"),
|
|
24656
24849
|
);
|
|
@@ -24706,7 +24899,14 @@ export class SharedLog<
|
|
|
24706
24899
|
recoveryTimer = setTimeout(() => {
|
|
24707
24900
|
recoveryTimer = undefined;
|
|
24708
24901
|
if (!continueWait()) return;
|
|
24709
|
-
|
|
24902
|
+
const recoveryTarget = confirmationRecoveryTarget;
|
|
24903
|
+
this.nudgePersistedReceiptPeerReadiness(
|
|
24904
|
+
key,
|
|
24905
|
+
operationSignal,
|
|
24906
|
+
recoveryTarget && Date.now() >= recoveryTarget.notBefore
|
|
24907
|
+
? recoveryTarget
|
|
24908
|
+
: undefined,
|
|
24909
|
+
);
|
|
24710
24910
|
if (checkInFlight) {
|
|
24711
24911
|
// Confirmation can legitimately span several recovery intervals.
|
|
24712
24912
|
// Keep repairing the exact current generation without aborting its
|
|
@@ -24719,6 +24919,34 @@ export class SharedLog<
|
|
|
24719
24919
|
}, recoveryDelayMs);
|
|
24720
24920
|
recoveryTimer.unref?.();
|
|
24721
24921
|
};
|
|
24922
|
+
const prepareConfirmationRecovery = () => {
|
|
24923
|
+
const target = this.persistedReceiptPeerSession(peerHash);
|
|
24924
|
+
if (!target) {
|
|
24925
|
+
confirmationRecoveryTarget = undefined;
|
|
24926
|
+
return undefined;
|
|
24927
|
+
}
|
|
24928
|
+
const retained = confirmationRecoveryTarget;
|
|
24929
|
+
if (
|
|
24930
|
+
retained?.peerSession !== target.peerSession ||
|
|
24931
|
+
retained.receiveEpoch !== target.receiveEpoch ||
|
|
24932
|
+
retained.capabilitySession !== target.capabilitySession
|
|
24933
|
+
) {
|
|
24934
|
+
confirmationRecoveryTarget = {
|
|
24935
|
+
peerSession: target.peerSession,
|
|
24936
|
+
receiveEpoch: target.receiveEpoch,
|
|
24937
|
+
capabilitySession: target.capabilitySession,
|
|
24938
|
+
notBefore: Date.now() + recoveryDelayMs,
|
|
24939
|
+
};
|
|
24940
|
+
}
|
|
24941
|
+
const recoveryTarget = confirmationRecoveryTarget!;
|
|
24942
|
+
this.nudgePersistedReceiptPeerReadiness(
|
|
24943
|
+
key,
|
|
24944
|
+
operationSignal,
|
|
24945
|
+
Date.now() >= recoveryTarget.notBefore ? recoveryTarget : undefined,
|
|
24946
|
+
);
|
|
24947
|
+
armRecoveryTick();
|
|
24948
|
+
return target;
|
|
24949
|
+
};
|
|
24722
24950
|
const runCheck = async () => {
|
|
24723
24951
|
checkScheduled = false;
|
|
24724
24952
|
if (!continueWait()) return;
|
|
@@ -24735,27 +24963,36 @@ export class SharedLog<
|
|
|
24735
24963
|
);
|
|
24736
24964
|
lastSnapshot = snapshot;
|
|
24737
24965
|
if (!continueWait()) return;
|
|
24738
|
-
if (rerun) return;
|
|
24739
24966
|
if (snapshot.status === "ready") {
|
|
24967
|
+
confirmationRecoveryTarget = undefined;
|
|
24968
|
+
if (rerun) return;
|
|
24740
24969
|
// A wake observed while the asynchronous inspection was running may
|
|
24741
24970
|
// already have invalidated this snapshot. Drain that coalesced wake
|
|
24742
24971
|
// before publishing readiness.
|
|
24743
|
-
resolve(
|
|
24972
|
+
resolve(
|
|
24973
|
+
this.attachPersistedReceiptReadinessDiagnostic(
|
|
24974
|
+
peerHash,
|
|
24975
|
+
snapshot,
|
|
24976
|
+
options.diagnostics,
|
|
24977
|
+
),
|
|
24978
|
+
);
|
|
24744
24979
|
return;
|
|
24745
24980
|
}
|
|
24746
24981
|
if (
|
|
24747
24982
|
snapshot.status === "pending" &&
|
|
24748
24983
|
snapshot.reason === "replication-confirmation-pending"
|
|
24749
24984
|
) {
|
|
24750
|
-
|
|
24985
|
+
// Retain one watchdog deadline for the exact generation across
|
|
24986
|
+
// coalesced inspection wakes. A sub-interval event stream must drive,
|
|
24987
|
+
// rather than indefinitely postpone, the mature recovery nudge.
|
|
24988
|
+
const target = prepareConfirmationRecovery();
|
|
24989
|
+
if (rerun) return;
|
|
24751
24990
|
if (target) {
|
|
24752
|
-
// A confirmation waiter cannot create an outbound V2 stream when
|
|
24753
|
-
// that exact session state is missing. Nudge first, then keep the
|
|
24754
|
-
// recovery tick alive while the single confirmation wait remains.
|
|
24755
|
-
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24756
|
-
armRecoveryTick();
|
|
24757
24991
|
const currentConfirmationController = new AbortController();
|
|
24758
24992
|
confirmationController = currentConfirmationController;
|
|
24993
|
+
const currentConfirmationRecoveryTarget =
|
|
24994
|
+
confirmationRecoveryTarget;
|
|
24995
|
+
let confirmationSucceeded = false;
|
|
24759
24996
|
try {
|
|
24760
24997
|
await this._v2Send.confirmLatestForPeer(
|
|
24761
24998
|
{
|
|
@@ -24771,6 +25008,7 @@ export class SharedLog<
|
|
|
24771
25008
|
]),
|
|
24772
25009
|
},
|
|
24773
25010
|
);
|
|
25011
|
+
confirmationSucceeded = true;
|
|
24774
25012
|
} catch (error) {
|
|
24775
25013
|
if (!continueWait()) return;
|
|
24776
25014
|
if (!(error instanceof AbortError)) {
|
|
@@ -24781,6 +25019,12 @@ export class SharedLog<
|
|
|
24781
25019
|
if (confirmationController === currentConfirmationController) {
|
|
24782
25020
|
confirmationController = undefined;
|
|
24783
25021
|
}
|
|
25022
|
+
if (
|
|
25023
|
+
confirmationSucceeded &&
|
|
25024
|
+
confirmationRecoveryTarget === currentConfirmationRecoveryTarget
|
|
25025
|
+
) {
|
|
25026
|
+
confirmationRecoveryTarget = undefined;
|
|
25027
|
+
}
|
|
24784
25028
|
}
|
|
24785
25029
|
if (!continueWait()) return;
|
|
24786
25030
|
snapshot = await this.inspectPersistedReceiptPeerReadiness(
|
|
@@ -24790,13 +25034,32 @@ export class SharedLog<
|
|
|
24790
25034
|
);
|
|
24791
25035
|
lastSnapshot = snapshot;
|
|
24792
25036
|
if (!continueWait()) return;
|
|
24793
|
-
if (rerun) return;
|
|
24794
25037
|
if (snapshot.status === "ready") {
|
|
24795
|
-
|
|
25038
|
+
confirmationRecoveryTarget = undefined;
|
|
25039
|
+
if (rerun) return;
|
|
25040
|
+
resolve(
|
|
25041
|
+
this.attachPersistedReceiptReadinessDiagnostic(
|
|
25042
|
+
peerHash,
|
|
25043
|
+
snapshot,
|
|
25044
|
+
options.diagnostics,
|
|
25045
|
+
),
|
|
25046
|
+
);
|
|
24796
25047
|
return;
|
|
24797
25048
|
}
|
|
25049
|
+
if (
|
|
25050
|
+
snapshot.status === "pending" &&
|
|
25051
|
+
snapshot.reason === "replication-confirmation-pending"
|
|
25052
|
+
) {
|
|
25053
|
+
prepareConfirmationRecovery();
|
|
25054
|
+
} else {
|
|
25055
|
+
confirmationRecoveryTarget = undefined;
|
|
25056
|
+
}
|
|
25057
|
+
if (rerun) return;
|
|
24798
25058
|
}
|
|
25059
|
+
} else {
|
|
25060
|
+
confirmationRecoveryTarget = undefined;
|
|
24799
25061
|
}
|
|
25062
|
+
if (rerun) return;
|
|
24800
25063
|
if (!continueWait()) return;
|
|
24801
25064
|
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24802
25065
|
} catch (error) {
|