Repository navigation
Conversation
|
Please add a test |
Merging this PR will degrade performance by 4.57%2 warnings, 1 note
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
|
Added regression test Also moved |
d664578 to
0b0b191
Compare
|
Adjusted the regression test depth to 400 so it stays safely within the default file descriptor limit ( |
| } else { | ||
| // Report the successful ownership change using the shared helper | ||
| self.report_ownership_change_success(&entry_path, meta.uid(), meta.gid()); | ||
| return; |
There was a problem hiding this comment.
before, this return only left the current directory. Now it stops the whole traversal. Is this intended?
There was a problem hiding this comment.
Not intended. Fixed, now popping the current frame and continuing.
| *ret = 1; | ||
| if self.verbosity.level != VerbosityLevel::Silent { | ||
| show_error!( | ||
| "cannot read directory {}: {}", |
There was a problem hiding this comment.
please use translate!() here
There was a problem hiding this comment.
Done, switched to translate!().
| top.my_stat.inodes += this_stat.inodes; | ||
| top.my_stat.latest_time = | ||
| match (top.my_stat.latest_time, this_stat.latest_time) { | ||
| (Some(a), Some(b)) => Some(a.max(b)), |
There was a problem hiding this comment.
the same size/blocks/inodes/latest_time merge is now there 4 times, could you please move it into a helper on Stat?
There was a problem hiding this comment.
Done, added merge() helper on Stat.
|
|
||
| while let Some(top) = stack.last_mut() { | ||
| if top.entry_idx < top.entries.len() { | ||
| let entry_name = top.entries[top.entry_idx].clone(); |
There was a problem hiding this comment.
codspeed shows an 18% regression. Could you please look at it? Cloning every entry name probably doesn't help, std::mem::take would avoid that
There was a problem hiding this comment.
Fixed. Converted entries to IntoIter to move them directly without cloning.
| } else { | ||
| // Backtrack so sibling subtrees that legitimately reach the same directory | ||
| // (e.g. two symlinks to one dir) are not mistaken for cycles. | ||
| let finished = stack.pop().unwrap(); |
There was a problem hiding this comment.
please avoid the unwrap here (same in perms.rs and du.rs). while let Some(top) = stack.last_mut() already guarantees it
There was a problem hiding this comment.
Fixed across chmod, perms, and du.
| } | ||
|
|
||
| #[cfg(target_os = "linux")] | ||
| struct PermsFrame { |
There was a problem hiding this comment.
ChmodFrame and PermsFrame are the same struct, and the loop is almost the same too. Could this be a shared walker in uucore safe_traversal?
There was a problem hiding this comment.
The frames are similar, but per-entry logic diverges quite a bit between chmod (symlink handling) and perms (preserve-root, filters, bitmask). I can extract a shared generic walker in a follow-up PR to keep this bugfix focused.
| } | ||
|
|
||
| Ok(my_stat) | ||
| unreachable!() |
There was a problem hiding this comment.
please avoid unreachable!(). Returning at the end of the loop would work
There was a problem hiding this comment.
Done. Returning at the end of the loop now.
| let mut depth = 0; | ||
| // Limit depth to 400 to stay safely below the default RLIMIT_NOFILE (1024) | ||
| // on WSL2 and macOS while testing deep traversal without stack overflow. | ||
| for _ in 0..400 { |
There was a problem hiding this comment.
400 levels never overflowed the stack before this fix either, so this test would pass on main.
could you please make it deep enough to crash without the patch (maybe raising RLIMIT_NOFILE in the test)? and please add the same kind of test for chmod -R and chown -R
There was a problem hiding this comment.
Alternatively, making the test unix only could be acceptable with 1500, right?
There was a problem hiding this comment.
Done. Marked as unix-only, raised RLIMIT_NOFILE to 4096, set depth to 1500, and added tests for both chmod -R and chown -R.
|
Pushed updates addressing the review comments and added tests for chmod/chown. |
|
Regarding the CodSpeed report: Most of the reported regressions are in dd, which this PR does not touch (caused by different runner base environments). The memory increase in du_deep_tree (+35 KB at depth 100) is expected. This PR moves traversal state from the call stack to a heap vector to avoid stack overflow on deep trees. Runtime speed itself did not regress. |
|
GNU testsuite comparison: |
4f09184 to
c44e5cc
Compare
|
Thanks for the review!
|
614f849 to
98966a7
Compare
941747d to
ffb4c0c
Compare
Replace recursive directory traversal in safe_du (du), safe_traverse_dir (chmod), and safe_traverse_dir (perms, used by chown and chgrp) with an iterative heap-allocated stack. Previously, traversing deep directory structures (depth >= 2000) exhausted the default 8MB thread stack on Linux due to recursive function calls, causing a segmentation fault (core dumped). Using heap-allocated stacks (DuFrame, ChmodFrame, PermsFrame) keeps the call stack strictly O(1) while preserving exact post-order / pre-order semantics and cycle detection via ancestors. Fixes: uutils#10173 Fixes: https://bugs.launchpad.net/ubuntu/+source/rust-coreutils/+bug/2167204
ffb4c0c to
2728039
Compare
Replace recursive directory traversal in
safe_du(du),safe_traverse_dir(chmod), andsafe_traverse_dir(perms, used bychownandchgrp) with an iterative heap-allocated stack.Problem
When traversing deep directory trees (depth >= 2000), recursive function calls cause a stack overflow and crash with SIGSEGV (signal 11, code 139) because Linux has an 8MB default stack limit (
ulimit -s 8192). Each stack frame holds open directory file descriptors (DirFd),Stat/Metadata, and path buffers.Fix
safe_duwith an iterative loop using a heap-allocatedVec<DuFrame>stack.safe_traverse_dir(chmod) with an iterative loop usingVec<ChmodFrame>, maintaining cycle detection viaancestors.safe_traverse_dir(perms, poweringchownandchgrp) with an iterative loop usingVec<PermsFrame>, maintaining cycle detection viaancestors.DirFd) are closed as soon as a directory frame completes and pops from the stack.Verified on Linux with test directory trees created via
openat/mkdiratup to 4000 levels deep:du -spasses with code 0 (previously crashed with SIGSEGV at depth 2000 and 4000).chmod -Rpasses with code 0.chown -Randchgrp -Rpass with code 0.Fixes #10173
Fixes: https://bugs.launchpad.net/ubuntu/+source/rust-coreutils/+bug/2167204