Skip to content

Instantly share code, notes, and snippets.

@wolever
Forked from garybernhardt/print-leaked-events.js
Last active March 6, 2019 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolever/d95c7143af5789161868c97a0ce9e2fe to your computer and use it in GitHub Desktop.
Save wolever/d95c7143af5789161868c97a0ce9e2fe to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
// My modifications to this code are public domain
const { writeSync, readFile } = require("fs")
const async_hooks = require("async_hooks")
function showStack() {
let e = new Error()
writeSync(1, e.stack.split('\n').slice(2).join('\n') + '\n')
}
const ignoreEventIds = new Set()
// For this example, only track timer (ex, setTimeout) and FS (ex, readFile)
// events.
const eventsToTrack = [
'Timer.emitInitNative',
'FSReqWrap.emitInitNative',
]
async function printLeakedEvents(f) {
// Track all active event IDs
const eventIDs = new Set()
// Set up an async hook to increment and decrement the counter
const asyncHook = async_hooks.createHook({
init: (asyncID) => {
writeSync(1, `init: ${asyncID}\n`)
showStack()
const stack = (new Error()).stack
const shouldTrack = eventsToTrack.filter(e => stack.indexOf(e) >= 0).length > 0
;(shouldTrack ? eventIDs : ignoreEventIds).add(asyncID)
},
before: (asyncID) => {
writeSync(1, `before: ${asyncID}\n`)
showStack()
},
after: (asyncID) => {
writeSync(1, `after: ${asyncID}\n`)
showStack()
eventIDs.delete(asyncID)
},
destroy: (asyncID) => {
writeSync(1, `destroy: ${asyncID}\n`)
showStack()
},
});
// run the function with the async hook enabled
writeSync(1, `\n\n+++ start\n`)
asyncHook.enable()
let x = f()
writeSync(1, `await:\n`)
try {
await x
} finally {
asyncHook.disable()
writeSync(1, `--- finally\n`)
}
// print the number of hooks (using writeSync to be 100% sure that we don't
// create any new events)
writeSync(1, `leaked: ${Array.from(eventIDs).join(', ')}\n`)
}
async function main() {
// do no async stuff
await printLeakedEvents(() => 0)
await printLeakedEvents(() => {
writeSync(1, `Leaky vvv\n`)
setTimeout(() => {}, 100)
writeSync(1, `Leaky ^^^\n`)
let x
writeSync(1, `NOT leaky vvv\n`)
let res = new Promise(res => x = res)
writeSync(1, `NOT leaky ^^^\n`)
writeSync(1, `timeout vvv\n`)
setTimeout(x, 10)
writeSync(1, `timeout ^^^\n`)
writeSync(1, `.then vvv\n`)
res = res.then(() => {
return new Promise(res => {
writeSync(1, `.readFile vvv\n`)
readFile(__filename, res)
writeSync(1, `.readFile ^^^\n`)
})
})
writeSync(1, `.then ^^^\n`)
return res
})
await new Promise(res => setTimeout(res, 100))
console.log('Done!')
/*
// do no async stuff
await printLeakedEvents(async () => 0)
// do no async stuff again to make sure it's reliable
await printLeakedEvents(async () => 0)
// leak a promise
await printLeakedEvents(async () => {
// unresolved promise
new Promise(function(resolve, reject) { setTimeout(resolve, 1000); })
})
// create a promise and resolve it correctly, including `then` and `await`
await printLeakedEvents(async () => {
// unresolved promise
await new Promise(function(resolve, reject) {
setTimeout(resolve, 100);
}).then(() => {
// do nothing, but we do want a "then"
})
})
*/
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment