Skip to content

User-reported memory leak in createListenerMiddleware #3020

Closed
@markerikson

Description

@markerikson

Pasted from https://stackoverflow.com/questions/74852458/memory-leak-with-redux-toolkits-createlistenermiddleware

Original Report

I use Redux Toolkit, and in particular the new listener api, to perform tasks similar to what I could do with Redux-Saga.

Unfortunately, since a few days, I'm stuck with a memory leak and I can't find the cause.

I have reproduced a minimal example of the code that produces this memory leak, link to the example : https://github.com/MrSquaare/rtk-memory-leak

To observe this memory leak :

  • I use Chromium, DevTools memory tool
  • I trigger a garbage collector
  • I make a heap memory snapshot
  • I dispatch entity/load (via the UI button)
  • I make several heap memory snapshots every 2-3 seconds
  • I use the comparison tool, I notice that I have the array allocation size growing infinitely

And after dispatch entity/unload, then make a snapshot heap memory, we can observe that the allocations disappear...

Has anyone observed similar behavior? Or does anyone have an idea of the cause? Thanks!

EDIT 1:

I made an example with only the listener middleware (only-middleware branch), and compared it with different ways of doing :

  • With forkApi.pause : Important leaks, especially of the generated entities
  • Without forkApi.pause : I use directly api.dispatch, no more leaks of the generated entities, some leaks of other kinds, but maybe normal things (I am not qualified enough to pronounce on this)
  • Without api.dispatch : I call directly the function that generates an entity, same result as with api.dispatch

It seems that the leak is related to forkApi.pause, but again I am not qualified enough to know the real cause...

Other Notes

Lenz pointed to nodejs/node#17469 (comment) - it may be related to use of Promise.race():

export const createPause = <T>(signal: AbortSignal) => {
  return (promise: Promise<T>): Promise<T> => {
    return catchRejection(
      Promise.race([promisifyAbortSignal(signal), promise]).then((output) => {
        validateActive(signal)
        return output
      })
    )
  }
}

Lenz's note:

So the problem is that promisifyAbortSignal will stick around for a very long time

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions