对于使用AsyncIterator的y的x的等待,将导致内存泄漏

时间:2019-10-18 16:27:36

标签: node.js typescript memory-leaks async-await iterator

使用AsyncIterator时,在for-x-of-y中使用时会发生实质性内存泄漏

在抓取包含有关下一个要抓取的HTML页面的信息的HTML页面时,我需要这样做:

  1. 报废数据
  2. 评估数据
  3. 抓取下一个数据

由于axios用于获取HTML,因此需要异步部分

这是一个复制程序,它可以使脚本结尾处的内存从4MB增加到25MB。直到程序终止,才释放内存。

const scraper = async ():Promise<void> => {
    let browser = new BrowserTest();
    let parser = new ParserTest();

    for await (const data of browser){
        console.log(await parser.parse(data))
    }
}

class BrowserTest {
    private i: number = 0;

    public async next(): Promise<IteratorResult<string>> {
        this.i += 1;
        return {
            done: this.i > 1000,
            value: 'peter '.repeat(this.i)
        }
    }

    [Symbol.asyncIterator](): AsyncIterator<string> {
        return this;
    }
}

class ParserTest {
    public async parse(data: string): Promise<string[]> {
        return data.split(' ');
    }
}

scraper()

y的data似乎在内存中晃来晃去。调用栈也会变得越来越庞大。

在复制中,问题仍然可以解决。但是对于我的实际代码,整个HTML页仍保留在内存中,每次调用约250kb。

在此屏幕快照中,您可以看到第一次迭代时的堆内存与最后一次迭代后的堆内存相比

Cannot post inline Screenshots yet

预期的工作流程如下:

  • 获取数据
  • 过程数据
  • 提取下一个“获取数据”的信息
  • 释放最后一个“获取数据”中的所有内存
  • 使用提取的信息重新开始获取新数据的循环。

我不确定在这里AsyncIterator是存档所需内容的正确选择。

任何帮助/提示都将得到帮助!

1 个答案:

答案 0 :(得分:1)

简而言之

使用AsyncIterator时,内存急剧增加。迭代完成后,它将丢弃。

直到迭代完成,才释放等待中的x(y的x)。同样,for循环中等待的每个Promise也不会释放。

我得出的结论是,垃圾收集器无法捕获迭代的内容,因为AsyncIterator生成的承诺仅在迭代完成后才能完全解决。 我认为这可能是错误。

解决方法复制

作为释放解析器内容的解决方法,我们将结果封装在轻量级容器中。然后,我们释放内容,因此只有容器本身保留在内存中。 即使您使用相同的技术封装data对象,也无法释放它,所以至少在调试时似乎是这种情况。

const scraper = async ():Promise<void> => {
    let browser = new BrowserTest();

    for await (const data of browser){
        let parser = new ParserTest();
        let result = await parser.parse(data);
        console.log(result);

        /**
         * This avoids memory leaks, due to a garbage collector bug
         * of async iterators in js
         */
        result.free();
    }
}

class BrowserTest {
    private i: number = 0;
    private value: string = "";

    public async next(): Promise<IteratorResult<string>> {
        this.i += 1;
        this.value = 'peter '.repeat(this.i);
        return {
            done: this.i > 1000,
            value: this.value
        }
    }

    public [Symbol.asyncIterator](): AsyncIterator<string> {
        return this;
    }
}

/**
 * Result class for wrapping the result of the parser.
 */
class Result {
    private result: string[] = [];

    constructor(result: string[]){
        this.setResult(result);
    }

    public setResult(result: string[]) {
        this.result = result;
    }

    public getResult(): string[] {
        return this.result;
    }

    public free(): void {
        delete this.result;
    }
}

class ParserTest {
    public async parse(data: string): Promise<Result>{
        let result = data.split(' ');
        return new Result(result);
    }
}

scraper())

实际情况下的解决方法

在Repro-Solution中未显示的是,我们还尝试释放Iteration本身的结果。似乎对tho(?)没有任何影响。

public static async scrape<D,M>(scraper: IScraper<D,M>, callback: (data: DataPackage<Object,Object> | null) => Promise<void>) {
        let browser = scraper.getBrowser();
        let parser = scraper.getParser();

        for await (const parserFragment of browser) {
            const fragment = await parserFragment;
            const json = await parser.parse(fragment);
            await callback(json);
            json.free();
            fragment.free();
        }
    }

请参阅:https://github.com/demokratie-live/scapacra/blob/master/src/Scraper.ts 要测试实际的应用程序,请执行以下操作:https://github.com/demokratie-live/scapacra-btyarn dev ConferenceWeekDetail

参考文献

结论

我们为我们找到了可行的解决方案。因此,我关闭了这个问题。后续操作针对Node.js Repo,以修复此潜在的Bug

  

https://github.com/nodejs/node/issues/30298