Skip to content

Commit 73deff5

Browse files
committed
fix: close short-lived connections first when pruning by tag value (#1509)
1 parent 1b30f81 commit 73deff5

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/connection-manager/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,18 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
650650
return -1
651651
}
652652

653+
// if the peers have an equal tag value then we want to close short-lived connections first
654+
const connectionALifespan = a.stat.timeline.open
655+
const connectionBLifespan = b.stat.timeline.open
656+
657+
if (connectionALifespan < connectionBLifespan) {
658+
return 1
659+
}
660+
661+
if (connectionALifespan > connectionBLifespan) {
662+
return -1
663+
}
664+
653665
return 0
654666
})
655667

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)