Skip to content

Commit 3a9d5f6

Browse files
authored
fix: stop dht before connection manager (#1041)
Stop the dht before the connection manager, otherwise in-flight eviction pings fail and we move on to the next one when we should just abort them all. Also pulls in the fix from #1039 and splits the auto-dialler out from the connection manager as during shutdown it can get into a weird state where it's simultaneously killing and creating connections so stop auto-dialling things before we cause connections to dip below the low watermark by killing existing connections. Fixes: ipfs/js-ipfs#3923
1 parent eacd7e8 commit 3a9d5f6

File tree

3 files changed

+127
-54
lines changed

3 files changed

+127
-54
lines changed
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict'
2+
3+
const debug = require('debug')
4+
const mergeOptions = require('merge-options')
5+
// @ts-ignore retimer does not have types
6+
const retimer = require('retimer')
7+
8+
const log = Object.assign(debug('libp2p:connection-manager:auto-dialler'), {
9+
error: debug('libp2p:connection-manager:auto-dialler:err')
10+
})
11+
12+
const defaultOptions = {
13+
enabled: true,
14+
minConnections: 0,
15+
autoDialInterval: 10000
16+
}
17+
18+
/**
19+
* @typedef {import('../index')} Libp2p
20+
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
21+
*/
22+
23+
/**
24+
* @typedef {Object} AutoDiallerOptions
25+
* @property {boolean} [enabled = true] - Should preemptively guarantee connections are above the low watermark
26+
* @property {number} [minConnections = 0] - The minimum number of connections to avoid pruning
27+
* @property {number} [autoDialInterval = 10000] - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark
28+
*/
29+
30+
class AutoDialler {
31+
/**
32+
* Proactively tries to connect to known peers stored in the PeerStore.
33+
* It will keep the number of connections below the upper limit and sort
34+
* the peers to connect based on wether we know their keys and protocols.
35+
*
36+
* @class
37+
* @param {Libp2p} libp2p
38+
* @param {AutoDiallerOptions} options
39+
*/
40+
constructor (libp2p, options = {}) {
41+
this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options)
42+
this._libp2p = libp2p
43+
this._running = false
44+
this._autoDialTimeout = null
45+
this._autoDial = this._autoDial.bind(this)
46+
47+
log('options: %j', this._options)
48+
}
49+
50+
/**
51+
* Starts the auto dialer
52+
*/
53+
start () {
54+
if (!this._options.enabled) {
55+
log('not enabled')
56+
return
57+
}
58+
59+
this._running = true
60+
this._autoDial()
61+
log('started')
62+
}
63+
64+
/**
65+
* Stops the auto dialler
66+
*/
67+
async stop () {
68+
if (!this._options.enabled) {
69+
log('not enabled')
70+
return
71+
}
72+
73+
this._running = false
74+
this._autoDialTimeout && this._autoDialTimeout.clear()
75+
log('stopped')
76+
}
77+
78+
async _autoDial () {
79+
const minConnections = this._options.minConnections
80+
81+
// Already has enough connections
82+
if (this._libp2p.connections.size >= minConnections) {
83+
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
84+
return
85+
}
86+
87+
// Sort peers on wether we know protocols of public keys for them
88+
const peers = Array.from(this._libp2p.peerStore.peers.values())
89+
.sort((a, b) => {
90+
if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) {
91+
return 1
92+
} else if (b.id.pubKey && !a.id.pubKey) {
93+
return 1
94+
}
95+
return -1
96+
})
97+
98+
for (let i = 0; this._running && i < peers.length && this._libp2p.connections.size < minConnections; i++) {
99+
if (!this._libp2p.connectionManager.get(peers[i].id)) {
100+
log('connecting to a peerStore stored peer %s', peers[i].id.toB58String())
101+
try {
102+
await this._libp2p.dialer.connectToPeer(peers[i].id)
103+
} catch (/** @type {any} */ err) {
104+
log.error('could not connect to peerStore stored peer', err)
105+
}
106+
}
107+
}
108+
109+
// Connection Manager was stopped
110+
if (!this._running) {
111+
return
112+
}
113+
114+
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
115+
}
116+
}
117+
118+
module.exports = AutoDialler

src/connection-manager/index.js

-52
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ class ConnectionManager extends EventEmitter {
9494

9595
this._started = false
9696
this._timer = null
97-
this._autoDialTimeout = null
9897
this._checkMetrics = this._checkMetrics.bind(this)
99-
this._autoDial = this._autoDial.bind(this)
10098

10199
this._latencyMonitor = new LatencyMonitor({
102100
latencyCheckIntervalMs: this._options.pollInterval,
@@ -128,8 +126,6 @@ class ConnectionManager extends EventEmitter {
128126

129127
this._started = true
130128
log('started')
131-
132-
this._options.autoDial && this._autoDial()
133129
}
134130

135131
/**
@@ -138,7 +134,6 @@ class ConnectionManager extends EventEmitter {
138134
* @async
139135
*/
140136
async stop () {
141-
this._autoDialTimeout && this._autoDialTimeout.clear()
142137
this._timer && this._timer.clear()
143138

144139
this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
@@ -312,53 +307,6 @@ class ConnectionManager extends EventEmitter {
312307
}
313308
}
314309

315-
/**
316-
* Proactively tries to connect to known peers stored in the PeerStore.
317-
* It will keep the number of connections below the upper limit and sort
318-
* the peers to connect based on wether we know their keys and protocols.
319-
*
320-
* @async
321-
* @private
322-
*/
323-
async _autoDial () {
324-
const minConnections = this._options.minConnections
325-
326-
// Already has enough connections
327-
if (this.size >= minConnections) {
328-
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
329-
return
330-
}
331-
332-
// Sort peers on wether we know protocols of public keys for them
333-
const peers = Array.from(this._libp2p.peerStore.peers.values())
334-
.sort((a, b) => {
335-
if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) {
336-
return 1
337-
} else if (b.id.pubKey && !a.id.pubKey) {
338-
return 1
339-
}
340-
return -1
341-
})
342-
343-
for (let i = 0; i < peers.length && this.size < minConnections; i++) {
344-
if (!this.get(peers[i].id)) {
345-
log('connecting to a peerStore stored peer %s', peers[i].id.toB58String())
346-
try {
347-
await this._libp2p.dialer.connectToPeer(peers[i].id)
348-
349-
// Connection Manager was stopped
350-
if (!this._started) {
351-
return
352-
}
353-
} catch (/** @type {any} */ err) {
354-
log.error('could not connect to peerStore stored peer', err)
355-
}
356-
}
357-
}
358-
359-
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
360-
}
361-
362310
/**
363311
* If we have more connections than our maximum, close a connection
364312
* to the lowest valued peer.

src/index.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { codes, messages } = require('./errors')
1818

1919
const AddressManager = require('./address-manager')
2020
const ConnectionManager = require('./connection-manager')
21+
const AutoDialler = require('./connection-manager/auto-dialler')
2122
const Circuit = require('./circuit/transport')
2223
const Relay = require('./circuit')
2324
const Dialer = require('./dialer')
@@ -193,9 +194,13 @@ class Libp2p extends EventEmitter {
193194

194195
// Create the Connection Manager
195196
this.connectionManager = new ConnectionManager(this, {
196-
autoDial: this._config.peerDiscovery.autoDial,
197197
...this._options.connectionManager
198198
})
199+
this._autodialler = new AutoDialler(this, {
200+
enabled: this._config.peerDiscovery.autoDial,
201+
minConnections: this._options.connectionManager.minConnections,
202+
autoDialInterval: this._options.connectionManager.autoDialInterval
203+
})
199204

200205
// Create Metrics
201206
if (this._options.metrics.enabled) {
@@ -380,6 +385,8 @@ class Libp2p extends EventEmitter {
380385

381386
this.relay && this.relay.stop()
382387
this.peerRouting.stop()
388+
this._autodialler.stop()
389+
await (this._dht && this._dht.stop())
383390

384391
for (const service of this._discovery.values()) {
385392
service.removeListener('peer', this._onDiscoveryPeer)
@@ -394,7 +401,6 @@ class Libp2p extends EventEmitter {
394401

395402
await Promise.all([
396403
this.pubsub && this.pubsub.stop(),
397-
this._dht && this._dht.stop(),
398404
this.metrics && this.metrics.stop()
399405
])
400406

@@ -650,6 +656,7 @@ class Libp2p extends EventEmitter {
650656
}
651657

652658
this.connectionManager.start()
659+
this._autodialler.start()
653660

654661
// Peer discovery
655662
await this._setupPeerDiscovery()

0 commit comments

Comments
 (0)