libp2p
Version:
JavaScript implementation of libp2p, a modular peer to peer network stack
141 lines • 5.36 kB
JavaScript
import { PeerMap } from '@libp2p/peer-collections';
import { safelyCloseConnectionIfUnused } from '@libp2p/utils/close';
import { multiaddrToIpNet } from './utils.js';
/**
* If we go over the max connections limit, choose some connections to close
*/
export class ConnectionPruner {
connectionManager;
peerStore;
allow;
events;
log;
constructor(components, init = {}) {
this.allow = (init.allow ?? []).map(ma => multiaddrToIpNet(ma));
this.connectionManager = components.connectionManager;
this.peerStore = components.peerStore;
this.events = components.events;
this.log = components.logger.forComponent('libp2p:connection-manager:connection-pruner');
this.maybePruneConnections = this.maybePruneConnections.bind(this);
}
start() {
this.events.addEventListener('connection:open', this.maybePruneConnections);
}
stop() {
this.events.removeEventListener('connection:open', this.maybePruneConnections);
}
maybePruneConnections() {
this._maybePruneConnections()
.catch(err => {
this.log.error('error while pruning connections %e', err);
});
}
/**
* If we have more connections than our maximum, select some excess connections
* to prune based on peer value
*/
async _maybePruneConnections() {
const connections = this.connectionManager.getConnections();
const numConnections = connections.length;
const maxConnections = this.connectionManager.getMaxConnections();
this.log('checking max connections limit %d/%d', numConnections, maxConnections);
if (numConnections <= maxConnections) {
return;
}
const peerValues = new PeerMap();
// work out peer values
for (const connection of connections) {
const remotePeer = connection.remotePeer;
if (peerValues.has(remotePeer)) {
continue;
}
peerValues.set(remotePeer, 0);
try {
const peer = await this.peerStore.get(remotePeer);
// sum all tag values
peerValues.set(remotePeer, [...peer.tags.values()].reduce((acc, curr) => {
return acc + curr.value;
}, 0));
}
catch (err) {
if (err.name !== 'NotFoundError') {
this.log.error('error loading peer tags', err);
}
}
}
const sortedConnections = this.sortConnections(connections, peerValues);
// close some connections
const toPrune = Math.max(numConnections - maxConnections, 0);
const toClose = [];
for (const connection of sortedConnections) {
this.log('too many connections open - closing a connection to %p', connection.remotePeer);
// check allow list
const connectionInAllowList = this.allow.some((ipNet) => {
return ipNet.contains(connection.remoteAddr.nodeAddress().address);
});
// Connections in the allow list should be excluded from pruning
if (!connectionInAllowList) {
toClose.push(connection);
}
if (toClose.length === toPrune) {
break;
}
}
// close connections
await Promise.all(toClose.map(async (connection) => {
await safelyCloseConnectionIfUnused(connection, {
signal: AbortSignal.timeout(1000)
});
}));
// despatch prune event
this.events.safeDispatchEvent('connection:prune', { detail: toClose });
}
sortConnections(connections, peerValues) {
return connections
// sort by connection age, newest to oldest
.sort((a, b) => {
const connectionALifespan = a.timeline.open;
const connectionBLifespan = b.timeline.open;
if (connectionALifespan < connectionBLifespan) {
return 1;
}
if (connectionALifespan > connectionBLifespan) {
return -1;
}
return 0;
})
// sort by direction, incoming first then outgoing
.sort((a, b) => {
if (a.direction === 'outbound' && b.direction === 'inbound') {
return 1;
}
if (a.direction === 'inbound' && b.direction === 'outbound') {
return -1;
}
return 0;
})
// sort by number of streams, lowest to highest
.sort((a, b) => {
if (a.streams.length > b.streams.length) {
return 1;
}
if (a.streams.length < b.streams.length) {
return -1;
}
return 0;
})
// sort by tag value, lowest to highest
.sort((a, b) => {
const peerAValue = peerValues.get(a.remotePeer) ?? 0;
const peerBValue = peerValues.get(b.remotePeer) ?? 0;
if (peerAValue > peerBValue) {
return 1;
}
if (peerAValue < peerBValue) {
return -1;
}
return 0;
});
}
}
//# sourceMappingURL=connection-pruner.js.map