Skip to content

Commit f76b27c

Browse files
authored
Merge pull request webtorrent#160 from yciabaud/lru-pruning
Limit peers in tracker server with LRU based cache fixes webtorrent#4
2 parents 38fec43 + 2c7ea4e commit f76b27c

File tree

4 files changed

+85
-45
lines changed

4 files changed

+85
-45
lines changed

lib/server/swarm.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
module.exports = Swarm
22

33
var debug = require('debug')('bittorrent-tracker')
4+
var LRU = require('lru')
45
var randomIterate = require('random-iterate')
56

67
// Regard this as the default implementation of an interface that you
78
// need to support when overriding Server.createSwarm() and Server.getSwarm()
89
function Swarm (infoHash, server) {
9-
this.peers = {}
10+
this.peers = new LRU({
11+
max: server.peersCacheLength || 1000,
12+
maxAge: server.peersCacheTtl || 900000 // 900 000ms = 15 minutes
13+
})
1014
this.complete = 0
1115
this.incomplete = 0
1216
}
1317

1418
Swarm.prototype.announce = function (params, cb) {
1519
var self = this
1620
var id = params.type === 'ws' ? params.peer_id : params.addr
17-
var peer = self.peers[id]
21+
// Mark the source peer as recently used in cache
22+
var peer = self.peers.get(id)
1823

1924
if (params.event === 'started') {
20-
self._onAnnounceStarted(params, peer)
25+
self._onAnnounceStarted(params, peer, id)
2126
} else if (params.event === 'stopped') {
22-
self._onAnnounceStopped(params, peer)
27+
self._onAnnounceStopped(params, peer, id)
2328
} else if (params.event === 'completed') {
24-
self._onAnnounceCompleted(params, peer)
29+
self._onAnnounceCompleted(params, peer, id)
2530
} else if (params.event === 'update') {
26-
self._onAnnounceUpdate(params, peer)
31+
self._onAnnounceUpdate(params, peer, id)
2732
} else {
2833
cb(new Error('invalid event'))
2934
return
@@ -42,38 +47,36 @@ Swarm.prototype.scrape = function (params, cb) {
4247
})
4348
}
4449

45-
Swarm.prototype._onAnnounceStarted = function (params, peer) {
50+
Swarm.prototype._onAnnounceStarted = function (params, peer, id) {
4651
if (peer) {
4752
debug('unexpected `started` event from peer that is already in swarm')
4853
return this._onAnnounceUpdate(params, peer) // treat as an update
4954
}
5055

5156
if (params.left === 0) this.complete += 1
5257
else this.incomplete += 1
53-
var id = params.type === 'ws' ? params.peer_id : params.addr
54-
peer = this.peers[id] = {
58+
peer = this.peers.set(id, {
5559
type: params.type,
5660
complete: params.left === 0,
5761
peerId: params.peer_id, // as hex
5862
ip: params.ip,
5963
port: params.port,
6064
socket: params.socket // only websocket
61-
}
65+
})
6266
}
6367

64-
Swarm.prototype._onAnnounceStopped = function (params, peer) {
68+
Swarm.prototype._onAnnounceStopped = function (params, peer, id) {
6569
if (!peer) {
6670
debug('unexpected `stopped` event from peer that is not in swarm')
6771
return // do nothing
6872
}
6973

7074
if (peer.complete) this.complete -= 1
7175
else this.incomplete -= 1
72-
var id = params.type === 'ws' ? params.peer_id : params.addr
73-
delete this.peers[id]
76+
this.peers.remove(id)
7477
}
7578

76-
Swarm.prototype._onAnnounceCompleted = function (params, peer) {
79+
Swarm.prototype._onAnnounceCompleted = function (params, peer, id) {
7780
if (!peer) {
7881
debug('unexpected `completed` event from peer that is not in swarm')
7982
return this._onAnnounceStarted(params, peer) // treat as a start
@@ -86,9 +89,10 @@ Swarm.prototype._onAnnounceCompleted = function (params, peer) {
8689
this.complete += 1
8790
this.incomplete -= 1
8891
peer.complete = true
92+
this.peers.set(id, peer)
8993
}
9094

91-
Swarm.prototype._onAnnounceUpdate = function (params, peer) {
95+
Swarm.prototype._onAnnounceUpdate = function (params, peer, id) {
9296
if (!peer) {
9397
debug('unexpected `update` event from peer that is not in swarm')
9498
return this._onAnnounceStarted(params, peer) // treat as a start
@@ -98,15 +102,18 @@ Swarm.prototype._onAnnounceUpdate = function (params, peer) {
98102
this.complete += 1
99103
this.incomplete -= 1
100104
peer.complete = true
105+
this.peers.set(id, peer)
101106
}
102107
}
103108

104109
Swarm.prototype._getPeers = function (numwant, ownPeerId, isWebRTC) {
105110
var peers = []
106-
var ite = randomIterate(Object.keys(this.peers))
111+
var ite = randomIterate(this.peers.keys)
107112
var peerId
108113
while ((peerId = ite()) && peers.length < numwant) {
109-
var peer = this.peers[peerId]
114+
// Don't mark the peer as most recently used on announce
115+
var peer = this.peers.peek(peerId)
116+
if (!peer) continue
110117
if (isWebRTC && peer.peerId === ownPeerId) continue // don't send peer to itself
111118
if ((isWebRTC && peer.type !== 'ws') || (!isWebRTC && peer.type === 'ws')) continue // send proper peer type
112119
peers.push(peer)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"hat": "0.0.3",
2929
"inherits": "^2.0.1",
3030
"ip": "^1.0.1",
31+
"lru": "^3.0.0",
3132
"minimist": "^1.1.1",
3233
"once": "^1.3.0",
3334
"random-iterate": "^1.0.1",

server.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ function Server (opts) {
5151
self._trustProxy = !!opts.trustProxy
5252
if (typeof opts.filter === 'function') self._filter = opts.filter
5353

54+
self.peersCacheLength = opts.peersCacheLength
55+
self.peersCacheTtl = opts.peersCacheTtl
56+
5457
self._listenCalled = false
5558
self.listening = false
5659
self.destroyed = false
@@ -191,7 +194,7 @@ function Server (opts) {
191194
if (req.method === 'GET' && (req.url === '/stats' || req.url === '/stats.json')) {
192195
infoHashes.forEach(function (infoHash) {
193196
var peers = self.torrents[infoHash].peers
194-
var keys = Object.keys(peers)
197+
var keys = peers.keys
195198
if (keys.length > 0) activeTorrents++
196199

197200
keys.forEach(function (peerId) {
@@ -203,7 +206,8 @@ function Server (opts) {
203206
leecher: false
204207
}
205208
}
206-
var peer = peers[peerId]
209+
// Don't mark the peer as most recently used for stats
210+
var peer = peers.peek(peerId)
207211
if (peer.ip.indexOf(':') >= 0) {
208212
allPeers[peerId].ipv6 = true
209213
} else {
@@ -533,7 +537,8 @@ Server.prototype._onWebSocketRequest = function (socket, opts, params) {
533537
if (!swarm) {
534538
return self.emit('warning', new Error('no swarm with that `info_hash`'))
535539
}
536-
var toPeer = swarm.peers[params.to_peer_id]
540+
// Mark the destination peer as recently used in cache
541+
var toPeer = swarm.peers.get(params.to_peer_id)
537542
if (!toPeer) {
538543
return self.emit('warning', new Error('no peer with that `to_peer_id`'))
539544
}

test/server.js

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ var test = require('tape')
1212
var infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705'
1313
var peerId = Buffer.from('01234567890123456789')
1414
var peerId2 = Buffer.from('12345678901234567890')
15+
var peerId3 = Buffer.from('23456789012345678901')
1516

1617
function serverTest (t, serverType, serverFamily) {
17-
t.plan(30)
18+
t.plan(32)
1819

1920
var hostname = serverFamily === 'inet6'
2021
? '[::1]'
@@ -23,7 +24,12 @@ function serverTest (t, serverType, serverFamily) {
2324
? '::1'
2425
: '127.0.0.1'
2526

26-
common.createServer(t, serverType, function (server) {
27+
var opts = {
28+
serverType: serverType,
29+
peersCacheLength: 2
30+
}
31+
32+
common.createServer(t, opts, function (server) {
2733
var port = server[serverType].address().port
2834
var announceUrl = serverType + '://' + hostname + ':' + port + '/announce'
2935

@@ -52,22 +58,23 @@ function serverTest (t, serverType, serverFamily) {
5258
t.equal(Object.keys(server.torrents).length, 1)
5359
t.equal(swarm.complete, 0)
5460
t.equal(swarm.incomplete, 1)
55-
t.equal(Object.keys(swarm.peers).length, 1)
61+
t.equal(swarm.peers.length, 1)
5662

5763
var id = serverType === 'ws'
5864
? peerId.toString('hex')
5965
: hostname + ':6881'
6066

61-
t.equal(swarm.peers[id].type, serverType)
62-
t.equal(swarm.peers[id].ip, clientIp)
63-
t.equal(swarm.peers[id].peerId, peerId.toString('hex'))
64-
t.equal(swarm.peers[id].complete, false)
67+
var peer = swarm.peers.peek(id)
68+
t.equal(peer.type, serverType)
69+
t.equal(peer.ip, clientIp)
70+
t.equal(peer.peerId, peerId.toString('hex'))
71+
t.equal(peer.complete, false)
6572
if (serverType === 'ws') {
66-
t.equal(typeof swarm.peers[id].port, 'number')
67-
t.ok(swarm.peers[id].socket)
73+
t.equal(typeof peer.port, 'number')
74+
t.ok(peer.socket)
6875
} else {
69-
t.equal(swarm.peers[id].port, 6881)
70-
t.notOk(swarm.peers[id].socket)
76+
t.equal(peer.port, 6881)
77+
t.notOk(peer.socket)
7178
}
7279

7380
client1.complete()
@@ -102,22 +109,42 @@ function serverTest (t, serverType, serverFamily) {
102109
client2.once('peer', function (addr) {
103110
t.ok(addr === hostname + ':6881' || addr === hostname + ':6882' || addr.id === peerId.toString('hex'))
104111

105-
client2.stop()
106-
client2.once('update', function (data) {
107-
t.equal(data.announce, announceUrl)
108-
t.equal(data.complete, 1)
109-
t.equal(data.incomplete, 0)
110-
client2.destroy()
112+
swarm.peers.once('evict', function (evicted) {
113+
t.equals(evicted.value.peerId, peerId.toString('hex'))
114+
})
115+
var client3 = new Client({
116+
infoHash: infoHash,
117+
announce: [ announceUrl ],
118+
peerId: peerId3,
119+
port: 6880
120+
// wrtc: wrtc
121+
})
122+
client3.start()
123+
124+
server.once('start', function () {
125+
t.pass('got start message from client3')
126+
})
111127

112-
client1.stop()
113-
client1.once('update', function (data) {
128+
client3.once('update', function () {
129+
client2.stop()
130+
client2.once('update', function (data) {
114131
t.equal(data.announce, announceUrl)
115-
t.equal(data.complete, 0)
116-
t.equal(data.incomplete, 0)
117-
118-
client1.destroy(function () {
119-
server.close()
120-
// if (serverType === 'ws') wrtc.close()
132+
t.equal(data.complete, 1)
133+
t.equal(data.incomplete, 1)
134+
client2.destroy()
135+
136+
client3.stop()
137+
client3.once('update', function (data) {
138+
t.equal(data.announce, announceUrl)
139+
t.equal(data.complete, 1)
140+
t.equal(data.incomplete, 0)
141+
142+
client3.destroy(function () {
143+
client1.destroy(function () {
144+
server.close()
145+
})
146+
// if (serverType === 'ws') wrtc.close()
147+
})
121148
})
122149
})
123150
})

0 commit comments

Comments
 (0)