Skip to content

Commit 9041d93

Browse files
authored
fix: context dependencies hash calculation use cache.snapshot configuration (#12947)
fix: context dependencies hash calculation use cache.snapshot configuration (#12937) * fix: snapshot watch symlink * fix: context dependencies use snapshot options * test: update test case
1 parent 95b6616 commit 9041d93

File tree

4 files changed

+165
-62
lines changed

4 files changed

+165
-62
lines changed

crates/rspack_core/src/cache/persistent/snapshot/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl Snapshot {
6868

6969
#[tracing::instrument("Cache::Snapshot::add", skip_all)]
7070
pub async fn add(&self, scope: SnapshotScope, paths: impl Iterator<Item = ArcPath>) {
71-
let helper = Arc::new(StrategyHelper::new(self.fs.clone()));
71+
let helper = Arc::new(StrategyHelper::new(self.fs.clone(), self.options.clone()));
7272
let codec = self.codec.clone();
7373
// TODO merge package version file
7474
paths
@@ -109,7 +109,7 @@ impl Snapshot {
109109
let mut modified_path = ArcPathSet::default();
110110
let mut deleted_path = ArcPathSet::default();
111111
let mut no_change_path = ArcPathSet::default();
112-
let helper = Arc::new(StrategyHelper::new(self.fs.clone()));
112+
let helper = Arc::new(StrategyHelper::new(self.fs.clone(), self.options.clone()));
113113
let codec = self.codec.clone();
114114

115115
let data = self.storage.load(scope.name()).await?;

crates/rspack_core/src/cache/persistent/snapshot/strategy/hash_helper.rs

Lines changed: 148 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use rspack_fs::{FileMetadata, ReadableFileSystem};
77
use rspack_paths::{ArcPath, ArcPathDashMap, AssertUtf8};
88
use rustc_hash::FxHasher;
99

10+
use super::{PackageHelper, SnapshotOptions};
11+
1012
/// Content hash with modification time.
1113
#[derive(Debug, Clone, Default)]
1214
pub struct ContentHash {
@@ -17,26 +19,31 @@ pub struct ContentHash {
1719
/// A helper for computing content hashes of files and directories.
1820
#[derive(Debug)]
1921
pub struct HashHelper {
20-
/// File system abstraction for reading file contents.
2122
fs: Arc<dyn ReadableFileSystem>,
22-
23-
/// Cache for file content hashes.
23+
snapshot_options: Arc<SnapshotOptions>,
24+
package_helper: Arc<PackageHelper>,
2425
file_cache: ArcPathDashMap<Option<ContentHash>>,
25-
/// Cache for directory content hashes.
2626
dir_cache: ArcPathDashMap<Option<ContentHash>>,
2727
}
2828

2929
impl HashHelper {
3030
/// Creates a new HashHelper instance with the given file system.
31-
pub fn new(fs: Arc<dyn ReadableFileSystem>) -> Self {
31+
pub fn new(
32+
fs: Arc<dyn ReadableFileSystem>,
33+
snapshot_options: Arc<SnapshotOptions>,
34+
package_helper: Arc<PackageHelper>,
35+
) -> Self {
3236
Self {
3337
fs,
38+
snapshot_options,
39+
package_helper,
3440
file_cache: Default::default(),
3541
dir_cache: Default::default(),
3642
}
3743
}
3844

39-
/// Calculate file hash, return default for non-files.
45+
/// Computes content hash for a file.
46+
/// Returns None if the file does not exist.
4047
async fn inner_file_hash(
4148
&self,
4249
path: &ArcPath,
@@ -57,28 +64,26 @@ impl HashHelper {
5764
metadata
5865
};
5966

60-
let hash = if metadata.is_file && !metadata.is_symlink {
61-
if let Ok(content) = self.fs.read(utf8_path).await {
62-
// mtime is the larger of ctime and mtime
63-
let mtime = if metadata.ctime_ms > metadata.mtime_ms {
64-
metadata.ctime_ms
65-
} else {
66-
metadata.mtime_ms
67-
};
68-
let mut hasher = FxHasher::default();
69-
content.hash(&mut hasher);
70-
Some(ContentHash {
71-
hash: hasher.finish(),
72-
mtime,
73-
})
74-
} else {
75-
None
76-
}
67+
// mtime is the larger of ctime and mtime
68+
let mtime = if metadata.ctime_ms > metadata.mtime_ms {
69+
metadata.ctime_ms
7770
} else {
78-
// directory & symlink
79-
Some(ContentHash::default())
71+
metadata.mtime_ms
8072
};
81-
73+
let mut hasher = FxHasher::default();
74+
if metadata.is_symlink {
75+
if let Ok(target) = self.fs.canonicalize(utf8_path).await {
76+
target.hash(&mut hasher)
77+
}
78+
} else if metadata.is_file
79+
&& let Ok(content) = self.fs.read(utf8_path).await
80+
{
81+
content.hash(&mut hasher);
82+
};
83+
let hash = Some(ContentHash {
84+
hash: hasher.finish(),
85+
mtime,
86+
});
8287
self.file_cache.insert(path.into(), hash.clone());
8388
hash
8489
}
@@ -103,10 +108,21 @@ impl HashHelper {
103108

104109
let hash = if metadata.is_directory && !metadata.is_symlink {
105110
if let Ok(mut children) = self.fs.read_dir(utf8_path).await {
106-
children.sort();
107111
let mut hasher = FxHasher::default();
112+
children.sort();
108113
for item in children {
109114
let child_path = ArcPath::from(path.join(item));
115+
let child_path_str = child_path.to_string_lossy();
116+
if self.snapshot_options.is_immutable_path(&child_path_str) {
117+
continue;
118+
}
119+
if self.snapshot_options.is_managed_path(&child_path_str) {
120+
if let Some(version) = self.package_helper.package_version(&child_path).await {
121+
version.hash(&mut hasher);
122+
}
123+
continue;
124+
}
125+
110126
if let Some(ContentHash { hash, .. }) = self.dir_hash(&child_path).await {
111127
hash.hash(&mut hasher);
112128
}
@@ -134,40 +150,57 @@ mod tests {
134150
use rspack_fs::{MemoryFileSystem, WritableFileSystem};
135151
use rspack_paths::ArcPath;
136152

137-
use super::HashHelper;
153+
use super::{
154+
super::super::super::snapshot::PathMatcher, HashHelper, PackageHelper, SnapshotOptions,
155+
};
156+
157+
fn new_helper(fs: Arc<MemoryFileSystem>) -> HashHelper {
158+
HashHelper::new(
159+
fs.clone(),
160+
Arc::new(SnapshotOptions::new(
161+
vec![PathMatcher::String("immutable".into())],
162+
vec![],
163+
vec![PathMatcher::String("node_modules".into())],
164+
)),
165+
Arc::new(PackageHelper::new(fs)),
166+
)
167+
}
138168

139169
#[tokio::test]
140170
async fn file_hash() {
141171
let fs = Arc::new(MemoryFileSystem::default());
142172
fs.create_dir_all("/".into()).await.unwrap();
143173
fs.write("/hash.js".into(), "abc".as_bytes()).await.unwrap();
144174

145-
let helper = HashHelper::new(fs.clone());
175+
let helper = new_helper(fs.clone());
146176
assert!(
147177
helper
148178
.file_hash(&ArcPath::from("/not_exist.js"))
149179
.await
150180
.is_none()
151181
);
182+
// check directory
152183
let hash0 = helper.file_hash(&ArcPath::from("/")).await.unwrap();
153184
assert_eq!(hash0.hash, 0);
154-
assert_eq!(hash0.mtime, 0);
155185

156186
let hash1 = helper.file_hash(&ArcPath::from("/hash.js")).await.unwrap();
157187

158-
helper.file_cache.clear();
159188
std::thread::sleep(std::time::Duration::from_millis(100));
189+
// do nothing
190+
let helper = new_helper(fs.clone());
160191
let hash2 = helper.file_hash(&ArcPath::from("/hash.js")).await.unwrap();
161192
assert_eq!(hash1.hash, hash2.hash);
162193
assert_eq!(hash1.mtime, hash2.mtime);
163194

164-
helper.file_cache.clear();
195+
// same content
196+
let helper = new_helper(fs.clone());
165197
fs.write("/hash.js".into(), "abc".as_bytes()).await.unwrap();
166198
let hash3 = helper.file_hash(&ArcPath::from("/hash.js")).await.unwrap();
167199
assert_eq!(hash1.hash, hash3.hash);
168200
assert!(hash1.mtime < hash3.mtime);
169201

170-
helper.file_cache.clear();
202+
// diff content
203+
let helper = new_helper(fs.clone());
171204
fs.write("/hash.js".into(), "abcd".as_bytes())
172205
.await
173206
.unwrap();
@@ -180,35 +213,106 @@ mod tests {
180213
async fn dir_hash() {
181214
let fs = Arc::new(MemoryFileSystem::default());
182215
fs.create_dir_all("/a".into()).await.unwrap();
216+
fs.create_dir_all("/node_modules/lib".into()).await.unwrap();
183217
fs.write("/a/a1.js".into(), "a1".as_bytes()).await.unwrap();
184218
fs.write("/a/a2.js".into(), "a2".as_bytes()).await.unwrap();
185219
fs.write("/b.js".into(), "b".as_bytes()).await.unwrap();
220+
fs.write("/immutable.js".into(), "immut".as_bytes())
221+
.await
222+
.unwrap();
223+
fs.write(
224+
"/node_modules/lib/index.js".into(),
225+
"const a = 1".as_bytes(),
226+
)
227+
.await
228+
.unwrap();
229+
fs.write(
230+
"/node_modules/lib/package.json".into(),
231+
r#"{"version": "0.0.1"}"#.as_bytes(),
232+
)
233+
.await
234+
.unwrap();
186235

187-
let helper = HashHelper::new(fs.clone());
188-
236+
let helper = new_helper(fs.clone());
189237
let hash1 = helper.dir_hash(&ArcPath::from("/")).await.unwrap();
238+
assert_eq!(hash1.mtime, 0);
190239

191-
helper.file_cache.clear();
192-
helper.dir_cache.clear();
193240
std::thread::sleep(std::time::Duration::from_millis(100));
241+
242+
// do nothing
243+
let helper = new_helper(fs.clone());
194244
let hash2 = helper.dir_hash(&ArcPath::from("/")).await.unwrap();
195245
assert_eq!(hash1.hash, hash2.hash);
196-
assert_eq!(hash1.mtime, 0);
197246
assert_eq!(hash2.mtime, 0);
198247

199-
helper.file_cache.clear();
200-
helper.dir_cache.clear();
201248
std::thread::sleep(std::time::Duration::from_millis(100));
249+
250+
// do something will not update hash
251+
let helper = new_helper(fs.clone());
252+
// write same content
202253
fs.write("/a/a2.js".into(), "a2".as_bytes()).await.unwrap();
254+
// edit immutable file
255+
fs.write("/immutable.js".into(), "next".as_bytes())
256+
.await
257+
.unwrap();
258+
// edit node_modules file
259+
fs.write(
260+
"/node_modules/lib/index.js".into(),
261+
"const a = 2".as_bytes(),
262+
)
263+
.await
264+
.unwrap();
265+
// update package.json
266+
fs.write(
267+
"/node_modules/lib/package.json".into(),
268+
r#"{"version": "0.0.2"}"#.as_bytes(),
269+
)
270+
.await
271+
.unwrap();
203272
let hash3 = helper.dir_hash(&ArcPath::from("/")).await.unwrap();
204-
assert_eq!(hash1.hash, hash3.hash);
273+
assert_eq!(hash2.hash, hash3.hash);
205274
assert_eq!(hash3.mtime, 0);
206275

207-
helper.file_cache.clear();
208-
helper.dir_cache.clear();
276+
// update file content
277+
let helper = new_helper(fs.clone());
209278
fs.write("/a/a2.js".into(), "a2a".as_bytes()).await.unwrap();
210279
let hash4 = helper.dir_hash(&ArcPath::from("/")).await.unwrap();
211-
assert_ne!(hash1.hash, hash4.hash);
280+
assert_ne!(hash3.hash, hash4.hash);
212281
assert_eq!(hash4.mtime, 0);
282+
283+
// node_modules lib test
284+
let helper = new_helper(fs.clone());
285+
let hash1 = helper
286+
.dir_hash(&ArcPath::from("/node_modules/lib/"))
287+
.await
288+
.unwrap();
289+
290+
// update lib content
291+
let helper = new_helper(fs.clone());
292+
fs.write(
293+
"/node_modules/lib/index.js".into(),
294+
"const a = 3".as_bytes(),
295+
)
296+
.await
297+
.unwrap();
298+
let hash2 = helper
299+
.dir_hash(&ArcPath::from("/node_modules/lib/"))
300+
.await
301+
.unwrap();
302+
assert_eq!(hash1.hash, hash2.hash);
303+
304+
// update package.json
305+
let helper = new_helper(fs.clone());
306+
fs.write(
307+
"/node_modules/lib/package.json".into(),
308+
r#"{"version": "0.0.3"}"#.as_bytes(),
309+
)
310+
.await
311+
.unwrap();
312+
let hash2 = helper
313+
.dir_hash(&ArcPath::from("/node_modules/lib/"))
314+
.await
315+
.unwrap();
316+
assert_ne!(hash1.hash, hash2.hash);
213317
}
214318
}

0 commit comments

Comments
 (0)