@dxos/messaging 0.1.57 → 0.1.58-main.0e9c99e

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