Skip to content

Commit a0e5996

Browse files
authored
[Cache Components] Allow sync IO inside console methods (#83843)
console methods may be patched to do things like add a timestamp to the log row. While we can't determine the meaning of reading the current time in arbitrary contexts we can know with reasonable certainty that any sync IO inside a console method is not being used in the logical output of the program. To account for this we can make a special affordance for sync IO inside of these methods that allows them to run without interrupting the prerender early. To do this we patch these properties on the console global and set up a wrapper in the setter. This wrapper will make any assigned function into another function that exits the `workUnitAsyncStorage` scope before executing the wrapped function. This will allow `new Date()` and other sync IO APIs to be called in the internal implementation of these methods without triggering an error while prerendering. Fixes NAR-403
1 parent 74b226f commit a0e5996

File tree

13 files changed

+902
-20
lines changed

13 files changed

+902
-20
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts",
6363
"packages/next/src/server/app-render/work-async-storage-instance.ts",
6464
"packages/next/src/server/app-render/work-unit-async-storage-instance.ts",
65+
"packages/next/src/server/app-render/dev-logs-async-storage-instance.ts",
6566
"packages/next/src/client/components/segment-cache-impl/*"
6667
],
6768
// Disable TypeScript surveys.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ import {
174174
workUnitAsyncStorage,
175175
type PrerenderStore,
176176
} from './work-unit-async-storage.external'
177+
import { devLogsAsyncStorage } from './dev-logs-async-storage.external'
177178
import { CacheSignal } from './cache-signal'
178179
import { getTracedMetadata } from '../lib/trace/utils'
179180
import { InvariantError } from '../../shared/lib/invariant-error'
@@ -2302,7 +2303,9 @@ async function renderToStream(
23022303
}
23032304
)
23042305

2305-
spawnDynamicValidationInDev(
2306+
devLogsAsyncStorage.run(
2307+
{ dim: true },
2308+
spawnDynamicValidationInDev,
23062309
resolveValidation,
23072310
tree,
23082311
ctx,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createAsyncLocalStorage } from './async-local-storage'
2+
import type { DevLogsAsyncStorage } from './dev-logs-async-storage.external'
3+
4+
export const devLogsAsyncStorageInstance: DevLogsAsyncStorage =
5+
createAsyncLocalStorage()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { AsyncLocalStorage } from 'async_hooks'
2+
3+
// Share the instance module in the next-shared layer
4+
import { devLogsAsyncStorageInstance } from './dev-logs-async-storage-instance' with { 'turbopack-transition': 'next-shared' }
5+
6+
export interface DevLogsStore {
7+
/**
8+
* if true the color of logs output will be dimmed to indicate the log is
9+
* from a repeat or validation render that is not typically relevant to
10+
* the primary action the server is taking.
11+
*/
12+
readonly dim: boolean
13+
}
14+
15+
export type DevLogsAsyncStorage = AsyncLocalStorage<DevLogsStore>
16+
17+
export { devLogsAsyncStorageInstance as devLogsAsyncStorage }

packages/next/src/server/node-environment-extensions/console-dev.tsx

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dim } from '../../lib/picocolors'
2-
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'
2+
import { devLogsAsyncStorage } from '../app-render/dev-logs-async-storage.external'
33

44
type InterceptableConsoleMethod =
55
| 'error'
@@ -163,25 +163,12 @@ function patchConsoleMethodDEV(methodName: InterceptableConsoleMethod): void {
163163
const originalMethod = descriptor.value
164164
const originalName = Object.getOwnPropertyDescriptor(originalMethod, 'name')
165165
const wrapperMethod = function (this: typeof console, ...args: any[]) {
166-
const workUnitStore = workUnitAsyncStorage.getStore()
166+
const devLogsStore = devLogsAsyncStorage.getStore()
167167

168-
switch (workUnitStore?.type) {
169-
case 'prerender':
170-
case 'prerender-client':
171-
case 'prerender-runtime':
172-
originalMethod.apply(this, dimConsoleCall(methodName, args))
173-
break
174-
case 'prerender-ppr':
175-
case 'prerender-legacy':
176-
case 'request':
177-
case 'cache':
178-
case 'private-cache':
179-
case 'unstable-cache':
180-
case undefined:
181-
originalMethod.apply(this, args)
182-
break
183-
default:
184-
workUnitStore satisfies never
168+
if (devLogsStore?.dim === true) {
169+
return originalMethod.apply(this, dimConsoleCall(methodName, args))
170+
} else {
171+
return originalMethod.apply(this, args)
185172
}
186173
}
187174
if (originalName) {

0 commit comments

Comments
 (0)