Skip to content

Commit 020bef2

Browse files
committed
feat(dev-overlay): Add a query parameter to restart endpoint to invalidate the persistent cache
1 parent 8d25e84 commit 020bef2

File tree

17 files changed

+182
-21
lines changed

17 files changed

+182
-21
lines changed

crates/napi/src/next_api/project.rs

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

570+
/// Invalidates the persistent cache so that it will be deleted next time that a turbopack project
571+
/// is created with persistent caching enabled.
572+
#[napi]
573+
pub async fn project_invalidate_persistent_cache(
574+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
575+
) -> napi::Result<()> {
576+
tokio::task::spawn_blocking(move || project.turbo_tasks.invalidate_persistent_cache())
577+
.await
578+
.context("panicked while invalidating persistent cache")??;
579+
Ok(())
580+
}
581+
570582
/// Runs exit handlers for the project registered using the [`ExitHandler`] API.
571583
///
572584
/// 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ 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) => turbo_tasks.backend().invalidate()?,
157+
}
158+
Ok(())
159+
}
152160
}
153161

154162
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
@@ -201,6 +201,13 @@ export declare function projectUpdate(
201201
project: { __napiType: 'Project' },
202202
options: NapiPartialProjectOptions
203203
): Promise<void>
204+
/**
205+
* Invalidates the persistent cache so that it will be deleted next time that a turbopack project
206+
* is created with persistent caching enabled.
207+
*/
208+
export declare function projectInvalidatePersistentCache(project: {
209+
__napiType: 'Project'
210+
}): Promise<void>
204211
/**
205212
* Runs exit handlers for the project registered using the [`ExitHandler`] API.
206213
*

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
@@ -227,6 +227,8 @@ export interface Project {
227227
TurbopackResult<CompilationEvent>
228228
>
229229

230+
invalidatePersistentCache(): Promise<void>
231+
230232
shutdown(): Promise<void>
231233

232234
onExit(): Promise<void>

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,33 @@ 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+
if (searchParams.has('invalidatePersistentCache')) {
29+
await turbopackProject?.invalidatePersistentCache()
30+
}
31+
1932
telemetry.record({
2033
eventName: EVENT_DEV_OVERLAY_RESTART_SERVER,
2134
payload: {},

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
@@ -176,7 +176,7 @@ impl TurboPersistence {
176176
Ok(db)
177177
}
178178

179-
/// Performas the initial check on the database directory.
179+
/// Performs the initial check on the database directory.
180180
fn open_directory(&mut self) -> Result<()> {
181181
match fs::read_dir(&self.path) {
182182
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
@@ -202,6 +202,10 @@ impl<B: BackingStorage> TurboTasksBackend<B> {
202202
backing_storage,
203203
)))
204204
}
205+
206+
pub fn invalidate(&self) -> Result<()> {
207+
self.0.backing_storage.invalidate()
208+
}
205209
}
206210

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

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ pub trait BackingStorage: 'static + Send + Sync {
6363
category: TaskDataCategory,
6464
) -> Vec<CachedDataItem>;
6565

66+
/// Called when the database should be invalidated upon re-initialization.
67+
///
68+
/// This typically means that we'll restart the process or `turbo-tasks` soon with a fresh
69+
/// database. If this happens, there's no point in writing anything else to disk, or flushing
70+
/// during [`KeyValueDatabase::shutdown`].
71+
///
72+
/// This can be implemented by calling [`crate::database::db_invalidation::invalidate_db`] with
73+
/// the database's non-versioned base path.
74+
fn invalidate(&self) -> Result<()>;
75+
6676
fn shutdown(&self) -> Result<()> {
6777
Ok(())
6878
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::{
2+
fs::{self, read_dir},
3+
io,
4+
path::Path,
5+
};
6+
7+
const INVALIDATION_MARKER: &str = "__turbo_tasks_invalidated_db";
8+
9+
/// Atomically write an invalidation marker.
10+
///
11+
/// Because attempting to delete currently open database files could cause issues, actual deletion
12+
/// of files is deferred until the next start-up (in [`check_db_invalidation`]).
13+
///
14+
/// In the case that no database is currently open (e.g. via a separate CLI subcommand), you should
15+
/// call [`cleanup_db`] after this to eagerly remove the database files.
16+
///
17+
/// This should be run with the base (non-versioned) path, as that likely aligns closest with user
18+
/// expectations (e.g. if they're clearing the cache for disk space reasons).
19+
pub fn invalidate_db(base_path: &Path) -> io::Result<()> {
20+
fs::write(base_path.join(INVALIDATION_MARKER), &[0u8; 0])?;
21+
Ok(())
22+
}
23+
24+
/// Called during startup. See if the db is in a partially-completed invalidation state. Find and
25+
/// delete any invalidated database files.
26+
///
27+
/// This should be run with the base (non-versioned) path.
28+
pub fn check_db_invalidation_and_cleanup(base_path: &Path) -> io::Result<()> {
29+
if fs::exists(base_path.join(INVALIDATION_MARKER))? {
30+
// if this cleanup fails, we might try to open an invalid database later, so it's best to
31+
// just propagate the error here.
32+
cleanup_db(base_path)?;
33+
};
34+
Ok(())
35+
}
36+
37+
/// Helper for `invalidate_db` and `check_db_invalidation`.
38+
pub fn cleanup_db(base_path: &Path) -> io::Result<()> {
39+
let Ok(contents) = read_dir(base_path) else {
40+
return Ok(());
41+
};
42+
43+
// delete everything except the invalidation marker
44+
for entry in contents {
45+
let entry = entry?;
46+
if entry.file_name() != INVALIDATION_MARKER {
47+
let _ = fs::remove_dir_all(entry.path());
48+
}
49+
}
50+
51+
// delete the invalidation marker last, once we're sure everything is cleaned up
52+
fs::remove_file(base_path.join(INVALIDATION_MARKER))?;
53+
Ok(())
54+
}

turbopack/crates/turbo-tasks-backend/src/database/key_value_database.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ pub trait KeyValueDatabase {
5252
&self,
5353
) -> Result<WriteBatch<'_, Self::SerialWriteBatch<'_>, Self::ConcurrentWriteBatch<'_>>>;
5454

55+
/// Called when the database has been invalidated via
56+
/// [`crate::backing_storage::BackingStorage::invalidate`]
57+
///
58+
/// This typically means that we'll restart the process or `turbo-tasks` soon with a fresh
59+
/// database. If this happens, there's no point in writing anything else to disk, or flushing
60+
/// during [`KeyValueDatabase::shutdown`].
61+
///
62+
/// This is a best-effort optimization hint, and the database may choose to ignore this and
63+
/// continue file writes.
64+
fn prevent_writes(&self) {
65+
// this is an optional performance hint to the database
66+
}
67+
5568
fn shutdown(&self) -> Result<()> {
5669
Ok(())
5770
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(feature = "lmdb")]
22
mod by_key_space;
3+
pub mod db_invalidation;
34
pub mod db_versioning;
45
#[cfg(feature = "lmdb")]
56
pub mod fresh_db_optimization;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ pub struct TurboKeyValueDatabase {
2323
}
2424

2525
impl TurboKeyValueDatabase {
26-
pub fn new(path: PathBuf) -> Result<Self> {
27-
let db = Arc::new(TurboPersistence::open(path.to_path_buf())?);
26+
pub fn new(versioned_path: PathBuf) -> Result<Self> {
27+
let db = Arc::new(TurboPersistence::open(versioned_path.to_path_buf())?);
2828
let mut this = Self {
2929
db: db.clone(),
3030
compact_join_handle: Mutex::new(None),
@@ -99,6 +99,8 @@ impl KeyValueDatabase for TurboKeyValueDatabase {
9999
}))
100100
}
101101

102+
fn prevent_writes(&self) {}
103+
102104
fn shutdown(&self) -> Result<()> {
103105
// Wait for the compaction to finish
104106
if let Some(join_handle) = self.compact_join_handle.lock().take() {

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{borrow::Borrow, cmp::max, sync::Arc};
1+
use std::{borrow::Borrow, cmp::max, path::PathBuf, sync::Arc};
22

33
use anyhow::{Context, Result, anyhow};
44
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
@@ -12,6 +12,7 @@ use crate::{
1212
backing_storage::BackingStorage,
1313
data::CachedDataItem,
1414
database::{
15+
db_invalidation::invalidate_db,
1516
key_value_database::{KeySpace, KeyValueDatabase},
1617
write_batch::{
1718
BaseWriteBatch, ConcurrentWriteBatch, SerialWriteBatch, WriteBatch, WriteBatchRef,
@@ -83,11 +84,17 @@ fn as_u32(bytes: impl Borrow<[u8]>) -> Result<u32> {
8384

8485
pub struct KeyValueDatabaseBackingStorage<T: KeyValueDatabase> {
8586
database: T,
87+
/// Used when calling [`BackingStorage::invalidate`]. Can be `None` in the memory-only/no-op
88+
/// storage case.
89+
base_path: Option<PathBuf>,
8690
}
8791

8892
impl<T: KeyValueDatabase> KeyValueDatabaseBackingStorage<T> {
89-
pub fn new(database: T) -> Self {
90-
Self { database }
93+
pub fn new(database: T, base_path: Option<PathBuf>) -> Self {
94+
Self {
95+
database,
96+
base_path,
97+
}
9198
}
9299

93100
fn with_tx<R>(
@@ -445,6 +452,15 @@ impl<T: KeyValueDatabase + Send + Sync + 'static> BackingStorage
445452
.unwrap_or_default()
446453
}
447454

455+
fn invalidate(&self) -> Result<()> {
456+
if let Some(base_path) = &self.base_path {
457+
// `base_path` can be `None` for a `NoopKvDb`
458+
invalidate_db(base_path)?;
459+
self.database.prevent_writes()
460+
}
461+
Ok(())
462+
}
463+
448464
fn shutdown(&self) -> Result<()> {
449465
self.database.shutdown()
450466
}

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod utils;
1414
use std::path::Path;
1515

1616
use anyhow::Result;
17+
use database::db_invalidation::check_db_invalidation_and_cleanup;
1718

1819
use crate::database::{
1920
db_versioning::handle_db_versioning, noop_kv::NoopKvDb, turbo::TurboKeyValueDatabase,
@@ -37,7 +38,7 @@ pub type LmdbBackingStorage = KeyValueDatabaseBackingStorage<
3738

3839
#[cfg(feature = "lmdb")]
3940
pub fn lmdb_backing_storage(
40-
path: &Path,
41+
base_path: &Path,
4142
version_info: &GitVersionInfo,
4243
) -> Result<LmdbBackingStorage> {
4344
use crate::database::{
@@ -46,30 +47,39 @@ pub fn lmdb_backing_storage(
4647
startup_cache::StartupCacheLayer,
4748
};
4849

49-
let path = handle_db_versioning(path, version_info)?;
50-
let fresh_db = is_fresh(&path);
51-
let database = crate::database::lmdb::LmbdKeyValueDatabase::new(&path)?;
50+
check_db_invalidation_and_cleanup(base_path)?;
51+
let versioned_path = handle_db_versioning(base_path, version_info)?;
52+
let fresh_db = is_fresh(&versioned_path);
53+
let database = crate::database::lmdb::LmbdKeyValueDatabase::new(&versioned_path)?;
5254
let database = FreshDbOptimization::new(database, fresh_db);
53-
let database = StartupCacheLayer::new(database, path.join("startup.cache"), fresh_db)?;
55+
let database =
56+
StartupCacheLayer::new(database, versioned_path.join("startup.cache"), fresh_db)?;
5457
let database = ReadTransactionCache::new(database);
55-
Ok(KeyValueDatabaseBackingStorage::new(database))
58+
Ok(KeyValueDatabaseBackingStorage::new(
59+
database,
60+
Some(base_path.to_owned()),
61+
))
5662
}
5763

5864
pub type TurboBackingStorage = KeyValueDatabaseBackingStorage<TurboKeyValueDatabase>;
5965

6066
pub fn turbo_backing_storage(
61-
path: &Path,
67+
base_path: &Path,
6268
version_info: &GitVersionInfo,
6369
) -> Result<TurboBackingStorage> {
64-
let path = handle_db_versioning(path, version_info)?;
65-
let database = TurboKeyValueDatabase::new(path)?;
66-
Ok(KeyValueDatabaseBackingStorage::new(database))
70+
check_db_invalidation_and_cleanup(base_path)?;
71+
let versioned_path = handle_db_versioning(base_path, version_info)?;
72+
let database = TurboKeyValueDatabase::new(versioned_path)?;
73+
Ok(KeyValueDatabaseBackingStorage::new(
74+
database,
75+
Some(base_path.to_owned()),
76+
))
6777
}
6878

6979
pub type NoopBackingStorage = KeyValueDatabaseBackingStorage<NoopKvDb>;
7080

7181
pub fn noop_backing_storage() -> NoopBackingStorage {
72-
KeyValueDatabaseBackingStorage::new(NoopKvDb)
82+
KeyValueDatabaseBackingStorage::new(NoopKvDb, None)
7383
}
7484

7585
#[cfg(feature = "lmdb")]

0 commit comments

Comments
 (0)