mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Update submodule and implement parallel I/O audit fixes
- Updated the `vscode-rojo` submodule to the latest commit. - Added a new plan for addressing issues found during the audit of parallelization and optimization commits, including fixes for a data loss bug in the orphan scanner, a duplicate-children bug in the prefetch cache, and a nested-detection correctness issue. - Implemented a small DRY refactor by extracting a shared helper function for UNC path stripping. - Enhanced the `syncback` logic to ensure hidden directories are preserved during clean mode operations and added tests to validate these changes. These updates aim to improve the reliability and correctness of the parallel I/O functionality while maintaining code clarity and performance.
This commit is contained in:
parent
1c25b928a8
commit
43834f357e
6 changed files with 536 additions and 57 deletions
|
|
@ -136,6 +136,15 @@ fn prefetch_project_files(project: &Project) -> io::Result<PrefetchCache> {
|
|||
let mut roots: Vec<std::path::PathBuf> = Vec::new();
|
||||
collect_path_roots(&project.tree, folder, &mut roots);
|
||||
|
||||
roots.sort();
|
||||
let mut deduped: Vec<std::path::PathBuf> = Vec::new();
|
||||
for root in roots {
|
||||
if !deduped.iter().any(|existing| root.starts_with(existing)) {
|
||||
deduped.push(root);
|
||||
}
|
||||
}
|
||||
let roots = deduped;
|
||||
|
||||
if roots.is_empty() {
|
||||
return Ok(PrefetchCache {
|
||||
files: HashMap::new(),
|
||||
|
|
|
|||
|
|
@ -254,6 +254,15 @@ pub fn syncback_loop_with_stats(
|
|||
|
||||
let project_path = project.folder_location();
|
||||
|
||||
fn strip_unc_prefix(p: PathBuf) -> PathBuf {
|
||||
let path_str = p.to_string_lossy();
|
||||
if path_str.starts_with(r"\\?\") || path_str.starts_with("//?/") {
|
||||
PathBuf::from(&path_str[4..])
|
||||
} else {
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
let phase_timer = std::time::Instant::now();
|
||||
let existing_paths: HashSet<PathBuf> = if !incremental {
|
||||
let mut paths = HashSet::new();
|
||||
|
|
@ -393,19 +402,25 @@ pub fn syncback_loop_with_stats(
|
|||
if !dir.is_dir() {
|
||||
continue;
|
||||
}
|
||||
if let Some(name) = dir.file_name().and_then(|n| n.to_str()) {
|
||||
if name.starts_with('.') && name != ".gitkeep" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let canonical_dir = std::fs::canonicalize(dir)
|
||||
.map(|p| {
|
||||
let s = p.to_string_lossy();
|
||||
if s.starts_with(r"\\?\") || s.starts_with("//?/") {
|
||||
PathBuf::from(&s[4..])
|
||||
} else {
|
||||
p
|
||||
}
|
||||
})
|
||||
.map(strip_unc_prefix)
|
||||
.unwrap_or_else(|_| dir.clone());
|
||||
for entry in walkdir::WalkDir::new(&canonical_dir)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_entry(|e| {
|
||||
if e.depth() == 0 {
|
||||
return true;
|
||||
}
|
||||
e.file_name()
|
||||
.to_str()
|
||||
.map_or(true, |n| !n.starts_with('.') || n == ".gitkeep")
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
if entry.depth() == 0 {
|
||||
|
|
@ -415,11 +430,6 @@ pub fn syncback_loop_with_stats(
|
|||
if !is_valid_path(&ignore_patterns, project_path, &path) {
|
||||
continue;
|
||||
}
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if name.starts_with('.') && name != ".gitkeep" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
paths.insert(path);
|
||||
}
|
||||
}
|
||||
|
|
@ -561,6 +571,12 @@ pub fn syncback_loop_with_stats(
|
|||
'remove: for inst in &syncback.removed_children {
|
||||
let path = inst.metadata().instigating_source.as_ref().unwrap().path();
|
||||
let inst_path = snapshot.get_old_inst_path(inst.id());
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if name.starts_with('.') && name != ".gitkeep" {
|
||||
log::debug!("Skipping removing {} (hidden path)", path.display());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !is_valid_path(&ignore_patterns, project_path, path) {
|
||||
log::debug!(
|
||||
"Skipping removing {} because its matches an ignore pattern",
|
||||
|
|
@ -645,16 +661,6 @@ pub fn syncback_loop_with_stats(
|
|||
if !incremental && !existing_paths.is_empty() {
|
||||
log::debug!("Clean mode: checking for orphaned files to remove");
|
||||
|
||||
// Helper to strip UNC prefix on Windows
|
||||
fn strip_unc_prefix(p: PathBuf) -> PathBuf {
|
||||
let path_str = p.to_string_lossy();
|
||||
if path_str.starts_with(r"\\?\") || path_str.starts_with("//?/") {
|
||||
PathBuf::from(&path_str[4..])
|
||||
} else {
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
let canonical_project_path = strip_unc_prefix(
|
||||
std::fs::canonicalize(project_path).unwrap_or_else(|_| project_path.to_path_buf()),
|
||||
);
|
||||
|
|
@ -700,17 +706,9 @@ pub fn syncback_loop_with_stats(
|
|||
) {
|
||||
if let Some(ref path_node) = node.path {
|
||||
let resolved = base_path.join(path_node.path());
|
||||
let canonical = match std::fs::canonicalize(&resolved) {
|
||||
Ok(p) => {
|
||||
let s = p.to_string_lossy();
|
||||
if s.starts_with(r"\\?\") || s.starts_with("//?/") {
|
||||
PathBuf::from(&s[4..])
|
||||
} else {
|
||||
p
|
||||
}
|
||||
}
|
||||
Err(_) => resolved.clone(),
|
||||
};
|
||||
let canonical = std::fs::canonicalize(&resolved)
|
||||
.map(strip_unc_prefix)
|
||||
.unwrap_or_else(|_| resolved.clone());
|
||||
protected.insert(canonical.clone());
|
||||
if !instance_path.is_empty() {
|
||||
mappings.push((canonical, instance_path.to_string()));
|
||||
|
|
@ -869,32 +867,20 @@ pub fn syncback_loop_with_stats(
|
|||
}
|
||||
|
||||
// Second pass: only remove top-level orphaned paths.
|
||||
let mut sorted_removals: Vec<_> = paths_to_remove.iter().cloned().collect();
|
||||
// Sorted path order guarantees all descendants appear consecutively
|
||||
// after their ancestor, so a single ancestor tracker suffices.
|
||||
let mut sorted_removals: Vec<_> = paths_to_remove.into_iter().collect::<Vec<_>>();
|
||||
sorted_removals.sort();
|
||||
|
||||
let paths_vec: Vec<_> = paths_to_remove.iter().collect();
|
||||
for old_path in &paths_vec {
|
||||
let is_nested = sorted_removals
|
||||
.binary_search(old_path)
|
||||
.ok()
|
||||
.and_then(|idx| {
|
||||
if idx > 0 {
|
||||
Some(
|
||||
old_path.starts_with(&sorted_removals[idx - 1])
|
||||
&& *old_path != &sorted_removals[idx - 1],
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_nested {
|
||||
continue;
|
||||
let mut current_ancestor: Option<&PathBuf> = None;
|
||||
for old_path in &sorted_removals {
|
||||
if let Some(ancestor) = current_ancestor {
|
||||
if old_path.starts_with(ancestor) && old_path != ancestor {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
current_ancestor = Some(old_path);
|
||||
|
||||
// Convert absolute path to relative path for FsSnapshot.
|
||||
// FsSnapshot expects relative paths and joins them with base_path in write_to_vfs.
|
||||
let relative_path = old_path
|
||||
.strip_prefix(&canonical_project_path)
|
||||
.unwrap_or(old_path);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue