@dxos/messaging 0.3.0 → 0.3.1-main.0fddbd4

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
@@ -26,9 +26,6 @@ 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
29
  const RECEIVED_MESSAGES_GC_INTERVAL = 120_000;
33
30
 
34
31
  /**
@@ -56,8 +53,6 @@ export class Messenger {
56
53
  private _ctx!: Context;
57
54
  private _closed = true;
58
55
  private readonly _retryDelay: number;
59
- private _errorCount = 0;
60
- private _rebooting = false;
61
56
 
62
57
  constructor({ signalManager, retryDelay = 300 }: MessengerOptions) {
63
58
  this._signalManager = signalManager;
@@ -136,10 +131,9 @@ export class Messenger {
136
131
 
137
132
  scheduleTask(
138
133
  messageContext,
139
- async () => {
134
+ () => {
140
135
  log('message not delivered', { messageId: reliablePayload.messageId });
141
136
  this._onAckCallbacks.delete(reliablePayload.messageId!);
142
- await this.errorLimitCheck();
143
137
  timeoutHit(
144
138
  new ProtocolTimeoutError(
145
139
  'signaling message not delivered',
@@ -161,33 +155,6 @@ export class Messenger {
161
155
  return promise;
162
156
  }
163
157
 
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
-
191
158
  /**
192
159
  * Subscribes onMessage function to messages that contains payload with payloadType.
193
160
  * @param payloadType if not specified, onMessage will be subscribed to all types of messages.
@@ -2,19 +2,21 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import { Event, synchronized } from '@dxos/async';
5
+ import { Event, sleep, synchronized } 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 { trace } from '@dxos/protocols';
11
+ import { RateLimitExceededError, trace } from '@dxos/protocols';
12
12
  import { Runtime } from '@dxos/protocols/proto/dxos/config';
13
13
  import { SwarmEvent } from '@dxos/protocols/proto/dxos/mesh/signal';
14
14
 
15
15
  import { SignalManager } from './signal-manager';
16
16
  import { CommandTrace, SignalClient, SignalStatus } from '../signal-client';
17
17
 
18
+ const MAX_SERVER_FAILURES = 5;
19
+ const WSS_SIGNAL_SERVER_REBOOT_DELAY = 3_000;
18
20
  /**
19
21
  * Manages connection to multiple Signal Servers over WebSocket
20
22
  */
@@ -24,6 +26,7 @@ export class WebsocketSignalManager implements SignalManager {
24
26
  private _ctx!: Context;
25
27
  private _opened = false;
26
28
 
29
+ readonly failureCount = new Map<string, number>();
27
30
  readonly statusChanged = new Event<SignalStatus[]>();
28
31
  readonly commandTrace = new Event<CommandTrace>();
29
32
  readonly swarmEvent = new Event<{
@@ -53,6 +56,7 @@ export class WebsocketSignalManager implements SignalManager {
53
56
  server.statusChanged.on(() => this.statusChanged.emit(this.getStatus()));
54
57
 
55
58
  this._servers.set(host.server, server);
59
+ this.failureCount.set(host.server, 0);
56
60
  server.commandTrace.on((trace) => this.commandTrace.emit(trace));
57
61
  }
58
62
  }
@@ -73,6 +77,7 @@ export class WebsocketSignalManager implements SignalManager {
73
77
  log.trace('dxos.mesh.websocket-signal-manager.open', trace.end({ id: this._instanceId }));
74
78
  }
75
79
 
80
+ @synchronized
76
81
  async close() {
77
82
  if (!this._opened) {
78
83
  return;
@@ -84,6 +89,18 @@ export class WebsocketSignalManager implements SignalManager {
84
89
  await Promise.all(Array.from(this._servers.values()).map((server) => server.close()));
85
90
  }
86
91
 
92
+ async restartServer(serverName: string) {
93
+ log('Restarting server', { serverName });
94
+ invariant(this._opened, 'server already closed');
95
+
96
+ const server = this._servers.get(serverName);
97
+ invariant(server, 'server not found');
98
+
99
+ await server.close();
100
+ await sleep(WSS_SIGNAL_SERVER_REBOOT_DELAY);
101
+ await server.open();
102
+ }
103
+
87
104
  getStatus(): SignalStatus[] {
88
105
  return Array.from(this._servers.values()).map((server) => server.getStatus());
89
106
  }
@@ -112,14 +129,33 @@ export class WebsocketSignalManager implements SignalManager {
112
129
  recipient: PublicKey;
113
130
  payload: Any;
114
131
  }): Promise<void> {
115
- log(`Signal ${recipient}`);
132
+ log(`Signal ${recipient.truncate()}`);
116
133
  invariant(this._opened, 'Closed');
117
134
 
118
- void this._forEachServer(async (server) => {
119
- void server.sendMessage({ author, recipient, payload }).catch((err) => log(err));
135
+ void this._forEachServer(async (server, serverName) => {
136
+ void server.sendMessage({ author, recipient, payload }).catch((err) => {
137
+ if (err instanceof RateLimitExceededError) {
138
+ log('WSS rate limit exceeded', { err });
139
+ } else {
140
+ log(`error sending to ${serverName}`, { err });
141
+ void this.checkServerFailure(serverName);
142
+ }
143
+ });
120
144
  });
121
145
  }
122
146
 
147
+ @synchronized
148
+ async checkServerFailure(serverName: string) {
149
+ const failureCount = this.failureCount.get(serverName!) ?? 0;
150
+ if (failureCount > MAX_SERVER_FAILURES) {
151
+ log.warn(`Too many failures sending to ${serverName} (${failureCount} > ${MAX_SERVER_FAILURES}), restarting`);
152
+ await this.restartServer(serverName!);
153
+ this.failureCount.set(serverName!, 0);
154
+ return;
155
+ }
156
+ this.failureCount.set(serverName!, (this.failureCount.get(serverName!) ?? 0) + 1);
157
+ }
158
+
123
159
  async subscribeMessages(peerId: PublicKey) {
124
160
  log(`Subscribed for message stream peerId=${peerId}`);
125
161
  invariant(this._opened, 'Closed');
@@ -140,7 +176,9 @@ export class WebsocketSignalManager implements SignalManager {
140
176
  });
141
177
  }
142
178
 
143
- private async _forEachServer<ReturnType>(fn: (server: SignalClient) => Promise<ReturnType>): Promise<ReturnType[]> {
144
- return Promise.all(Array.from(this._servers.values()).map(fn));
179
+ private async _forEachServer<ReturnType>(
180
+ fn: (server: SignalClient, serverName: string) => Promise<ReturnType>,
181
+ ): Promise<ReturnType[]> {
182
+ return Promise.all(Array.from(this._servers.entries()).map(([serverName, server]) => fn(server, serverName)));
145
183
  }
146
184
  }