diff options
author | Feross Aboukhadijeh <feross@feross.org> | 2016-04-21 11:10:30 +0300 |
---|---|---|
committer | Feross Aboukhadijeh <feross@feross.org> | 2016-04-21 11:10:30 +0300 |
commit | 63e4aee7bd016f258d81708af9c03cf608968816 (patch) | |
tree | 9b28e5e486c8a0bcd53d18a1eed2eb57a879f2b0 /lib | |
parent | a47d2ce4b27896a4c2e2e3820f69c712c2a0de3e (diff) |
More thorough object cleanup
- Only pass `torrent.infoHash` to the Chunk Store constructor, instead
of the `Torrent`
instance itself, to prevent accidental memory leaks of the `Torrent`
object by the
store. (Open an issue if you were using other properties. They can be
re-added.)
- Non-fatal errors with a single torrent will be emitted at
`torrent.on('error')`. You
should listen to this event. Previously, all torrent errors were also
emitted on
`client.on('error')` and handling `torrent.on('error')` was optional.
This design is
better since now it is possible to distinguish between fatal client
errors
(`client.on('error')`) when the whole client becomes unusable versus
recoverable errors
where only a single torrent fails (`torrent.on('error')`) but the
client can continue to
be used. However, if there is no `torrent.on('error')` event, then
the error will be
forwarded to `client.on('error')`. This prevents crashing the client
when the user
only has a listener on the client, but it makes it impossible for
them to determine
a client error versus a torrent error.
- Errors creating a torrent with `client.seed` are now emitted on the
returned `torrent`
object instead of the client (unless there is no event listeners on
`torrent.on('error')` as previously discussed). The torrent object is
now also destroyed
automatically for the user, as was probably expected.
- If `client.get` is passed a `Torrent` instance, it now only returns
it if it is present
in the client.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/file.js | 6 | ||||
-rw-r--r-- | lib/rarity-map.js | 2 | ||||
-rw-r--r-- | lib/torrent.js | 33 |
3 files changed, 31 insertions, 10 deletions
diff --git a/lib/file.js b/lib/file.js index bfa032e..c73fc38 100644 --- a/lib/file.js +++ b/lib/file.js @@ -1,5 +1,3 @@ -// TODO: cleanup reference to torrent (i.e. Torrent object) - module.exports = File var eos = require('end-of-stream') @@ -89,3 +87,7 @@ File.prototype.renderTo = function (elem, cb) { if (typeof window === 'undefined') throw new Error('browser-only method') render.render(this, elem, cb) } + +File.prototype._destroy = function () { + this._torrent = null +} diff --git a/lib/rarity-map.js b/lib/rarity-map.js index c362077..430f62f 100644 --- a/lib/rarity-map.js +++ b/lib/rarity-map.js @@ -3,8 +3,6 @@ module.exports = RarityMap /** * Mapping of torrent pieces to their respective availability in the torrent swarm. Used * by the torrent manager for implementing the rarest piece first selection strategy. - * - * @param {Torrent} torrent */ function RarityMap (torrent) { var self = this diff --git a/lib/torrent.js b/lib/torrent.js index fc5f495..296f579 100644 --- a/lib/torrent.js +++ b/lib/torrent.js @@ -92,10 +92,10 @@ function Torrent (torrentId, client, opts) { this.metadata = null this.store = null - this.files = null + this.files = [] + this.pieces = [] this._amInterested = false - this.pieces = [] this._selections = [] this._critical = [] @@ -253,6 +253,7 @@ Torrent.prototype._onParsedTorrent = function (parsedTorrent) { if (self._rechokeIntervalId.unref) self._rechokeIntervalId.unref() self.emit('infoHash', self.infoHash) + if (self.destroyed) return // user might destroy torrent in `infoHash` event handler if (self.client.listening) { self._onListening() @@ -371,7 +372,9 @@ Torrent.prototype._onMetadata = function (metadata) { self.store = new ImmediateChunkStore( new self._store(self.pieceLength, { - torrent: self, + torrent: { + infoHash: self.infoHash + }, files: self.files.map(function (file) { return { path: path.join(self.path, file.path), @@ -464,6 +467,7 @@ Torrent.prototype._verifyPieces = function () { var self = this parallelLimit(self.pieces.map(function (_, index) { return function (cb) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) self.store.get(index, function (err, buf) { if (err) return cb(null) // ignore error sha1(buf, function (hash) { @@ -523,7 +527,7 @@ Torrent.prototype._destroy = function (err, cb) { self.destroyed = true self._debug('destroy') - self.client.remove(self) + self.client._remove(self) clearInterval(self._rechokeIntervalId) @@ -535,6 +539,10 @@ Torrent.prototype._destroy = function (err, cb) { self.removePeer(id) } + self.files.forEach(function (file) { + if (file instanceof File) file._destroy() + }) + var tasks = self._servers.map(function (server) { return function (cb) { server.destroy(cb) @@ -546,6 +554,7 @@ Torrent.prototype._destroy = function (err, cb) { self.discovery.destroy(cb) }) } + if (self.store) { tasks.push(function (cb) { self.store.close(cb) @@ -555,14 +564,26 @@ Torrent.prototype._destroy = function (err, cb) { parallel(tasks, cb) if (err) { - // When there is no `torrent.on('error')` listener, emit `client.on('error')` instead. - // The more-specific, torrent error handler is preferred. + // Torrent errors are emitted at `torrent.on('error')`. If there are no 'error' event + // handlers on the torrent instance, the error will be emitted at + // `client.on('error')`. This prevents crashing the user's program, but it makes it + // impossible to determine a client error versus a torrent error (where the client + // is still usable afterwards). Users are recommended for errors in both places + // to distinguish between the error types. if (self.listenerCount('error') === 0) { self.client.emit('error', err) } else { self.emit('error', err) } } + + self.client = null + self.files = [] + self.discovery = null + self.store = null + self._rarityMap = null + self._peers = null + self._servers = null } Torrent.prototype.addPeer = function (peer) { |