-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Conversation
… EINTR to make sure locks get broken
The macOS test failure is relevant:
|
The Windows build failure is unrelated. |
@filipnavara, thanks for working to fix this. We're duplicating more and more code now, though. What would it look like if we reverted the previous change and still opened the FileStream in managed, getting all of these various checks "for free". If we didn't use clonefile, everything else would continue to operate the same. If we did use clonefile, then we would just end up overwriting the FileStream we opened in managed, which already did all of the appropriate access checks and the like, and on the clonefile path we'd just clobber it. It would of course mean an extra file getting opened/closed, but if that approach works it feels much cleaner. Thoughts? |
I am also unhappy about the code duplication but the solution is not easy. It boils down to the fact that Any solution I came up with resulted in pretty bad trade offs in terms of file access API calls and/or number of P/Invokes. |
Why not? Having an open file descriptor to the file shouldn't prevent us from unlinking it. My suggestion was basically to unlink and then clonefile. The only part of the previous change to managed code we'd need to keep would be changing the DllImport to also pass the destination path (in addition to the destination file descriptor). |
I can try it when I get home but I was quite convinced the |
Also, if we |
That's... true. We could do something like:
Or something like that. Presumably clonefile failing will be relatively rare and the extra retry cost thus not very impactful. I'm just trying to keep the code as simple as possible and avoid duplicating all of FileStream's logic in native. Let's explore a few options and see what turns out the best. |
I think that's a wrong assumption. Depending on machine configuration and use case it could fail quite often, or not at all. It only works within the same APFS volume. Machines configured with HFS+ will fail every time. Copying on NTFS/FAT will fail every time, and so will copying between different volumes. I know this sounds bleak and you may even ask whether
The using (var src = new FileStream(sourceFullPath, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, FileOptions.None))
{
if (!CloneFile(src.SafeFileHandle, sourceFullPath, destFullPath, overwrite))
{
using (var dst = new FileStream(destFullPath, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, DefaultBufferSize, FileOptions.None))
{
Interop.CheckIo(Interop.Sys.CopyFile(src.SafeFileHandle, dst.SafeFileHandle)); The downside is more P/Invokes. The |
@@ -1315,7 +1353,8 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char | |||
if (!copied && CopyFile_ReadWrite(inFd, outFd) != 0) | |||
{ | |||
tmpErrno = errno; | |||
close(outFd); | |||
while ((ret = flock(outFd, LOCK_UN)) < 0 && errno == EINTR); | |||
while ((ret = close(outFd)) < 0 && errno == EINTR); |
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.
Retrying close(2)
in Linux is a race condition (please see the notes section in the man page for details).
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.
Thanks for pointing that out. In the last iteration it may not be necessary.
@@ -1265,6 +1285,23 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, const char* srcPath, const char | |||
fcntl(outFd, F_SETFD, FD_CLOEXEC); | |||
#endif | |||
|
|||
while ((ret = flock(outFd, LOCK_EX | LOCK_NB)) < 0 && errno == EINTR); |
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.
For Linux, it might be a better idea to use O_TMPFILE to create a temporary file while the copy is being performed, and when it's done, hardlink it with the new name. Then there's no need to use advisory file locking, which isn't really that useful.
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.
The advisory locking is used by the managed FileStream so whatever is used it has to be done on both sides and match.
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 would still like us to try hard to find ways around duplicating all this logic.
I am asking that ;) Can you please share the impact of the change on such a scenario? We had been using fcopyfile. What is the perf impact of using clonefile instead? |
fcopyfile has no perf improvement over the fallback code whatsoever. I'll try to produce some perf numbers for clonefile. The anecdotal evidence is the following:
|
The big difference is that |
Why would it take half a second then to copy 5gb? Shouldn't it be almost instantaneous "copying" any file, regardless of size? |
Not my number so I cannot comment on it. It should be near instantaneous. I will do the test and post back numbers, no need to speculate :) |
I really dislike the two FileStream approach because you lose the ability of using the
variant to explicitly specify the file mode of the new file. The advantage of using this system call is that it does the right thing when you don't have permission to actually set the permissions to the ones of the source file. Whereas the For instance, |
Thank you for your contribution. As announced in #27549 the dotnet/runtime repository will be used going forward for changes to this code base. Closing this PR as no more changes will be accepted into master for this repository. If you’d like to continue working on this change please move it to dotnet/runtime. |
Fixes #42455, regression introduced in #37583.