-
-
Save wolever/d95c7143af5789161868c97a0ce9e2fe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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