@peerbit/shared-log 13.2.8 → 13.2.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "13.2.8",
3
+ "version": "13.2.10",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -61,21 +61,21 @@
61
61
  "pidusage": "^4.0.1",
62
62
  "pino": "^9.4.0",
63
63
  "uint8arrays": "^5.1.0",
64
+ "@peerbit/blocks": "4.2.5",
64
65
  "@peerbit/blocks-interface": "2.1.2",
65
- "@peerbit/any-store": "2.2.12",
66
- "@peerbit/blocks": "4.2.4",
67
66
  "@peerbit/cache": "3.1.0",
68
67
  "@peerbit/crypto": "3.1.4",
68
+ "@peerbit/any-store": "2.2.12",
69
69
  "@peerbit/indexer-interface": "3.0.8",
70
- "@peerbit/log": "6.2.7",
71
- "@peerbit/program": "6.0.37",
70
+ "@peerbit/diagnostics": "0.0.1",
71
+ "@peerbit/log": "6.2.8",
72
+ "@peerbit/indexer-sqlite3": "3.0.11",
73
+ "@peerbit/program": "6.0.38",
74
+ "@peerbit/pubsub": "5.3.3",
75
+ "@peerbit/riblt": "1.2.0",
72
76
  "@peerbit/logger": "2.0.1",
73
- "@peerbit/pubsub": "5.3.2",
74
77
  "@peerbit/pubsub-interface": "5.1.7",
75
- "@peerbit/riblt": "1.2.0",
76
- "@peerbit/indexer-sqlite3": "3.0.11",
77
- "@peerbit/rpc": "6.1.5",
78
- "@peerbit/diagnostics": "0.0.1",
78
+ "@peerbit/rpc": "6.1.6",
79
79
  "@peerbit/stream-interface": "6.0.13",
80
80
  "@peerbit/time": "3.0.1"
81
81
  },
@@ -87,10 +87,10 @@
87
87
  "@types/libsodium-wrappers": "^0.7.14",
88
88
  "@types/pidusage": "^2.0.5",
89
89
  "uuid": "^11.1.1",
90
- "@peerbit/indexer-rust": "1.0.6",
91
90
  "@peerbit/any-store-rust": "0.1.2",
92
- "@peerbit/test-utils": "3.1.8",
93
- "peerbit": "5.3.8"
91
+ "@peerbit/test-utils": "3.1.9",
92
+ "@peerbit/indexer-rust": "1.0.6",
93
+ "peerbit": "5.3.9"
94
94
  },
95
95
  "repository": {
96
96
  "type": "git",
package/src/index.ts CHANGED
@@ -3155,6 +3155,9 @@ export class SharedLog<
3155
3155
 
3156
3156
  private _onSubscriptionFn!: (arg: any) => any;
3157
3157
  private _onUnsubscriptionFn!: (arg: any) => any;
3158
+ private _subscriptionChangeCallbacks?: Set<Promise<void>>;
3159
+ private _acceptSubscriptionChangeCallbacks = false;
3160
+ private _replicationLifecycleController?: AbortController;
3158
3161
  private _onFanoutDataFn?: (arg: any) => void;
3159
3162
  private _onFanoutUnicastFn?: (arg: any) => void;
3160
3163
  private _fanoutChannel?: FanoutChannel;
@@ -5558,8 +5561,9 @@ export class SharedLog<
5558
5561
  }
5559
5562
 
5560
5563
  // @deprecated
5561
- private async getRole() {
5562
- const segments = await this.getMyReplicationSegments();
5564
+ private getRoleFromReplicationSegments(
5565
+ segments: ReplicationRangeIndexable<R>[],
5566
+ ) {
5563
5567
  if (segments.length > 1) {
5564
5568
  throw new Error(
5565
5569
  "More than one replication segment found. Can only use one segment for compatbility with v8",
@@ -5578,6 +5582,92 @@ export class SharedLog<
5578
5582
  return new Observer();
5579
5583
  }
5580
5584
 
5585
+ private isTerminating() {
5586
+ return (
5587
+ this.acceptsParentAttachments === false ||
5588
+ this.closed ||
5589
+ this._closeController?.signal.aborted === true
5590
+ );
5591
+ }
5592
+
5593
+ private isReplicationLifecycleActive(
5594
+ controller: AbortController | undefined,
5595
+ ) {
5596
+ return (
5597
+ controller != null &&
5598
+ controller === this._replicationLifecycleController &&
5599
+ !controller.signal.aborted &&
5600
+ !this.isTerminating()
5601
+ );
5602
+ }
5603
+
5604
+ private resetSubscriptionChangeCallbackTracking() {
5605
+ this._subscriptionChangeCallbacks = new Set();
5606
+ this._acceptSubscriptionChangeCallbacks = true;
5607
+ this._replicationLifecycleController = new AbortController();
5608
+ }
5609
+
5610
+ private runSubscriptionChangeCallback(
5611
+ callback: () => Promise<void>,
5612
+ ): Promise<void> | undefined {
5613
+ if (!this._acceptSubscriptionChangeCallbacks || this.isTerminating()) {
5614
+ return;
5615
+ }
5616
+
5617
+ const running = (async () => callback())();
5618
+ const observed = running.catch((error) => {
5619
+ if (!(this.isTerminating() && isNotStartedError(error as Error))) {
5620
+ logger.error(error?.toString?.() ?? String(error));
5621
+ }
5622
+ });
5623
+ const callbacks = (this._subscriptionChangeCallbacks ??= new Set());
5624
+ callbacks.add(observed);
5625
+ void observed.finally(() => callbacks.delete(observed));
5626
+ return observed;
5627
+ }
5628
+
5629
+ private stopSubscriptionChangeCallbackAdmission() {
5630
+ this._acceptSubscriptionChangeCallbacks = false;
5631
+ if (!this._replicationLifecycleController?.signal.aborted) {
5632
+ this._replicationLifecycleController?.abort(
5633
+ new AbortError("SharedLog is terminating"),
5634
+ );
5635
+ }
5636
+ if (this._onSubscriptionFn) {
5637
+ this.node.services.pubsub.removeEventListener(
5638
+ "subscribe",
5639
+ this._onSubscriptionFn,
5640
+ );
5641
+ }
5642
+ if (this._onUnsubscriptionFn) {
5643
+ this.node.services.pubsub.removeEventListener(
5644
+ "unsubscribe",
5645
+ this._onUnsubscriptionFn,
5646
+ );
5647
+ }
5648
+ }
5649
+
5650
+ private async drainSubscriptionChangeCallbacks() {
5651
+ const callbacks = this._subscriptionChangeCallbacks;
5652
+ while (callbacks && callbacks.size > 0) {
5653
+ await Promise.all([...callbacks]);
5654
+ }
5655
+ }
5656
+
5657
+ private handleReplicationLifecycleSendError(
5658
+ error: unknown,
5659
+ controller = this._replicationLifecycleController,
5660
+ ) {
5661
+ if (
5662
+ (controller?.signal.aborted ||
5663
+ !this.isReplicationLifecycleActive(controller)) &&
5664
+ (error instanceof AbortError || isNotStartedError(error as Error))
5665
+ ) {
5666
+ return;
5667
+ }
5668
+ logger.error((error as any)?.toString?.() ?? String(error));
5669
+ }
5670
+
5581
5671
  async isReplicating() {
5582
5672
  if (!this._isReplicating) {
5583
5673
  return false;
@@ -11987,6 +12077,7 @@ export class SharedLog<
11987
12077
  async open(options?: Args<T, D, R>): Promise<void> {
11988
12078
  this.ensureNativeDurabilityRuntimeState();
11989
12079
  this._nativeStrictDurableTransactionsClosing = false;
12080
+ this.resetSubscriptionChangeCallbackTracking();
11990
12081
  const recoveringNativeDurableFailure =
11991
12082
  this._nativeDurableCommitFailure !== undefined;
11992
12083
  options = applySharedLogNativeDefaults(
@@ -12606,9 +12697,19 @@ export class SharedLog<
12606
12697
 
12607
12698
  // Open for communcation
12608
12699
  this._onSubscriptionFn =
12609
- this._onSubscriptionFn || this._onSubscription.bind(this);
12700
+ this._onSubscriptionFn ||
12701
+ ((event) => {
12702
+ void this.runSubscriptionChangeCallback(() =>
12703
+ this._onSubscription(event),
12704
+ );
12705
+ });
12610
12706
  this._onUnsubscriptionFn =
12611
- this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
12707
+ this._onUnsubscriptionFn ||
12708
+ ((event) => {
12709
+ void this.runSubscriptionChangeCallback(() =>
12710
+ this._onUnsubscription(event),
12711
+ );
12712
+ });
12612
12713
  await Promise.all([
12613
12714
  this.rpc.open({
12614
12715
  queryType: TransportMessage,
@@ -13204,7 +13305,9 @@ export class SharedLog<
13204
13305
  if (this.closed) {
13205
13306
  return;
13206
13307
  }
13207
- this.handleSubscriptionChange(v, [this.topic], true);
13308
+ void this.runSubscriptionChangeCallback(() =>
13309
+ this.handleSubscriptionChange(v, [this.topic], true),
13310
+ );
13208
13311
  });
13209
13312
  }
13210
13313
 
@@ -14490,6 +14593,8 @@ export class SharedLog<
14490
14593
  // Match Program.end()'s synchronous terminal admission fence before any
14491
14594
  // SharedLog-specific await or observable teardown can admit a new owner.
14492
14595
  this.preventParentAttachments();
14596
+ this.stopSubscriptionChangeCallbackAdmission();
14597
+ await this.drainSubscriptionChangeCallbacks();
14493
14598
  this.ensureNativeDurabilityRuntimeState();
14494
14599
  this.cancelCurrentReplicationStateAnnouncementRetry();
14495
14600
  // Best-effort: announce that we are going offline before tearing down
@@ -14609,6 +14714,8 @@ export class SharedLog<
14609
14714
  // Adapter capability validation above is explicitly unstarted. Establish
14610
14715
  // the terminal fence only after that precondition succeeds.
14611
14716
  this.preventParentAttachments();
14717
+ this.stopSubscriptionChangeCallbackAdmission();
14718
+ await this.drainSubscriptionChangeCallbacks();
14612
14719
  this.cancelCurrentReplicationStateAnnouncementRetry();
14613
14720
  // Best-effort: announce that we are going offline before tearing down
14614
14721
  // RPC/subscription state (same reasoning as in `close()`).
@@ -17012,37 +17119,81 @@ export class SharedLog<
17012
17119
  if (context.from.equals(this.node.identity.publicKey)) {
17013
17120
  return;
17014
17121
  }
17122
+ const replicationLifecycleController =
17123
+ this._replicationLifecycleController;
17124
+ if (
17125
+ !replicationLifecycleController ||
17126
+ !this.isReplicationLifecycleActive(replicationLifecycleController)
17127
+ ) {
17128
+ return;
17129
+ }
17015
17130
 
17016
- const segments = (await this.getMyReplicationSegments()).map((x) =>
17017
- x.toReplicationRange(),
17018
- );
17131
+ let replicationSegments: ReplicationRangeIndexable<R>[];
17132
+ try {
17133
+ replicationSegments = await this.getMyReplicationSegments();
17134
+ } catch (error) {
17135
+ if (
17136
+ !this.isReplicationLifecycleActive(
17137
+ replicationLifecycleController,
17138
+ ) &&
17139
+ isNotStartedError(error as Error)
17140
+ ) {
17141
+ return;
17142
+ }
17143
+ throw error;
17144
+ }
17145
+ if (
17146
+ !this.isReplicationLifecycleActive(replicationLifecycleController)
17147
+ ) {
17148
+ return;
17149
+ }
17150
+ const segments = replicationSegments.map((x) => x.toReplicationRange());
17019
17151
 
17020
- this.rpc
17152
+ await this.rpc
17021
17153
  .send(new AllReplicatingSegmentsMessage({ segments }), {
17022
17154
  mode: new AcknowledgeDelivery({
17023
17155
  to: [context.from],
17024
17156
  redundancy: 1,
17025
17157
  }),
17158
+ signal: replicationLifecycleController.signal,
17026
17159
  })
17027
- .catch((e) => logger.error(e.toString()));
17160
+ .catch((error) =>
17161
+ this.handleReplicationLifecycleSendError(
17162
+ error,
17163
+ replicationLifecycleController,
17164
+ ),
17165
+ );
17166
+ if (
17167
+ !this.isReplicationLifecycleActive(replicationLifecycleController)
17168
+ ) {
17169
+ return;
17170
+ }
17028
17171
 
17029
17172
  // for backwards compatibility (v8) remove this when we are sure that all nodes are v9+
17030
17173
  if (this.v8Behaviour) {
17031
- const role = this.getRole();
17174
+ const role = this.getRoleFromReplicationSegments(replicationSegments);
17032
17175
  if (role instanceof Replicator) {
17033
17176
  const fixedSettings = !this._isAdaptiveReplicating;
17034
17177
  if (fixedSettings) {
17035
- await this.rpc.send(
17036
- new ResponseRoleMessage({
17037
- role,
17038
- }),
17039
- {
17040
- mode: new SilentDelivery({
17041
- to: [context.from],
17042
- redundancy: 1,
17178
+ await this.rpc
17179
+ .send(
17180
+ new ResponseRoleMessage({
17181
+ role,
17043
17182
  }),
17044
- },
17045
- );
17183
+ {
17184
+ mode: new SilentDelivery({
17185
+ to: [context.from],
17186
+ redundancy: 1,
17187
+ }),
17188
+ signal: replicationLifecycleController.signal,
17189
+ },
17190
+ )
17191
+ .catch((error) =>
17192
+ this.handleReplicationLifecycleSendError(
17193
+ error,
17194
+ replicationLifecycleController,
17195
+ ),
17196
+ );
17046
17197
  }
17047
17198
  }
17048
17199
  }
@@ -20917,16 +21068,35 @@ export class SharedLog<
20917
21068
  this._replicationInfoRequestByPeer.delete(peerHash);
20918
21069
  }
20919
21070
 
20920
- private scheduleReplicationInfoRequests(peer: PublicSignKey) {
21071
+ private scheduleReplicationInfoRequests(
21072
+ peer: PublicSignKey,
21073
+ replicationLifecycleController = this._replicationLifecycleController,
21074
+ ) {
21075
+ if (
21076
+ !replicationLifecycleController ||
21077
+ !this.isReplicationLifecycleActive(replicationLifecycleController)
21078
+ ) {
21079
+ return;
21080
+ }
20921
21081
  const peerHash = peer.hashcode();
20922
- if (this._replicationInfoRequestByPeer.has(peerHash)) {
21082
+ const requestStates = this._replicationInfoRequestByPeer;
21083
+ if (requestStates.has(peerHash)) {
20923
21084
  return;
20924
21085
  }
20925
21086
 
20926
21087
  const state: { attempts: number; timer?: ReturnType<typeof setTimeout> } = {
20927
21088
  attempts: 0,
20928
21089
  };
20929
- this._replicationInfoRequestByPeer.set(peerHash, state);
21090
+ requestStates.set(peerHash, state);
21091
+ const cancel = () => {
21092
+ if (requestStates.get(peerHash) !== state) {
21093
+ return;
21094
+ }
21095
+ if (state.timer) {
21096
+ clearTimeout(state.timer);
21097
+ }
21098
+ requestStates.delete(peerHash);
21099
+ };
20930
21100
 
20931
21101
  const intervalMs = Math.max(50, this.waitForReplicatorRequestIntervalMs);
20932
21102
  const maxAttempts =
@@ -20937,8 +21107,8 @@ export class SharedLog<
20937
21107
  );
20938
21108
 
20939
21109
  const tick = () => {
20940
- if (this.closed || this._closeController.signal.aborted) {
20941
- this.cancelReplicationInfoRequests(peerHash);
21110
+ if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
21111
+ cancel();
20942
21112
  return;
20943
21113
  }
20944
21114
 
@@ -20947,17 +21117,22 @@ export class SharedLog<
20947
21117
  this.rpc
20948
21118
  .send(new RequestReplicationInfoMessage(), {
20949
21119
  mode: new AcknowledgeDelivery({ redundancy: 1, to: [peer] }),
21120
+ signal: replicationLifecycleController.signal,
20950
21121
  })
20951
21122
  .catch((e) => {
20952
21123
  // Best-effort: missing peers / unopened RPC should not fail join flows.
20953
- if (isNotStartedError(e as Error)) {
21124
+ if (
21125
+ isNotStartedError(e as Error) ||
21126
+ (replicationLifecycleController.signal.aborted &&
21127
+ e instanceof AbortError)
21128
+ ) {
20954
21129
  return;
20955
21130
  }
20956
21131
  logger.error(e?.toString?.() ?? String(e));
20957
21132
  });
20958
21133
 
20959
21134
  if (state.attempts >= maxAttempts) {
20960
- this.cancelReplicationInfoRequests(peerHash);
21135
+ cancel();
20961
21136
  return;
20962
21137
  }
20963
21138
 
@@ -20976,6 +21151,13 @@ export class SharedLog<
20976
21151
  if (!topics.includes(this.topic)) {
20977
21152
  return;
20978
21153
  }
21154
+ const replicationLifecycleController = this._replicationLifecycleController;
21155
+ if (
21156
+ !replicationLifecycleController ||
21157
+ !this.isReplicationLifecycleActive(replicationLifecycleController)
21158
+ ) {
21159
+ return;
21160
+ }
20979
21161
 
20980
21162
  const peerHash = publicKey.hashcode();
20981
21163
  if (!subscribed) {
@@ -20997,6 +21179,9 @@ export class SharedLog<
20997
21179
  throw error;
20998
21180
  }
20999
21181
  }
21182
+ if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
21183
+ return;
21184
+ }
21000
21185
 
21001
21186
  this._replicatorJoinEmitted.delete(peerHash);
21002
21187
  this.cleanupPeerDisconnectTracking(peerHash);
@@ -21026,38 +21211,86 @@ export class SharedLog<
21026
21211
  }),
21027
21212
  {
21028
21213
  mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
21214
+ signal: replicationLifecycleController.signal,
21029
21215
  },
21030
21216
  )
21031
- .catch((e) => logger.error(e.toString()));
21217
+ .catch((error) =>
21218
+ this.handleReplicationLifecycleSendError(
21219
+ error,
21220
+ replicationLifecycleController,
21221
+ ),
21222
+ );
21032
21223
  }
21033
21224
 
21034
- const replicationSegments = await this.getMyReplicationSegments();
21225
+ let replicationSegments: ReplicationRangeIndexable<R>[];
21226
+ try {
21227
+ replicationSegments = await this.getMyReplicationSegments();
21228
+ } catch (error) {
21229
+ if (
21230
+ !this.isReplicationLifecycleActive(replicationLifecycleController) &&
21231
+ isNotStartedError(error as Error)
21232
+ ) {
21233
+ return;
21234
+ }
21235
+ throw error;
21236
+ }
21237
+ if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
21238
+ return;
21239
+ }
21035
21240
  if (replicationSegments.length > 0) {
21036
- this.rpc
21241
+ await this.rpc
21037
21242
  .send(
21038
21243
  new AllReplicatingSegmentsMessage({
21039
21244
  segments: replicationSegments.map((x) => x.toReplicationRange()),
21040
21245
  }),
21041
21246
  {
21042
21247
  mode: new AcknowledgeDelivery({ redundancy: 1, to: [publicKey] }),
21248
+ signal: replicationLifecycleController.signal,
21043
21249
  },
21044
21250
  )
21045
- .catch((e) => logger.error(e.toString()));
21251
+ .catch((error) =>
21252
+ this.handleReplicationLifecycleSendError(
21253
+ error,
21254
+ replicationLifecycleController,
21255
+ ),
21256
+ );
21257
+ if (!this.isReplicationLifecycleActive(replicationLifecycleController)) {
21258
+ return;
21259
+ }
21046
21260
 
21047
21261
  if (this.v8Behaviour) {
21048
21262
  // for backwards compatibility
21049
- this.rpc
21050
- .send(new ResponseRoleMessage({ role: await this.getRole() }), {
21051
- mode: new AcknowledgeDelivery({ redundancy: 1, to: [publicKey] }),
21052
- })
21053
- .catch((e) => logger.error(e.toString()));
21263
+ await this.rpc
21264
+ .send(
21265
+ new ResponseRoleMessage({
21266
+ role: this.getRoleFromReplicationSegments(replicationSegments),
21267
+ }),
21268
+ {
21269
+ mode: new AcknowledgeDelivery({
21270
+ redundancy: 1,
21271
+ to: [publicKey],
21272
+ }),
21273
+ signal: replicationLifecycleController.signal,
21274
+ },
21275
+ )
21276
+ .catch((error) =>
21277
+ this.handleReplicationLifecycleSendError(
21278
+ error,
21279
+ replicationLifecycleController,
21280
+ ),
21281
+ );
21054
21282
  }
21055
21283
  }
21056
21284
 
21057
21285
  // Request the remote peer's replication info. This makes joins resilient to
21058
21286
  // timing-sensitive delivery/order issues where we may miss their initial
21059
21287
  // replication announcement.
21060
- this.scheduleReplicationInfoRequests(publicKey);
21288
+ if (this.isReplicationLifecycleActive(replicationLifecycleController)) {
21289
+ this.scheduleReplicationInfoRequests(
21290
+ publicKey,
21291
+ replicationLifecycleController,
21292
+ );
21293
+ }
21061
21294
  }
21062
21295
 
21063
21296
  private getClampedReplicas(customValue?: MinReplicas) {