@dxos/messaging 0.3.1-main.cf090ba → 0.3.1-main.d8f3335

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.
@@ -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
  }