@jambonz/mrf 0.1.1 → 0.1.2
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/lib/mediaserver.js +68 -15
- package/package.json +1 -1
- package/test/client.test.js +12 -3
- package/test/support/mock-server.js +2 -2
package/lib/mediaserver.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
const { EventEmitter } = require('events');
|
|
2
2
|
const Endpoint = require('./endpoint');
|
|
3
|
+
const Connection = require('./connection');
|
|
4
|
+
|
|
5
|
+
const RECONNECT_DELAY_MS = 500;
|
|
6
|
+
const RECONNECT_DELAY_MAX_MS = 5000;
|
|
7
|
+
|
|
8
|
+
function clientInfo() {
|
|
9
|
+
const { name, version } = require('../package.json');
|
|
10
|
+
return `${name}/${version}`;
|
|
11
|
+
}
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* MediaServer represents one connection to a mediajam server, exposing the
|
|
@@ -28,25 +37,69 @@ class MediaServer extends EventEmitter {
|
|
|
28
37
|
// fsmrf compatibility: feature-server listens on ms.conn for esl events
|
|
29
38
|
this.conn = new EventEmitter();
|
|
30
39
|
|
|
40
|
+
this._wireConnection(connection);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_wireConnection(connection) {
|
|
44
|
+
this._connection = connection;
|
|
31
45
|
connection.on('evt', (frame) => this._onEvent(frame));
|
|
32
46
|
connection.on('stats', (data) => this._onStats(data));
|
|
33
|
-
connection.on('close', () =>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
connection.on('close', () => this._onConnectionClose());
|
|
48
|
+
connection.on('error', (err) => this.emit('error', err));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* An unexpected loss of the media server ends its endpoints (the
|
|
52
|
+
* server died with them) but NOT this MediaServer: mediajam restarts
|
|
53
|
+
* (deploys) must not take the feature-server down with them, so we
|
|
54
|
+
* quietly redial with backoff and re-emit esl::ready on recovery —
|
|
55
|
+
* mirroring the ESL auto-reconnect of the freeswitch world. esl::end
|
|
56
|
+
* is never emitted for a transient loss because the feature-server
|
|
57
|
+
* treats it as fatal (it tears down drachtio when its last media
|
|
58
|
+
* server is gone). A self-initiated destroy()/disconnect() tears down
|
|
59
|
+
* quietly with no reconnect. */
|
|
60
|
+
_onConnectionClose() {
|
|
61
|
+
this.connected = false;
|
|
62
|
+
if (this._selfDestroyed) {
|
|
63
|
+
this._endpoints.clear();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
for (const [, ep] of this._endpoints) {
|
|
67
|
+
ep._onEvent('endpoint.destroyed', { reason: 'connectionLost' });
|
|
68
|
+
}
|
|
69
|
+
this._endpoints.clear();
|
|
70
|
+
this.emit('disconnect');
|
|
71
|
+
this._reconnect();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async _reconnect() {
|
|
75
|
+
if (this._reconnecting) return;
|
|
76
|
+
this._reconnecting = true;
|
|
77
|
+
this.logger?.info(`mediajam at ${this.address}:${this.port} lost; reconnecting`);
|
|
78
|
+
let delay = RECONNECT_DELAY_MS;
|
|
79
|
+
let attempts = 0;
|
|
80
|
+
while (!this._selfDestroyed) {
|
|
81
|
+
// unref: a pending reconnect must not hold the process open
|
|
82
|
+
await new Promise((r) => setTimeout(r, delay).unref());
|
|
83
|
+
try {
|
|
84
|
+
const connection = new Connection(this.logger);
|
|
85
|
+
const helloData = await connection.connect(
|
|
86
|
+
{ address: this.address, port: this.port }, clientInfo());
|
|
87
|
+
this._wireConnection(connection);
|
|
88
|
+
this._reconnecting = false;
|
|
89
|
+
this._onHello(helloData);
|
|
90
|
+
this.logger?.info(`reconnected to mediajam at ${this.address}:${this.port}` +
|
|
91
|
+
` after ${attempts + 1} attempt(s)`);
|
|
40
92
|
return;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
attempts++;
|
|
95
|
+
if (attempts % 10 === 0) {
|
|
96
|
+
this.logger?.info(`still unable to reach mediajam at ${this.address}:${this.port}` +
|
|
97
|
+
` (${attempts} attempts): ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
delay = Math.min(delay * 2, RECONNECT_DELAY_MAX_MS);
|
|
41
100
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
this._endpoints.clear();
|
|
46
|
-
this.conn.emit('esl::end');
|
|
47
|
-
this.emit('disconnect');
|
|
48
|
-
});
|
|
49
|
-
connection.on('error', (err) => this.emit('error', err));
|
|
101
|
+
}
|
|
102
|
+
this._reconnecting = false;
|
|
50
103
|
}
|
|
51
104
|
|
|
52
105
|
_onHello(helloData) {
|
package/package.json
CHANGED
package/test/client.test.js
CHANGED
|
@@ -174,19 +174,28 @@ test('stats update mediaserver gauges', async(t) => {
|
|
|
174
174
|
assert.equal(ms.cpuIdle, 88);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
test('connection loss destroys endpoints
|
|
177
|
+
test('connection loss destroys endpoints and reconnects', async() => {
|
|
178
178
|
const mock = new MockMediajam();
|
|
179
179
|
const port = await mock.listen();
|
|
180
180
|
const mrf = new Mrf();
|
|
181
181
|
const ms = await mrf.connect({ address: '127.0.0.1', port });
|
|
182
182
|
const ep = await ms.createEndpoint({});
|
|
183
183
|
const destroyed = new Promise((resolve) => ep.once('destroy', resolve));
|
|
184
|
-
const
|
|
184
|
+
const disconnected = new Promise((resolve) => ms.once('disconnect', resolve));
|
|
185
|
+
const ready = new Promise((resolve) => ms.conn.once('esl::ready', resolve));
|
|
185
186
|
mock.close();
|
|
186
187
|
for (const socket of mock.sockets) socket.destroy();
|
|
187
188
|
const evt = await destroyed;
|
|
188
|
-
await
|
|
189
|
+
await disconnected;
|
|
189
190
|
assert.equal(evt.reason, 'connectionLost');
|
|
191
|
+
// esl::end must NOT fire on transient loss (it is fatal to the
|
|
192
|
+
// feature-server); the wrapper redials and re-emits esl::ready
|
|
193
|
+
ms.conn.once('esl::end', () => assert.fail('esl::end fired on transient loss'));
|
|
194
|
+
await mock.listen(port);
|
|
195
|
+
await ready;
|
|
196
|
+
assert.equal(ms.connected, true);
|
|
197
|
+
ms.destroy();
|
|
198
|
+
mock.close();
|
|
190
199
|
});
|
|
191
200
|
|
|
192
201
|
test('setLogLevel changes and queries server log level', async(t) => {
|
|
@@ -14,10 +14,10 @@ class MockMediajam {
|
|
|
14
14
|
this.requests = []; // every req frame received, for assertions
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
listen() {
|
|
17
|
+
listen(port = 0) {
|
|
18
18
|
return new Promise((resolve) => {
|
|
19
19
|
this.server = net.createServer((socket) => this._onConnection(socket));
|
|
20
|
-
this.server.listen(
|
|
20
|
+
this.server.listen(port, '127.0.0.1', () => {
|
|
21
21
|
this.port = this.server.address().port;
|
|
22
22
|
resolve(this.port);
|
|
23
23
|
});
|