Skip to content

Commit

Permalink
feat: persistent cache (#8687)
Browse files Browse the repository at this point in the history
* feat: persistent cache

* fix: ci
  • Loading branch information
jerrykingxyz authored Dec 13, 2024
1 parent a313b28 commit 2e65ed6
Show file tree
Hide file tree
Showing 38 changed files with 451 additions and 225 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ rspack_plugin_wasm = { version = "0.2.0", path = "crates/rsp
rspack_plugin_web_worker_template = { version = "0.2.0", path = "crates/rspack_plugin_web_worker_template" }
rspack_plugin_worker = { version = "0.2.0", path = "crates/rspack_plugin_worker" }
rspack_regex = { version = "0.2.0", path = "crates/rspack_regex" }
rspack_storage = { version = "0.2.0", path = "crates/rspack_storage" }
rspack_swc_plugin_import = { version = "0.2.0", path = "crates/swc_plugin_import" }
rspack_testing = { version = "0.2.0", path = "crates/rspack_testing" }
rspack_tracing = { version = "0.2.0", path = "crates/rspack_tracing" }
Expand Down
4 changes: 3 additions & 1 deletion crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1379,8 +1379,10 @@ export interface RawExperimentCacheOptionsCommon {

export interface RawExperimentCacheOptionsPersistent {
type: "persistent"
buildDependencies: Array<string>
version: string
snapshot: RawExperimentSnapshotOptions
storage: Array<RawStorageOptions>
storage: RawStorageOptions
}

export interface RawExperiments {
Expand Down
8 changes: 4 additions & 4 deletions crates/node_binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extern crate napi_derive;
extern crate rspack_allocator;

use std::pin::Pin;
use std::sync::Mutex;
use std::sync::{Arc, Mutex};

use compiler::{Compiler, CompilerState, CompilerStateGuard};
use napi::bindgen_prelude::*;
Expand Down Expand Up @@ -71,9 +71,9 @@ impl Rspack {
let loader_resolver_factory = (*resolver_factory_reference)
.get_loader_resolver_factory(compiler_options.resolve_loader.clone());

let intermediate_filesystem: Option<Box<dyn IntermediateFileSystem>> =
let intermediate_filesystem: Option<Arc<dyn IntermediateFileSystem>> =
if let Some(fs) = intermediate_filesystem {
Some(Box::new(NodeFileSystem::new(fs).map_err(|e| {
Some(Arc::new(NodeFileSystem::new(fs).map_err(|e| {
Error::from_reason(format!("Failed to create intermediate filesystem: {e}",))
})?))
} else {
Expand All @@ -85,7 +85,7 @@ impl Rspack {
compiler_options,
plugins,
rspack_binding_options::buildtime_plugins::buildtime_plugins(),
Some(Box::new(NodeFileSystem::new(output_filesystem).map_err(
Some(Arc::new(NodeFileSystem::new(output_filesystem).map_err(
|e| Error::from_reason(format!("Failed to create writable filesystem: {e}",)),
)?)),
intermediate_filesystem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,21 @@ pub struct RawExperimentCacheOptionsCommon {
pub struct RawExperimentCacheOptionsPersistent {
#[napi(ts_type = r#""persistent""#)]
pub r#type: String,
// pub build_dependencies: Vec<String>,
// pub version: String,
pub build_dependencies: Vec<String>,
pub version: String,
pub snapshot: RawExperimentSnapshotOptions,
pub storage: Vec<RawStorageOptions>,
pub storage: RawStorageOptions,
}

pub fn normalize_raw_experiment_cache_options(
options: RawExperimentCacheOptions,
) -> ExperimentCacheOptions {
match options {
Either::A(persistent_options) => ExperimentCacheOptions::Persistent(PersistentCacheOptions {
build_dependencies: persistent_options.build_dependencies,
version: persistent_options.version,
snapshot: persistent_options.snapshot.into(),
storage: persistent_options
.storage
.into_iter()
.map(Into::into)
.collect(),
storage: persistent_options.storage.into(),
}),
Either::B(options) => match options.r#type.as_str() {
"disable" => ExperimentCacheOptions::Disabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ pub struct RawStorageOptions {
}

impl From<RawStorageOptions> for StorageOptions {
fn from(_value: RawStorageOptions) -> Self {
StorageOptions::FileSystem
fn from(value: RawStorageOptions) -> Self {
match value.r#type.as_str() {
"filesystem" => StorageOptions::FileSystem {
directory: value.directory.into(),
},
s => panic!("unsupported storage type {s}"),
}
}
}
1 change: 1 addition & 0 deletions crates/rspack_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ rspack_paths = { workspace = true }
rspack_regex = { workspace = true }
rspack_resolver = { workspace = true }
rspack_sources = { workspace = true }
rspack_storage = { workspace = true }
rspack_util = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion crates/rspack_core/src/cache/disable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use crate::make::MakeArtifact;
#[derive(Debug)]
pub struct DisableCache;

#[async_trait::async_trait]
impl Cache for DisableCache {
fn before_make(&self, make_artifact: &mut MakeArtifact) {
async fn before_make(&self, make_artifact: &mut MakeArtifact) {
*make_artifact = Default::default();
}
}
31 changes: 22 additions & 9 deletions crates/rspack_core/src/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pub mod persistent;

use std::{fmt::Debug, sync::Arc};

use rspack_fs::FileSystem;
use rspack_fs::{FileSystem, IntermediateFileSystem};

use self::{disable::DisableCache, memory::MemoryCache, persistent::PersistentCache};
pub use self::{disable::DisableCache, memory::MemoryCache, persistent::PersistentCache};
use crate::{make::MakeArtifact, Compilation, CompilerOptions, ExperimentCacheOptions};

/// Cache trait
Expand All @@ -21,25 +21,38 @@ use crate::{make::MakeArtifact, Compilation, CompilerOptions, ExperimentCacheOpt
/// * This API does not need to cooperate with the js side.
///
/// We can consider change to Hook when we need to open the API to js side.
#[async_trait::async_trait]
pub trait Cache: Debug + Send + Sync {
fn before_compile(&self, _compilation: &mut Compilation) {}
async fn before_compile(&self, _compilation: &mut Compilation) {}
fn after_compile(&self, _compilation: &Compilation) {}

fn before_make(&self, _make_artifact: &mut MakeArtifact) {}
async fn before_make(&self, _make_artifact: &mut MakeArtifact) {}
fn after_make(&self, _make_artifact: &MakeArtifact) {}
}

pub fn new_cache(
compiler_path: &String,
compiler_option: Arc<CompilerOptions>,
input_filesystem: Arc<dyn FileSystem>,
intermediate_filesystem: Arc<dyn IntermediateFileSystem>,
) -> Arc<dyn Cache> {
match &compiler_option.experiments.cache {
ExperimentCacheOptions::Disabled => Arc::new(DisableCache),
ExperimentCacheOptions::Memory => Arc::new(MemoryCache),
ExperimentCacheOptions::Persistent(option) => Arc::new(PersistentCache::new(
option,
input_filesystem,
compiler_option.clone(),
)),
ExperimentCacheOptions::Persistent(option) => {
match PersistentCache::new(
compiler_path,
option,
compiler_option.clone(),
input_filesystem,
intermediate_filesystem,
) {
Ok(cache) => Arc::new(cache),
Err(e) => {
tracing::warn!("create persistent cache failed {e:?}");
Arc::new(MemoryCache)
}
}
}
}
}
45 changes: 31 additions & 14 deletions crates/rspack_core/src/cache/persistent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ mod cacheable_context;
mod occasion;
pub mod snapshot;
pub mod storage;
mod version;
use std::sync::Arc;

pub use cacheable_context::{CacheableContext, FromContext};
use occasion::MakeOccasion;
use rspack_fs::FileSystem;
use rspack_fs::{FileSystem, IntermediateFileSystem, Result};
use rspack_paths::ArcPath;
use rustc_hash::FxHashSet as HashSet;

use self::{
snapshot::{Snapshot, SnapshotOptions},
storage::{MemoryStorage, Storage, StorageOptions},
storage::{create_storage, Storage, StorageOptions},
};
use super::Cache;
use crate::{make::MakeArtifact, Compilation, CompilerOptions};

#[derive(Debug, Clone)]
pub struct PersistentCacheOptions {
pub build_dependencies: Vec<String>,
pub version: String,
pub snapshot: SnapshotOptions,
pub storage: Vec<StorageOptions>,
pub storage: StorageOptions,
}

/// Persistent cache implementation
Expand All @@ -33,29 +36,39 @@ pub struct PersistentCache {

impl PersistentCache {
pub fn new(
compiler_path: &String,
option: &PersistentCacheOptions,
input_filesystem: Arc<dyn FileSystem>,
compiler_options: Arc<CompilerOptions>,
) -> Self {
let storage = Arc::new(MemoryStorage::default());
input_filesystem: Arc<dyn FileSystem>,
intermediate_filesystem: Arc<dyn IntermediateFileSystem>,
) -> Result<Self> {
let version = version::get_version(
compiler_options.context.as_ref(),
input_filesystem.clone(),
&option.build_dependencies,
vec![compiler_path, &option.version],
)?;
let storage = create_storage(option.storage.clone(), version, intermediate_filesystem);
let context = Arc::new(CacheableContext {
options: compiler_options,
input_filesystem: input_filesystem.clone(),
});
let make_occasion = MakeOccasion::new(storage.clone(), context);
Self {
Ok(Self {
snapshot: Snapshot::new(option.snapshot.clone(), input_filesystem, storage.clone()),
storage,
make_occasion,
}
})
}
}

#[async_trait::async_trait]
impl Cache for PersistentCache {
fn before_compile(&self, compilation: &mut Compilation) {
async fn before_compile(&self, compilation: &mut Compilation) {
if compilation.modified_files.is_empty() && compilation.removed_files.is_empty() {
// inject modified_files and removed_files
let (modified_paths, removed_paths) = self.snapshot.calc_modified_paths();
let (modified_paths, removed_paths) = self.snapshot.calc_modified_paths().await;
tracing::info!("cache::snapshot recovery {modified_paths:?} {removed_paths:?}",);
compilation.modified_files = modified_paths;
compilation.removed_files = removed_paths;
}
Expand Down Expand Up @@ -92,13 +105,17 @@ impl Cache for PersistentCache {
.snapshot
.add(modified_paths.iter().map(|item| item.as_ref()));

self.storage.trigger_save();
// TODO listen for storage finish in build mode
let _ = self.storage.trigger_save();
}

fn before_make(&self, make_artifact: &mut MakeArtifact) {
async fn before_make(&self, make_artifact: &mut MakeArtifact) {
if !make_artifact.initialized {
if let Ok(artifact) = self.make_occasion.recovery() {
*make_artifact = artifact;
match self.make_occasion.recovery().await {
Ok(artifact) => *make_artifact = artifact,
Err(err) => {
tracing::warn!("recovery error with {err:?}")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap as HashMap;
use super::Storage;
use crate::FileCounter;

const SCOPE: &str = "occasion::make::dependencies";
const SCOPE: &str = "occasion_make_dependencies";

/// Dependency type
#[cacheable]
Expand Down Expand Up @@ -109,39 +109,46 @@ pub fn save_dependencies_info(
})
}

pub fn recovery_dependencies_info(
pub async fn recovery_dependencies_info(
storage: &Arc<dyn Storage>,
) -> Result<(FileCounter, FileCounter, FileCounter, FileCounter), DeserializeError> {
let file_dep = Mutex::new(HashMap::default());
let context_dep = Mutex::new(HashMap::default());
let missing_dep = Mutex::new(HashMap::default());
let build_dep = Mutex::new(HashMap::default());
storage.load(SCOPE).into_par_iter().try_for_each(|(k, v)| {
let count = usize::from_ne_bytes(
v.try_into()
.map_err(|_| DeserializeError::MessageError("deserialize count failed"))?,
);
let Dependency { r#type, path } = from_bytes(&k, &())?;
match r#type {
DepType::File => file_dep
.lock()
.expect("should get file dep")
.insert(path, count),
DepType::Context => context_dep
.lock()
.expect("should get context dep")
.insert(path, count),
DepType::Missing => missing_dep
.lock()
.expect("should get missing dep")
.insert(path, count),
DepType::Build => build_dep
.lock()
.expect("should get build dep")
.insert(path, count),
};
Ok(())
})?;
storage
.load(SCOPE)
.await
.unwrap_or_default()
.into_par_iter()
.try_for_each(|(k, v)| {
let count = usize::from_ne_bytes(
v.as_ref()
.clone()
.try_into()
.map_err(|_| DeserializeError::MessageError("deserialize count failed"))?,
);
let Dependency { r#type, path } = from_bytes(&k, &())?;
match r#type {
DepType::File => file_dep
.lock()
.expect("should get file dep")
.insert(path, count),
DepType::Context => context_dep
.lock()
.expect("should get context dep")
.insert(path, count),
DepType::Missing => missing_dep
.lock()
.expect("should get missing dep")
.insert(path, count),
DepType::Build => build_dep
.lock()
.expect("should get build dep")
.insert(path, count),
};
Ok(())
})?;

Ok((
FileCounter::new(file_dep.into_inner().expect("into_inner should be success")),
Expand Down
6 changes: 3 additions & 3 deletions crates/rspack_core/src/cache/persistent/occasion/make/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustc_hash::FxHashSet as HashSet;
use super::Storage;
use crate::{BuildDependency, DEPENDENCY_ID};

const SCOPE: &str = "occasion::make::meta";
const SCOPE: &str = "occasion_make_meta";

/// The value struct of current storage scope
#[cacheable]
Expand Down Expand Up @@ -44,10 +44,10 @@ pub fn save_meta(
Ok(())
}

pub fn recovery_meta(
pub async fn recovery_meta(
storage: &Arc<dyn Storage>,
) -> Result<(HashSet<BuildDependency>, IdentifierSet), DeserializeError> {
let Some((_, value)) = storage.load(SCOPE).pop() else {
let Some((_, value)) = storage.load(SCOPE).await.unwrap_or_default().pop() else {
return Err(DeserializeError::MessageError("can not get meta data"));
};
let meta: Meta = from_bytes(&value, &())?;
Expand Down
Loading

2 comments on commit 2e65ed6

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ❌ failure
_selftest ✅ success
rsdoctor ✅ success
rspress ✅ success
rslib ✅ success
rsbuild ❌ failure
examples ❌ failure
devserver ✅ success
nuxt ✅ success

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-12-13 c81fa8d) Current Change
10000_big_production-mode_disable-minimize + exec 37.7 s ± 976 ms 37.7 s ± 695 ms +0.14 %
10000_development-mode + exec 1.79 s ± 21 ms 1.78 s ± 32 ms -0.59 %
10000_development-mode_hmr + exec 656 ms ± 7.9 ms 660 ms ± 21 ms +0.52 %
10000_production-mode + exec 2.33 s ± 42 ms 2.36 s ± 46 ms +1.48 %
arco-pro_development-mode + exec 1.74 s ± 73 ms 1.78 s ± 73 ms +2.26 %
arco-pro_development-mode_hmr + exec 379 ms ± 1.3 ms 379 ms ± 1.3 ms -0.03 %
arco-pro_production-mode + exec 3.11 s ± 87 ms 3.09 s ± 60 ms -0.51 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.17 s ± 78 ms 3.13 s ± 51 ms -1.20 %
arco-pro_production-mode_traverse-chunk-modules + exec 3.12 s ± 93 ms 3.14 s ± 68 ms +0.64 %
threejs_development-mode_10x + exec 1.6 s ± 9.6 ms 1.62 s ± 18 ms +1.14 %
threejs_development-mode_10x_hmr + exec 789 ms ± 24 ms 820 ms ± 13 ms +3.94 %
threejs_production-mode_10x + exec 4.94 s ± 110 ms 4.94 s ± 52 ms +0.01 %
10000_big_production-mode_disable-minimize + rss memory 9737 MiB ± 290 MiB 9608 MiB ± 273 MiB -1.32 %
10000_development-mode + rss memory 808 MiB ± 16.8 MiB 812 MiB ± 27.2 MiB +0.49 %
10000_development-mode_hmr + rss memory 1906 MiB ± 321 MiB 1945 MiB ± 289 MiB +2.06 %
10000_production-mode + rss memory 699 MiB ± 38.9 MiB 723 MiB ± 42.1 MiB +3.41 %
arco-pro_development-mode + rss memory 732 MiB ± 47.1 MiB 710 MiB ± 28 MiB -3.04 %
arco-pro_development-mode_hmr + rss memory 825 MiB ± 19.7 MiB 779 MiB ± 62.2 MiB -5.69 %
arco-pro_production-mode + rss memory 792 MiB ± 69.8 MiB 818 MiB ± 68.8 MiB +3.30 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 815 MiB ± 54.2 MiB 849 MiB ± 60.2 MiB +4.16 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 841 MiB ± 32.3 MiB 840 MiB ± 62 MiB -0.12 %
threejs_development-mode_10x + rss memory 782 MiB ± 63.4 MiB 759 MiB ± 64.1 MiB -2.92 %
threejs_development-mode_10x_hmr + rss memory 1686 MiB ± 334 MiB 1676 MiB ± 245 MiB -0.59 %
threejs_production-mode_10x + rss memory 1103 MiB ± 83.2 MiB 1097 MiB ± 53.2 MiB -0.60 %

Please sign in to comment.