Skip to content

Commit dc673a3

Browse files
authored
Merge pull request #2595 from reduxjs/bugfix/settimeout-max-value
2 parents f768913 + e67b93d commit dc673a3

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

packages/toolkit/src/query/core/buildMiddleware/cacheCollection.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ declare module '../../endpointDefinitions' {
2828
}
2929
}
3030

31+
// Per https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value , browsers store
32+
// `setTimeout()` timer values in a 32-bit int. If we pass a value in that's larger than that,
33+
// it wraps and ends up executing immediately.
34+
// Our `keepUnusedDataFor` values are in seconds, so adjust the numbers here accordingly.
35+
export const THIRTY_TWO_BIT_MAX_INT = 2_147_483_647
36+
export const THIRTY_TWO_BIT_MAX_TIMER_SECONDS = 2_147_483_647 / 1_000 - 1
37+
3138
export const build: SubMiddlewareBuilder = ({ reducerPath, api, context }) => {
3239
const { removeQueryResult, unsubscribeQueryResult } = api.internalActions
3340

@@ -87,6 +94,14 @@ export const build: SubMiddlewareBuilder = ({ reducerPath, api, context }) => {
8794
] as QueryDefinition<any, any, any, any>
8895
const keepUnusedDataFor =
8996
endpointDefinition?.keepUnusedDataFor ?? config.keepUnusedDataFor
97+
// Prevent `setTimeout` timers from overflowing a 32-bit internal int, by
98+
// clamping the max value to be at most 1000ms less than the 32-bit max.
99+
// Look, a 24.8-day keepalive ought to be enough for anybody, right? :)
100+
// Also avoid negative values too.
101+
const finalKeepUnusedDataFor = Math.max(
102+
0,
103+
Math.min(keepUnusedDataFor, THIRTY_TWO_BIT_MAX_TIMER_SECONDS)
104+
)
90105

91106
const currentTimeout = currentRemovalTimeouts[queryCacheKey]
92107
if (currentTimeout) {
@@ -99,7 +114,7 @@ export const build: SubMiddlewareBuilder = ({ reducerPath, api, context }) => {
99114
api.dispatch(removeQueryResult({ queryCacheKey }))
100115
}
101116
delete currentRemovalTimeouts![queryCacheKey]
102-
}, keepUnusedDataFor * 1000)
117+
}, finalKeepUnusedDataFor * 1000)
103118
}
104119
}
105120
}

packages/toolkit/src/query/tests/cacheCollection.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
22
import { configureStore } from '@reduxjs/toolkit'
33
import { waitMs } from './helpers'
44
import type { Middleware, Reducer } from 'redux'
5+
import {
6+
THIRTY_TWO_BIT_MAX_INT,
7+
THIRTY_TWO_BIT_MAX_TIMER_SECONDS,
8+
} from '../core/buildMiddleware/cacheCollection'
59

610
beforeAll(() => {
711
jest.useFakeTimers('legacy')
@@ -52,6 +56,35 @@ test(`query: await cleanup, keepUnusedDataFor set`, async () => {
5256
expect(onCleanup).toHaveBeenCalled()
5357
})
5458

59+
test(`query: handles large keepUnuseDataFor values over 32-bit ms`, async () => {
60+
const { store, api } = storeForApi(
61+
createApi({
62+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
63+
endpoints: (build) => ({
64+
query: build.query<unknown, string>({
65+
query: () => '/success',
66+
}),
67+
}),
68+
keepUnusedDataFor: THIRTY_TWO_BIT_MAX_TIMER_SECONDS - 10,
69+
})
70+
)
71+
72+
store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe()
73+
74+
// Shouldn't have been called right away
75+
jest.advanceTimersByTime(1000), await waitMs()
76+
expect(onCleanup).not.toHaveBeenCalled()
77+
78+
// Shouldn't have been called any time in the next few minutes
79+
jest.advanceTimersByTime(1_000_000), await waitMs()
80+
expect(onCleanup).not.toHaveBeenCalled()
81+
82+
// _Should_ be called _wayyyy_ in the future (like 24.8 days from now)
83+
jest.advanceTimersByTime(THIRTY_TWO_BIT_MAX_TIMER_SECONDS * 1000),
84+
await waitMs()
85+
expect(onCleanup).toHaveBeenCalled()
86+
})
87+
5588
describe(`query: await cleanup, keepUnusedDataFor set`, () => {
5689
const { store, api } = storeForApi(
5790
createApi({

0 commit comments

Comments
 (0)