Skip to content

Commit 5fae29c

Browse files
committed
[Cache Components] Allow sync IO inside console methods
console methods may be patched to do things like add a timestamp to the log row. While we can't determine the meaning of readin 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. Originally I wanted to make it so that even if you patch these methods in userland we can still exit the context but it turns out this is very complex and difficult because we have many patches layered together and they need to run in the correct order. So essentially this just permits sync IO in the preexisting implementations of the console methods. This is probably sufficient since typically these methods aren't patched in userspace but in the runtime host itself. If we recieve feedback that this is insufficient we can consider alternative later. The implementation moves the console dimming logic to it's own AsyncLocalStorage so we can exit the workUnitAsyncStorage without altering the console dimming behavior. Conceptually you can think of the internal implementation of console methods as existing outside of any particular render (prerender or otherwise).
1 parent 4d7f836 commit 5fae29c

File tree

12 files changed

+900
-19
lines changed

12 files changed

+900
-19
lines changed

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
dim: boolean
8+
}
9+
10+
export type DevLogsAsyncStorage = AsyncLocalStorage<DevLogsStore>
11+
12+
export { devLogsAsyncStorageInstance as devLogsAsyncStorage }

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

Lines changed: 7 additions & 18 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,14 @@ 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
168+
switch (devLogsStore?.dim) {
169+
case true:
170+
return originalMethod.apply(this, dimConsoleCall(methodName, args))
171+
case false:
183172
default:
184-
workUnitStore satisfies never
173+
return originalMethod.apply(this, args)
185174
}
186175
}
187176
if (originalName) {

0 commit comments

Comments
 (0)