@peerbit/pubsub 5.4.3 → 5.4.5
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/fanout-tree.d.ts +7 -2
- package/dist/src/fanout-tree.d.ts.map +1 -1
- package/dist/src/fanout-tree.js +339 -81
- package/dist/src/fanout-tree.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +299 -77
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -4
- package/src/fanout-tree.ts +462 -111
- package/src/index.ts +375 -96
package/dist/src/index.d.ts
CHANGED
|
@@ -166,6 +166,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
166
166
|
private hostOwnedShardRootsInFlight?;
|
|
167
167
|
private hostOwnedShardRootsDirty;
|
|
168
168
|
private readonly signedTopicRootCandidateClaims;
|
|
169
|
+
private readonly suppressedDepartedTopicRootCandidateClaims;
|
|
169
170
|
private readonly topicRootCandidateClaimReplayFloors;
|
|
170
171
|
private localSignedTopicRootCandidateClaim?;
|
|
171
172
|
private topicRootCandidateClaimMaintenanceTimer?;
|
|
@@ -211,6 +212,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
211
212
|
private pruneExpiredTopicRootCandidateClaims;
|
|
212
213
|
private rebuildAutoTopicRootCandidatesFromClaims;
|
|
213
214
|
private retainSignedTopicRootCandidateClaim;
|
|
215
|
+
private isDepartedTopicRootCandidateClaimSuppressed;
|
|
214
216
|
private canAdvanceTopicRootCandidateClaimReplayFloor;
|
|
215
217
|
private canRetainTopicRootCandidateClaimOrigin;
|
|
216
218
|
private consumeTopicRootCandidateClaimVerifyBudget;
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACN,aAAa,EAGb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAIN,KAAK,MAAM,EAEX,KAAK,YAAY,EAIjB,gBAAgB,EAehB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,YAAY,EACZ,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAEhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEN,mBAAmB,EAEnB,WAAW,EAEX,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAGd,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,SAAS,EACd,cAAc,EACd,KAAK,gBAAgB,EAOrB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,KAAK,EACX,UAAU,EACV,wBAAwB,EAExB,qBAAqB,EACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,MAAM,+BAA+B,CAAC;AAEvC,cAAc,kBAAkB,CAAC;AAIjC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,mBAAmB,MAAM,iCAAiC,CAAC;AACvE,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,MAAM,oCAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -134,9 +134,15 @@ const TOPIC_ROOT_CANDIDATE_CLAIM_VERIFY_REFILL_MS = 60_000;
|
|
|
134
134
|
const TOPIC_ROOT_CANDIDATE_CLAIM_VERIFY_PEERS_MAX = 4_096;
|
|
135
135
|
const sameCandidates = (left, right) => left.length === right.length &&
|
|
136
136
|
left.every((candidate, index) => candidate === right[index]);
|
|
137
|
-
//
|
|
138
|
-
//
|
|
137
|
+
// Retain the previous 12 second allowance as one deadline for the complete
|
|
138
|
+
// peer-query phase. It must not multiply by peer count or retry count.
|
|
139
139
|
const DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS = 12_000;
|
|
140
|
+
// A responder may need to open an outbound stream after an inbound-only dial.
|
|
141
|
+
// Bounded rounds leave enough time for that setup while still allowing a lost
|
|
142
|
+
// request or a newly-ready stream to be retried within the phase deadline.
|
|
143
|
+
const TOPIC_ROOT_QUERY_ROUND_TIMEOUT_MS = 3_000;
|
|
144
|
+
const TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS = DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS;
|
|
145
|
+
const TOPIC_ROOT_HOST_SCAN_CONCURRENCY = 8;
|
|
140
146
|
const DIRECT_SHARD_ROOT_CONFIRM_TIMEOUT_MS = 2_000;
|
|
141
147
|
const DEFAULT_PUBSUB_FANOUT_CHANNEL_OPTIONS = {
|
|
142
148
|
msgRate: 30,
|
|
@@ -218,6 +224,12 @@ export class TopicControlPlane extends DirectStream {
|
|
|
218
224
|
hostOwnedShardRootsInFlight;
|
|
219
225
|
hostOwnedShardRootsDirty = false;
|
|
220
226
|
signedTopicRootCandidateClaims = new Map();
|
|
227
|
+
// A directly observed departure makes only that origin's current lease
|
|
228
|
+
// ineffective locally. This is not a component-wide withdrawal: claims learned
|
|
229
|
+
// only through relays keep their existing lease semantics. Keeping the verified
|
|
230
|
+
// bytes + replay floor prevents a relay from resurrecting the departed lease;
|
|
231
|
+
// the map is bounded by the same retained-origin cap as the claim map.
|
|
232
|
+
suppressedDepartedTopicRootCandidateClaims = new Map();
|
|
221
233
|
// Active claims expire, but their per-origin timestamp floors survive for the
|
|
222
234
|
// auto-mode lifecycle so a frozen wall clock cannot renew a replayed lease.
|
|
223
235
|
// Retaining only the deterministic lowest origins keeps this state hard-bounded.
|
|
@@ -494,12 +506,28 @@ export class TopicControlPlane extends DirectStream {
|
|
|
494
506
|
const protocol = this.peers.get(hash)?.protocol;
|
|
495
507
|
const hadSignedPeer = [...this.peers.values()].some((peer) => peer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1);
|
|
496
508
|
const removed = await super._removePeer(publicKey);
|
|
509
|
+
let suppressedDepartedOrigin = false;
|
|
510
|
+
if (removed &&
|
|
511
|
+
this.autoTopicRootCandidates &&
|
|
512
|
+
protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1 &&
|
|
513
|
+
this.signedTopicRootCandidateClaims.has(hash)) {
|
|
514
|
+
const claim = this.signedTopicRootCandidateClaims.get(hash);
|
|
515
|
+
const suppressed = this.suppressedDepartedTopicRootCandidateClaims.get(hash);
|
|
516
|
+
suppressedDepartedOrigin =
|
|
517
|
+
!suppressed ||
|
|
518
|
+
suppressed.timestamp !== claim.timestamp ||
|
|
519
|
+
!bytesEqual(suppressed.bytes, claim.bytes);
|
|
520
|
+
this.suppressedDepartedTopicRootCandidateClaims.set(hash, {
|
|
521
|
+
bytes: claim.bytes,
|
|
522
|
+
timestamp: claim.timestamp,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
497
525
|
if (this.autoTopicRootCandidates &&
|
|
498
526
|
(protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_0 ||
|
|
499
527
|
protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1)) {
|
|
500
528
|
const hasSignedPeer = [...this.peers.values()].some((peer) => peer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1);
|
|
501
529
|
this.rebuildAutoTopicRootCandidatesFromClaims(BigInt(Date.now()), {
|
|
502
|
-
immediate: hadSignedPeer !== hasSignedPeer,
|
|
530
|
+
immediate: suppressedDepartedOrigin || hadSignedPeer !== hasSignedPeer,
|
|
503
531
|
});
|
|
504
532
|
}
|
|
505
533
|
return removed;
|
|
@@ -538,6 +566,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
538
566
|
clearSignedTopicRootCandidateState() {
|
|
539
567
|
this.clearTopicRootCandidateClaimTimer();
|
|
540
568
|
this.signedTopicRootCandidateClaims.clear();
|
|
569
|
+
this.suppressedDepartedTopicRootCandidateClaims.clear();
|
|
541
570
|
this.topicRootCandidateClaimReplayFloors.clear();
|
|
542
571
|
this.localSignedTopicRootCandidateClaim = undefined;
|
|
543
572
|
this.topicRootCandidateClaimRefreshNotBefore = undefined;
|
|
@@ -749,6 +778,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
749
778
|
for (const [origin, claim] of this.signedTopicRootCandidateClaims) {
|
|
750
779
|
if (claim.expires <= now || claim.acceptUntil <= monotonicNow) {
|
|
751
780
|
this.signedTopicRootCandidateClaims.delete(origin);
|
|
781
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
752
782
|
changed = true;
|
|
753
783
|
}
|
|
754
784
|
}
|
|
@@ -768,7 +798,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
768
798
|
? []
|
|
769
799
|
: [this.publicKeyHash]),
|
|
770
800
|
...legacyDirectCandidates,
|
|
771
|
-
...this.signedTopicRootCandidateClaims.
|
|
801
|
+
...[...this.signedTopicRootCandidateClaims.entries()]
|
|
802
|
+
.filter(([origin, claim]) => !this.isDepartedTopicRootCandidateClaimSuppressed(origin, claim))
|
|
803
|
+
.map(([origin]) => origin),
|
|
772
804
|
]),
|
|
773
805
|
]
|
|
774
806
|
.filter(isCanonicalTopicRootCandidate)
|
|
@@ -802,6 +834,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
802
834
|
// because the retained maximum only decreases, it cannot re-enter.
|
|
803
835
|
this.topicRootCandidateClaimReplayFloors.delete(highest);
|
|
804
836
|
this.signedTopicRootCandidateClaims.delete(highest);
|
|
837
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(highest);
|
|
805
838
|
if (highest === this.publicKeyHash) {
|
|
806
839
|
this.localSignedTopicRootCandidateClaim = undefined;
|
|
807
840
|
}
|
|
@@ -809,8 +842,14 @@ export class TopicControlPlane extends DirectStream {
|
|
|
809
842
|
}
|
|
810
843
|
this.topicRootCandidateClaimReplayFloors.set(origin, claim.timestamp);
|
|
811
844
|
this.signedTopicRootCandidateClaims.set(origin, claim);
|
|
845
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
812
846
|
return true;
|
|
813
847
|
}
|
|
848
|
+
isDepartedTopicRootCandidateClaimSuppressed(origin, claim) {
|
|
849
|
+
const suppressed = this.suppressedDepartedTopicRootCandidateClaims.get(origin);
|
|
850
|
+
return (suppressed?.timestamp === claim.timestamp &&
|
|
851
|
+
bytesEqual(suppressed.bytes, claim.bytes));
|
|
852
|
+
}
|
|
814
853
|
canAdvanceTopicRootCandidateClaimReplayFloor(origin, timestamp) {
|
|
815
854
|
const floor = this.topicRootCandidateClaimReplayFloors.get(origin);
|
|
816
855
|
if (floor !== undefined) {
|
|
@@ -871,7 +910,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
871
910
|
this.topicRootCandidateClaimGlobalVerifyBudget.remaining -= 1;
|
|
872
911
|
return true;
|
|
873
912
|
}
|
|
874
|
-
async importSignedTopicRootCandidateClaim(rawClaim, outerPeerHash) {
|
|
913
|
+
async importSignedTopicRootCandidateClaim(rawClaim, outerPeerHash, expectedPeer) {
|
|
875
914
|
const lifecycleRevision = this.topicControlPlaneLifecycleRevision;
|
|
876
915
|
if (rawClaim.byteLength === 0 ||
|
|
877
916
|
rawClaim.byteLength > TOPIC_ROOT_CANDIDATE_CLAIM_MAX_BYTES) {
|
|
@@ -910,10 +949,30 @@ export class TopicControlPlane extends DirectStream {
|
|
|
910
949
|
if (acceptanceDeadline === undefined) {
|
|
911
950
|
return false;
|
|
912
951
|
}
|
|
952
|
+
this.rebuildAutoTopicRootCandidatesFromClaims(now);
|
|
953
|
+
const retained = this.signedTopicRootCandidateClaims.get(origin);
|
|
954
|
+
if (outerPeerHash === origin &&
|
|
955
|
+
expectedPeer !== undefined &&
|
|
956
|
+
expectedPeer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1 &&
|
|
957
|
+
this.peers.get(outerPeerHash) === expectedPeer &&
|
|
958
|
+
lifecycleRevision === this.topicControlPlaneLifecycleRevision &&
|
|
959
|
+
this.autoTopicRootCandidates &&
|
|
960
|
+
this.started &&
|
|
961
|
+
!this.stopping &&
|
|
962
|
+
!this.topicControlPlaneStopping &&
|
|
963
|
+
retained?.timestamp === timestamp &&
|
|
964
|
+
bytesEqual(retained.bytes, rawClaim) &&
|
|
965
|
+
this.isDepartedTopicRootCandidateClaimSuppressed(origin, retained)) {
|
|
966
|
+
// These exact bytes were already verified and remain under the original
|
|
967
|
+
// monotonic acceptance deadline. A current authenticated origin stream is
|
|
968
|
+
// sufficient liveness evidence; do not advance the floor or lease.
|
|
969
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
970
|
+
this.rebuildAutoTopicRootCandidatesFromClaims(now, { immediate: true });
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
913
973
|
if (!this.canAdvanceTopicRootCandidateClaimReplayFloor(origin, timestamp)) {
|
|
914
974
|
return false;
|
|
915
975
|
}
|
|
916
|
-
this.rebuildAutoTopicRootCandidatesFromClaims(now);
|
|
917
976
|
if (!this.consumeTopicRootCandidateClaimVerifyBudget(outerPeerHash)) {
|
|
918
977
|
return false;
|
|
919
978
|
}
|
|
@@ -933,7 +992,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
933
992
|
!this.autoTopicRootCandidates ||
|
|
934
993
|
!this.started ||
|
|
935
994
|
this.stopping ||
|
|
936
|
-
this.topicControlPlaneStopping
|
|
995
|
+
this.topicControlPlaneStopping ||
|
|
996
|
+
(expectedPeer !== undefined &&
|
|
997
|
+
this.peers.get(outerPeerHash) !== expectedPeer)) {
|
|
937
998
|
return false;
|
|
938
999
|
}
|
|
939
1000
|
if (!this.retainSignedTopicRootCandidateClaim(origin, {
|
|
@@ -985,6 +1046,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
985
1046
|
}
|
|
986
1047
|
}
|
|
987
1048
|
const claims = [...this.signedTopicRootCandidateClaims.entries()]
|
|
1049
|
+
.filter(([origin, claim]) => !this.isDepartedTopicRootCandidateClaimSuppressed(origin, claim))
|
|
988
1050
|
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
989
1051
|
.map(([, claim]) => claim.bytes);
|
|
990
1052
|
await this.sendSignedTopicRootCandidateClaims(claims, signedStreams);
|
|
@@ -1483,27 +1545,68 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1483
1545
|
if (tracked) {
|
|
1484
1546
|
return { root: tracked, authoritative: true };
|
|
1485
1547
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
// above and remain authoritative. A leaf relying on a queried gateway for
|
|
1492
|
-
// shard roots must first disable auto mode with setTopicRootCandidates([]).
|
|
1493
|
-
return this.normalizePeerTopicRootState(topic, resolvedThroughPeers);
|
|
1548
|
+
if (options?.queryConnectedPeers === false) {
|
|
1549
|
+
return {
|
|
1550
|
+
root: this.topicRootControlPlane.resolveDeterministicTopicRoot(topic),
|
|
1551
|
+
authoritative: false,
|
|
1552
|
+
};
|
|
1494
1553
|
}
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1554
|
+
const deadlineController = new AbortController();
|
|
1555
|
+
const deadlineTimer = setTimeout(() => deadlineController.abort(new AbortError("topic root peer-query deadline reached")), DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS);
|
|
1556
|
+
deadlineTimer.unref?.();
|
|
1557
|
+
const querySignals = linkAbortSignals([
|
|
1558
|
+
options?.signal,
|
|
1559
|
+
deadlineController.signal,
|
|
1560
|
+
]);
|
|
1561
|
+
try {
|
|
1562
|
+
let attempt = 0;
|
|
1563
|
+
for (;;) {
|
|
1564
|
+
throwIfAborted(querySignals.signal);
|
|
1565
|
+
// Preserve the immediate deterministic fallback when no queryable stream
|
|
1566
|
+
// exists at entry. Once a peer was queryable, keep the bounded readiness
|
|
1567
|
+
// window alive: later rounds re-snapshot streams that can become writable
|
|
1568
|
+
// after an inbound-only dial.
|
|
1569
|
+
if (attempt === 0 &&
|
|
1570
|
+
this.getConnectedTopicRootTrackers().length === 0) {
|
|
1571
|
+
break;
|
|
1572
|
+
}
|
|
1573
|
+
if (attempt > 0) {
|
|
1574
|
+
await withAbort(delay(150 * Math.min(attempt, 2), {
|
|
1575
|
+
signal: querySignals.signal,
|
|
1576
|
+
}), querySignals.signal);
|
|
1577
|
+
}
|
|
1578
|
+
attempt += 1;
|
|
1579
|
+
const resolvedThroughPeers = await this.resolveTopicRootThroughPeers(topic, {
|
|
1580
|
+
signal: querySignals.signal,
|
|
1581
|
+
timeoutMs: TOPIC_ROOT_QUERY_ROUND_TIMEOUT_MS,
|
|
1582
|
+
});
|
|
1583
|
+
if (resolvedThroughPeers !== undefined) {
|
|
1584
|
+
// Unconfigured peer-query replies cannot override the locally
|
|
1585
|
+
// deterministic root for internal shards in auto mode. Roots,
|
|
1586
|
+
// resolvers, and trackers explicitly configured on this control
|
|
1587
|
+
// plane are resolved above and remain authoritative. A leaf relying
|
|
1588
|
+
// on a queried gateway for shard roots must first disable auto mode
|
|
1589
|
+
// with setTopicRootCandidates([]).
|
|
1590
|
+
return this.normalizePeerTopicRootState(topic, resolvedThroughPeers);
|
|
1504
1591
|
}
|
|
1505
1592
|
}
|
|
1506
1593
|
}
|
|
1594
|
+
catch (error) {
|
|
1595
|
+
// Candidate, lifecycle, and caller cancellation are terminal to this
|
|
1596
|
+
// invocation and must retain their exact reason. Only the private phase
|
|
1597
|
+
// deadline falls through to the same deterministic result as exhausted
|
|
1598
|
+
// peer queries.
|
|
1599
|
+
if (options?.signal?.aborted) {
|
|
1600
|
+
throw options.signal.reason ?? error;
|
|
1601
|
+
}
|
|
1602
|
+
if (!deadlineController.signal.aborted) {
|
|
1603
|
+
throw error;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
finally {
|
|
1607
|
+
clearTimeout(deadlineTimer);
|
|
1608
|
+
querySignals.clear();
|
|
1609
|
+
}
|
|
1507
1610
|
return {
|
|
1508
1611
|
root: this.topicRootControlPlane.resolveDeterministicTopicRoot(topic),
|
|
1509
1612
|
authoritative: false,
|
|
@@ -1527,8 +1630,13 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1527
1630
|
this.maybeDisableAutoTopicRootCandidatesIfExternallyConfigured();
|
|
1528
1631
|
}
|
|
1529
1632
|
const candidateGeneration = this.getTopicRootCandidateGeneration();
|
|
1530
|
-
const hasConnectedTrackers =
|
|
1531
|
-
|
|
1633
|
+
const hasConnectedTrackers = options?.queryConnectedPeers !== false &&
|
|
1634
|
+
this.getConnectedTopicRootTrackers().length > 0;
|
|
1635
|
+
// A proactive local scan must not inherit an authoritative cache entry that
|
|
1636
|
+
// originated from a connected-peer reply under the same candidate generation.
|
|
1637
|
+
const cached = options?.queryConnectedPeers === false
|
|
1638
|
+
? undefined
|
|
1639
|
+
: this.shardRootCache.get(shardTopic);
|
|
1532
1640
|
if (cached && (cached.authoritative || !hasConnectedTrackers)) {
|
|
1533
1641
|
return { root: cached.root, candidateGeneration };
|
|
1534
1642
|
}
|
|
@@ -1569,7 +1677,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1569
1677
|
requestId = this.nextTopicRootQueryId >>> 0;
|
|
1570
1678
|
} while (true);
|
|
1571
1679
|
}
|
|
1572
|
-
async sendDirectControlMessage(peer, pubsubMessage) {
|
|
1680
|
+
async sendDirectControlMessage(peer, pubsubMessage, signal) {
|
|
1573
1681
|
const embedded = await this.createMessage(this.encodePubSubMessage(pubsubMessage), {
|
|
1574
1682
|
mode: new SilentDelivery({
|
|
1575
1683
|
to: [peer.publicKey.hashcode()],
|
|
@@ -1578,7 +1686,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1578
1686
|
priority: 1,
|
|
1579
1687
|
skipRecipientValidation: true,
|
|
1580
1688
|
});
|
|
1581
|
-
await this.publishMessage(this.publicKey, embedded, [peer]);
|
|
1689
|
+
await this.publishMessage(this.publicKey, embedded, [peer], undefined, signal);
|
|
1582
1690
|
}
|
|
1583
1691
|
resolvePendingTopicRootQuery(message, responderPeerHash) {
|
|
1584
1692
|
const pending = this.pendingTopicRootQueries.get(message.requestId);
|
|
@@ -1598,6 +1706,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1598
1706
|
return undefined;
|
|
1599
1707
|
const expectedPeerHash = peer.publicKey.hashcode();
|
|
1600
1708
|
const requestId = this.nextTopicRootRequestIdValue();
|
|
1709
|
+
const queryController = new AbortController();
|
|
1710
|
+
const querySignals = linkAbortSignals([signal, queryController.signal]);
|
|
1711
|
+
let timedOut = false;
|
|
1601
1712
|
const responsePromise = new Promise((resolve) => {
|
|
1602
1713
|
let settled = false;
|
|
1603
1714
|
const settle = (root) => {
|
|
@@ -1609,13 +1720,15 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1609
1720
|
this.pendingTopicRootQueries.delete(requestId);
|
|
1610
1721
|
}
|
|
1611
1722
|
clearTimeout(timer);
|
|
1612
|
-
|
|
1613
|
-
signal.removeEventListener("abort", onAbort);
|
|
1614
|
-
}
|
|
1723
|
+
querySignals.signal.removeEventListener("abort", onAbort);
|
|
1615
1724
|
resolve(root);
|
|
1616
1725
|
};
|
|
1617
|
-
const onAbort =
|
|
1618
|
-
const timer = setTimeout(() =>
|
|
1726
|
+
const onAbort = () => settle(undefined);
|
|
1727
|
+
const timer = setTimeout(() => {
|
|
1728
|
+
timedOut = true;
|
|
1729
|
+
settle(undefined);
|
|
1730
|
+
queryController.abort(new AbortError("topic root query timed out"));
|
|
1731
|
+
}, Math.max(1, Math.floor(timeoutMs)));
|
|
1619
1732
|
timer.unref?.();
|
|
1620
1733
|
this.pendingTopicRootQueries.set(requestId, {
|
|
1621
1734
|
expectedPeerHash,
|
|
@@ -1623,26 +1736,39 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1623
1736
|
resolve: settle,
|
|
1624
1737
|
timer,
|
|
1625
1738
|
});
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
onAbort();
|
|
1630
|
-
}
|
|
1739
|
+
querySignals.signal.addEventListener("abort", onAbort, { once: true });
|
|
1740
|
+
if (querySignals.signal.aborted)
|
|
1741
|
+
onAbort();
|
|
1631
1742
|
});
|
|
1632
1743
|
try {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1744
|
+
try {
|
|
1745
|
+
const sending = withAbort(this.sendDirectControlMessage(peer, new TopicRootQuery({ requestId, topic }), querySignals.signal), querySignals.signal);
|
|
1746
|
+
// A write can finish after the request is already on the wire (or stall
|
|
1747
|
+
// while opening the reverse stream). Let either a response or the same
|
|
1748
|
+
// query timeout release this await instead of extending the timeout around
|
|
1749
|
+
// the outbound send.
|
|
1750
|
+
await Promise.race([
|
|
1751
|
+
sending,
|
|
1752
|
+
responsePromise.then(() => undefined),
|
|
1753
|
+
]);
|
|
1641
1754
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1755
|
+
catch (error) {
|
|
1756
|
+
const pending = this.pendingTopicRootQueries.get(requestId);
|
|
1757
|
+
if (pending && !timedOut) {
|
|
1758
|
+
this.pendingTopicRootQueries.delete(requestId);
|
|
1759
|
+
clearTimeout(pending.timer);
|
|
1760
|
+
pending.resolve(undefined);
|
|
1761
|
+
}
|
|
1762
|
+
if (signal?.aborted) {
|
|
1763
|
+
throw signal.reason ?? error;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
return await withAbort(responsePromise, signal);
|
|
1767
|
+
}
|
|
1768
|
+
finally {
|
|
1769
|
+
queryController.abort(new AbortError("topic root query settled"));
|
|
1770
|
+
querySignals.clear();
|
|
1644
1771
|
}
|
|
1645
|
-
return withAbort(responsePromise, signal);
|
|
1646
1772
|
}
|
|
1647
1773
|
async confirmDirectShardRoot(shardTopic, root, candidateGeneration, lifecycleRevision, signal) {
|
|
1648
1774
|
this.assertTopicControlPlaneActive(lifecycleRevision);
|
|
@@ -1712,18 +1838,69 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1712
1838
|
if (peers.length === 0) {
|
|
1713
1839
|
return undefined;
|
|
1714
1840
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1841
|
+
const roundController = new AbortController();
|
|
1842
|
+
const roundTimeoutMs = Math.max(1, Math.floor(options?.timeoutMs ?? DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS));
|
|
1843
|
+
let roundTimedOut = false;
|
|
1844
|
+
const roundTimer = setTimeout(() => {
|
|
1845
|
+
roundTimedOut = true;
|
|
1846
|
+
roundController.abort(new AbortError("topic root query round timed out"));
|
|
1847
|
+
}, roundTimeoutMs);
|
|
1848
|
+
roundTimer.unref?.();
|
|
1849
|
+
const roundSignals = linkAbortSignals([
|
|
1850
|
+
options?.signal,
|
|
1851
|
+
roundController.signal,
|
|
1852
|
+
]);
|
|
1853
|
+
const queries = peers.map((peer) => this.queryTopicRootFromPeer(peer, topic, roundTimeoutMs, roundSignals.signal));
|
|
1854
|
+
const unresolved = Symbol("unresolved topic root query");
|
|
1855
|
+
const results = peers.map(() => unresolved);
|
|
1856
|
+
// All requests start together, but the stable peer ordering remains the
|
|
1857
|
+
// conflict-resolution policy: a later answer is usable only after every
|
|
1858
|
+
// earlier peer has answered undefined or reached its round timeout.
|
|
1859
|
+
const prioritizedResult = new Promise((resolve, reject) => {
|
|
1860
|
+
let settled = false;
|
|
1861
|
+
const resolveInPeerOrder = () => {
|
|
1862
|
+
for (const result of results) {
|
|
1863
|
+
if (result === unresolved)
|
|
1864
|
+
return;
|
|
1865
|
+
if (result !== undefined) {
|
|
1866
|
+
settled = true;
|
|
1867
|
+
resolve(result);
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1720
1870
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1871
|
+
settled = true;
|
|
1872
|
+
resolve(undefined);
|
|
1873
|
+
};
|
|
1874
|
+
queries.forEach((query, index) => {
|
|
1875
|
+
query.then((root) => {
|
|
1876
|
+
if (settled)
|
|
1877
|
+
return;
|
|
1878
|
+
results[index] = root;
|
|
1879
|
+
resolveInPeerOrder();
|
|
1880
|
+
}, (error) => {
|
|
1881
|
+
if (settled)
|
|
1882
|
+
return;
|
|
1883
|
+
if (roundTimedOut && !options?.signal?.aborted) {
|
|
1884
|
+
results[index] = undefined;
|
|
1885
|
+
resolveInPeerOrder();
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
settled = true;
|
|
1889
|
+
reject(error);
|
|
1890
|
+
});
|
|
1891
|
+
});
|
|
1892
|
+
});
|
|
1893
|
+
try {
|
|
1894
|
+
return await prioritizedResult;
|
|
1895
|
+
}
|
|
1896
|
+
finally {
|
|
1897
|
+
clearTimeout(roundTimer);
|
|
1898
|
+
// Cancel response timers and in-flight sends for every losing query before
|
|
1899
|
+
// the winning root (or an abort) can leave this round.
|
|
1900
|
+
roundController.abort(new AbortError("topic root query round settled"));
|
|
1901
|
+
await Promise.allSettled(queries);
|
|
1902
|
+
roundSignals.clear();
|
|
1725
1903
|
}
|
|
1726
|
-
return undefined;
|
|
1727
1904
|
}
|
|
1728
1905
|
async ensureFanoutChannel(shardTopic, options) {
|
|
1729
1906
|
const topic = shardTopic.toString();
|
|
@@ -2107,26 +2284,71 @@ export class TopicControlPlane extends DirectStream {
|
|
|
2107
2284
|
if (!this.started)
|
|
2108
2285
|
throw new NotStartedError();
|
|
2109
2286
|
const lifecycleSignal = this.topicRootResolutionAbortController.signal;
|
|
2110
|
-
return this.withTopicRootCandidateResolution(async ({
|
|
2287
|
+
return this.withTopicRootCandidateResolution(async ({ signal }) => {
|
|
2111
2288
|
const joins = [];
|
|
2289
|
+
const scanController = new AbortController();
|
|
2290
|
+
const scanTimer = setTimeout(() => {
|
|
2291
|
+
scanController.abort(new AbortError(`topic root host scan timed out after ${TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS}ms`));
|
|
2292
|
+
}, TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS);
|
|
2293
|
+
scanTimer.unref?.();
|
|
2294
|
+
const scanSignals = linkAbortSignals([signal, scanController.signal]);
|
|
2295
|
+
let nextShard = 0;
|
|
2296
|
+
let scanFailure;
|
|
2297
|
+
const scanWorker = async () => {
|
|
2298
|
+
try {
|
|
2299
|
+
for (;;) {
|
|
2300
|
+
if (scanFailure)
|
|
2301
|
+
return;
|
|
2302
|
+
throwIfAborted(scanSignals.signal);
|
|
2303
|
+
const shard = nextShard++;
|
|
2304
|
+
if (shard >= this.shardCount)
|
|
2305
|
+
return;
|
|
2306
|
+
const shardTopic = `${this.shardTopicPrefix}${shard}`;
|
|
2307
|
+
const resolved = await this.resolveShardRootState(shardTopic, {
|
|
2308
|
+
signal: scanSignals.signal,
|
|
2309
|
+
// Proactive hosting follows the generation-bound local control
|
|
2310
|
+
// plane. Connected-peer queries remain on the lazy open path;
|
|
2311
|
+
// asking every peer for every shard multiplies one stale-peer
|
|
2312
|
+
// timeout by the complete shard count.
|
|
2313
|
+
queryConnectedPeers: false,
|
|
2314
|
+
});
|
|
2315
|
+
throwIfAborted(scanSignals.signal);
|
|
2316
|
+
if (scanFailure || resolved.root !== this.publicKeyHash)
|
|
2317
|
+
continue;
|
|
2318
|
+
const joining = this.ensureFanoutChannel(shardTopic, {
|
|
2319
|
+
pin: true,
|
|
2320
|
+
root: resolved.root,
|
|
2321
|
+
rootCandidateGeneration: resolved.candidateGeneration,
|
|
2322
|
+
// The shared deadline bounds root discovery only. Preserve the
|
|
2323
|
+
// existing contract that joins already started are drained.
|
|
2324
|
+
signal,
|
|
2325
|
+
});
|
|
2326
|
+
void joining.catch(() => { });
|
|
2327
|
+
joins.push(joining);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
catch (error) {
|
|
2331
|
+
if (!scanFailure) {
|
|
2332
|
+
scanFailure = { error };
|
|
2333
|
+
// Do not make a sibling resolver consume the rest of the shared
|
|
2334
|
+
// deadline after this scan already has a terminal failure. Joins use
|
|
2335
|
+
// the outer signal and are intentionally drained below.
|
|
2336
|
+
scanController.abort(error);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2112
2340
|
try {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
const joining = this.ensureFanoutChannel(shardTopic, {
|
|
2122
|
-
pin: true,
|
|
2123
|
-
root: resolved.root,
|
|
2124
|
-
rootCandidateGeneration: resolved.candidateGeneration,
|
|
2125
|
-
signal,
|
|
2126
|
-
});
|
|
2127
|
-
void joining.catch(() => { });
|
|
2128
|
-
joins.push(joining);
|
|
2341
|
+
try {
|
|
2342
|
+
await Promise.all(Array.from({
|
|
2343
|
+
length: Math.min(TOPIC_ROOT_HOST_SCAN_CONCURRENCY, this.shardCount),
|
|
2344
|
+
}, () => scanWorker()));
|
|
2345
|
+
}
|
|
2346
|
+
finally {
|
|
2347
|
+
clearTimeout(scanTimer);
|
|
2348
|
+
scanSignals.clear();
|
|
2129
2349
|
}
|
|
2350
|
+
if (scanFailure)
|
|
2351
|
+
throw scanFailure.error;
|
|
2130
2352
|
await Promise.all(joins);
|
|
2131
2353
|
}
|
|
2132
2354
|
catch (error) {
|
|
@@ -2856,7 +3078,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
2856
3078
|
const imported = [];
|
|
2857
3079
|
const outerPeerHash = stream.publicKey.hashcode();
|
|
2858
3080
|
for (const claim of pubsubMessage.claims) {
|
|
2859
|
-
if (await this.importSignedTopicRootCandidateClaim(claim, outerPeerHash)) {
|
|
3081
|
+
if (await this.importSignedTopicRootCandidateClaim(claim, outerPeerHash, stream)) {
|
|
2860
3082
|
imported.push(claim);
|
|
2861
3083
|
}
|
|
2862
3084
|
}
|