Skip to content

Commit 2ef787b

Browse files
authored
[devtools] Add a query parameter to restart endpoint to invalidate the persistent cache (#79425)
Creates and passes through an option to turbopack to invalidate the cache. The dev overlay can then (not implemented) call this endpoint. Invalidation is performed by writing a marker file to disk, and then actually deleting the cache upon the next startup because: - Getting the database to drop all file handles is tricky. - You can't delete open files on Windows. - Writing an invalidation file is atomic, recursively deleting a directory is not. ### Testing Set up a small app with a large dependency (three.js). Ran the build, restarted the dev server, saw that the cached run was fast. Ran ``` curl -v --request POST --header "Content-Type: application/json" --data '{}' http://localhost:3000/__nextjs_restart_dev\?invalidatePersistentCache\= ``` And saw that the server restarted and the next page load was slow. ### Remaining Work (will come in subsequent PRs) In rough order of priority: - A separate endpoint to poll to see when the server is back up. - A webpack+rspack implementation. It looks like I can do a hack to forcibly change the webpack cache version, which should trigger similar behavior upon the next restart. - Passing a boolean to the dev overlay telling it if persistent caching is enabled or not. - The devtools UI implementation. - Telemetry. - A CLI subcommand that leverages this, so that you can do it from the terminal.
1 parent b7b546e commit 2ef787b

File tree

17 files changed

+225
-27
lines changed

17 files changed

+225
-27
lines changed

crates/napi/src/next_api/project.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,18 @@ pub async fn project_update(
568568
Ok(())
569569
}
570570

571+
/// Invalidates the persistent cache so that it will be deleted next time that a turbopack project
572+
/// is created with persistent caching enabled.
573+
#[napi]
574+
pub async fn project_invalidate_persistent_cache(
575+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
576+
) -> napi::Result<()> {
577+
tokio::task::spawn_blocking(move || project.turbo_tasks.invalidate_persistent_cache())
578+
.await
579+
.context("panicked while invalidating persistent cache")??;
580+
Ok(())
581+
}
582+
571583
/// Runs exit handlers for the project registered using the [`ExitHandler`] API.
572584
///
573585
/// This is called by `project_shutdown`, so if you're calling that API, you shouldn't call this

crates/napi/src/next_api/utils.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ impl NextTurboTasks {
149149
}
150150
}
151151
}
152+
153+
pub fn invalidate_persistent_cache(&self) -> Result<()> {
154+
match self {
155+
NextTurboTasks::Memory(_) => {}
156+
NextTurboTasks::PersistentCaching(turbo_tasks) => {
157+
turbo_tasks.backend().invalidate_storage()?
158+
}
159+
}
160+
Ok(())
161+
}
152162
}
153163

154164
pub fn create_turbo_tasks(

packages/next/src/build/swc/generated-native.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ export declare function projectUpdate(
204204
project: { __napiType: 'Project' },
205205
options: NapiPartialProjectOptions
206206
): Promise<void>
207+
/**
208+
* Invalidates the persistent cache so that it will be deleted next time that a turbopack project
209+
* is created with persistent caching enabled.
210+
*/
211+
export declare function projectInvalidatePersistentCache(project: {
212+
__napiType: 'Project'
213+
}): Promise<void>
207214
/**
208215
* Runs exit handlers for the project registered using the [`ExitHandler`] API.
209216
*

packages/next/src/build/swc/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,10 @@ function bindingToApi(
723723
)
724724
}
725725

726+
invalidatePersistentCache(): Promise<void> {
727+
return binding.projectInvalidatePersistentCache(this._nativeProject)
728+
}
729+
726730
shutdown(): Promise<void> {
727731
return binding.projectShutdown(this._nativeProject)
728732
}

packages/next/src/build/swc/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ export interface Project {
226226
TurbopackResult<CompilationEvent>
227227
>
228228

229+
invalidatePersistentCache(): Promise<void>
230+
229231
shutdown(): Promise<void>
230232

231233
onExit(): Promise<void>

packages/next/src/client/components/react-dev-overlay/server/restart-dev-server-middleware.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,39 @@ import type { ServerResponse, IncomingMessage } from 'http'
22
import type { Telemetry } from '../../../../telemetry/storage'
33
import { RESTART_EXIT_CODE } from '../../../../server/lib/utils'
44
import { middlewareResponse } from './middleware-response'
5+
import type { Project } from '../../../../build/swc/types'
56

67
const EVENT_DEV_OVERLAY_RESTART_SERVER = 'DEV_OVERLAY_RESTART_SERVER'
78

8-
export function getRestartDevServerMiddleware(telemetry: Telemetry) {
9+
interface RestartDevServerMiddlewareConfig {
10+
telemetry: Telemetry
11+
turbopackProject?: Project
12+
}
13+
14+
export function getRestartDevServerMiddleware({
15+
telemetry,
16+
turbopackProject,
17+
}: RestartDevServerMiddlewareConfig) {
918
return async function (
1019
req: IncomingMessage,
1120
res: ServerResponse,
1221
next: () => void
1322
): Promise<void> {
14-
const { pathname } = new URL(`http://n${req.url}`)
23+
const { pathname, searchParams } = new URL(`http://n${req.url}`)
1524
if (pathname !== '/__nextjs_restart_dev' || req.method !== 'POST') {
1625
return next()
1726
}
1827

28+
const invalidatePersistentCache = searchParams.has(
29+
'invalidatePersistentCache'
30+
)
31+
if (invalidatePersistentCache) {
32+
await turbopackProject?.invalidatePersistentCache()
33+
}
34+
1935
telemetry.record({
2036
eventName: EVENT_DEV_OVERLAY_RESTART_SERVER,
21-
payload: {},
37+
payload: { invalidatePersistentCache },
2238
})
2339

2440
// TODO: Use flushDetached

packages/next/src/server/dev/hot-reloader-turbopack.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,10 @@ export async function createHotReloaderTurbopack(
650650
getNextErrorFeedbackMiddleware(opts.telemetry),
651651
getDevOverlayFontMiddleware(),
652652
getDisableDevIndicatorMiddleware(),
653-
getRestartDevServerMiddleware(opts.telemetry),
653+
getRestartDevServerMiddleware({
654+
telemetry: opts.telemetry,
655+
turbopackProject: project,
656+
}),
654657
]
655658

656659
const versionInfoPromise = getVersionInfo()

packages/next/src/server/dev/hot-reloader-webpack.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1570,7 +1570,9 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface {
15701570
getNextErrorFeedbackMiddleware(this.telemetry),
15711571
getDevOverlayFontMiddleware(),
15721572
getDisableDevIndicatorMiddleware(),
1573-
getRestartDevServerMiddleware(this.telemetry),
1573+
getRestartDevServerMiddleware({
1574+
telemetry: this.telemetry,
1575+
}),
15741576
]
15751577
}
15761578

turbopack/crates/turbo-persistence/src/db.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ impl TurboPersistence {
190190
Ok(db)
191191
}
192192

193-
/// Performas the initial check on the database directory.
193+
/// Performs the initial check on the database directory.
194194
fn open_directory(&mut self) -> Result<()> {
195195
match fs::read_dir(&self.path) {
196196
Ok(entries) => {

turbopack/crates/turbo-tasks-backend/src/backend/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ impl<B: BackingStorage> TurboTasksBackend<B> {
207207
backing_storage,
208208
)))
209209
}
210+
211+
pub fn invalidate_storage(&self) -> Result<()> {
212+
self.0.backing_storage.invalidate()
213+
}
210214
}
211215

212216
impl<B: BackingStorage> TurboTasksBackendInner<B> {

0 commit comments

Comments
 (0)