comq 0.27.0 → 0.28.0
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/.github/workflows/features.yaml +25 -0
- package/docs/shards.md +72 -0
- package/features/steps/call.js +25 -1
- package/package.json +1 -1
- package/readme.md +15 -12
- package/source/shards/connection.js +110 -23
- package/source/shards/name.js +299 -0
- package/source/shards/resolve.js +12 -13
- package/test/shards/connection.test.js +68 -1
- package/test/shards/name.test.js +348 -0
- package/test/shards/resolve.test.js +47 -1
- package/types/shards.d.ts +16 -3
- package/source/shards/watch.js +0 -257
- package/test/shards/watch.test.js +0 -264
|
@@ -37,3 +37,28 @@ jobs:
|
|
|
37
37
|
|
|
38
38
|
- name: Run fast feature tests
|
|
39
39
|
run: npm run features:fast
|
|
40
|
+
|
|
41
|
+
# a minor or patch bump by Dependabot is merged once its features pass
|
|
42
|
+
automerge:
|
|
43
|
+
needs: features-fast
|
|
44
|
+
if: github.actor == 'dependabot[bot]' && startsWith(github.ref_name, 'dependabot/')
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 5
|
|
47
|
+
permissions:
|
|
48
|
+
contents: write
|
|
49
|
+
pull-requests: write
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- name: Merge
|
|
53
|
+
env:
|
|
54
|
+
GH_TOKEN: ${{ github.token }}
|
|
55
|
+
MESSAGE: ${{ github.event.head_commit.message }}
|
|
56
|
+
run: |
|
|
57
|
+
types=$(grep -oP 'update-type: version-update:semver-\K\w+' <<< "$MESSAGE" || true)
|
|
58
|
+
|
|
59
|
+
if [ -z "$types" ] || grep -qvx -e minor -e patch <<< "$types"; then
|
|
60
|
+
echo "Not a minor or patch bump, left for a review"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
gh pr merge "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --merge --match-head-commit "$GITHUB_SHA"
|
package/docs/shards.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changing the number of brokers
|
|
2
|
+
|
|
3
|
+
How brokers are added to and removed from under a connection that follows
|
|
4
|
+
[names](../readme.md#virtual-shards), with everyone connected staying as they are.
|
|
5
|
+
|
|
6
|
+
## Names
|
|
7
|
+
|
|
8
|
+
- **As many names as there may ever be brokers.** A broker is in the pool while at least one name
|
|
9
|
+
stands for it, so the number of names is the most brokers there can be. Names cost nothing:
|
|
10
|
+
thirty-two of them over two brokers are two connections.
|
|
11
|
+
- **How many names a broker has does not matter.** A publication goes to a broker chosen at random
|
|
12
|
+
among those in the pool, not to a name: a broker with one name out of thirty-two takes as much as
|
|
13
|
+
one with the rest.
|
|
14
|
+
- **One address per name.** A name with several addresses is still one broker, connected through
|
|
15
|
+
one of them. Records that rotate between brokers spread nothing.
|
|
16
|
+
- **A short TTL.** Unless [given](../readme.md#timing), the `settle` and `linger` times are the
|
|
17
|
+
longest TTL of the names and no less than 15 minutes, so the TTL is what a change takes.
|
|
18
|
+
- **The same names for everyone.** One who connects by addresses, or by names of their own, follows
|
|
19
|
+
nothing, and is cut off once a broker is removed.
|
|
20
|
+
|
|
21
|
+
## Adding a broker
|
|
22
|
+
|
|
23
|
+
1. **Start the broker and give it what the others have**: the virtual hosts, the users and their
|
|
24
|
+
permissions.
|
|
25
|
+
2. **Copy the exchanges, the queues and the bindings between them** from a broker in the pool.
|
|
26
|
+
Everyone follows the names on their own, so the one who emits an Event may join the new broker
|
|
27
|
+
before the one who consumes it has: an exchange with no queue bound to it yet drops what is
|
|
28
|
+
published to it. A Request needs none of this, its queue being declared by the one who sends it.
|
|
29
|
+
|
|
30
|
+
Queues exclusive to a connection, and their bindings, are not to be copied: they are declared
|
|
31
|
+
by the connection that owns them when it joins.
|
|
32
|
+
3. **Point names at it.** Which ones and how many is of no consequence, so move as few as it
|
|
33
|
+
takes for the names to be spread evenly: these are the ones to move back once it is removed.
|
|
34
|
+
4. **See the connections arrive.** A `settle` time after the records have changed, everyone who
|
|
35
|
+
follows the names is connected to the new broker as well as to the others.
|
|
36
|
+
|
|
37
|
+
Nothing is taken away while a broker is being added: a name pointing at a broker that is not there
|
|
38
|
+
yet, or not reachable, retires no one.
|
|
39
|
+
|
|
40
|
+
## Removing a broker
|
|
41
|
+
|
|
42
|
+
1. **Point its names at the others.** A `settle` time later nothing new is published to it. It is
|
|
43
|
+
still consumed from, and what was sent through it is still answered through it.
|
|
44
|
+
2. **Wait until it has no connections.** Each one leaves by itself once, for the `linger` time,
|
|
45
|
+
it has had nothing to do there. There is no deadline: a connection with a message still in
|
|
46
|
+
work, or a Request still unanswered, stays.
|
|
47
|
+
3. **Look at what is left** before the broker is stopped:
|
|
48
|
+
- *a connection that stays* is of one who does not follow the names, or of one whose work
|
|
49
|
+
there does not end;
|
|
50
|
+
- *a message in a queue nobody consumes* had no consumer before the broker was retired
|
|
51
|
+
either, and goes with the broker.
|
|
52
|
+
4. **Stop the broker.**
|
|
53
|
+
|
|
54
|
+
Until it is stopped, the removal is undone by pointing a name back at the broker: it is in the
|
|
55
|
+
pool again once the answer has settled.
|
|
56
|
+
|
|
57
|
+
## Replacing a broker
|
|
58
|
+
|
|
59
|
+
Both of the above in one change of the records: the names of the old broker are pointed at the new
|
|
60
|
+
one. The new broker is joined before the old one is retired, so the order need not be kept by hand.
|
|
61
|
+
The new broker is prepared as [one being added](#adding-a-broker) is, and the old one is stopped as
|
|
62
|
+
[one being removed](#removing-a-broker) is.
|
|
63
|
+
|
|
64
|
+
## What to expect
|
|
65
|
+
|
|
66
|
+
- A change of the records may be made in any order and need not be made at once: nothing is acted
|
|
67
|
+
upon until the answers have stayed the same for the `settle` time.
|
|
68
|
+
- A broker that is in the pool and goes out of reach is a [lost shard](../readme.md#sharded-connection),
|
|
69
|
+
whatever the names say: what was sent through it and is not answered is sent again through the
|
|
70
|
+
others. Retiring a broker first is what makes its removal lose nothing.
|
|
71
|
+
- No one is restarted or reconfigured. One who starts while the names are moving connects to what
|
|
72
|
+
they resolve to then, and follows them from there.
|
package/features/steps/call.js
CHANGED
|
@@ -6,7 +6,7 @@ const { Given, When, Then, After } = require('@cucumber/cucumber')
|
|
|
6
6
|
|
|
7
7
|
const { connect, Unroutable } = require('../../')
|
|
8
8
|
const { parse } = require('./yaml')
|
|
9
|
-
const { getAddress, USER, PASSWORD } = require('./brokers')
|
|
9
|
+
const { getAddress, USER, PASSWORD, BROKERS_AMOUNT } = require('./brokers')
|
|
10
10
|
const { timeout, until } = require('../../test/helpers')
|
|
11
11
|
|
|
12
12
|
Given('a holder answering {token} under the {token} key:',
|
|
@@ -35,6 +35,8 @@ Given('a holder on another connection answering {token} under the {token} key',
|
|
|
35
35
|
this.holder = await another(this)
|
|
36
36
|
|
|
37
37
|
await this.holder.back(exchange, key, echo)
|
|
38
|
+
|
|
39
|
+
if (this.sharded) await everywhere(exchange, key)
|
|
38
40
|
})
|
|
39
41
|
|
|
40
42
|
Given('a holder on another connection answering {token} under the {token} key in {number}ms',
|
|
@@ -494,6 +496,28 @@ function url (broker) {
|
|
|
494
496
|
return `amqp://${USER}:${PASSWORD}@${getAddress(broker)}`
|
|
495
497
|
}
|
|
496
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Waits for a key to be held on every broker. It is held once any shard holds it, and on a
|
|
501
|
+
* broker where the holder of the scenario before still has it, it is claimed for a while yet:
|
|
502
|
+
* a scenario that takes the other broker down by then has nobody to call.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} exchange
|
|
505
|
+
* @param {string} key
|
|
506
|
+
*/
|
|
507
|
+
async function everywhere (exchange, key) {
|
|
508
|
+
for (let broker = 0; broker < BROKERS_AMOUNT; broker++) {
|
|
509
|
+
const caller = await connect(url(broker))
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
const held = await eventually(() => caller.call(exchange, key, { key }, { timeout: 1000 }), 30)
|
|
513
|
+
|
|
514
|
+
assert.equal(held, true, `The key is not held on broker ${broker}`)
|
|
515
|
+
} finally {
|
|
516
|
+
await caller.close()
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
497
521
|
/**
|
|
498
522
|
* Makes an attempt until it succeeds, or until the time is up.
|
|
499
523
|
*
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -514,7 +514,7 @@ await io.close()
|
|
|
514
514
|
|
|
515
515
|
### Virtual shards
|
|
516
516
|
|
|
517
|
-
|
|
517
|
+
Where the URLs of a sharded connection are *names*, several of them may stand for one broker. A
|
|
518
518
|
connection is made to each broker rather than to each name: thirty-two names resolving to two
|
|
519
519
|
addresses are two connections.
|
|
520
520
|
|
|
@@ -523,7 +523,7 @@ const io = await connect('amqp://rmq0.example.com', 'amqp://rmq1.example.com', /
|
|
|
523
523
|
```
|
|
524
524
|
|
|
525
525
|
This is what lets the number of brokers change without anyone being reconfigured: the names stay,
|
|
526
|
-
and what they resolve to moves.
|
|
526
|
+
and what they resolve to moves. See [how a broker is added, removed or replaced](docs/shards.md).
|
|
527
527
|
|
|
528
528
|
A broker is told by its address, port, virtual host and credentials. A name with several
|
|
529
529
|
addresses is still one broker, connected through one of them: two connections to one broker
|
|
@@ -534,9 +534,12 @@ for different brokers.
|
|
|
534
534
|
|
|
535
535
|
The names are resolved again every `interval`. What they resolve to is seldom seen changing
|
|
536
536
|
whole: records are updated one after another, and until their TTL has run out caches answer
|
|
537
|
-
with the old address and the new one in turn. So a change is acted upon only once
|
|
538
|
-
stayed the same for the `settle` time, and anything else observed meanwhile starts
|
|
539
|
-
anew. A name that fails to resolve keeps its address, which is reported as `error`.
|
|
537
|
+
with the old address and the new one in turn. So a change of a name is acted upon only once its
|
|
538
|
+
answer has stayed the same for the `settle` time, and anything else observed meanwhile starts
|
|
539
|
+
that time anew. A name that fails to resolve keeps its address, which is reported as `error`.
|
|
540
|
+
|
|
541
|
+
A name is resolved once per process, however many connections stand on it: those made with the
|
|
542
|
+
same timing share what it resolves to, and each is told when it has changed.
|
|
540
543
|
|
|
541
544
|
Then the brokers are brought in line with the names, making before breaking:
|
|
542
545
|
|
|
@@ -570,18 +573,18 @@ The trailing argument of `connect` takes it next to the [topology settings](#set
|
|
|
570
573
|
milliseconds:
|
|
571
574
|
|
|
572
575
|
```javascript
|
|
573
|
-
await connect(...urls, { resolution: { interval:
|
|
576
|
+
await connect(...urls, { resolution: { interval: 300_000, settle: 900_000, linger: 900_000 } })
|
|
574
577
|
```
|
|
575
578
|
|
|
576
|
-
- `interval`: how often the names are resolved,
|
|
579
|
+
- `interval`: how often the names are resolved, 5 minutes by default.
|
|
577
580
|
- `settle`: how long an answer is to hold before it is acted upon.
|
|
578
581
|
- `linger`: how long a retired broker is to stay idle before it is left.
|
|
579
582
|
|
|
580
|
-
Unless given, `settle`
|
|
581
|
-
no less than
|
|
582
|
-
and says nothing of records being updated one by one. A name that is not found
|
|
583
|
-
from the hosts file, or one completed by the resolver's search list) has no
|
|
584
|
-
|
|
583
|
+
Unless given, `settle` is the longest TTL a name has been answered with, `linger` the longest
|
|
584
|
+
of the names, and both no less than 15 minutes: a TTL bounds how long a cache may answer with what
|
|
585
|
+
is no longer true, and says nothing of records being updated one by one. A name that is not found
|
|
586
|
+
in DNS itself (one from the hosts file, or one completed by the resolver's search list) has no
|
|
587
|
+
TTL, and makes them 30 minutes.
|
|
585
588
|
|
|
586
589
|
## Singleton connection
|
|
587
590
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const events = require('../events')
|
|
4
4
|
const channel = require('./channel')
|
|
5
5
|
const emitter = require('../emitter')
|
|
6
|
-
const {
|
|
6
|
+
const { Name, INTERVAL_MS, FLOOR_MS } = require('./name')
|
|
7
7
|
const { hostOf, locate } = require('./resolve')
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -23,8 +23,17 @@ class Connection {
|
|
|
23
23
|
/** @type {(url: string, servername?: string) => comq.Connection} */
|
|
24
24
|
#create
|
|
25
25
|
|
|
26
|
-
/** @type {
|
|
27
|
-
#
|
|
26
|
+
/** @type {string[]} the names the URLs are of */
|
|
27
|
+
#hosts
|
|
28
|
+
|
|
29
|
+
/** @type {comq.shards.Timing} */
|
|
30
|
+
#timing
|
|
31
|
+
|
|
32
|
+
/** @type {Name[]} the names followed while open */
|
|
33
|
+
#names = []
|
|
34
|
+
|
|
35
|
+
/** @type {Array<() => void>} what stops following each of them */
|
|
36
|
+
#leaving = []
|
|
28
37
|
|
|
29
38
|
/** @type {Map<string, string>} the address of each name */
|
|
30
39
|
#addresses = new Map()
|
|
@@ -38,6 +47,9 @@ class Connection {
|
|
|
38
47
|
/** @type {number} the number the next shard gets: one is never given twice */
|
|
39
48
|
#next = 0
|
|
40
49
|
|
|
50
|
+
/** @type {Set<string>} the brokers that failed to join, asked again only once an interval is up */
|
|
51
|
+
#refused = new Set()
|
|
52
|
+
|
|
41
53
|
/** @type {Set<comq.Channel>} */
|
|
42
54
|
#channels = new Set()
|
|
43
55
|
|
|
@@ -58,17 +70,14 @@ class Connection {
|
|
|
58
70
|
this.#urls = urls
|
|
59
71
|
this.#create = create
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.#watch = new Watch(hosts, timing, this.#urgent)
|
|
64
|
-
this.#watch.on('change', (addresses) => this.#follow(addresses))
|
|
65
|
-
this.#watch.on('error', (exception) => this.#diagnostics.emit('error', exception))
|
|
73
|
+
this.#hosts = Array.from(new Set(urls.map(hostOf).filter((host) => host !== undefined)))
|
|
74
|
+
this.#timing = timing ?? {}
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
async open () {
|
|
69
78
|
this.#closed = false
|
|
70
79
|
|
|
71
|
-
const addresses = await this.#
|
|
80
|
+
const addresses = await this.#start()
|
|
72
81
|
|
|
73
82
|
if (this.#closed) return
|
|
74
83
|
|
|
@@ -95,7 +104,7 @@ class Connection {
|
|
|
95
104
|
async close () {
|
|
96
105
|
this.#closed = true
|
|
97
106
|
this.#opened = false
|
|
98
|
-
this.#
|
|
107
|
+
this.#unfollow()
|
|
99
108
|
|
|
100
109
|
clearTimeout(this.#inspection)
|
|
101
110
|
|
|
@@ -191,7 +200,7 @@ class Connection {
|
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
// a connection that is lost may be so because its broker has moved
|
|
194
|
-
const lost = (error) => { if (error !== undefined) this.#
|
|
203
|
+
const lost = (error) => { if (error !== undefined) this.#poke(shard.address) }
|
|
195
204
|
|
|
196
205
|
connection.diagnose('close', lost)
|
|
197
206
|
shard.listeners.push(['close', lost])
|
|
@@ -241,14 +250,74 @@ class Connection {
|
|
|
241
250
|
}
|
|
242
251
|
|
|
243
252
|
/**
|
|
244
|
-
*
|
|
253
|
+
* Follows the names, each of which is shared with whoever else in the process follows it.
|
|
254
|
+
*
|
|
255
|
+
* @return {Promise<Map<string, string>>} the address of each name
|
|
245
256
|
*/
|
|
246
|
-
#
|
|
247
|
-
this.#
|
|
248
|
-
|
|
257
|
+
async #start () {
|
|
258
|
+
this.#unfollow()
|
|
259
|
+
|
|
260
|
+
const follower = { change: this.#follow, error: this.#error, urgent: this.#urgent }
|
|
261
|
+
|
|
262
|
+
this.#names = this.#hosts.map((host) => Name.of(host, this.#timing))
|
|
263
|
+
this.#leaving = this.#names.map((name) => name.follow(follower))
|
|
264
|
+
|
|
265
|
+
await Promise.all(this.#names.map((name) => name.start()))
|
|
266
|
+
|
|
267
|
+
// a name followed by others may have moved while the rest were being resolved
|
|
268
|
+
return new Map(this.#names.map((name) => [name.host, /** @type {string} */ (name.address)]))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#unfollow () {
|
|
272
|
+
for (const leave of this.#leaving) leave()
|
|
273
|
+
|
|
274
|
+
this.#names = []
|
|
275
|
+
this.#leaving = []
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Looks now at the names that stand for a broker.
|
|
280
|
+
*
|
|
281
|
+
* @param {string | undefined} address
|
|
282
|
+
*/
|
|
283
|
+
#poke (address) {
|
|
284
|
+
for (const name of this.#names) {
|
|
285
|
+
if (this.#addresses.get(name.host) === address) name.poke()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {string} host
|
|
291
|
+
* @param {string} address
|
|
292
|
+
*/
|
|
293
|
+
#follow = (host, address) => {
|
|
294
|
+
if (this.#addresses.get(host) === address) return
|
|
295
|
+
|
|
296
|
+
this.#addresses.set(host, address)
|
|
297
|
+
this.#wanted = this.#locations(this.#addresses)
|
|
249
298
|
this.#reconcile()
|
|
250
299
|
}
|
|
251
300
|
|
|
301
|
+
/**
|
|
302
|
+
* @param {Error} exception
|
|
303
|
+
*/
|
|
304
|
+
#error = (exception) => {
|
|
305
|
+
this.#diagnostics.emit('error', exception)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** How often the shards are looked at again. */
|
|
309
|
+
get #interval () {
|
|
310
|
+
return this.#timing.interval ?? INTERVAL_MS
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* How long a retired broker is to stay idle before it is left: as long as any of the names
|
|
315
|
+
* may be cached for, unless told.
|
|
316
|
+
*/
|
|
317
|
+
get #linger () {
|
|
318
|
+
return this.#timing.linger ?? Math.max(FLOOR_MS, ...this.#names.map((name) => name.period))
|
|
319
|
+
}
|
|
320
|
+
|
|
252
321
|
/**
|
|
253
322
|
* Brings the shards in line with the brokers the names stand for, making before breaking:
|
|
254
323
|
* nothing is retired until every broker that is named has joined, so a name that points at
|
|
@@ -257,13 +326,16 @@ class Connection {
|
|
|
257
326
|
#reconcile () {
|
|
258
327
|
if (!this.#opened || this.#closed) return
|
|
259
328
|
|
|
329
|
+
let made = true
|
|
330
|
+
|
|
260
331
|
for (const [url, location] of this.#wanted) {
|
|
332
|
+
if (this.#shardOf(url) !== undefined) continue
|
|
333
|
+
|
|
261
334
|
// one that is being left is joined anew once it has been
|
|
262
|
-
if (this.#
|
|
335
|
+
if (this.#refused.has(url)) made = false
|
|
336
|
+
else this.#join(location).catch(noop)
|
|
263
337
|
}
|
|
264
338
|
|
|
265
|
-
let made = true
|
|
266
|
-
|
|
267
339
|
for (const shard of this.#shards.values()) {
|
|
268
340
|
const named = this.#wanted.has(shard.url)
|
|
269
341
|
|
|
@@ -295,7 +367,7 @@ class Connection {
|
|
|
295
367
|
this.#diagnostics.emit('error', exception, shard.index)
|
|
296
368
|
this.#dismiss(shard)
|
|
297
369
|
|
|
298
|
-
return this.#
|
|
370
|
+
return this.#defer(location.url)
|
|
299
371
|
}
|
|
300
372
|
|
|
301
373
|
if (shard.state !== JOINING) return
|
|
@@ -369,10 +441,25 @@ class Connection {
|
|
|
369
441
|
// one that never joined is not said to have left
|
|
370
442
|
if (retired) this.#diagnostics.emit(LEAVE, shard.index, shard.address)
|
|
371
443
|
|
|
372
|
-
if (failed) this.#
|
|
444
|
+
if (failed) this.#defer(shard.url)
|
|
373
445
|
else this.#reconcile()
|
|
374
446
|
}
|
|
375
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Asks a broker that failed to join again once an interval is up, rather than whenever
|
|
450
|
+
* another name moves meanwhile.
|
|
451
|
+
*
|
|
452
|
+
* @param {string} url
|
|
453
|
+
*/
|
|
454
|
+
#defer (url) {
|
|
455
|
+
this.#refused.add(url)
|
|
456
|
+
|
|
457
|
+
this.#later(() => {
|
|
458
|
+
this.#refused.delete(url)
|
|
459
|
+
this.#reconcile()
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
|
|
376
463
|
/**
|
|
377
464
|
* Looks at the retiring shards for as long as there are any.
|
|
378
465
|
*/
|
|
@@ -403,7 +490,7 @@ class Connection {
|
|
|
403
490
|
*/
|
|
404
491
|
async #idle (shard) {
|
|
405
492
|
const now = Date.now()
|
|
406
|
-
const linger = this.#
|
|
493
|
+
const linger = this.#linger
|
|
407
494
|
|
|
408
495
|
if (shard.connection.connected === false) {
|
|
409
496
|
shard.down ??= now
|
|
@@ -424,7 +511,7 @@ class Connection {
|
|
|
424
511
|
* @return {ReturnType<setTimeout>}
|
|
425
512
|
*/
|
|
426
513
|
#later (fn) {
|
|
427
|
-
const timer = setTimeout(() => Promise.resolve().then(fn).catch(noop), this.#
|
|
514
|
+
const timer = setTimeout(() => Promise.resolve().then(fn).catch(noop), this.#interval)
|
|
428
515
|
|
|
429
516
|
timer.unref()
|
|
430
517
|
|
|
@@ -437,7 +524,7 @@ class Connection {
|
|
|
437
524
|
* @param {Error} exception
|
|
438
525
|
*/
|
|
439
526
|
async #cancel (openings, shards, exception) {
|
|
440
|
-
this.#
|
|
527
|
+
this.#unfollow()
|
|
441
528
|
|
|
442
529
|
const settled = await Promise.allSettled(openings)
|
|
443
530
|
const opened = shards.filter((_, index) => settled[index].status === 'fulfilled')
|