@peerbit/shared-log 16.0.26 → 16.0.28
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 +43 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +160 -31
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +27 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +135 -0
- 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 +15 -15
- package/src/index.ts +274 -43
- package/src/replication-info-v2-receive.ts +209 -0
- 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;
|
|
@@ -2680,6 +2723,10 @@ export class SharedLog<
|
|
|
2680
2723
|
// The epoch tokens are PeerSession objects (src/peer-session.ts); the map
|
|
2681
2724
|
// itself lives on the registry. Stage-2 of the fence refactor.
|
|
2682
2725
|
private _peerSessions!: PeerSessionRegistry;
|
|
2726
|
+
// An exact successor session gets at most one unchanged-Full repair. Weak
|
|
2727
|
+
// ownership keeps retired transport generations collectible without an
|
|
2728
|
+
// identity-keyed cleanup path.
|
|
2729
|
+
private _successorFullRepairSessions!: WeakSet<PeerSession>;
|
|
2683
2730
|
// A superseded removal may be the queue item that actually observed an active
|
|
2684
2731
|
// replicator. Carry that leave obligation to the transition that ultimately
|
|
2685
2732
|
// wins, while a winning reconnect clears it without emitting a stale leave.
|
|
@@ -4264,6 +4311,7 @@ export class SharedLog<
|
|
|
4264
4311
|
// replication-info blocked set (fence B5) alongside the session maps —
|
|
4265
4312
|
// the legacy inline `new Set()` that sat above moved there.
|
|
4266
4313
|
this._peerSessions = this.createPeerSessionRegistry();
|
|
4314
|
+
this._successorFullRepairSessions = new WeakSet();
|
|
4267
4315
|
this._pendingReplicatorLeaveByPeer = new Set();
|
|
4268
4316
|
this._activeReceiveHandlersByPeer = new Map();
|
|
4269
4317
|
this._receiveHandlerDrainByPeer = new Map();
|
|
@@ -4447,8 +4495,7 @@ export class SharedLog<
|
|
|
4447
4495
|
|
|
4448
4496
|
const profile = this._logProperties?.sync?.profile;
|
|
4449
4497
|
const startedAt = syncProfileStart(profile);
|
|
4450
|
-
const mode =
|
|
4451
|
-
resolvedRoot === fanoutService.publicKeyHash ? "root" : "node";
|
|
4498
|
+
const mode = resolvedRoot === fanoutService.publicKeyHash ? "root" : "node";
|
|
4452
4499
|
const before = profile
|
|
4453
4500
|
? snapshotFanoutOpenMetrics(fanoutService, this.topic, resolvedRoot)
|
|
4454
4501
|
: undefined;
|
|
@@ -5770,6 +5817,117 @@ export class SharedLog<
|
|
|
5770
5817
|
});
|
|
5771
5818
|
}
|
|
5772
5819
|
|
|
5820
|
+
private persistedReceiptReadinessDiagnostic(
|
|
5821
|
+
peerHash: string,
|
|
5822
|
+
): PersistedReceiptPeerReadinessDiagnostic {
|
|
5823
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
5824
|
+
const receiveEpoch = this._peerSessions.receiveEpoch(peerHash);
|
|
5825
|
+
const capabilitySession = this._peerSyncCapabilitySessions.get(peerHash);
|
|
5826
|
+
const capabilities = this._peerSyncCapabilities.get(peerHash) ?? 0;
|
|
5827
|
+
const replicationInfoBlocked =
|
|
5828
|
+
this._peerSessions.isReplicationInfoBlocked(peerHash);
|
|
5829
|
+
const receiveCleanupGateOpen =
|
|
5830
|
+
this._peerSessions.isReceiveCleanupGateOpen(peerHash);
|
|
5831
|
+
const active = peerSession?.isActive() ?? false;
|
|
5832
|
+
const established =
|
|
5833
|
+
!this.closed &&
|
|
5834
|
+
peerSession?.phase === "open" &&
|
|
5835
|
+
active &&
|
|
5836
|
+
!replicationInfoBlocked &&
|
|
5837
|
+
receiveCleanupGateOpen;
|
|
5838
|
+
const session = Object.freeze({
|
|
5839
|
+
state: peerSession ? ("current" as const) : ("absent" as const),
|
|
5840
|
+
...(peerSession ? { phase: peerSession.phase } : {}),
|
|
5841
|
+
established,
|
|
5842
|
+
suspended: !established,
|
|
5843
|
+
openingBarrierActive: peerSession?.openingBarrierActive ?? false,
|
|
5844
|
+
replicationInfoBlocked,
|
|
5845
|
+
receiveCleanupGateOpen,
|
|
5846
|
+
});
|
|
5847
|
+
const capabilityObserved =
|
|
5848
|
+
capabilitySession !== undefined &&
|
|
5849
|
+
this._peerSyncCapabilityTimestamps.has(peerHash);
|
|
5850
|
+
const capability = Object.freeze({
|
|
5851
|
+
state: capabilityObserved ? ("observed" as const) : ("absent" as const),
|
|
5852
|
+
persistedReceipts:
|
|
5853
|
+
(capabilities & SYNC_CAPABILITY_PERSISTED_ENTRY_RECEIPTS) !== 0,
|
|
5854
|
+
replicationConfirmation:
|
|
5855
|
+
(capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !== 0,
|
|
5856
|
+
});
|
|
5857
|
+
return Object.freeze({
|
|
5858
|
+
version: 1 as const,
|
|
5859
|
+
log: this.closed ? ("closed" as const) : ("open" as const),
|
|
5860
|
+
session,
|
|
5861
|
+
capability,
|
|
5862
|
+
sender: this._v2Send.diagnosePeer({
|
|
5863
|
+
peerHash,
|
|
5864
|
+
...(peerSession ? { peerSession } : {}),
|
|
5865
|
+
...(capabilitySession === undefined
|
|
5866
|
+
? {}
|
|
5867
|
+
: { receiverTransportSession: capabilitySession }),
|
|
5868
|
+
}),
|
|
5869
|
+
receiver: this._v2Receive.diagnosePeer({
|
|
5870
|
+
peerHash,
|
|
5871
|
+
...(peerSession ? { peerSession } : {}),
|
|
5872
|
+
receiveEpoch,
|
|
5873
|
+
...(capabilitySession === undefined
|
|
5874
|
+
? {}
|
|
5875
|
+
: { senderTransportSession: capabilitySession }),
|
|
5876
|
+
}),
|
|
5877
|
+
});
|
|
5878
|
+
}
|
|
5879
|
+
|
|
5880
|
+
private attachPersistedReceiptReadinessDiagnostic<
|
|
5881
|
+
Snapshot extends PersistedReceiptPeerReadiness,
|
|
5882
|
+
>(
|
|
5883
|
+
peerHash: string,
|
|
5884
|
+
snapshot: Snapshot,
|
|
5885
|
+
enabled: boolean | undefined,
|
|
5886
|
+
): Snapshot {
|
|
5887
|
+
if (enabled !== true) return snapshot;
|
|
5888
|
+
return Object.freeze({
|
|
5889
|
+
...snapshot,
|
|
5890
|
+
diagnostic: this.persistedReceiptReadinessDiagnostic(peerHash),
|
|
5891
|
+
}) as Snapshot;
|
|
5892
|
+
}
|
|
5893
|
+
|
|
5894
|
+
private formatPersistedReceiptReadinessDiagnostic(peerHash: string): string {
|
|
5895
|
+
const diagnostic = this.persistedReceiptReadinessDiagnostic(peerHash);
|
|
5896
|
+
const { session, capability, sender, receiver } = diagnostic;
|
|
5897
|
+
const summary = [
|
|
5898
|
+
`session=${session.state}/${session.phase ?? "none"}/${
|
|
5899
|
+
session.established ? "established" : "suspended"
|
|
5900
|
+
}`,
|
|
5901
|
+
`capability=${capability.state}/persisted:${
|
|
5902
|
+
capability.persistedReceipts ? 1 : 0
|
|
5903
|
+
}/confirm:${capability.replicationConfirmation ? 1 : 0}`,
|
|
5904
|
+
`sender=${sender.state}/${sender.reason}/revision:${
|
|
5905
|
+
sender.currentRevision
|
|
5906
|
+
}/pending:${sender.pendingRevision ?? "none"}/inflight:${
|
|
5907
|
+
sender.inFlightSequence ?? "none"
|
|
5908
|
+
}@${sender.inFlightRevision ?? "none"}/applied:${
|
|
5909
|
+
sender.appliedRevision ?? "none"
|
|
5910
|
+
}/confirmation:${sender.confirmationSequence ?? "none"}@${
|
|
5911
|
+
sender.confirmationRevision ?? "none"
|
|
5912
|
+
}/waiters:${sender.confirmationWaiters}/oldest-ms:${
|
|
5913
|
+
sender.oldestConfirmationAgeMs ?? "none"
|
|
5914
|
+
}/retries:${sender.retryAttempts ?? 0}`,
|
|
5915
|
+
`receiver=${receiver.state}/${receiver.reason}/phase:${
|
|
5916
|
+
receiver.phase ?? "none"
|
|
5917
|
+
}/request:${receiver.requestState ?? "none"}/applied-sequence:${
|
|
5918
|
+
receiver.lastAppliedSequence ?? "none"
|
|
5919
|
+
}/retries:${receiver.requestAttempts ?? 0}/rearm:${
|
|
5920
|
+
receiver.remoteFullRearm
|
|
5921
|
+
}/rearm-attempts:${receiver.remoteFullRearmAttempts}/rearm-outstanding:${
|
|
5922
|
+
receiver.remoteFullRearmOutstanding
|
|
5923
|
+
}/rearm-global:${receiver.remoteFullRearmGlobalOutstanding}`,
|
|
5924
|
+
].join(" ");
|
|
5925
|
+
return summary.slice(
|
|
5926
|
+
0,
|
|
5927
|
+
MAX_PERSISTED_RECEIPT_READINESS_DIAGNOSTIC_SUMMARY_CHARS,
|
|
5928
|
+
);
|
|
5929
|
+
}
|
|
5930
|
+
|
|
5773
5931
|
private dispatchPersistedReceiptReadinessChange(peerHash: string): void {
|
|
5774
5932
|
this.events.dispatchEvent(
|
|
5775
5933
|
new CustomEvent<PersistedReceiptPeerReadinessEvent>(
|
|
@@ -5861,9 +6019,7 @@ export class SharedLog<
|
|
|
5861
6019
|
};
|
|
5862
6020
|
}
|
|
5863
6021
|
|
|
5864
|
-
private persistedReceiptPeerSession(
|
|
5865
|
-
peerHash: string,
|
|
5866
|
-
):
|
|
6022
|
+
private persistedReceiptPeerSession(peerHash: string):
|
|
5867
6023
|
| {
|
|
5868
6024
|
capabilitySession: bigint;
|
|
5869
6025
|
peerSession: PeerSession;
|
|
@@ -17808,6 +17964,7 @@ export class SharedLog<
|
|
|
17808
17964
|
})) > 0;
|
|
17809
17965
|
|
|
17810
17966
|
this._gidPeersHistory = new Map();
|
|
17967
|
+
this._successorFullRepairSessions = new WeakSet();
|
|
17811
17968
|
this.resetGidPeerHistoryCleanupState();
|
|
17812
17969
|
const replicationChangeOwnershipLifecycleController =
|
|
17813
17970
|
this.captureReplicationOwnershipLifecycle();
|
|
@@ -20607,6 +20764,7 @@ export class SharedLog<
|
|
|
20607
20764
|
this._receiveHandlerDrainByPeer?.clear();
|
|
20608
20765
|
this._openingSyncCapabilitiesByPeer?.clear();
|
|
20609
20766
|
this._gidPeersHistory?.clear();
|
|
20767
|
+
this._successorFullRepairSessions = new WeakSet();
|
|
20610
20768
|
this._peerSyncCapabilities?.clear();
|
|
20611
20769
|
this._peerSyncCapabilitySessions?.clear();
|
|
20612
20770
|
this._peerSyncCapabilityTimestamps?.clear();
|
|
@@ -23875,6 +24033,10 @@ export class SharedLog<
|
|
|
23875
24033
|
) {
|
|
23876
24034
|
return;
|
|
23877
24035
|
}
|
|
24036
|
+
const isFirstSuccessorFull =
|
|
24037
|
+
msg instanceof FullReplicationInfoV2Message &&
|
|
24038
|
+
receiveSession.hasPredecessor &&
|
|
24039
|
+
!this._successorFullRepairSessions.has(receiveSession);
|
|
23878
24040
|
const admission = this._v2Receive.reserve(msg, {
|
|
23879
24041
|
from,
|
|
23880
24042
|
peerSession: receiveSession,
|
|
@@ -23908,6 +24070,8 @@ export class SharedLog<
|
|
|
23908
24070
|
const exactGate = () =>
|
|
23909
24071
|
hostGate() && this._v2Receive.isAdmissionCurrent(admission);
|
|
23910
24072
|
let durableCommitted = false;
|
|
24073
|
+
let committedSuccessorFull = false;
|
|
24074
|
+
let repairUnchangedSuccessorFull = false;
|
|
23911
24075
|
|
|
23912
24076
|
try {
|
|
23913
24077
|
if (
|
|
@@ -23951,6 +24115,12 @@ export class SharedLog<
|
|
|
23951
24115
|
) {
|
|
23952
24116
|
return;
|
|
23953
24117
|
}
|
|
24118
|
+
committedSuccessorFull =
|
|
24119
|
+
isFirstSuccessorFull &&
|
|
24120
|
+
msg instanceof FullReplicationInfoV2Message &&
|
|
24121
|
+
msg.segments.length > 0;
|
|
24122
|
+
repairUnchangedSuccessorFull =
|
|
24123
|
+
committedSuccessorFull && result.length === 0;
|
|
23954
24124
|
} else {
|
|
23955
24125
|
if (!exactGate()) {
|
|
23956
24126
|
return;
|
|
@@ -24024,6 +24194,30 @@ export class SharedLog<
|
|
|
24024
24194
|
if (!hostGate()) {
|
|
24025
24195
|
return;
|
|
24026
24196
|
}
|
|
24197
|
+
if (committedSuccessorFull) {
|
|
24198
|
+
if (!exactGate()) {
|
|
24199
|
+
return;
|
|
24200
|
+
}
|
|
24201
|
+
this._successorFullRepairSessions.add(receiveSession);
|
|
24202
|
+
if (repairUnchangedSuccessorFull) {
|
|
24203
|
+
// The unchanged range rows belong to the public-key principal, but
|
|
24204
|
+
// delivery evidence may belong to the superseded process. Reuse the
|
|
24205
|
+
// receipt-driven authoritative repair path and bypass only those stale
|
|
24206
|
+
// hints; do not synthesize public membership or range-change events.
|
|
24207
|
+
this._repairFrontierBypassKnownPeersByMode
|
|
24208
|
+
.get("join-authoritative")
|
|
24209
|
+
?.add(fromHash);
|
|
24210
|
+
const peers = new Set([fromHash]);
|
|
24211
|
+
this.scheduleRepairSweep(
|
|
24212
|
+
{ mode: "join-authoritative", peers },
|
|
24213
|
+
lane.ownershipLifecycleController,
|
|
24214
|
+
);
|
|
24215
|
+
this.scheduleJoinAuthoritativeRepair(
|
|
24216
|
+
peers,
|
|
24217
|
+
lane.ownershipLifecycleController,
|
|
24218
|
+
);
|
|
24219
|
+
}
|
|
24220
|
+
}
|
|
24027
24221
|
this._liveness.markReplicatorActivity(fromHash);
|
|
24028
24222
|
// A committed V2 announcement is applied progress: the peer answers,
|
|
24029
24223
|
// so recovery re-solicitation may restart from the base interval.
|
|
@@ -24398,8 +24592,7 @@ export class SharedLog<
|
|
|
24398
24592
|
if (
|
|
24399
24593
|
!peerSession ||
|
|
24400
24594
|
peerSession.phase === "departing" ||
|
|
24401
|
-
(peerSession.phase === "opening" &&
|
|
24402
|
-
!peerSession.openingBarrierActive)
|
|
24595
|
+
(peerSession.phase === "opening" && !peerSession.openingBarrierActive)
|
|
24403
24596
|
) {
|
|
24404
24597
|
// A barrier rejection deliberately leaves the current session in its
|
|
24405
24598
|
// fail-closed opening phase after the barrier window has settled. Ask the
|
|
@@ -24465,7 +24658,18 @@ export class SharedLog<
|
|
|
24465
24658
|
key: PublicSignKey,
|
|
24466
24659
|
options: PersistedReceiptPeerReadinessOptions<T, R> = {},
|
|
24467
24660
|
): Promise<PersistedReceiptPeerReadiness> {
|
|
24468
|
-
|
|
24661
|
+
if (options.diagnostics !== true) {
|
|
24662
|
+
return this.inspectPersistedReceiptPeerReadiness(key, options);
|
|
24663
|
+
}
|
|
24664
|
+
const snapshot = await this.inspectPersistedReceiptPeerReadiness(
|
|
24665
|
+
key,
|
|
24666
|
+
options,
|
|
24667
|
+
);
|
|
24668
|
+
return this.attachPersistedReceiptReadinessDiagnostic(
|
|
24669
|
+
key.hashcode(),
|
|
24670
|
+
snapshot,
|
|
24671
|
+
options.diagnostics,
|
|
24672
|
+
);
|
|
24469
24673
|
}
|
|
24470
24674
|
|
|
24471
24675
|
private async inspectPersistedReceiptPeerReadiness(
|
|
@@ -24657,8 +24861,10 @@ export class SharedLog<
|
|
|
24657
24861
|
"reason" in lastSnapshot ? `/${lastSnapshot.reason}` : ""
|
|
24658
24862
|
})`
|
|
24659
24863
|
: "";
|
|
24864
|
+
const diagnostic =
|
|
24865
|
+
this.formatPersistedReceiptReadinessDiagnostic(peerHash);
|
|
24660
24866
|
return new TimeoutError(
|
|
24661
|
-
`Timeout waiting for persisted-receipt readiness from ${peerHash}${suffix}`,
|
|
24867
|
+
`Timeout waiting for persisted-receipt readiness from ${peerHash}${suffix}; diagnostic: ${diagnostic}`,
|
|
24662
24868
|
);
|
|
24663
24869
|
};
|
|
24664
24870
|
|
|
@@ -24806,7 +25012,13 @@ export class SharedLog<
|
|
|
24806
25012
|
// A wake observed while the asynchronous inspection was running may
|
|
24807
25013
|
// already have invalidated this snapshot. Drain that coalesced wake
|
|
24808
25014
|
// before publishing readiness.
|
|
24809
|
-
resolve(
|
|
25015
|
+
resolve(
|
|
25016
|
+
this.attachPersistedReceiptReadinessDiagnostic(
|
|
25017
|
+
peerHash,
|
|
25018
|
+
snapshot,
|
|
25019
|
+
options.diagnostics,
|
|
25020
|
+
),
|
|
25021
|
+
);
|
|
24810
25022
|
return;
|
|
24811
25023
|
}
|
|
24812
25024
|
if (
|
|
@@ -24868,7 +25080,13 @@ export class SharedLog<
|
|
|
24868
25080
|
if (snapshot.status === "ready") {
|
|
24869
25081
|
confirmationRecoveryTarget = undefined;
|
|
24870
25082
|
if (rerun) return;
|
|
24871
|
-
resolve(
|
|
25083
|
+
resolve(
|
|
25084
|
+
this.attachPersistedReceiptReadinessDiagnostic(
|
|
25085
|
+
peerHash,
|
|
25086
|
+
snapshot,
|
|
25087
|
+
options.diagnostics,
|
|
25088
|
+
),
|
|
25089
|
+
);
|
|
24872
25090
|
return;
|
|
24873
25091
|
}
|
|
24874
25092
|
if (
|
|
@@ -24966,6 +25184,11 @@ export class SharedLog<
|
|
|
24966
25184
|
timeout?: number;
|
|
24967
25185
|
},
|
|
24968
25186
|
) {
|
|
25187
|
+
if (this.closed) {
|
|
25188
|
+
throw new ClosedError();
|
|
25189
|
+
}
|
|
25190
|
+
const closeSignal = this._closeController.signal;
|
|
25191
|
+
if (closeSignal.aborted) throw new ClosedError();
|
|
24969
25192
|
if (options?.signal?.aborted) {
|
|
24970
25193
|
throw new AbortError();
|
|
24971
25194
|
}
|
|
@@ -24974,6 +25197,9 @@ export class SharedLog<
|
|
|
24974
25197
|
const resolvedRoleAge = options?.eager
|
|
24975
25198
|
? undefined
|
|
24976
25199
|
: (options?.roleAge ?? (await this.getDefaultMinRoleAge()));
|
|
25200
|
+
if (this.closed || closeSignal.aborted) {
|
|
25201
|
+
throw new ClosedError();
|
|
25202
|
+
}
|
|
24977
25203
|
if (options?.signal?.aborted) {
|
|
24978
25204
|
throw new AbortError();
|
|
24979
25205
|
}
|
|
@@ -24989,6 +25215,7 @@ export class SharedLog<
|
|
|
24989
25215
|
this.events.removeEventListener("replicator:mature", runCheck);
|
|
24990
25216
|
this.events.removeEventListener("replication:change", runCheck);
|
|
24991
25217
|
options?.signal?.removeEventListener("abort", onAbort);
|
|
25218
|
+
closeSignal.removeEventListener("abort", onClose);
|
|
24992
25219
|
if (timer != null) {
|
|
24993
25220
|
clearTimeout(timer);
|
|
24994
25221
|
timer = undefined;
|
|
@@ -25028,9 +25255,11 @@ export class SharedLog<
|
|
|
25028
25255
|
};
|
|
25029
25256
|
|
|
25030
25257
|
const onAbort = () => reject(new AbortError());
|
|
25258
|
+
const onClose = () => reject(new ClosedError());
|
|
25031
25259
|
if (options?.signal) {
|
|
25032
|
-
options.signal.addEventListener("abort", onAbort);
|
|
25260
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
25033
25261
|
}
|
|
25262
|
+
closeSignal.addEventListener("abort", onClose, { once: true });
|
|
25034
25263
|
|
|
25035
25264
|
timer = setTimeout(() => {
|
|
25036
25265
|
reject(
|
|
@@ -25073,31 +25302,32 @@ export class SharedLog<
|
|
|
25073
25302
|
return;
|
|
25074
25303
|
}
|
|
25075
25304
|
|
|
25076
|
-
if (requestAttempts
|
|
25077
|
-
|
|
25078
|
-
}
|
|
25079
|
-
|
|
25080
|
-
requestAttempts++;
|
|
25305
|
+
if (requestAttempts < maxRequestAttempts) {
|
|
25306
|
+
requestAttempts++;
|
|
25081
25307
|
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25308
|
+
const peerHash = key.hashcode();
|
|
25309
|
+
const peerSession = this._peerSessions.current(peerHash);
|
|
25310
|
+
if (peerSession?.phase === "open") {
|
|
25311
|
+
this._v2Receive.ensureRequestProgress({
|
|
25312
|
+
peerHash,
|
|
25313
|
+
peerSession,
|
|
25314
|
+
receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
|
|
25315
|
+
});
|
|
25316
|
+
} else if (peerSession === null || peerSession.phase === "departing") {
|
|
25317
|
+
// A peer can be known to routing before its SharedLog topic
|
|
25318
|
+
// subscription has been observed. Legacy requests used to bootstrap
|
|
25319
|
+
// that case directly; V2 needs an authoritative Subscribe snapshot
|
|
25320
|
+
// before it can create a fenced PeerSession and request a Full.
|
|
25321
|
+
requestSubscriberSnapshot();
|
|
25322
|
+
}
|
|
25096
25323
|
}
|
|
25097
25324
|
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25325
|
+
// Replication change events are wake hints, not replayable state. Keep
|
|
25326
|
+
// checking the authoritative index after the bounded network recovery
|
|
25327
|
+
// attempts are exhausted so a transiently empty transition read cannot
|
|
25328
|
+
// strand an otherwise-ready waiter until its outer timeout.
|
|
25329
|
+
runCheck();
|
|
25330
|
+
requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
|
|
25101
25331
|
};
|
|
25102
25332
|
|
|
25103
25333
|
const check = async () => {
|
|
@@ -25149,8 +25379,9 @@ export class SharedLog<
|
|
|
25149
25379
|
// replay a maturity/change event that fires while that read is in flight.
|
|
25150
25380
|
this.events.addEventListener("replicator:mature", runCheck);
|
|
25151
25381
|
this.events.addEventListener("replication:change", runCheck);
|
|
25152
|
-
|
|
25153
|
-
|
|
25382
|
+
if (closeSignal.aborted) onClose();
|
|
25383
|
+
else if (options?.signal?.aborted) onAbort();
|
|
25384
|
+
else requestReplicationInfo();
|
|
25154
25385
|
|
|
25155
25386
|
return deferred.promise.finally(clear);
|
|
25156
25387
|
}
|