Puppeteer生产环境JS堆内存溢出求助(HTML分页代码异常)
HTML分页函数的生产环境内存溢出问题
我编写了一个JavaScript函数,可按指定字符数将HTML字符串拆分为多个页面,核心是保证分页位置处于标签开闭之间时HTML仍保持有效:
function paginate(text, pageLength) { // strip outter div. This would screw things up text = text.replace('<div>', '') text = text.replace('</div>', '') const template = document.createElement('template'); template.innerHTML = text; const fragment = template.content; // This is a DocumentFragment const nodes = fragment.childNodes const rawNodesArray = Array.from(nodes) const nodesArray = [] // break text nodes by words rawNodesArray.map( (node) => { const fullText = node.textContent const wordList = fullText.match(/\S+\s?/g) if (wordList) { wordList.map( (word) => { const newNode = node.cloneNode(true) newNode.textContent = word nodesArray.push(newNode) }) } }) const pages = [] let page = [] let pageCharCount = 0 while (nodesArray.length) { // dont include leading whitespace if its the start of the page const nextNodeLength = nodesArray[0].textContent.trimEnd().length const lengthWithNextChunk = pageCharCount + nextNodeLength if ( lengthWithNextChunk <= pageLength ) { const nodeToAdd = nodesArray.shift() page.push(nodeToAdd) // add node to current page list pageCharCount = page.reduce( (sum, node) => { return sum + node.textContent.length }, 0) // update count and scrap corresponding text chunk } else if (nextNodeLength > pageLength) { const node = nodesArray.shift() const chunk = node.textContent const hyphen = '' //optional const remainingChars = pageLength - pageCharCount const appendToPage = (remainingChars - hyphen.length > 0) const spliceIndex = (appendToPage) ? remainingChars - hyphen.length : pageLength - hyphen.length const firstHalf = chunk.slice(0, spliceIndex) + hyphen const secondHalf = chunk.slice(spliceIndex) const clonedNode = node.cloneNode(true) node.textContent = firstHalf clonedNode.textContent = secondHalf if (appendToPage) { pageCharCount += node.textContent.length page.push(node) nodesArray.unshift(clonedNode) } else { pages.push(page) page = [] pageCharCount = 0 nodesArray.unshift(node, clonedNode) } } else { pages.push(page) page = [] pageCharCount = 0 } } if (page.length) { pages.push(page) } const template_pages = pages.map( (page) => { const tpl = document.createElement('template'); for(let i=0; i<page.length; i++) { tpl.content.appendChild(page[i]) } return tpl }) const html_pages = template_pages.map( (tpl) => { tpl.innerHTML = tpl.innerHTML.trim() // This fixes pages like <em>foo </em> const lastChild = tpl.lastChild if (lastChild) { lastChild.textContent = lastChild.textContent.trimEnd() } return tpl.innerHTML }) return html_pages } export default paginate
该函数在以下场景测试运行正常:
- Vitest测试套件
- 桌面Chrome浏览器
- 本地Puppeteer实例(无头Chrome)
但在生产环境的Puppeteer实例中测试时,出现以下错误:
2022-11-15T23:27:39.993980+00:00 app[web.1]: <--- JS stacktrace ---> 2022-11-15T23:27:39.993980+00:00 app[web.1]: 2022-11-15T23:27:39.993981+00:00 app[web.1]: ==== JS stack trace ========================================= 2022-11-15T23:27:39.993981+00:00 app[web.1]: 2022-11-15T23:27:39.993981+00:00 app[web.1]: 0: ExitFrame [pc: 0x242aff6dbf1d] 2022-11-15T23:27:39.993982+00:00 app[web.1]: 1: StubFrame [pc: 0x242aff6dd4a6] 2022-11-15T23:27:39.993982+00:00 app[web.1]: Security context: 0x3876ddb9e6c1 <JSObject> 2022-11-15T23:27:39.993984+00:00 app[web.1]: 2: /* anonymous */ [0x3cf2c0102b69] [/app/node_modules/winston/lib/winston/common.js:~321] [pc=0x242aff9713bd](this=0x233d4e1c4959 <Object map = 0x2d2eefa874d9>,obj=115,key=0x385eb8f429e1 <String[5]: 30444>) 2022-11-15T23:27:39.993985+00:00 app[web.1]: 3: /* anonymous */ [0x3cf2c0102b69] [/app/node_modules/winston/lib/winston/common.js:~321] [pc=0x242aff971... 2022-11-15T23:27:39.993985+00:00 app[web.1]: 2022-11-15T23:27:39.993985+00:00 app[web.1]: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 2022-11-15T23:27:39.994847+00:00 app[web.1]: 1: 0x8fb090 node::Abort() [node] 2022-11-15T23:27:39.995408+00:00 app[web.1]: 2: 0x8fb0dc [node] 2022-11-15T23:27:39.996258+00:00 app[web.1]: 3: 0xb0336e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node] 2022-11-15T23:27:39.997489+00:00 app[web.1]: 4: 0xb035a4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node] 2022-11-15T23:27:39.999339+00:00 app[web.1]: 5: 0xef7602 [node] 2022-11-15T23:27:40.000024+00:00 app[web.1]: 6: 0xef7708 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [node] 2022-11-15T23:27:40.003356+00:00 app[web.1]: 7: 0xf037e2 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node] 2022-11-15T23:27:40.004037+00:00 app[web.1]: 8: 0xf04114 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node] 2022-11-15T23:27:40.004744+00:00 app[web.1]: 9: 0xf06d81 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [node] 2022-11-15T23:27:40.005418+00:00 app[web.1]: 10: 0xed0204 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [node] 2022-11-15T23:27:40.006141+00:00 app[web.1]: 11: 0x11702de v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [node] 2022-11-15T23:27:40.006145+00:00 app[web.1]: 12: 0x242aff6dbf1d 2022-11-15T23:27:40.258485+00:00 heroku[web.1]: State changed from up to crashed 2022-11-15T23:30:58.444906+00:00 heroku[web.1]: State changed from crashed to down
运行环境:Node v16.x,部署在Heroku 2X dyno(配备1GB内存)。
该问题仅在Heroku生产环境中100%复现,其他环境均无法重现。已尝试以下方案:
- 排查内存泄漏:观察Chrome内存时间线未发现内存持续堆积
- 调整Node垃圾回收参数:如
node --optimize_for_size --max_old_space_size=900 src/index.js,尝试不同--max_old_space_size值均无效 - 升级至最大Heroku dyno(14GB内存):问题仍存在
我的代码中是否存在潜在问题?为何仅该环境出现此错误?我还遗漏了哪些排查方向?
内容的提问来源于stack exchange,提问作者emersonthis
相关产品推荐
相关产品推荐

