Skip to content

Commit f4ca795

Browse files
unstubbablehuozhi
authored andcommitted
Resolve request ID confusion (#85809)
Setting the cache status for a page in dev mode is a sequential operation that only requires the HTML request ID to identify WebSocket clients across different browser tabs/windows. This is different from connecting the React debug channel (after which the cache status handling was modeled), which requires both the HTML request ID as well as the request ID to correctly associate debug chunks for a given client with the matching request, which might be for the HTML document, a client-side navigation, or a server function call. With this PR, we're removing the unused `requestId` parameter from the `setCacheStatus` function. We're also renaming the WebSocket client's request ID to `htmlRequestId`, as well as renaming the associated maps and variables accordingly. This avoids confusion and better reflects the purpose of these IDs. One of those confusions was apparent in the `setReactDebugChannel` methods, which is now cleared up as well.
1 parent 8943cd0 commit f4ca795

File tree

9 files changed

+175
-137
lines changed

9 files changed

+175
-137
lines changed

packages/next/src/server/app-render/app-render.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev(
792792
// Before we kick off the render, we set the cache status back to it's initial state
793793
// in case a previous render bypassed the cache.
794794
if (setCacheStatus) {
795-
setCacheStatus('ready', htmlRequestId, requestId)
795+
setCacheStatus('ready', htmlRequestId)
796796
}
797797

798798
const result = await renderWithRestartOnCacheMissInDev(
@@ -812,7 +812,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev(
812812

813813
// Set cache status to bypass when specifically bypassing caches in dev
814814
if (setCacheStatus) {
815-
setCacheStatus('bypass', htmlRequestId, requestId)
815+
setCacheStatus('bypass', htmlRequestId)
816816
}
817817

818818
debugChannel = setReactDebugChannel && createDebugChannel()
@@ -2613,7 +2613,7 @@ async function renderToStream(
26132613
// This lets the client know not to cache anything based on this render.
26142614
if (renderOpts.setCacheStatus) {
26152615
// we know this is available when cacheComponents is enabled, but typeguard to be safe
2616-
renderOpts.setCacheStatus('bypass', htmlRequestId, requestId)
2616+
renderOpts.setCacheStatus('bypass', htmlRequestId)
26172617
}
26182618
payload._bypassCachesInDev = createElement(WarnForBypassCachesInDev, {
26192619
route: workStore.route,
@@ -3060,7 +3060,6 @@ async function renderWithRestartOnCacheMissInDev(
30603060
const {
30613061
htmlRequestId,
30623062
renderOpts,
3063-
requestId,
30643063
componentMod: {
30653064
routeModule: {
30663065
userland: { loaderTree },
@@ -3203,7 +3202,7 @@ async function renderWithRestartOnCacheMissInDev(
32033202
}
32043203

32053204
if (process.env.NODE_ENV === 'development' && setCacheStatus) {
3206-
setCacheStatus('filling', htmlRequestId, requestId)
3205+
setCacheStatus('filling', htmlRequestId)
32073206
}
32083207

32093208
// Cache miss. We will use the initial render to fill caches, and discard its result.
@@ -3276,7 +3275,7 @@ async function renderWithRestartOnCacheMissInDev(
32763275
)
32773276

32783277
if (process.env.NODE_ENV === 'development' && setCacheStatus) {
3279-
setCacheStatus('filled', htmlRequestId, requestId)
3278+
setCacheStatus('filled', htmlRequestId)
32803279
}
32813280

32823281
return {

packages/next/src/server/app-render/types.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,7 @@ export interface RenderOptsPartial {
109109
}
110110
isOnDemandRevalidate?: boolean
111111
isPossibleServerAction?: boolean
112-
setCacheStatus?: (
113-
status: ServerCacheStatus,
114-
htmlRequestId: string,
115-
requestId: string
116-
) => void
112+
setCacheStatus?: (status: ServerCacheStatus, htmlRequestId: string) => void
117113
setIsrStatus?: (key: string, value: boolean | undefined) => void
118114
setReactDebugChannel?: (
119115
debugChannel: { readable: ReadableStream<Uint8Array> },

packages/next/src/server/dev/debug-channel.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,16 @@ export interface ReactDebugChannelForBrowser {
99
// Might also get a writable stream as return channel in the future.
1010
}
1111

12-
const reactDebugChannelsByRequestId = new Map<
12+
const reactDebugChannelsByHtmlRequestId = new Map<
1313
string,
1414
ReactDebugChannelForBrowser
1515
>()
1616

1717
export function connectReactDebugChannel(
1818
requestId: string,
19+
debugChannel: ReactDebugChannelForBrowser,
1920
sendToClient: (message: HmrMessageSentToBrowser) => void
2021
) {
21-
const debugChannel = reactDebugChannelsByRequestId.get(requestId)
22-
23-
if (!debugChannel) {
24-
return
25-
}
26-
2722
const reader = debugChannel.readable
2823
.pipeThrough(
2924
// We're sending the chunks in batches to reduce overhead in the browser.
@@ -37,8 +32,6 @@ export function connectReactDebugChannel(
3732
requestId,
3833
chunk: null,
3934
})
40-
41-
reactDebugChannelsByRequestId.delete(requestId)
4235
}
4336

4437
const onError = (err: unknown) => {
@@ -63,13 +56,30 @@ export function connectReactDebugChannel(
6356
reader.read().then(progress, onError)
6457
}
6558

66-
export function setReactDebugChannel(
67-
requestId: string,
59+
export function connectReactDebugChannelForHtmlRequest(
60+
htmlRequestId: string,
61+
sendToClient: (message: HmrMessageSentToBrowser) => void
62+
) {
63+
const debugChannel = reactDebugChannelsByHtmlRequestId.get(htmlRequestId)
64+
65+
if (!debugChannel) {
66+
return
67+
}
68+
69+
reactDebugChannelsByHtmlRequestId.delete(htmlRequestId)
70+
71+
connectReactDebugChannel(htmlRequestId, debugChannel, sendToClient)
72+
}
73+
74+
export function setReactDebugChannelForHtmlRequest(
75+
htmlRequestId: string,
6876
debugChannel: ReactDebugChannelForBrowser
6977
) {
70-
reactDebugChannelsByRequestId.set(requestId, debugChannel)
78+
// TODO: Clean up after a timeout, in case the client never connects, e.g.
79+
// when CURL'ing the page, or loading the page with JavaScript disabled etc.
80+
reactDebugChannelsByHtmlRequestId.set(htmlRequestId, debugChannel)
7181
}
7282

73-
export function deleteReactDebugChannel(requestId: string) {
74-
reactDebugChannelsByRequestId.delete(requestId)
83+
export function deleteReactDebugChannelForHtmlRequest(htmlRequestId: string) {
84+
reactDebugChannelsByHtmlRequestId.delete(htmlRequestId)
7585
}

packages/next/src/server/dev/hot-middleware.ts

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ function getStatsForSyncEvent(
7171
}
7272

7373
export class WebpackHotMiddleware {
74-
private clientsWithoutRequestId = new Set<ws>()
75-
private clientsByRequestId: Map<string, ws> = new Map()
74+
private clientsWithoutHtmlRequestId = new Set<ws>()
75+
private clientsByHtmlRequestId: Map<string, ws> = new Map()
7676
private closed = false
7777
private clientLatestStats: { ts: number; stats: webpack.Stats } | null = null
7878
private middlewareLatestStats: { ts: number; stats: webpack.Stats } | null =
@@ -163,20 +163,20 @@ export class WebpackHotMiddleware {
163163
* and we still want to show the client overlay with the error while
164164
* the error page should be rendered just fine.
165165
*/
166-
onHMR = (client: ws, requestId: string | null) => {
166+
onHMR = (client: ws, htmlRequestId: string | null) => {
167167
if (this.closed) return
168168

169-
if (requestId) {
170-
this.clientsByRequestId.set(requestId, client)
169+
if (htmlRequestId) {
170+
this.clientsByHtmlRequestId.set(htmlRequestId, client)
171171
} else {
172-
this.clientsWithoutRequestId.add(client)
172+
this.clientsWithoutHtmlRequestId.add(client)
173173
}
174174

175175
client.addEventListener('close', () => {
176-
if (requestId) {
177-
this.clientsByRequestId.delete(requestId)
176+
if (htmlRequestId) {
177+
this.clientsByHtmlRequestId.delete(htmlRequestId)
178178
} else {
179-
this.clientsWithoutRequestId.delete(client)
179+
this.clientsWithoutHtmlRequestId.delete(client)
180180
}
181181
})
182182

@@ -228,8 +228,8 @@ export class WebpackHotMiddleware {
228228
})
229229
}
230230

231-
getClient = (requestId: string): ws | undefined => {
232-
return this.clientsByRequestId.get(requestId)
231+
getClient = (htmlRequestId: string): ws | undefined => {
232+
return this.clientsByHtmlRequestId.get(htmlRequestId)
233233
}
234234

235235
publishToClient = (client: ws, message: HmrMessageSentToBrowser) => {
@@ -251,8 +251,8 @@ export class WebpackHotMiddleware {
251251
}
252252

253253
for (const wsClient of [
254-
...this.clientsWithoutRequestId,
255-
...this.clientsByRequestId.values(),
254+
...this.clientsWithoutHtmlRequestId,
255+
...this.clientsByHtmlRequestId.values(),
256256
]) {
257257
this.publishToClient(wsClient, message)
258258
}
@@ -270,12 +270,12 @@ export class WebpackHotMiddleware {
270270
// inferring it from the presence of a request ID.
271271

272272
if (!this.config.cacheComponents) {
273-
for (const wsClient of this.clientsByRequestId.values()) {
273+
for (const wsClient of this.clientsByHtmlRequestId.values()) {
274274
this.publishToClient(wsClient, message)
275275
}
276276
}
277277

278-
for (const wsClient of this.clientsWithoutRequestId) {
278+
for (const wsClient of this.clientsWithoutHtmlRequestId) {
279279
this.publishToClient(wsClient, message)
280280
}
281281
}
@@ -290,30 +290,35 @@ export class WebpackHotMiddleware {
290290
this.closed = true
291291

292292
for (const wsClient of [
293-
...this.clientsWithoutRequestId,
294-
...this.clientsByRequestId.values(),
293+
...this.clientsWithoutHtmlRequestId,
294+
...this.clientsByHtmlRequestId.values(),
295295
]) {
296296
// it's okay to not cleanly close these websocket connections, this is dev
297297
wsClient.terminate()
298298
}
299299

300-
this.clientsWithoutRequestId.clear()
301-
this.clientsByRequestId.clear()
300+
this.clientsWithoutHtmlRequestId.clear()
301+
this.clientsByHtmlRequestId.clear()
302302
}
303303

304-
deleteClient = (client: ws, requestId: string | null) => {
305-
if (requestId) {
306-
this.clientsByRequestId.delete(requestId)
304+
deleteClient = (client: ws, htmlRequestId: string | null) => {
305+
if (htmlRequestId) {
306+
this.clientsByHtmlRequestId.delete(htmlRequestId)
307307
} else {
308-
this.clientsWithoutRequestId.delete(client)
308+
this.clientsWithoutHtmlRequestId.delete(client)
309309
}
310310
}
311311

312312
hasClients = () => {
313-
return this.clientsWithoutRequestId.size + this.clientsByRequestId.size > 0
313+
return (
314+
this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size >
315+
0
316+
)
314317
}
315318

316319
getClientCount = () => {
317-
return this.clientsWithoutRequestId.size + this.clientsByRequestId.size
320+
return (
321+
this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size
322+
)
318323
}
319324
}

0 commit comments

Comments
 (0)