@peerbit/pubsub 5.3.9 → 5.3.11

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.
@@ -4,7 +4,7 @@ import { type PubSub, type PubSubEvents, SubscriptionData } from "@peerbit/pubsu
4
4
  import { DirectStream, type DirectStreamComponents, type DirectStreamOptions, type PeerStreams } from "@peerbit/stream";
5
5
  import { AcknowledgeDelivery, DataMessage, type ExpiresAtOptions, type IdOptions, type PriorityOptions, type ResponsePriorityOptions, type RouteHint, SilentDelivery, type WithExtraSigners } from "@peerbit/stream-interface";
6
6
  import type { FanoutTree, FanoutTreeChannelOptions, FanoutTreeJoinOptions } from "./fanout-tree.js";
7
- import { TopicRootControlPlane } from "./topic-root-control-plane.js";
7
+ import { TopicRootControlPlane, type TopicRootResolutionOptions } from "./topic-root-control-plane.js";
8
8
  export * from "./fanout-tree.js";
9
9
  export * as fanoutWire from "./fanout-tree-codec.js";
10
10
  export * as fanoutParentUpgrade from "./fanout-tree-parent-upgrade.js";
@@ -153,10 +153,15 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
153
153
  private readonly fanoutPublishMaxEphemeralChannels;
154
154
  private autoTopicRootCandidates;
155
155
  private autoTopicRootCandidateSet?;
156
+ private pendingAutoTopicRootCandidates?;
157
+ private autoTopicRootCandidateUpdateTimer?;
156
158
  private reconcileShardOverlaysInFlight?;
157
159
  private reconcileShardOverlaysDirty;
158
160
  private topicControlPlaneStopping;
159
161
  private topicControlPlaneLifecycleRevision;
162
+ private topicRootResolutionAbortController;
163
+ private topicRootCandidateResolutionGeneration?;
164
+ private topicRootCandidateResolutionAbortController;
160
165
  private hostOwnedShardRootsInFlight?;
161
166
  private hostOwnedShardRootsDirty;
162
167
  private autoCandidatesBroadcastTimers;
@@ -194,6 +199,11 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
194
199
  private ensureAutoCandidatesGossipInterval;
195
200
  private sendAutoTopicRootCandidates;
196
201
  private mergeAutoTopicRootCandidatesFromPeer;
202
+ private queueAutoTopicRootCandidateUpdate;
203
+ private applyAutoTopicRootCandidates;
204
+ private scheduleAutoTopicRootCandidateUpdateCooldown;
205
+ private flushPendingAutoTopicRootCandidateUpdate;
206
+ private clearAutoTopicRootCandidateUpdateSchedule;
197
207
  private scheduleHostOwnedShardRoots;
198
208
  private scheduleReconcileShardOverlays;
199
209
  private reconcileShardOverlays;
@@ -221,9 +231,11 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
221
231
  private decodePubSubMessage;
222
232
  private getTopicRootCandidateGeneration;
223
233
  private assertTopicControlPlaneActive;
234
+ private syncTopicRootCandidateResolutionSignal;
235
+ private withTopicRootCandidateResolution;
224
236
  private normalizePeerTopicRootState;
225
237
  private resolveTopicRootState;
226
- resolveTopicRoot(topic: string): Promise<string | undefined>;
238
+ resolveTopicRoot(topic: string, options?: TopicRootResolutionOptions): Promise<string | undefined>;
227
239
  private resolveShardRootState;
228
240
  private getConnectedTopicRootTrackers;
229
241
  private nextTopicRootRequestIdValue;
@@ -236,7 +248,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
236
248
  private ensureFanoutChannel;
237
249
  private ensureFanoutChannelOnce;
238
250
  private closeFanoutChannel;
239
- hostShardRootsNow(): Promise<void>;
251
+ hostShardRootsNow(options?: TopicRootResolutionOptions): Promise<void>;
240
252
  subscribe(topic: string): Promise<void>;
241
253
  private _subscribe;
242
254
  unsubscribe(topic: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/dist/src/index.js CHANGED
@@ -9,7 +9,7 @@ import { Uint8ArrayList } from "uint8arraylist";
9
9
  import { toUint8Array } from "./bytes.js";
10
10
  import { debouncedAccumulatorSetCounter, } from "./debounced-set.js";
11
11
  import { FanoutChannel } from "./fanout-channel.js";
12
- import { TopicRootControlPlane } from "./topic-root-control-plane.js";
12
+ import { TopicRootControlPlane, } from "./topic-root-control-plane.js";
13
13
  export * from "./fanout-tree.js";
14
14
  // The complete /peerbit/fanout-tree/0.5.0 wire codec and the
15
15
  // parent-upgrade decision module (the TS references the native rust-core
@@ -43,6 +43,7 @@ const withAbort = async (promise, signal) => {
43
43
  if (!signal)
44
44
  return promise;
45
45
  if (signal.aborted) {
46
+ void promise.catch(() => { });
46
47
  throw signal.reason ?? new AbortError("Operation was aborted");
47
48
  }
48
49
  return new Promise((resolve, reject) => {
@@ -68,6 +69,41 @@ const withAbort = async (promise, signal) => {
68
69
  });
69
70
  });
70
71
  };
72
+ const throwIfAborted = (signal) => {
73
+ if (signal?.aborted) {
74
+ throw signal.reason ?? new AbortError("Operation was aborted");
75
+ }
76
+ };
77
+ const linkAbortSignals = (signals) => {
78
+ const unique = [...new Set(signals.filter((signal) => signal !== undefined))];
79
+ if (unique.length === 1) {
80
+ return { signal: unique[0], clear: () => { } };
81
+ }
82
+ const controller = new AbortController();
83
+ const listeners = new Map();
84
+ const clear = () => {
85
+ for (const [signal, listener] of listeners) {
86
+ signal.removeEventListener("abort", listener);
87
+ }
88
+ listeners.clear();
89
+ };
90
+ const abortFrom = (signal) => {
91
+ clear();
92
+ controller.abort(signal.reason ?? new AbortError("Topic-root resolution aborted"));
93
+ };
94
+ const aborted = unique.find((signal) => signal.aborted);
95
+ if (aborted) {
96
+ abortFrom(aborted);
97
+ }
98
+ else {
99
+ for (const signal of unique) {
100
+ const listener = () => abortFrom(signal);
101
+ listeners.set(signal, listener);
102
+ signal.addEventListener("abort", listener, { once: true });
103
+ }
104
+ }
105
+ return { signal: controller.signal, clear };
106
+ };
71
107
  const SUBSCRIBER_CACHE_MAX_ENTRIES_HARD_CAP = 100_000;
72
108
  const SUBSCRIBER_CACHE_DEFAULT_MAX_ENTRIES = 4_096;
73
109
  const DEFAULT_FANOUT_PUBLISH_IDLE_CLOSE_MS = 60_000;
@@ -75,6 +111,7 @@ const DEFAULT_FANOUT_PUBLISH_MAX_EPHEMERAL_CHANNELS = 64;
75
111
  const DEFAULT_PUBSUB_SHARD_COUNT = 256;
76
112
  const PUBSUB_SHARD_COUNT_HARD_CAP = 16_384;
77
113
  const DEFAULT_PUBSUB_SHARD_TOPIC_PREFIX = "/peerbit/pubsub-shard/1/";
114
+ const AUTO_TOPIC_ROOT_CANDIDATE_UPDATE_COOLDOWN_MS = 2_000;
78
115
  const sameCandidates = (left, right) => left.length === right.length &&
79
116
  left.every((candidate, index) => candidate === right[index]);
80
117
  // Topic-root queries may need to wait for the responder to finish opening an
@@ -148,10 +185,15 @@ export class TopicControlPlane extends DirectStream {
148
185
  // This keeps small ad-hoc networks working without explicit bootstraps.
149
186
  autoTopicRootCandidates = false;
150
187
  autoTopicRootCandidateSet;
188
+ pendingAutoTopicRootCandidates;
189
+ autoTopicRootCandidateUpdateTimer;
151
190
  reconcileShardOverlaysInFlight;
152
191
  reconcileShardOverlaysDirty = false;
153
192
  topicControlPlaneStopping = false;
154
193
  topicControlPlaneLifecycleRevision = 0;
194
+ topicRootResolutionAbortController = new AbortController();
195
+ topicRootCandidateResolutionGeneration;
196
+ topicRootCandidateResolutionAbortController = new AbortController();
155
197
  hostOwnedShardRootsInFlight;
156
198
  hostOwnedShardRootsDirty = false;
157
199
  autoCandidatesBroadcastTimers = [];
@@ -250,6 +292,7 @@ export class TopicControlPlane extends DirectStream {
250
292
  // Disable auto mode and stop its background gossip/timers.
251
293
  this.autoTopicRootCandidates = false;
252
294
  this.autoTopicRootCandidateSet = undefined;
295
+ this.clearAutoTopicRootCandidateUpdateSchedule();
253
296
  for (const t of this.autoCandidatesBroadcastTimers)
254
297
  clearTimeout(t);
255
298
  this.autoCandidatesBroadcastTimers = [];
@@ -269,6 +312,9 @@ export class TopicControlPlane extends DirectStream {
269
312
  }
270
313
  async start() {
271
314
  this.topicControlPlaneStopping = false;
315
+ if (this.topicRootResolutionAbortController.signal.aborted) {
316
+ this.topicRootResolutionAbortController = new AbortController();
317
+ }
272
318
  await this.fanout.start();
273
319
  this._onFanoutPeerUnreachable =
274
320
  this._onFanoutPeerUnreachable ||
@@ -287,6 +333,8 @@ export class TopicControlPlane extends DirectStream {
287
333
  }
288
334
  async stop() {
289
335
  this.topicControlPlaneStopping = true;
336
+ this.topicRootResolutionAbortController.abort(new AbortError("topic control plane stopped"));
337
+ this.clearAutoTopicRootCandidateUpdateSchedule();
290
338
  this.topicControlPlaneLifecycleRevision += 1;
291
339
  this.reconcileShardOverlaysDirty = false;
292
340
  for (const opening of this.ensureFanoutChannelInFlight.values()) {
@@ -398,6 +446,7 @@ export class TopicControlPlane extends DirectStream {
398
446
  // intact and reconcile shard overlays under the new mapping.
399
447
  this.autoTopicRootCandidates = false;
400
448
  this.autoTopicRootCandidateSet = undefined;
449
+ this.clearAutoTopicRootCandidateUpdateSchedule();
401
450
  this.shardRootCache.clear();
402
451
  // Ensure we host any shard roots we're now responsible for. This is important
403
452
  // in tests where candidates may be configured before protocol streams have
@@ -413,29 +462,7 @@ export class TopicControlPlane extends DirectStream {
413
462
  if (!isCanonicalTopicRootCandidate(peerHash) ||
414
463
  peerHash === this.publicKeyHash)
415
464
  return;
416
- if (this.maybeDisableAutoTopicRootCandidatesIfExternallyConfigured())
417
- return;
418
- const current = this.topicRootControlPlane.getTopicRootCandidates();
419
- const managed = this.autoTopicRootCandidateSet;
420
- if (current.includes(peerHash))
421
- return;
422
- managed?.add(peerHash);
423
- const next = this.normalizeAutoTopicRootCandidates(managed ? [...managed] : [...current, peerHash]);
424
- this.autoTopicRootCandidateSet = new Set(next);
425
- if (sameCandidates(current, next))
426
- return;
427
- this.topicRootControlPlane.setTopicRootCandidates(next);
428
- this.shardRootCache.clear();
429
- this.scheduleReconcileShardOverlays();
430
- // In auto-candidate mode, shard roots are selected deterministically across
431
- // *all* connected peers (not just those currently subscribed to a shard).
432
- // That means a peer can be selected as root for shards it isn't using yet.
433
- // Ensure we proactively host the shard roots we're responsible for so other
434
- // peers can join without timing out in small ad-hoc networks.
435
- this.scheduleHostOwnedShardRoots();
436
- // Share the updated candidate set so other peers converge on the same
437
- // deterministic mapping even in partially connected topologies.
438
- this.scheduleAutoTopicRootCandidatesBroadcast();
465
+ this.queueAutoTopicRootCandidateUpdate([peerHash]);
439
466
  }
440
467
  normalizeAutoTopicRootCandidates(candidates) {
441
468
  const canonicalCandidates = candidates.filter(isCanonicalTopicRootCandidate);
@@ -520,27 +547,78 @@ export class TopicControlPlane extends DirectStream {
520
547
  mergeAutoTopicRootCandidatesFromPeer(candidates) {
521
548
  if (!this.autoTopicRootCandidates)
522
549
  return false;
550
+ return this.queueAutoTopicRootCandidateUpdate(candidates);
551
+ }
552
+ queueAutoTopicRootCandidateUpdate(candidates) {
553
+ if (!this.autoTopicRootCandidates ||
554
+ this.stopping ||
555
+ this.topicControlPlaneStopping)
556
+ return false;
523
557
  if (this.maybeDisableAutoTopicRootCandidatesIfExternallyConfigured())
524
558
  return false;
525
559
  const managed = this.autoTopicRootCandidateSet;
526
560
  if (!managed)
527
561
  return false;
528
- const before = this.topicRootControlPlane.getTopicRootCandidates();
529
- for (const c of candidates) {
530
- if (!isCanonicalTopicRootCandidate(c))
531
- continue;
532
- managed.add(c);
533
- }
534
- const next = this.normalizeAutoTopicRootCandidates([...managed]);
535
- this.autoTopicRootCandidateSet = new Set(next);
562
+ const before = this.pendingAutoTopicRootCandidates ?? [...managed];
563
+ const next = this.normalizeAutoTopicRootCandidates([
564
+ ...before,
565
+ ...candidates,
566
+ ]);
536
567
  if (sameCandidates(before, next))
537
568
  return false;
569
+ if (this.autoTopicRootCandidateUpdateTimer) {
570
+ this.pendingAutoTopicRootCandidates = next;
571
+ return true;
572
+ }
573
+ this.scheduleAutoTopicRootCandidateUpdateCooldown();
574
+ this.applyAutoTopicRootCandidates(next);
575
+ return true;
576
+ }
577
+ applyAutoTopicRootCandidates(next) {
578
+ this.autoTopicRootCandidateSet = new Set(next);
538
579
  this.topicRootControlPlane.setTopicRootCandidates(next);
539
580
  this.shardRootCache.clear();
540
581
  this.scheduleReconcileShardOverlays();
541
582
  this.scheduleHostOwnedShardRoots();
542
583
  this.scheduleAutoTopicRootCandidatesBroadcast();
543
- return true;
584
+ }
585
+ scheduleAutoTopicRootCandidateUpdateCooldown() {
586
+ if (this.autoTopicRootCandidateUpdateTimer)
587
+ return;
588
+ const timer = setTimeout(() => {
589
+ if (this.autoTopicRootCandidateUpdateTimer !== timer)
590
+ return;
591
+ this.flushPendingAutoTopicRootCandidateUpdate();
592
+ }, AUTO_TOPIC_ROOT_CANDIDATE_UPDATE_COOLDOWN_MS);
593
+ timer.unref?.();
594
+ this.autoTopicRootCandidateUpdateTimer = timer;
595
+ }
596
+ flushPendingAutoTopicRootCandidateUpdate() {
597
+ if (this.autoTopicRootCandidateUpdateTimer) {
598
+ clearTimeout(this.autoTopicRootCandidateUpdateTimer);
599
+ this.autoTopicRootCandidateUpdateTimer = undefined;
600
+ }
601
+ const next = this.pendingAutoTopicRootCandidates;
602
+ this.pendingAutoTopicRootCandidates = undefined;
603
+ if (!next)
604
+ return;
605
+ if (!this.autoTopicRootCandidates ||
606
+ this.stopping ||
607
+ this.topicControlPlaneStopping)
608
+ return;
609
+ if (this.maybeDisableAutoTopicRootCandidatesIfExternallyConfigured())
610
+ return;
611
+ // The trailing update starts its own fixed window so another inbound update
612
+ // cannot create a second candidate generation immediately afterwards.
613
+ this.scheduleAutoTopicRootCandidateUpdateCooldown();
614
+ this.applyAutoTopicRootCandidates(next);
615
+ }
616
+ clearAutoTopicRootCandidateUpdateSchedule() {
617
+ if (this.autoTopicRootCandidateUpdateTimer) {
618
+ clearTimeout(this.autoTopicRootCandidateUpdateTimer);
619
+ this.autoTopicRootCandidateUpdateTimer = undefined;
620
+ }
621
+ this.pendingAutoTopicRootCandidates = undefined;
544
622
  }
545
623
  scheduleHostOwnedShardRoots() {
546
624
  this.hostOwnedShardRootsDirty = true;
@@ -574,6 +652,7 @@ export class TopicControlPlane extends DirectStream {
574
652
  }
575
653
  scheduleReconcileShardOverlays() {
576
654
  const candidateGeneration = this.getTopicRootCandidateGeneration();
655
+ this.syncTopicRootCandidateResolutionSignal(candidateGeneration);
577
656
  for (const opening of this.ensureFanoutChannelInFlight.values()) {
578
657
  if (opening.candidateGeneration !== candidateGeneration) {
579
658
  opening.abortController.abort(new AbortError("topic root candidates changed"));
@@ -885,6 +964,53 @@ export class TopicControlPlane extends DirectStream {
885
964
  throw new NotStartedError();
886
965
  }
887
966
  }
967
+ syncTopicRootCandidateResolutionSignal(candidateGeneration = this.getTopicRootCandidateGeneration()) {
968
+ if (this.topicRootCandidateResolutionGeneration === undefined) {
969
+ this.topicRootCandidateResolutionGeneration = candidateGeneration;
970
+ }
971
+ else if (candidateGeneration !== this.topicRootCandidateResolutionGeneration) {
972
+ this.topicRootCandidateResolutionAbortController.abort(new AbortError("topic root candidates changed"));
973
+ this.topicRootCandidateResolutionAbortController = new AbortController();
974
+ this.topicRootCandidateResolutionGeneration = candidateGeneration;
975
+ }
976
+ return this.topicRootCandidateResolutionAbortController.signal;
977
+ }
978
+ async withTopicRootCandidateResolution(operation, terminalSignals = []) {
979
+ for (;;) {
980
+ for (const signal of terminalSignals)
981
+ throwIfAborted(signal);
982
+ const candidateGeneration = this.getTopicRootCandidateGeneration();
983
+ const candidateSignal = this.syncTopicRootCandidateResolutionSignal(candidateGeneration);
984
+ const linked = linkAbortSignals([...terminalSignals, candidateSignal]);
985
+ try {
986
+ const result = await operation({
987
+ candidateGeneration,
988
+ signal: linked.signal,
989
+ });
990
+ for (const signal of terminalSignals)
991
+ throwIfAborted(signal);
992
+ if (candidateSignal.aborted ||
993
+ candidateGeneration !== this.getTopicRootCandidateGeneration()) {
994
+ continue;
995
+ }
996
+ return result;
997
+ }
998
+ catch (error) {
999
+ for (const signal of terminalSignals) {
1000
+ if (signal?.aborted)
1001
+ throw signal.reason ?? error;
1002
+ }
1003
+ if (candidateSignal.aborted ||
1004
+ candidateGeneration !== this.getTopicRootCandidateGeneration()) {
1005
+ continue;
1006
+ }
1007
+ throw error;
1008
+ }
1009
+ finally {
1010
+ linked.clear();
1011
+ }
1012
+ }
1013
+ }
888
1014
  normalizePeerTopicRootState(topic, root) {
889
1015
  const deterministic = this.topicRootControlPlane.resolveDeterministicTopicRoot(topic);
890
1016
  if (this.autoTopicRootCandidates &&
@@ -895,12 +1021,13 @@ export class TopicControlPlane extends DirectStream {
895
1021
  }
896
1022
  return { root, authoritative: true };
897
1023
  }
898
- async resolveTopicRootState(topic) {
899
- const tracked = await this.topicRootControlPlane.resolveTrackedTopicRoot(topic);
1024
+ async resolveTopicRootState(topic, options) {
1025
+ throwIfAborted(options?.signal);
1026
+ const tracked = await this.topicRootControlPlane.resolveTrackedTopicRoot(topic, options);
900
1027
  if (tracked) {
901
1028
  return { root: tracked, authoritative: true };
902
1029
  }
903
- const resolvedThroughPeers = await this.resolveTopicRootThroughPeers(topic);
1030
+ const resolvedThroughPeers = await this.resolveTopicRootThroughPeers(topic, options);
904
1031
  if (resolvedThroughPeers) {
905
1032
  // Unconfigured peer-query replies cannot override the locally
906
1033
  // deterministic root for internal shards in auto mode. Roots, resolvers,
@@ -914,8 +1041,8 @@ export class TopicControlPlane extends DirectStream {
914
1041
  this.autoTopicRootCandidates &&
915
1042
  this.getConnectedTopicRootTrackers().length > 0) {
916
1043
  for (let attempt = 0; attempt < 8; attempt++) {
917
- await delay(150 * (attempt < 4 ? 1 : 2));
918
- const retried = await this.resolveTopicRootThroughPeers(topic);
1044
+ await withAbort(delay(150 * (attempt < 4 ? 1 : 2), options), options?.signal);
1045
+ const retried = await this.resolveTopicRootThroughPeers(topic, options);
919
1046
  if (retried) {
920
1047
  return this.normalizePeerTopicRootState(topic, retried);
921
1048
  }
@@ -926,12 +1053,16 @@ export class TopicControlPlane extends DirectStream {
926
1053
  authoritative: false,
927
1054
  };
928
1055
  }
929
- async resolveTopicRoot(topic) {
930
- return (await this.resolveTopicRootState(topic)).root;
1056
+ async resolveTopicRoot(topic, options) {
1057
+ const lifecycleSignal = this.started && !this.stopping && !this.topicControlPlaneStopping
1058
+ ? this.topicRootResolutionAbortController.signal
1059
+ : undefined;
1060
+ return this.withTopicRootCandidateResolution(async ({ signal }) => (await this.resolveTopicRootState(topic, { signal })).root, [lifecycleSignal, options?.signal]);
931
1061
  }
932
- async resolveShardRootState(shardTopic) {
1062
+ async resolveShardRootState(shardTopic, options) {
933
1063
  const lifecycleRevision = this.topicControlPlaneLifecycleRevision;
934
1064
  for (;;) {
1065
+ throwIfAborted(options?.signal);
935
1066
  this.assertTopicControlPlaneActive(lifecycleRevision);
936
1067
  // If someone configured topic-root candidates externally (e.g.
937
1068
  // TestSession router selection or Peerbit.bootstrap) after this peer
@@ -945,7 +1076,7 @@ export class TopicControlPlane extends DirectStream {
945
1076
  if (cached && (cached.authoritative || !hasConnectedTrackers)) {
946
1077
  return { root: cached.root, candidateGeneration };
947
1078
  }
948
- const resolved = await this.resolveTopicRootState(shardTopic);
1079
+ const resolved = await this.resolveTopicRootState(shardTopic, options);
949
1080
  this.assertTopicControlPlaneActive(lifecycleRevision);
950
1081
  if (candidateGeneration !== this.getTopicRootCandidateGeneration()) {
951
1082
  continue;
@@ -1005,36 +1136,57 @@ export class TopicControlPlane extends DirectStream {
1005
1136
  pending.resolve(message.root);
1006
1137
  return true;
1007
1138
  }
1008
- async queryTopicRootFromPeer(peer, topic, timeoutMs = DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS) {
1139
+ async queryTopicRootFromPeer(peer, topic, timeoutMs = DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS, signal) {
1140
+ throwIfAborted(signal);
1009
1141
  if (!this.started || this.stopping)
1010
1142
  return undefined;
1011
1143
  const expectedPeerHash = peer.publicKey.hashcode();
1012
1144
  const requestId = this.nextTopicRootRequestIdValue();
1013
1145
  const responsePromise = new Promise((resolve) => {
1014
- const timer = setTimeout(() => {
1015
- this.pendingTopicRootQueries.delete(requestId);
1016
- resolve(undefined);
1017
- }, Math.max(1, Math.floor(timeoutMs)));
1146
+ let settled = false;
1147
+ const settle = (root) => {
1148
+ if (settled)
1149
+ return;
1150
+ settled = true;
1151
+ const pending = this.pendingTopicRootQueries.get(requestId);
1152
+ if (pending?.resolve === settle) {
1153
+ this.pendingTopicRootQueries.delete(requestId);
1154
+ }
1155
+ clearTimeout(timer);
1156
+ if (signal && onAbort) {
1157
+ signal.removeEventListener("abort", onAbort);
1158
+ }
1159
+ resolve(root);
1160
+ };
1161
+ const onAbort = signal ? () => settle(undefined) : undefined;
1162
+ const timer = setTimeout(() => settle(undefined), Math.max(1, Math.floor(timeoutMs)));
1018
1163
  timer.unref?.();
1019
1164
  this.pendingTopicRootQueries.set(requestId, {
1020
1165
  expectedPeerHash,
1021
1166
  topic,
1022
- resolve,
1167
+ resolve: settle,
1023
1168
  timer,
1024
1169
  });
1170
+ if (signal && onAbort) {
1171
+ signal.addEventListener("abort", onAbort, { once: true });
1172
+ if (signal.aborted)
1173
+ onAbort();
1174
+ }
1025
1175
  });
1026
1176
  try {
1027
- await this.sendDirectControlMessage(peer, new TopicRootQuery({ requestId, topic }));
1177
+ await withAbort(this.sendDirectControlMessage(peer, new TopicRootQuery({ requestId, topic })), signal);
1028
1178
  }
1029
- catch {
1179
+ catch (error) {
1030
1180
  const pending = this.pendingTopicRootQueries.get(requestId);
1031
1181
  if (pending) {
1032
1182
  this.pendingTopicRootQueries.delete(requestId);
1033
1183
  clearTimeout(pending.timer);
1034
1184
  pending.resolve(undefined);
1035
1185
  }
1186
+ if (signal?.aborted)
1187
+ throw error;
1036
1188
  }
1037
- return responsePromise;
1189
+ return withAbort(responsePromise, signal);
1038
1190
  }
1039
1191
  async confirmDirectShardRoot(shardTopic, root, candidateGeneration, lifecycleRevision, signal) {
1040
1192
  this.assertTopicControlPlaneActive(lifecycleRevision);
@@ -1043,7 +1195,7 @@ export class TopicControlPlane extends DirectStream {
1043
1195
  const rootPeer = this.peers.get(root);
1044
1196
  if (!rootPeer)
1045
1197
  return root;
1046
- const confirmation = await withAbort(this.queryTopicRootFromPeer(rootPeer, shardTopic, DIRECT_SHARD_ROOT_CONFIRM_TIMEOUT_MS), signal);
1198
+ const confirmation = await withAbort(this.queryTopicRootFromPeer(rootPeer, shardTopic, DIRECT_SHARD_ROOT_CONFIRM_TIMEOUT_MS, signal), signal);
1047
1199
  this.assertTopicControlPlaneActive(lifecycleRevision);
1048
1200
  if (candidateGeneration !== this.getTopicRootCandidateGeneration()) {
1049
1201
  return root;
@@ -1064,14 +1216,14 @@ export class TopicControlPlane extends DirectStream {
1064
1216
  }
1065
1217
  async resolveQueryableTopicRoot(topic) {
1066
1218
  const lifecycleRevision = this.topicControlPlaneLifecycleRevision;
1067
- for (;;) {
1219
+ const lifecycleSignal = this.topicRootResolutionAbortController.signal;
1220
+ return this.withTopicRootCandidateResolution(async ({ candidateGeneration, signal }) => {
1221
+ throwIfAborted(signal);
1068
1222
  this.assertTopicControlPlaneActive(lifecycleRevision);
1069
- const rootCandidateGeneration = this.getTopicRootCandidateGeneration();
1070
- const root = await this.topicRootControlPlane.resolveCanonicalTopicRoot(topic);
1223
+ const root = await this.topicRootControlPlane.resolveCanonicalTopicRoot(topic, {
1224
+ signal,
1225
+ });
1071
1226
  this.assertTopicControlPlaneActive(lifecycleRevision);
1072
- if (rootCandidateGeneration !== this.getTopicRootCandidateGeneration()) {
1073
- continue;
1074
- }
1075
1227
  if (root !== this.publicKeyHash) {
1076
1228
  return root;
1077
1229
  }
@@ -1082,34 +1234,37 @@ export class TopicControlPlane extends DirectStream {
1082
1234
  await this.ensureFanoutChannel(topic, {
1083
1235
  pin: true,
1084
1236
  root,
1085
- rootCandidateGeneration,
1237
+ rootCandidateGeneration: candidateGeneration,
1238
+ signal,
1086
1239
  });
1087
1240
  this.assertTopicControlPlaneActive(lifecycleRevision);
1088
- if (rootCandidateGeneration !== this.getTopicRootCandidateGeneration()) {
1089
- continue;
1090
- }
1091
1241
  return root;
1092
1242
  }
1093
1243
  catch (error) {
1244
+ if (signal.aborted ||
1245
+ candidateGeneration !== this.getTopicRootCandidateGeneration()) {
1246
+ throw error;
1247
+ }
1094
1248
  warn(`Failed to host shard root ${topic} before answering root query: ${error instanceof Error ? error.message : String(error)}`);
1095
1249
  return undefined;
1096
1250
  }
1097
- }
1251
+ }, [lifecycleSignal]);
1098
1252
  }
1099
- async resolveTopicRootThroughPeers(topic) {
1253
+ async resolveTopicRootThroughPeers(topic, options) {
1254
+ throwIfAborted(options?.signal);
1100
1255
  const peers = this.getConnectedTopicRootTrackers();
1101
1256
  if (peers.length === 0) {
1102
1257
  return undefined;
1103
1258
  }
1104
1259
  for (let attempt = 0; attempt < 3; attempt++) {
1105
1260
  for (const peer of peers) {
1106
- const resolved = await this.queryTopicRootFromPeer(peer, topic);
1261
+ const resolved = await this.queryTopicRootFromPeer(peer, topic, DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS, options?.signal);
1107
1262
  if (resolved) {
1108
1263
  return resolved;
1109
1264
  }
1110
1265
  }
1111
1266
  if (attempt < 2) {
1112
- await delay(150 * (attempt + 1));
1267
+ await withAbort(delay(150 * (attempt + 1), options), options?.signal);
1113
1268
  }
1114
1269
  }
1115
1270
  return undefined;
@@ -1233,7 +1388,9 @@ export class TopicControlPlane extends DirectStream {
1233
1388
  const existing = this.fanoutChannels.get(t);
1234
1389
  if (existing) {
1235
1390
  if (!root) {
1236
- const resolved = await this.resolveShardRootState(t);
1391
+ const resolved = await this.resolveShardRootState(t, {
1392
+ signal: options?.signal,
1393
+ });
1237
1394
  this.assertTopicControlPlaneActive(lifecycleRevision);
1238
1395
  root = resolved.root;
1239
1396
  resolvedGeneration = resolved.candidateGeneration;
@@ -1270,7 +1427,9 @@ export class TopicControlPlane extends DirectStream {
1270
1427
  }
1271
1428
  }
1272
1429
  if (!root) {
1273
- const resolved = await this.resolveShardRootState(t);
1430
+ const resolved = await this.resolveShardRootState(t, {
1431
+ signal: options?.signal,
1432
+ });
1274
1433
  this.assertTopicControlPlaneActive(lifecycleRevision);
1275
1434
  root = resolved.root;
1276
1435
  resolvedGeneration = resolved.candidateGeneration;
@@ -1488,22 +1647,37 @@ export class TopicControlPlane extends DirectStream {
1488
1647
  }
1489
1648
  }
1490
1649
  }
1491
- async hostShardRootsNow() {
1650
+ async hostShardRootsNow(options) {
1492
1651
  if (!this.started)
1493
1652
  throw new NotStartedError();
1494
- const joins = [];
1495
- for (let i = 0; i < this.shardCount; i++) {
1496
- const shardTopic = `${this.shardTopicPrefix}${i}`;
1497
- const resolved = await this.resolveShardRootState(shardTopic);
1498
- if (resolved.root !== this.publicKeyHash)
1499
- continue;
1500
- joins.push(this.ensureFanoutChannel(shardTopic, {
1501
- pin: true,
1502
- root: resolved.root,
1503
- rootCandidateGeneration: resolved.candidateGeneration,
1504
- }));
1505
- }
1506
- await Promise.all(joins);
1653
+ const lifecycleSignal = this.topicRootResolutionAbortController.signal;
1654
+ return this.withTopicRootCandidateResolution(async ({ candidateGeneration, signal }) => {
1655
+ const joins = [];
1656
+ try {
1657
+ for (let i = 0; i < this.shardCount; i++) {
1658
+ throwIfAborted(signal);
1659
+ const shardTopic = `${this.shardTopicPrefix}${i}`;
1660
+ const resolved = await this.resolveShardRootState(shardTopic, {
1661
+ signal,
1662
+ });
1663
+ if (resolved.root !== this.publicKeyHash)
1664
+ continue;
1665
+ const joining = this.ensureFanoutChannel(shardTopic, {
1666
+ pin: true,
1667
+ root: resolved.root,
1668
+ rootCandidateGeneration: resolved.candidateGeneration,
1669
+ signal,
1670
+ });
1671
+ void joining.catch(() => { });
1672
+ joins.push(joining);
1673
+ }
1674
+ await Promise.all(joins);
1675
+ }
1676
+ catch (error) {
1677
+ await Promise.allSettled(joins);
1678
+ throw error;
1679
+ }
1680
+ }, [lifecycleSignal, options?.signal]);
1507
1681
  }
1508
1682
  async subscribe(topic) {
1509
1683
  this.pendingSubscriptions.add(topic);