Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

[Memory-Leak]: Unsubscribe and return from iterator is missed, when using subscription´s onDisconnect #16

@TimSusa

Description

@TimSusa

Notes

  • Memory-Leak is appearing
  • We like to make this project more resilient to memory leaks
  • Maybe a code Smell at pullValue could be assumed
  • In some eslint rules, for example, it is strongly recommended to avoid creating promises via "new"
  • The code was taken into Steffi-Graph for debugging analysis
  • Implemented subscriptions onDisconnect to close the connection, which is triggered from the client (closing his browsertab for example)

Analysis of Log Data (We found a moment, where unsubscribe would be missed):

steffi-graph: subscribe  { topicName: 'article',
steffi-graph:   onMessage: [AsyncFunction: bound pushValue],
steffi-graph:   options: undefined }
steffi-graph: getSubscriptions  
steffi-graph: exists?   true article
steffi-graph: subscribe all [ 6 ]
steffi-graph: next  Promise { [ 6 ] }  listening?:  true
steffi-graph: (not resolved yet) pullQueue: pushed resolve reference to queue with length:  1
//
// Unsubscribe and return seems to be missing here!
// 
steffi-graph: onDissconnect initial context  { auth:
steffi-graph:    { user:
steffi-graph:       { accountname: 'admin',
steffi-graph:        
steffi-graph:      isAuthenticated: true,
steffi-graph:      scope:
steffi-graph:   ...
steffi-graph:   token: ...

//
// Websocket connection to specific client should be closed here after onDisconnect
//

Question

Would an alternative approach make sense here?

New Approach to discuss

...

Feedback from Nacho (Google) to this Implementation

It seems that you have 800k instances of Promises, which is taking 80% of your memory. This most frequently happens when a Promise is neither resolved or rejected, which looking at your code could be happening at pullValue:

pullValue() {
    return new Promise(resolve => {
      if (this.pushQueue.length !== 0) {
        const res = this.pushQueue.shift()
        resolve({ value: res, done: false })
      } else {
        this.pullQueue.push(resolve)
      }
    })
  }

You are delaying the resolution of the Promise by adding it to pullQueue, could it be that nobody is pulling that data later? My guess is that there is no cleanup of these delayed Promises by either resolving or rejecting.

A test case for this would be useful. I am forwarding this to a colleague in case they can offer an additional insight.

Metadata

Metadata

Labels

help wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions