-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FileSystem.Unix: Directory.Delete: remove per item syscall. #59520
Conversation
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsBy recursing using FileSystemEnumerable we know the file type and For the top level path, we can omit the call also and handle For the recursive case we can avoid recursion when the top level path rmdir FileSystemEntry is updated so IsSymbolicLink remembers the file is symbolic link
|
{ | ||
if (!DirectoryExists(fullPath)) | ||
{ | ||
throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath, isDirectory: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept ENOENT
for compatibility. ENOTDIR
would be more appropriate since something exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamsitnik I would prefer to change this error code because I think the previous behavior was unintentional.
If you try to delete a socket file using Directory.Delete
, the code used to throw ENOENT
.
I think it is more appropriate to throw ENOTDIR
. Do you agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamsitnik can you share your opinion?
CI fails are not related: |
Some changes overlap with #60214. Please review that first, and I'll rebase here to remove the overlap. |
As soon as we merge #60214 I am going to review this PR. |
3a1d58e
to
34a3869
Compare
By recursing using FileSystemEnumerable we know the file type and can omit the stat calls made by DirectoryInfo.Exists. For the top level path, we can omit the call also and handle non-directories when rmdir errno is ENOTDIR. For the recursive case we can avoid recursion when the top level path rmdir succeeds immediately. FileSystemEntry is updated so IsSymbolicLink remembers the file is symbolic link and does not make a syscall for it.
I've rebased and removed the overlap with #60214. This is up for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far. I left some comments for you to consider. Thanks for the change, @tmds.
Can you please run the enumeration benchmarks to ensure there's no perf regression?
src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs
Outdated
Show resolved
Hide resolved
@adamsitnik @carlossanlop I've renamed the methods and added some comments. I hope it is more clear now what the implementation does. I still need to add the test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmds thank you for another great contribution!
I've benchmarked this PR for removing a copy of a dotnet/runtime
repository with all the build artifacts.
On average it's 8% faster and allocates 50% less memory:
Method | Job | Mean | Ratio | Allocated |
---|---|---|---|---|
RecursiveDeleteDirectory | before | 2.550 s | 1.00 | 78 MB |
RecursiveDeleteDirectory | after | 2.356 s | 0.92 | 34 MB |
Just out of curiosity I've compared .NET to the the rm core utility, .NET is still 10-20% slower. Most likely because rm
is not using recursive code (it uses fts
):
But the implementation is non-trivial ;)
@carlossanlop I've updated the test so it also runs against the |
@tmds some of the tests are failing now. Could you PTAL? |
@adamsitnik it seems that this fails on Windows. Is that expected? var target = GetTestFilePath();
Directory.CreateDirectory(target);
var linkParent = GetTestFilePath();
Directory.CreateDirectory(linkParent);
var linkPath = Path.Combine(linkParent, GetTestFileName());
Directory.CreateSymbolicLink(linkPath, target);
Assert.True(Directory.Exists(linkPath), "linkPath should exist"); |
It's not expected to me, but I am not a symlink expert. @carlossanlop @jozkee is that expected? @tmds Since you have not touched the Windows part, could you please just mark this test as Unix-specific for now so we can merge the PR? |
nvmd, I had unintentionally made changes to another similar test which caused it to fail. The test should pass now on Linux and Windows. |
By recursing using FileSystemEnumerable we know the file type and
can omit the stat calls made by DirectoryInfo.Exists.
For the top level path, we can omit the call also and handle
non-directories when rmdir errno is ENOTDIR.
For the recursive case we can avoid recursion when the top level path rmdir
succeeds immediately.
FileSystemEntry is updated so IsSymbolicLink remembers the file is symbolic link
and does not make a syscall for it.