使用AsyncIterator时,在for-x-of-y中使用时会发生实质性内存泄漏
在抓取包含有关下一个要抓取的HTML页面的信息的HTML页面时,我需要这样做:
由于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是存档所需内容的正确选择。
任何帮助/提示都将得到帮助!
答案 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-bt(yarn dev ConferenceWeekDetail
)
我们为我们找到了可行的解决方案。因此,我关闭了这个问题。后续操作针对Node.js Repo,以修复此潜在的Bug