Jeff-Lewis / cls-hooked

cls-hooked : CLS using AsynWrap or async_hooks instead of async-listener for node 4.7+

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Memory leak] Context is not cleared if it references a Promise

MayurRJoshi opened this issue · comments

Summary
If the context contains a key which references a Promise, then that context object is never cleared. Hence, the _contexts map keeps growing and hogs up all the heap memory.

Steps to reproduce

  1. Install express and cls-hooked.
  2. Run the script below, node --inspect index.js
const express = require('express')
const clsHooked = require('cls-hooked')
clsHooked.createNamespace('test')

const app = express()

app.get('/', (req, res, next) => {
    const namespace = clsHooked.getNamespace('test')
    
    console.log(namespace._contexts)

    namespace.run(() => {
        namespace.set('x', Promise.resolve())
        res.json({})
    })
})

app.listen(3030, () => console.log(('Listening')))
  1. Call the server api, curl localhost:3030
  2. [Optional] Manually trigger a garbage collection from the Chrome debugger.
  3. Repeat 3 and 4 and observe the logs. Notice that the _contexts keeps growing even after a manual garbage collection
node --inspect index.js 
Debugger listening on ws://127.0.0.1:9229/05303a92-c1ac-4cdb-816a-4b5e15de611a
For help, see: https://nodejs.org/en/docs/inspector
Listening
Debugger attached.
Map {}
Map {
  16 => { _ns_name: 'test', id: 11, x: Promise { undefined } },
  22 => { _ns_name: 'test', id: 11, x: Promise { undefined } } }
Map {
  16 => { _ns_name: 'test', id: 11, x: Promise { undefined } },
  39 => { _ns_name: 'test', id: 34, x: Promise { undefined } } }
Map {
  16 => { _ns_name: 'test', id: 11, x: Promise { undefined } },
  39 => { _ns_name: 'test', id: 34, x: Promise { undefined } },
  61 => { _ns_name: 'test', id: 56, x: Promise { undefined } } }
Map {
  16 => { _ns_name: 'test', id: 11, x: Promise { undefined } },
  39 => { _ns_name: 'test', id: 34, x: Promise { undefined } },
  61 => { _ns_name: 'test', id: 56, x: Promise { undefined } },
  84 => { _ns_name: 'test', id: 79, x: Promise { undefined } },
  90 => { _ns_name: 'test', id: 79, x: Promise { undefined } } }

If I change the set to namespace.set('x', '') (or any other primitive or object), context is cleared sometimes automatically after every request and always if a manual garbage collection is triggered.

Output with no manual GC.

node --inspect index.js 
Debugger listening on ws://127.0.0.1:9229/d39a47e8-72a2-4603-885a-ba0e3dbd8d53
For help, see: https://nodejs.org/en/docs/inspector
Listening
Map {}
Map { 21 => { _ns_name: 'test', id: 11, x: '' } }
Map {}
Map {}
Map {}