fix: memory leaks

This commit is contained in:
ThaUnknown 2023-06-14 20:35:21 +02:00
parent 68c05a71f9
commit 5e5aa7b6ab
4 changed files with 393 additions and 329 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "Miru", "name": "Miru",
"version": "4.0.8", "version": "4.0.9",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>", "author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.", "description": "Stream anime torrents, real-time with no waiting for downloads.",
"main": "build/main.js", "main": "build/main.js",
@ -43,7 +43,7 @@
"webpack": "^5.85.0", "webpack": "^5.85.0",
"webpack-cli": "^5.1.3", "webpack-cli": "^5.1.3",
"webpack-dev-server": "^4.15.0", "webpack-dev-server": "^4.15.0",
"webtorrent": "^2.0.37" "webtorrent": "^2.1.0"
}, },
"standard": { "standard": {
"ignore": [ "ignore": [

File diff suppressed because it is too large Load diff

View file

@ -51,13 +51,19 @@ class TorrentClient extends WebTorrent {
async handleMessage ({ data }) { async handleMessage ({ data }) {
switch (data.type) { switch (data.type) {
case 'current': { case 'current': {
this.current?.removeListener('done', this.boundParse)
this.cancelParse()
this.current = null
this.metadata = null
this.parsed = false
if (data.data) { if (data.data) {
this.current = (await this.get(data.data.infoHash))?.files.find(file => file.path === data.data.path) const found = (await this.get(data.data.infoHash))?.files.find(file => file.path === data.data.path)
if (this.current) {
this.current?.removeListener('done', this.boundParse)
this.current?.removeAllListeners('iterator')
// this is a patch, idfk why these leak
for (const iterator of this.current._iterators) {
iterator.destroy()
}
}
this.cancelParse()
this.parsed = false
this.current = found
if (this.current?.name.endsWith('.mkv')) { if (this.current?.name.endsWith('.mkv')) {
// if (this.current.done) this.parseSubtitles() // if (this.current.done) this.parseSubtitles()
// this.current.once('done', this.boundParse) // this.current.once('done', this.boundParse)
@ -125,22 +131,23 @@ class TorrentClient extends WebTorrent {
cancelParse () { cancelParse () {
this.parser?.destroy() this.parser?.destroy()
this.metadata?.destroy()
this.metadata = undefined
this.parser = undefined this.parser = undefined
} }
parseFonts (file) { parseFonts (file) {
const stream = new SubtitleParser(file) this.metadata = new SubtitleParser(file)
this.handleSubtitleParser(stream) this.handleSubtitleParser(this.metadata)
stream.once('tracks', tracks => { this.metadata.once('tracks', tracks => {
if (!tracks.length) { if (!tracks.length) {
this.parsed = true this.parsed = true
stream.destroy() this.metadata.destroy()
} }
}) })
stream.once('subtitle', () => { this.metadata.once('subtitle', () => {
stream.destroy() this.metadata.destroy()
}) })
this.metadata = stream
} }
handleSubtitleParser (parser, skipFile) { handleSubtitleParser (parser, skipFile) {

View file

@ -1,6 +1,5 @@
import { EbmlIteratorDecoder, EbmlTagId } from 'ebml-iterator' import { EbmlIteratorDecoder, EbmlTagId } from 'ebml-iterator'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import join from 'join-async-iterator'
import { inflate } from 'pako' import { inflate } from 'pako'
const SSA_TYPES = new Set(['ssa', 'ass']) const SSA_TYPES = new Set(['ssa', 'ass'])
@ -53,10 +52,7 @@ export class SubtitleParserBase extends EventEmitter {
[EbmlTagId.BlockGroup]: this.handleBlockGroup.bind(this), [EbmlTagId.BlockGroup]: this.handleBlockGroup.bind(this),
[EbmlTagId.Chapters]: this.handleChapters.bind(this) [EbmlTagId.Chapters]: this.handleChapters.bind(this)
} }
} this.decoder = new EbmlIteratorDecoder({
async * [Symbol.asyncIterator] (stream) {
const decoder = new EbmlIteratorDecoder({
bufferTagIds: [ bufferTagIds: [
EbmlTagId.TimecodeScale, EbmlTagId.TimecodeScale,
EbmlTagId.Tracks, EbmlTagId.Tracks,
@ -64,20 +60,17 @@ export class SubtitleParserBase extends EventEmitter {
EbmlTagId.AttachedFile, EbmlTagId.AttachedFile,
EbmlTagId.Chapters, EbmlTagId.Chapters,
EbmlTagId.Duration EbmlTagId.Duration
], ]
stream
}) })
}
for await (const chunk of stream) { decoderWrite (chunk) {
if (this.destroyed) return null const tags = this.decoder.parseTags(chunk)
const tags = decoder.parseTags(chunk) for (const tag of tags) {
for (const tag of tags) { this._tagMap[tag.id]?.(tag)
this._tagMap[tag.id]?.(tag) if (tag.id === EbmlTagId.Tracks) {
if (tag.id === EbmlTagId.Tracks) { if (!tag.Children.some(({ id }) => id === EbmlTagId.TrackEntry)) return this.destroy()
if (!tag.Children.some(({ id }) => id === EbmlTagId.TrackEntry)) return this.destroy()
}
} }
yield chunk
} }
} }
@ -200,12 +193,16 @@ export class SubtitleParser extends SubtitleParserBase {
super() super()
;(async () => { ;(async () => {
const iterator = stream[Symbol.asyncIterator]()
try { try {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
for await (const _ of super[Symbol.asyncIterator](stream)) { for await (const chunk of iterator) {
if (this.destroyed) break if (this.destroyed) return iterator.return()
this.decoderWrite(chunk)
} }
} catch (e) {} } catch (e) {
iterator.return()
}
this.emit('finish') this.emit('finish')
})() })()
} }
@ -227,35 +224,40 @@ export class SubtitleStream extends SubtitleParserBase {
} }
} }
async * [Symbol.asyncIterator] (stream = this._stream) { destroy () {
while (true) { this.destroyed = true
if (this.destroyed) return this.emit('finish')
if (this.unstable) { this._stream.return()
const iterator = stream[Symbol.asyncIterator]() }
const { value: chunk } = await iterator.next()
if (!chunk) return async * [Symbol.asyncIterator] () {
// the ebml decoder expects to see a tag, so we won't use it until we find a cluster try {
for (let i = 0; i < chunk.length - 12; i++) { for await (const chunk of this._stream) {
// cluster id 0x1F43B675 if (this.destroyed) return this._stream.return()
// https://matroska.org/technical/elements.html#LevelCluster if (this.unstable) {
if (chunk[i] === 0x1f && chunk[i + 1] === 0x43 && chunk[i + 2] === 0xb6 && chunk[i + 3] === 0x75) { // the ebml decoder expects to see a tag, so we won't use it until we find a cluster
// length of cluster size tag for (let i = 0; i < chunk.length - 12; i++) {
const len = 8 - Math.floor(Math.log2(chunk[i + 4])) // cluster id 0x1F43B675
// first tag in cluster is a valid EbmlTag // https://matroska.org/technical/elements.html#LevelCluster
if (EbmlTagId[chunk[i + 4 + len]]) { if (chunk[i] === 0x1f && chunk[i + 1] === 0x43 && chunk[i + 2] === 0xb6 && chunk[i + 3] === 0x75) {
// okay this is probably a cluster // length of cluster size tag
this.unstable = false const len = 8 - Math.floor(Math.log2(chunk[i + 4]))
yield chunk.slice(0, i) // first tag in cluster is a valid EbmlTag
yield * super[Symbol.asyncIterator](join([[chunk.slice(i)], iterator])) if (EbmlTagId[chunk[i + 4 + len]]) {
return // okay this is probably a cluster
this.unstable = false
this.decoderWrite(chunk.slice(i))
}
} }
} }
} else {
this.decoderWrite(chunk)
} }
yield chunk yield chunk
} else {
yield * super[Symbol.asyncIterator](stream)
return
} }
} finally {
this._stream.return()
} }
this._stream.return()
} }
} }