Skip to content

Commit 40fe372

Browse files
authored
fix: close short-lived connections first when pruning by tag value (#1517)
When choosing connections to close, choose shorter lived connections over longer lived ones. Closes #1509
1 parent 43d0bc6 commit 40fe372

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

doc/LIMITS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const node = await createLibp2pNode({
5353

5454
## Closing connections
5555

56-
When choosing connections to close the connection manager sorts the list of connections by the value derived from the tags given to each peer. The values of all tags are summed and connections with lower valued peers are eligible for closing first.
56+
When choosing connections to close the connection manager sorts the list of connections by the value derived from the tags given to each peer. The values of all tags are summed and connections with lower valued peers are eligible for closing first. If there are tags with equal values, the shortest-lived connection will be closed first.
5757

5858
```js
5959
// tag a peer

src/connection-manager/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,18 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
631631
return -1
632632
}
633633

634+
// if the peers have an equal tag value then we want to close short-lived connections first
635+
const connectionALifespan = a.stat.timeline.open
636+
const connectionBLifespan = b.stat.timeline.open
637+
638+
if (connectionALifespan < connectionBLifespan) {
639+
return 1
640+
}
641+
642+
if (connectionALifespan > connectionBLifespan) {
643+
return -1
644+
}
645+
634646
return 0
635647
})
636648

test/connection-manager/index.spec.ts

+55
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,61 @@ describe('Connection Manager', () => {
114114
expect(lowestSpy).to.have.property('callCount', 1)
115115
})
116116

117+
it('should close shortest-lived connection if the tag values are equal', async () => {
118+
const max = 5
119+
libp2p = await createNode({
120+
config: createBaseOptions({
121+
connectionManager: {
122+
maxConnections: max,
123+
minConnections: 2
124+
}
125+
}),
126+
started: false
127+
})
128+
129+
await libp2p.start()
130+
131+
const connectionManager = libp2p.connectionManager as DefaultConnectionManager
132+
const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections')
133+
const spies = new Map<string, sinon.SinonSpy<[], Promise<void>>>()
134+
135+
const createConnection = async (value: number, open: number = Date.now(), peerTag: string = 'test-tag') => {
136+
// #TODO: Mock the connection timeline to simulate an older connection
137+
const connection = mockConnection(mockMultiaddrConnection({ ...mockDuplex(), timeline: { open } }, await createEd25519PeerId()))
138+
const spy = sinon.spy(connection, 'close')
139+
140+
// The lowest tag value will have the longest connection
141+
spies.set(peerTag, spy)
142+
await libp2p.peerStore.tagPeer(connection.remotePeer, peerTag, {
143+
value
144+
})
145+
146+
await connectionManager._onConnect(new CustomEvent('connection', { detail: connection }))
147+
}
148+
149+
// Create one short of enough connections to iniate pruning
150+
for (let i = 1; i < max; i++) {
151+
const value = i * 10
152+
await createConnection(value)
153+
}
154+
155+
const value = 0 * 10
156+
// Add a connection with the lowest tag value BUT the longest lived connection
157+
await createConnection(value, 18000, 'longest')
158+
// Add one more connection with the lowest tag value BUT the shortest-lived connection
159+
await createConnection(value, Date.now(), 'shortest')
160+
161+
// get the lowest tagged value, but this would be also the longest lived connection
162+
const longestLivedWithLowestTagSpy = spies.get('longest')
163+
164+
// Get lowest tagged connection but with a shorter-lived connection
165+
const shortestLivedWithLowestTagSpy = spies.get('shortest')
166+
167+
expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1)
168+
expect(longestLivedWithLowestTagSpy).to.have.property('callCount', 0)
169+
expect(shortestLivedWithLowestTagSpy).to.have.property('callCount', 1)
170+
})
171+
117172
it('should close connection when the maximum has been reached even without tags', async () => {
118173
const max = 5
119174
libp2p = await createNode({

0 commit comments

Comments
 (0)