comq 0.16.0 → 0.16.1
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/features/connection.feature +14 -0
- package/features/silence.feature +16 -0
- package/package.json +1 -1
- package/source/connection.js +21 -9
- package/test/connection.test.js +31 -1
|
@@ -18,6 +18,20 @@ Feature: Connection Tolerance
|
|
|
18
18
|
When the broker is up
|
|
19
19
|
Then the connection is restored
|
|
20
20
|
|
|
21
|
+
Scenario: Restoring singleton connection after broker restart
|
|
22
|
+
Given an active singleton connection
|
|
23
|
+
When the broker is down
|
|
24
|
+
Then the connection is lost
|
|
25
|
+
When the broker is up
|
|
26
|
+
Then the connection is restored
|
|
27
|
+
|
|
28
|
+
Scenario: Restoring sharded singleton connection after broker crash
|
|
29
|
+
Given an active sharded singleton connection
|
|
30
|
+
When the broker has crashed
|
|
31
|
+
Then the connection is lost
|
|
32
|
+
When the broker is up
|
|
33
|
+
Then the connection is restored
|
|
34
|
+
|
|
21
35
|
Scenario: Connecting with wrong credentials
|
|
22
36
|
When I attempt to connect to the broker as "someone" with password "whatever"
|
|
23
37
|
Then an exception is thrown: "Handshake terminated by server: 403 (ACCESS-REFUSED)"
|
package/features/silence.feature
CHANGED
|
@@ -24,6 +24,22 @@ Feature: Silent Connection Tolerance
|
|
|
24
24
|
When the consumer sends a request to the `echo` queue
|
|
25
25
|
Then the consumer receives the reply
|
|
26
26
|
|
|
27
|
+
Scenario: Restoring a singleton connection that has gone silent
|
|
28
|
+
Given an active singleton connection
|
|
29
|
+
And a producer replying `echo` queue
|
|
30
|
+
When the network goes silent
|
|
31
|
+
Then the connection is lost within 10 seconds
|
|
32
|
+
When the consumer sends a request to the `echo` queue
|
|
33
|
+
Then the consumer receives the reply
|
|
34
|
+
|
|
35
|
+
Scenario: Restoring a sharded singleton connection whose every shard has gone silent
|
|
36
|
+
Given an active sharded singleton connection
|
|
37
|
+
And a producer replying `echo` queue
|
|
38
|
+
When the network goes silent
|
|
39
|
+
Then the connection is lost within 10 seconds
|
|
40
|
+
When the consumer sends a request to the `echo` queue
|
|
41
|
+
Then the consumer receives the reply
|
|
42
|
+
|
|
27
43
|
Scenario: Answering a request sent as the connection goes silent
|
|
28
44
|
Given an active connection to the broker
|
|
29
45
|
And a producer replying `echo` queue
|
package/package.json
CHANGED
package/source/connection.js
CHANGED
|
@@ -64,17 +64,10 @@ class Connection {
|
|
|
64
64
|
async open () {
|
|
65
65
|
this.#closed = false
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.#opening = retry(this.#open).finally(() => { this.#opening = null })
|
|
70
|
-
|
|
71
|
-
await this.#opening
|
|
67
|
+
await this.#reopen()
|
|
72
68
|
|
|
73
69
|
if (this.#closed) return
|
|
74
70
|
|
|
75
|
-
// close may have landed after this attempt succeeded but before `#opening` was cleared
|
|
76
|
-
if (this.#connection === undefined) return this.open()
|
|
77
|
-
|
|
78
71
|
this.#running = true
|
|
79
72
|
}
|
|
80
73
|
|
|
@@ -172,10 +165,29 @@ class Connection {
|
|
|
172
165
|
this.#connection = undefined
|
|
173
166
|
|
|
174
167
|
if (error !== undefined && !this.#closed) {
|
|
175
|
-
this
|
|
168
|
+
this.#reopen().catch((exception) => this.#diagnostics.emit('error', exception))
|
|
176
169
|
}
|
|
177
170
|
}
|
|
178
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Establishes the connection, or joins the attempt under way. A lost connection is
|
|
174
|
+
* restored through here and not through `open()`: what a subclass makes of `open()` is
|
|
175
|
+
* its own — the singleton answers it with the first opening, made once and remembered,
|
|
176
|
+
* which is no way to make a second one.
|
|
177
|
+
*
|
|
178
|
+
* @return {Promise<void>}
|
|
179
|
+
*/
|
|
180
|
+
async #reopen () {
|
|
181
|
+
this.#opening ??= retry(this.#open).finally(() => { this.#opening = null })
|
|
182
|
+
|
|
183
|
+
await this.#opening
|
|
184
|
+
|
|
185
|
+
if (this.#closed) return
|
|
186
|
+
|
|
187
|
+
// close may have landed after this attempt succeeded but before `#opening` was cleared
|
|
188
|
+
if (this.#connection === undefined) return this.#reopen()
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
/**
|
|
180
192
|
* An AMQP connection is only closed once the broker has replied with Close-Ok,
|
|
181
193
|
* which never happens on a connection that has already been lost.
|
package/test/connection.test.js
CHANGED
|
@@ -184,7 +184,7 @@ describe('reconnection', () => {
|
|
|
184
184
|
|
|
185
185
|
const boom = new Error('reconnect failed')
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
amqplib.connect.mockRejectedValueOnce(boom)
|
|
188
188
|
|
|
189
189
|
conn.emit('close', new Error('broker down'))
|
|
190
190
|
|
|
@@ -194,8 +194,38 @@ describe('reconnection', () => {
|
|
|
194
194
|
|
|
195
195
|
expect(errors).toContain(boom)
|
|
196
196
|
expect(unhandled).not.toHaveBeenCalled()
|
|
197
|
+
|
|
198
|
+
// the attempt is retried in the background, and would connect in a later case
|
|
199
|
+
await connection.close()
|
|
197
200
|
})
|
|
198
201
|
|
|
202
|
+
// the singleton answers `open()` with the first opening, made once and remembered
|
|
203
|
+
it('should reconnect a connection whose open() is remembered', async () => {
|
|
204
|
+
class Remembered extends Connection {
|
|
205
|
+
#opened = null
|
|
206
|
+
|
|
207
|
+
async open () {
|
|
208
|
+
this.#opened ??= super.open()
|
|
209
|
+
|
|
210
|
+
await this.#opened
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const remembered = new Remembered(generate())
|
|
215
|
+
|
|
216
|
+
await remembered.open()
|
|
217
|
+
|
|
218
|
+
const first = await amqplib.connect.mock.results.at(-1).value
|
|
219
|
+
|
|
220
|
+
first.emit('close', new Error('lost'))
|
|
221
|
+
|
|
222
|
+
await expect(remembered.createChannel('event')).resolves.toBeDefined()
|
|
223
|
+
|
|
224
|
+
expect(amqplib.connect).toHaveBeenCalledTimes(3)
|
|
225
|
+
|
|
226
|
+
await remembered.close()
|
|
227
|
+
}, 10000)
|
|
228
|
+
|
|
199
229
|
it('should reconnect when connection is lost while channels recover', async () => {
|
|
200
230
|
const channel = await connection.createChannel('request')
|
|
201
231
|
|