Skip to content

Conversation

@markerikson
Copy link
Collaborator

@markerikson markerikson commented Oct 21, 2025

This PR:

  • Fixes a bug caused by Rewrite subscription handling and polling calculations for better perf #5064 that could allow some active subscription entries to leak when the same RTKQ middleware definition is used with multiple stores.
  • Fixes another issue where the isRunning value in anySubscriptionsRemaining was always false, because it tried to access the runningQueries map too early and there was no per-store lookup table of promises yet

Fixes #5110

Background

In #5064 , I rewrote RTKQ's internals to improve perf. As part of that, I hoisted the InternalMiddlewareState object out of buildMiddleware and into core/module.ts (part of createApi), so that both the middleware and the thunks could access the same subscription data directly.

Redux middleware are factory functions for closures. When createStore is called, it executes the outermost middleware function, which creates a closure scoped to just that store instance. When the InternalMiddlewareState object was being created there, this was fine - every store would get its own InternalMiddlewareState.

When I moved that instantiation up to createApi, though, now that value is outside the scope of the middleware, and thus outside the lifetime of a given store. If multiple stores use the same RTKQ middleware definition, they each create their own unique middleware instance, but those middleware instances are all sharing the same InternalMiddlewareState reference.

To be clear, this doesn't appear to have leaked any actual cache data - just that there were subscriptions of some kind, with RTKQ's internal cache entry IDs.

This PR resolves that by using the same technique we did for thunk promises - a WeakMap<Dispatch, Thing> that ensures we cache the value on a per-store basis. In fact, since the runningQueries/Mutations maps were already on the InternalMiddlewareState, we're really just moving the Dispatch keying up one level, which simplifies a few other checks.

I found I'd caused another issue in #5064 in the process. I had tried to save const runningQueries = internalState.runningQueries.get(dispatch) at the top of cacheCollection.ts, and then use it in anySubscriptionsRemaining. When I made my fixes for this issue, a single test in buildHooks.test.tsx failed, indicating some subscriptions weren't getting cleaned up. After a lot of investigation, I finally realized that the problem was the isRunning check itself. I added logging to the pre-fix logic and found that runningQueries itself was always undefined... because we only inserted the per-store lookup table whenever we had active running promises. Since we were trying to read that at the start of the middleware, there was no lookup table for this store yet. My fix accidentally uncovered that broken logic.

It turns out that with the various other changes we've had, the isRunning check in anySubscriptionsRemaining isn't even needed! I removed it and all the tests passed, with both the pre- and post-fix subscription handling. I actually don't have a full explanation for that right now, but also it's late and my mind's kinda fried :)

@codesandbox
Copy link

codesandbox bot commented Oct 21, 2025

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@codesandbox-ci
Copy link

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 0106e42:

Sandbox Source
@examples-query-react/basic Configuration
@examples-query-react/advanced Configuration
@examples-action-listener/counter Configuration
rtk-esm-cra Configuration

@netlify
Copy link

netlify bot commented Oct 21, 2025

Deploy Preview for redux-starter-kit-docs ready!

Name Link
🔨 Latest commit 0106e42
🔍 Latest deploy log https://app.netlify.com/projects/redux-starter-kit-docs/deploys/68f7202c7af89900083c1338
😎 Deploy Preview https://deploy-preview-5111--redux-starter-kit-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link

size-limit report 📦

Path Size
1. entry point: @reduxjs/toolkit/query/react (modern.mjs) 15.17 KB (-0.32% 🔽)
1. entry point: @reduxjs/toolkit/query (cjs, production.min.cjs) 24.33 KB (-0.19% 🔽)
1. entry point: @reduxjs/toolkit/query/react (cjs, production.min.cjs) 26.67 KB (+0.11% 🔺)
2. entry point: @reduxjs/toolkit/query (without dependencies) (cjs, production.min.cjs) 10.98 KB (+0.1% 🔺)
3. createApi (.modern.mjs) 15.58 KB (-0.45% 🔽)
3. createApi (react) (.modern.mjs) 17.57 KB (-0.29% 🔽)

@markerikson markerikson merged commit 7b7faea into master Oct 21, 2025
172 of 173 checks passed
@aryaemami59 aryaemami59 deleted the bugfix/5110-ssr-leaks branch October 21, 2025 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RTK Query subscription info can persist when middleware is used in multiple stores

2 participants