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
  • Select an option

  • Save wolever/d95c7143af5789161868c97a0ce9e2fe to your computer and use it in GitHub Desktop.

Select an option

Save wolever/d95c7143af5789161868c97a0ce9e2fe to your computer and use it in GitHub Desktop.

Revisions

  1. wolever revised this gist Mar 6, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    #!/usr/bin/env node

    # This code is public domain.
    // My modifications to this code are public domain

    const { writeSync, readFile } = require("fs")
    const async_hooks = require("async_hooks")
  2. wolever revised this gist Mar 6, 2019. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    #!/usr/bin/env node

    # This code is public domain.

    const { writeSync, readFile } = require("fs")
    const async_hooks = require("async_hooks")

  3. wolever revised this gist Mar 6, 2019. 1 changed file with 74 additions and 22 deletions.
    96 changes: 74 additions & 22 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -1,37 +1,102 @@
    // This has been updated. You'll have to go back in time in the gist history to
    // see older versions.
    #!/usr/bin/env node

    const { writeSync } = require("fs")
    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) => {
    eventIDs.add(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 f()
    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, eventIDs.size + "\n")
    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)
    @@ -48,25 +113,12 @@ async function main() {
    await printLeakedEvents(async () => {
    // unresolved promise
    await new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000);
    setTimeout(resolve, 100);
    }).then(() => {
    // do nothing, but we do want a "then"
    })
    })
    */
    }

    main()

    // Output:
    // 6
    // 6
    // 8
    // 8

    // The questions:
    //
    // 1. Why are 6 events created ("init"ed) but not resolved when I call a
    // synchronous function?
    //
    // 2. Why do I see a net event change of 8 regardless of whether I correctly
    // `await` the promise with a `then`, vs. simply leaking it?
    main()
  4. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 27 additions and 10 deletions.
    37 changes: 27 additions & 10 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@ async function printLeakedEvents(f) {

    // Set up an async hook to increment and decrement the counter
    const asyncHook = async_hooks.createHook({
    before: (asyncID) => {
    init: (asyncID) => {
    eventIDs.add(asyncID)
    },
    after: (asyncID) => {
    @@ -32,24 +32,41 @@ async function printLeakedEvents(f) {
    }

    async function main() {
    // 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, 1000);
    }).then(() => {
    // do nothing, but we do want a "then"
    })
    })
    }

    main()

    // Output:
    // 1
    // 1
    // 1

    // The question: Why doesn't the unresolved promise cause more leaked events
    // than the `() => 0` function?
    // 6
    // 6
    // 8
    // 8

    // The old question (fixed in this version): why does the function `() => 0`
    // seem to create one event the first time it's called, but zero events the
    // second time?
    // The questions:
    //
    // 1. Why are 6 events created ("init"ed) but not resolved when I call a
    // synchronous function?
    //
    // 2. Why do I see a net event change of 8 regardless of whether I correctly
    // `await` the promise with a `then`, vs. simply leaking it?
  5. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -36,7 +36,7 @@ async function main() {
    await printLeakedEvents(async () => 0)
    await printLeakedEvents(async () => {
    // unresolved promise
    new Promise(function(resolve, reject) { resolve(); })
    new Promise(function(resolve, reject) { setTimeout(resolve, 1000); })
    })
    }

  6. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -28,7 +28,7 @@ async function printLeakedEvents(f) {

    // print the number of hooks (using writeSync to be 100% sure that we don't
    // create any new events)
    writeSync(1, hookIDs.size + "\n")
    writeSync(1, eventIDs.size + "\n")
    }

    async function main() {
  7. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 24 additions and 8 deletions.
    32 changes: 24 additions & 8 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,21 @@
    // This has been updated. You'll have to go back in time in the gist history to
    // see older versions.

    const { writeSync } = require("fs")
    const async_hooks = require("async_hooks")

    async function printLeakedEvents(f) {
    // Counter for the number of active hooks at any given time
    let activeHookCount = 0
    // 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({
    before: () => { activeHookCount += 1 },
    after: () => { activeHookCount -= 1 },
    before: (asyncID) => {
    eventIDs.add(asyncID)
    },
    after: (asyncID) => {
    eventIDs.delete(asyncID)
    },
    });

    // run the function with the async hook enabled
    @@ -21,19 +28,28 @@ async function printLeakedEvents(f) {

    // print the number of hooks (using writeSync to be 100% sure that we don't
    // create any new events)
    writeSync(1, activeHookCount + "\n")
    writeSync(1, hookIDs.size + "\n")
    }

    async function main() {
    await printLeakedEvents(async () => 0)
    await printLeakedEvents(async () => 0)
    await printLeakedEvents(async () => {
    // unresolved promise
    new Promise(function(resolve, reject) { resolve(); })
    })
    }

    main()

    // Output:
    // 1
    // 0
    // 1
    // 1

    // The question: Why doesn't the unresolved promise cause more leaked events
    // than the `() => 0` function?

    // The question: why does the function `() => 0` seem to create one event the
    // first time it's called, but zero events the second time?
    // The old question (fixed in this version): why does the function `() => 0`
    // seem to create one event the first time it's called, but zero events the
    // second time?
  8. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -35,4 +35,5 @@ main()
    // 1
    // 0

    // The question: why does the function `() => 0` seem to create one event the first time it's called, but zero events the second time?
    // The question: why does the function `() => 0` seem to create one event the
    // first time it's called, but zero events the second time?
  9. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -29,4 +29,10 @@ async function main() {
    await printLeakedEvents(async () => 0)
    }

    main()
    main()

    // Output:
    // 1
    // 0

    // The question: why does the function `() => 0` seem to create one event the first time it's called, but zero events the second time?
  10. @garybernhardt garybernhardt revised this gist Mar 4, 2019. 1 changed file with 7 additions and 5 deletions.
    12 changes: 7 additions & 5 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -2,24 +2,26 @@ const { writeSync } = require("fs")
    const async_hooks = require("async_hooks")

    async function printLeakedEvents(f) {
    // Counter for the number of active hooks at any given time
    let activeHookCount = 0

    // Set up an async hook to increment and decrement the counter
    const asyncHook = async_hooks.createHook({
    before: () => { activeHookCount += 1 },
    after: () => { activeHookCount -= 1 },
    });
    asyncHook.enable()

    let result
    // run the function with the async hook enabled
    asyncHook.enable()
    try {
    result = await f()
    await f()
    } finally {
    asyncHook.disable()
    }

    // print the number of hooks (using writeSync to be 100% sure that we don't
    // create any new events)
    writeSync(1, activeHookCount + "\n")

    return result
    }

    async function main() {
  11. @garybernhardt garybernhardt created this gist Mar 4, 2019.
    30 changes: 30 additions & 0 deletions print-leaked-events.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    const { writeSync } = require("fs")
    const async_hooks = require("async_hooks")

    async function printLeakedEvents(f) {
    let activeHookCount = 0

    const asyncHook = async_hooks.createHook({
    before: () => { activeHookCount += 1 },
    after: () => { activeHookCount -= 1 },
    });
    asyncHook.enable()

    let result
    try {
    result = await f()
    } finally {
    asyncHook.disable()
    }

    writeSync(1, activeHookCount + "\n")

    return result
    }

    async function main() {
    await printLeakedEvents(async () => 0)
    await printLeakedEvents(async () => 0)
    }

    main()