2019-08-16 17:30:03 +02:00
|
|
|
'use strict'
|
|
|
|
|
2020-02-14 13:42:23 +00:00
|
|
|
const errcode = require('err-code')
|
2019-12-12 10:19:57 +01:00
|
|
|
const mergeOptions = require('merge-options')
|
2020-04-24 16:10:40 +01:00
|
|
|
const LatencyMonitor = require('./latency-monitor')
|
2019-08-16 17:30:03 +02:00
|
|
|
const debug = require('debug')('libp2p:connection-manager')
|
2019-12-12 10:19:57 +01:00
|
|
|
const retimer = require('retimer')
|
2019-08-16 17:30:03 +02:00
|
|
|
|
2020-02-14 13:42:23 +00:00
|
|
|
const {
|
|
|
|
ERR_INVALID_PARAMETERS
|
|
|
|
} = require('../errors')
|
|
|
|
|
2019-08-16 17:30:03 +02:00
|
|
|
const defaultOptions = {
|
2019-12-12 10:19:57 +01:00
|
|
|
maxConnections: Infinity,
|
|
|
|
minConnections: 0,
|
2019-08-16 17:30:03 +02:00
|
|
|
maxData: Infinity,
|
|
|
|
maxSentData: Infinity,
|
|
|
|
maxReceivedData: Infinity,
|
|
|
|
maxEventLoopDelay: Infinity,
|
|
|
|
pollInterval: 2000,
|
|
|
|
movingAverageInterval: 60000,
|
|
|
|
defaultPeerValue: 1
|
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
class ConnectionManager {
|
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param {Libp2p} libp2p
|
|
|
|
* @param {object} options
|
|
|
|
* @param {Number} options.maxConnections The maximum number of connections allowed. Default=Infinity
|
|
|
|
* @param {Number} options.minConnections The minimum number of connections to avoid pruning. Default=0
|
|
|
|
* @param {Number} options.maxData The max data (in and out), per average interval to allow. Default=Infinity
|
|
|
|
* @param {Number} options.maxSentData The max outgoing data, per average interval to allow. Default=Infinity
|
|
|
|
* @param {Number} options.maxReceivedData The max incoming data, per average interval to allow.. Default=Infinity
|
|
|
|
* @param {Number} options.maxEventLoopDelay The upper limit the event loop can take to run. Default=Infinity
|
|
|
|
* @param {Number} options.pollInterval How often, in milliseconds, metrics and latency should be checked. Default=2000
|
|
|
|
* @param {Number} options.movingAverageInterval How often, in milliseconds, to compute averages. Default=60000
|
|
|
|
* @param {Number} options.defaultPeerValue The value of the peer. Default=1
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
constructor (libp2p, options) {
|
|
|
|
this._libp2p = libp2p
|
2019-12-12 10:19:57 +01:00
|
|
|
this._registrar = libp2p.registrar
|
2020-01-22 11:47:30 +01:00
|
|
|
this._peerId = libp2p.peerInfo.id.toB58String()
|
fix: conn mngr min/max connection values (#528)
Fixes the case when options are passed with `maxConnections` and/or `minConnections` set to `undefined`:
```console
{
defaultOptions: {
maxConnections: Infinity,
minConnections: 0,
maxData: Infinity,
maxSentData: Infinity,
maxReceivedData: Infinity,
maxEventLoopDelay: Infinity,
pollInterval: 2000,
movingAverageInterval: 60000,
defaultPeerValue: 1
},
options: {
minPeers: 25,
maxConnections: undefined,
minConnections: undefined
}
}
{ maxConnections: undefined, minConnections: undefined }
1) "before all" hook in "custom config"
(node:67176) UnhandledPromiseRejectionWarning: AssertionError [ERR_ASSERTION]: Connection Manager maxConnections must be greater than minConnections
at new ConnectionManager (node_modules/libp2p/src/connection-manager/index.js:43:5)
at new Libp2p (node_modules/libp2p/src/index.js:92:30)
at Object.module.exports [as libp2p] (src/core/components/libp2p.js:27:10)
at Proxy.start (src/core/components/start.js:48:31)
at async Daemon.start (src/cli/daemon.js:63:31)
at async startHttpAPI (test/http-api/routes.js:29:5)
at async Context.<anonymous> (test/http-api/routes.js:48:7)
```
2020-01-06 14:03:07 +00:00
|
|
|
this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options)
|
2020-02-14 13:42:23 +00:00
|
|
|
if (this._options.maxConnections < this._options.minConnections) {
|
|
|
|
throw errcode(new Error('Connection Manager maxConnections must be greater than minConnections'), ERR_INVALID_PARAMETERS)
|
|
|
|
}
|
2019-08-16 17:30:03 +02:00
|
|
|
|
|
|
|
debug('options: %j', this._options)
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
this._metrics = libp2p.metrics
|
2019-08-16 17:30:03 +02:00
|
|
|
|
|
|
|
this._peerValues = new Map()
|
2019-12-12 10:19:57 +01:00
|
|
|
this._connections = new Map()
|
|
|
|
this._timer = null
|
|
|
|
this._checkMetrics = this._checkMetrics.bind(this)
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Starts the Connection Manager. If Metrics are not enabled on libp2p
|
|
|
|
* only event loop and connection limits will be monitored.
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
start () {
|
2019-12-12 10:19:57 +01:00
|
|
|
if (this._metrics) {
|
|
|
|
this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval)
|
|
|
|
}
|
|
|
|
|
2019-08-16 17:30:03 +02:00
|
|
|
// latency monitor
|
|
|
|
this._latencyMonitor = new LatencyMonitor({
|
2019-12-12 10:19:57 +01:00
|
|
|
latencyCheckIntervalMs: this._options.pollInterval,
|
2019-08-16 17:30:03 +02:00
|
|
|
dataEmitIntervalMs: this._options.pollInterval
|
|
|
|
})
|
|
|
|
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
|
|
|
|
this._latencyMonitor.on('data', this._onLatencyMeasure)
|
2019-12-12 10:19:57 +01:00
|
|
|
debug('started')
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Stops the Connection Manager
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
stop () {
|
2019-12-12 10:19:57 +01:00
|
|
|
this._timer && this._timer.clear()
|
|
|
|
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
|
|
|
|
debug('stopped')
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Sets the value of the given peer. Peers with lower values
|
|
|
|
* will be disconnected first.
|
|
|
|
* @param {PeerId} peerId
|
|
|
|
* @param {number} value A number between 0 and 1
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
setPeerValue (peerId, value) {
|
|
|
|
if (value < 0 || value > 1) {
|
|
|
|
throw new Error('value should be a number between 0 and 1')
|
|
|
|
}
|
2020-01-22 11:47:30 +01:00
|
|
|
if (peerId.toB58String) {
|
|
|
|
peerId = peerId.toB58String()
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
this._peerValues.set(peerId, value)
|
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Checks the libp2p metrics to determine if any values have exceeded
|
|
|
|
* the configured maximums.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_checkMetrics () {
|
|
|
|
const movingAverages = this._metrics.global.movingAverages
|
|
|
|
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
|
2019-08-16 17:30:03 +02:00
|
|
|
this._checkLimit('maxReceivedData', received)
|
2019-12-12 10:19:57 +01:00
|
|
|
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
|
2019-08-16 17:30:03 +02:00
|
|
|
this._checkLimit('maxSentData', sent)
|
|
|
|
const total = received + sent
|
|
|
|
this._checkLimit('maxData', total)
|
2019-12-12 10:19:57 +01:00
|
|
|
debug('metrics update', total)
|
|
|
|
this._timer.reschedule(this._options.pollInterval)
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Tracks the incoming connection and check the connection limit
|
|
|
|
* @param {Connection} connection
|
|
|
|
*/
|
|
|
|
onConnect (connection) {
|
2020-01-22 11:47:30 +01:00
|
|
|
const peerId = connection.remotePeer.toB58String()
|
2019-12-12 10:19:57 +01:00
|
|
|
this._connections.set(connection.id, connection)
|
|
|
|
if (!this._peerValues.has(peerId)) {
|
|
|
|
this._peerValues.set(peerId, this._options.defaultPeerValue)
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
2019-12-12 10:19:57 +01:00
|
|
|
this._checkLimit('maxConnections', this._connections.size)
|
|
|
|
}
|
2019-08-16 17:30:03 +02:00
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* Removes the connection from tracking
|
|
|
|
* @param {Connection} connection
|
|
|
|
*/
|
|
|
|
onDisconnect (connection) {
|
|
|
|
this._connections.delete(connection.id)
|
2020-01-22 11:47:30 +01:00
|
|
|
this._peerValues.delete(connection.remotePeer.toB58String())
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* If the event loop is slow, maybe close a connection
|
|
|
|
* @private
|
|
|
|
* @param {*} summary The LatencyMonitor summary
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
_onLatencyMeasure (summary) {
|
|
|
|
this._checkLimit('maxEventLoopDelay', summary.avgMs)
|
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* If the `value` of `name` has exceeded its limit, maybe close a connection
|
|
|
|
* @private
|
|
|
|
* @param {string} name The name of the field to check limits for
|
|
|
|
* @param {number} value The current value of the field
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
_checkLimit (name, value) {
|
|
|
|
const limit = this._options[name]
|
|
|
|
debug('checking limit of %s. current value: %d of %d', name, value, limit)
|
|
|
|
if (value > limit) {
|
|
|
|
debug('%s: limit exceeded: %s, %d', this._peerId, name, value)
|
|
|
|
this._maybeDisconnectOne()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 10:19:57 +01:00
|
|
|
/**
|
|
|
|
* If we have more connections than our maximum, close a connection
|
|
|
|
* to the lowest valued peer.
|
|
|
|
* @private
|
|
|
|
*/
|
2019-08-16 17:30:03 +02:00
|
|
|
_maybeDisconnectOne () {
|
2019-12-12 10:19:57 +01:00
|
|
|
if (this._options.minConnections < this._connections.size) {
|
2019-08-16 17:30:03 +02:00
|
|
|
const peerValues = Array.from(this._peerValues).sort(byPeerValue)
|
|
|
|
debug('%s: sorted peer values: %j', this._peerId, peerValues)
|
|
|
|
const disconnectPeer = peerValues[0]
|
|
|
|
if (disconnectPeer) {
|
|
|
|
const peerId = disconnectPeer[0]
|
|
|
|
debug('%s: lowest value peer is %s', this._peerId, peerId)
|
2019-12-12 10:19:57 +01:00
|
|
|
debug('%s: closing a connection to %j', this._peerId, peerId)
|
|
|
|
for (const connection of this._connections.values()) {
|
2020-01-22 11:47:30 +01:00
|
|
|
if (connection.remotePeer.toB58String() === peerId) {
|
2019-12-12 10:19:57 +01:00
|
|
|
connection.close()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-08-16 17:30:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = ConnectionManager
|
|
|
|
|
|
|
|
function byPeerValue (peerValueEntryA, peerValueEntryB) {
|
|
|
|
return peerValueEntryA[1] - peerValueEntryB[1]
|
|
|
|
}
|