@dxos/messaging 0.1.58-main.ffbb8a1 → 0.1.58-next.3349da8

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/src/messenger.ts CHANGED
@@ -2,9 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { TimeoutError, scheduleExponentialBackoffTaskInterval, scheduleTask } from '@dxos/async';
5
+ import { TimeoutError, scheduleExponentialBackoffTaskInterval, scheduleTask, scheduleTaskInterval } from '@dxos/async';
6
6
  import { Any } from '@dxos/codec-protobuf';
7
7
  import { Context } from '@dxos/context';
8
+ import { TimeoutError as ProtocolTimeoutError } from '@dxos/errors';
8
9
  import { invariant } from '@dxos/invariant';
9
10
  import { PublicKey } from '@dxos/keys';
10
11
  import { log } from '@dxos/log';
@@ -26,6 +27,11 @@ export interface MessengerOptions {
26
27
  const ReliablePayload = schema.getCodecForType('dxos.mesh.messaging.ReliablePayload');
27
28
  const Acknowledgement = schema.getCodecForType('dxos.mesh.messaging.Acknowledgement');
28
29
 
30
+ const ERROR_LIMIT = 5;
31
+ const NETWORK_REBOOT_DELAY = 3_000;
32
+
33
+ const RECEIVED_MESSAGES_GC_INTERVAL = 120_000;
34
+
29
35
  /**
30
36
  * Reliable messenger that works trough signal network.
31
37
  */
@@ -41,11 +47,18 @@ export class Messenger {
41
47
 
42
48
  private readonly _onAckCallbacks = new ComplexMap<PublicKey, () => void>(PublicKey.hash);
43
49
 
44
- private readonly _receivedMessages = new ComplexSet<PublicKey>((key) => key.toHex());
50
+ private readonly _receivedMessages = new ComplexSet<PublicKey>(PublicKey.hash);
51
+
52
+ /**
53
+ * Keys scheduled to be cleared from _receivedMessages on the next iteration.
54
+ */
55
+ private readonly _toClear = new ComplexSet<PublicKey>(PublicKey.hash);
45
56
 
46
57
  private _ctx!: Context;
47
58
  private _closed = true;
48
59
  private readonly _retryDelay: number;
60
+ private _errorCount = 0;
61
+ private _rebooting = false;
49
62
 
50
63
  constructor({ signalManager, retryDelay = 300 }: MessengerOptions) {
51
64
  this._signalManager = signalManager;
@@ -69,6 +82,16 @@ export class Messenger {
69
82
  await this._handleMessage(message);
70
83
  }),
71
84
  );
85
+
86
+ // Clear the map periodically.
87
+ scheduleTaskInterval(
88
+ this._ctx,
89
+ async () => {
90
+ this._performGc();
91
+ },
92
+ RECEIVED_MESSAGES_GC_INTERVAL,
93
+ );
94
+
72
95
  this._closed = false;
73
96
  log.trace('dxos.mesh.messenger.open', trace.end({ id: traceId }));
74
97
  }
@@ -114,10 +137,16 @@ export class Messenger {
114
137
 
115
138
  scheduleTask(
116
139
  messageContext,
117
- () => {
140
+ async () => {
118
141
  log('message not delivered', { messageId: reliablePayload.messageId });
119
142
  this._onAckCallbacks.delete(reliablePayload.messageId!);
120
- timeoutHit(new TimeoutError(MESSAGE_TIMEOUT, 'Message not delivered'));
143
+ await this.errorLimitCheck();
144
+ timeoutHit(
145
+ new ProtocolTimeoutError(
146
+ 'signaling message not delivered',
147
+ new TimeoutError(MESSAGE_TIMEOUT, 'Message not delivered'),
148
+ ),
149
+ );
121
150
  void messageContext.dispose();
122
151
  },
123
152
  MESSAGE_TIMEOUT,
@@ -133,6 +162,33 @@ export class Messenger {
133
162
  return promise;
134
163
  }
135
164
 
165
+ private async errorLimitCheck() {
166
+ log(`checking error limit ${this._errorCount}`);
167
+ if (this._errorCount++ > ERROR_LIMIT) {
168
+ await this.rebootNetwork();
169
+ }
170
+ }
171
+
172
+ private async rebootNetwork() {
173
+ if (this._rebooting) {
174
+ return;
175
+ }
176
+ this._rebooting = true;
177
+
178
+ log('rebooting Messenger/SignalManager');
179
+ await this.close();
180
+ await this._signalManager.close();
181
+ log('pausing');
182
+ await new Promise((resolve) => setTimeout(resolve, NETWORK_REBOOT_DELAY));
183
+ log('done pausing');
184
+ await this.open();
185
+ await this._signalManager.open();
186
+ log('done rebooting');
187
+
188
+ this._errorCount = 0;
189
+ this._rebooting = false;
190
+ }
191
+
136
192
  /**
137
193
  * Subscribes onMessage function to messages that contains payload with payloadType.
138
194
  * @param payloadType if not specified, onMessage will be subscribed to all types of messages.
@@ -279,6 +335,23 @@ export class Messenger {
279
335
  }
280
336
  }
281
337
  }
338
+
339
+ private _performGc() {
340
+ const start = performance.now();
341
+
342
+ for (const key of this._toClear.keys()) {
343
+ this._receivedMessages.delete(key);
344
+ }
345
+ this._toClear.clear();
346
+ for (const key of this._receivedMessages.keys()) {
347
+ this._toClear.add(key);
348
+ }
349
+
350
+ const elapsed = performance.now() - start;
351
+ if (elapsed > 100) {
352
+ log.warn('GC took too long', { elapsed });
353
+ }
354
+ }
282
355
  }
283
356
 
284
357
  export interface ListeningHandle {