-
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
use fstatfs to detect whether current file system supports shared locks for files opened for writing #55256
Conversation
…ks for files opened for writing
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsI am soon going to add description and benchmark numbers, until then please ignore the PR
|
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Show resolved
Hide resolved
(I know you said ignore the PR, but I commented on a few things that could impact your benchmarking. I'll reserve judgment further until I see the perf numbers, but even though I suggested this as one possible approach, it seems like it'll be adding overhead to try to salvage a fundamentally flawed attempt at mimicking .NET behavior on Windows... I'm not sure it's the right tradeoff. ) |
…e correct file system type
…k the file system type
@stephentoub I've added the description which hopefully answers all the questions. PTAL |
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
That will only apply if you never read or write the file. In the case of regular files, the data we got back from stat allowed us to avoid a subsequent lseek to determine whether the file was seekable. By not calling fstat, we're no longer able to avoid that: This also presumes that all syscalls have the same performance impact. How do the overheads of fstat and fstatfs compare? |
@adamsitnik I understand we won't be able to test all configurations before the Preview 7 snap. We should move this PR forward to get it in for Preview 7 regardless. We can seek community/partner help in testing against Preview 7 / RC1 bits and address issues if we find any. We would want to capture the list of configs tested and update it along the way though. |
…eLib does not need to references other types related to DriveInfo
I've just finished testing the fix using client and server machine provided to me by @rtestardi and it works fine for NFS. Thanks to help of @sba923 I was able to confirm that it's going to work for Samba as well (#53182 (comment)) |
I remember, I was the person who added this optimization quite recently
That is a very good point. I've rerun all the benchmarks to see how all of that affected the performance. Here are the results:
Based on the first benchmark, where we just open the file and perform a single small write (1024 bytes) we can see that adding this additional syscall does not regress the perfomance (even small write takes much more time than opening the file), |
if (CanLockTheFile(lockOperation, access) && !(_isLocked = Interop.Sys.FLock(this, lockOperation | Interop.Sys.LockOperations.LOCK_NB) >= 0)) | ||
{ |
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.
This might be more clear.
if (CanLockTheFile(lockOperation, access) && !(_isLocked = Interop.Sys.FLock(this, lockOperation | Interop.Sys.LockOperations.LOCK_NB) >= 0)) | |
{ | |
if (CanLockTheFile(lockOperation, access) && Interop.Sys.FLock(this, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)) | |
{ | |
_isLocked = 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.
LGTM; thanks, Adam.
Thanks to all for the great work, will this be backported to 3.1 or will it stay in 6.x only? |
yes, thank you adam and all!!! |
OK, thanks for checking. |
@stephentoub @jeffhandley @danmoseley what is your opinion on that? based on the number of reactions for workaround there seems to be multiple customers affected |
I'm not sure if this is related, but we added the
|
if it's only 6.x, it will take a looooooooong time before this ripples into PowerShell (where I first detected the issue) 😭 |
Re 3.1 backport - my initial reaction would be concern that we would change something so fundamental in a release that folks are explicitly using for stability. If the need is sufficiently high, would it suffice to backport an appcontext flag, without all the fstatfs work? This would be a different appcontext flag, which opted into the no-locking behavior, so almost everyone would see no change. The appcontext flag would only exist in 3.1. |
BTW kudos for your thorough writeup @adamsitnik . |
@sba923 .NET 6.0 ships next month and I expect PowerShell 7.2 will ship approximately then. It should already contain this fix: https://github.com/PowerShell/PowerShell/releases |
I've spent entire week of my life thinking about file locking, so please excuse me if the following description is too grumpy.
Windows background
As we all know, .NET originally supported only Windows. Because of that, many of our APIs have a very strong Windows influence.
All available Windows API that allow for opening files (
CreateFileW
,NtCreateFile
and more), require the caller to provideshareMode
which is translated to a mandatory file lock:0
(in .NET represented withFilShare.None
): the file can't be opened for read, write or delete until the handle is closed.1
(FileShare.Read
): the file can be opened for reading.2
(FileShare.Write
): the file can be opened for writing.4
(FileShare.Delete
): the file can be opened for deleting.Unix
Unix systems have a different approach to file locking. It's only advisory and requires an additional syscall (flock).
flock
offers two kind of locks:LOCK_EX
- exclusive lock, only one process can hold it for a given file at a given time.LOCK_SH
- shared lock, which should be translated to "nobody can request an exclusive lock". There can be multiple processes holding a shared lock on the same file.Our mappig for
FileShare
is following:FileShare.None
is translated toLOCK_EX
, while everything else is mapped toLOCK_SH
.Depending on the File System and Kernel version the lock can be:
The problem
When we open a file for writing and use
flock
to acquire a shared lock, some file systems (NFS, CIFS and SMB) map it to a lock that... prevents ourselves from writing to the file.The biggest issue is that in contrary to CIFS and SMB, NFS reports following writes as successful despite the fact that they actually fail to write anything to disk (#44546 (comment)):
Disclaimer: this is why I don't perceive this as a .NET bug, but more as a workaround for few File System imperfections...
Possible solutions
@stephentoub has listed an excellent list of possible solutions:
I am afraid that this is not an option, as simply there are too many File Systems that exhibit similar issues (NFS, CIFS and SMB).
My main concern is that removing the locking entirely could result in silent errors that would be hard to diagnose for scenarios where our users use it for process synchronization.
I have given it a try and I got simply scared of how many tests were failing. As an owner of
System.IO
area I am not willing to introduce such a breaking change, especially so late in the release.This is a great idea, I've added
System.IO.DisableFileLocking
app context switch andDOTNET_SYSTEM_IO_DISABLEFILELOCKING
env var to control it.However, it's not a complete solution because for NFS we are not observing any exceptions, so users might not even notice the problem.
It just works without introducing any breaking changes and thanks to the fact that we have recently removed one sys call for opening files for writing (#55206) .NET 6 would still be faster than .NET 5:
For all the benchmarks that don't just open the file but actually perform at least a single write there is no visible difference. Moreover, we perform the extra sys call only when the file is opened for writing and file share is different than
FileShare.None
. So even if it becomes a problem for someone, they can useFileShare.None
or use the flags mentioned above.This solution has a weak point: we might miss adding some File System(s) to the list. In such a case, users can quickly workaround it by using the config switch mentioned above. Assuming, that call to
write
is going to fail for this File System.It would work only for the code that we control and would require our users to modify their source code, which is not always possible (using closed source libraries).
I did not find such a way, however I've came up with one more idea: release the lock when a write fails with
EACCES
. It worked fine for SMB (#53182 (comment)), however it would not work for NFS because again the failed writes are reported as successful.On NFS it would work if we had opened the file with
O_SYNC
(#44546 (comment)). I've tried it and all tests were passing but the performance regressed by fromx100
tox800
. That is definitely not acceptable.fixes #42790
fixes #44546
fixes #53182
fixes #54966
Edit:
I am soon going to add description and benchmark numbers, until then please ignore the PR