Skip to content

Commit

Permalink
fix: fix OOM when collections leak multiple times (#90)
Browse files Browse the repository at this point in the history
Fixes #89
  • Loading branch information
nolanlawson authored Jan 27, 2024
1 parent cfb7161 commit f115a74
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 4 deletions.
9 changes: 5 additions & 4 deletions src/metrics/collections/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { sortBy } from '../../util.js'
import { prettifyStacktrace } from '../../prettifyStacktrace.js'
import { promisePool } from '../../promisePool.js'

const PROMISE_POOL_SIZE = 100 // avoid OOMs when lots of collections are leaking
const COLLECTIONS_PROMISE_POOL_SIZE = 100 // avoid OOMs when lots of collections are leaking
const STACKTRACES_PROMISE_POOL_SIZE = 10 // avoid OOMs when collections are leaking in multiple places

export async function startTrackingCollections (page) {
// The basic idea for this comes from
Expand Down Expand Up @@ -317,12 +318,12 @@ export async function augmentLeakingCollectionsWithStacktraces (page, collection
return result
}

return (await promisePool(PROMISE_POOL_SIZE, collections.map(collection => async () => {
return (await promisePool(COLLECTIONS_PROMISE_POOL_SIZE, collections.map(collection => async () => {
const res = { ...collection }
if (collection.id in idsToStacktraces) {
const stacktraces = idsToStacktraces[collection.id]
res.stacktraces = await Promise.all(stacktraces.map(async stacktrace => {
return getStacktraceWithOriginalAndPretty(stacktrace)
res.stacktraces = await promisePool(STACKTRACES_PROMISE_POOL_SIZE, stacktraces.map(stacktrace => async () => {
return (await getStacktraceWithOriginalAndPretty(stacktrace))
}))
}
return res
Expand Down
49 changes: 49 additions & 0 deletions test/spec/collections.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,55 @@ context webpack://navigo/src/Q.ts:31:31
next webpack://navigo/src/Q.ts:34:10
done webpack://navigo/src/middlewares/callHandler.ts:9:2
context webpack://navigo/src/Q.ts:31:31
next webpack://navigo/src/Q.ts:34:10
`
const { pretty } = firstCollection.stacktraces[0]
expect(normalizeStackTrace(pretty)).to.deep.equal(normalizeStackTrace(expected))
})

it('collections with lots of leaks', async () => {
const results = await asyncIterableToArray(findLeaks('http://localhost:3000/test/www/collectionsWithLotsOfLeaks/', {
iterations: 3
}))

const result = results[0].result
expect(result.leaks.detected).to.equal(true)
expect(result.leaks.collections.length).to.equal(5)

const firstCollection = result.leaks.collections[0]
const {
delta,
deltaPerIteration,
preview,
sizeAfter,
sizeBefore,
type
} = firstCollection
expect({
delta,
deltaPerIteration,
preview,
sizeAfter,
sizeBefore,
type
}).to.deep.equal({
delta: 6000,
deltaPerIteration: 2000,
preview: '[0, ...]',
sizeAfter: 8000,
sizeBefore: 2000,
type: 'Array'
})

const expected = `
aboutHook http://localhost:3000/test/www/collectionsWithLotsOfLeaks/script.js:10:18
webpack://navigo/src/middlewares/checkForAfterHook.ts:10:52
Array.forEach <anonymous>
forEach webpack://navigo/src/middlewares/checkForAfterHook.ts:10:36
context webpack://navigo/src/Q.ts:31:31
next webpack://navigo/src/Q.ts:34:10
done webpack://navigo/src/middlewares/callHandler.ts:9:2
context webpack://navigo/src/Q.ts:31:31
next webpack://navigo/src/Q.ts:34:10
`
const { pretty } = firstCollection.stacktraces[0]
Expand Down
10 changes: 10 additions & 0 deletions test/www/collectionsWithLotsOfLeaks/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="../../../node_modules/navigo/lib/navigo.js"></script>
<script type="module" src="./script.js"></script>
</body>
</html>
13 changes: 13 additions & 0 deletions test/www/collectionsWithLotsOfLeaks/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { makeRouter } from '../basicRouter.js'

const router = makeRouter(['', 'about'])

const collections = Array(5).fill().map(_ => [])

router.addAfterHook('/about', function aboutHook () {
for (const collection of collections) {
for (let i = 0; i < 2000; i++) {
collection.push(0)
}
}
})

0 comments on commit f115a74

Please sign in to comment.