File sharing not working as expected
Asked Answered
F

4

19

I have a file sharing issue where my process is trying to read a log file whilst it is currently still open by NLog. In diagnosing the issue, I found something surprising. The following fails:

using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read))
using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.Read))
{
}

The second FileStream constructor call fails with:

System.IO.IOException was unhandled
  Message=The process cannot access the file 'c:\...\test.file' because it is being used by another process.
  Source=mscorlib
  StackTrace:
       at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
       at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath)
       at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

This is despite the fact that the first FileStream indicates its willingness to share reading. What I found even more surprising was that this works:

using (var fileStream1 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read))
using (var fileStream2 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
}

Um, yes, requesting more access when opening the second stream actually bypasses the issue. I am completely baffled as to why that is the case, and can only assume I am misunderstanding something. I've read through the API docs but they just support my current mental model for how this should work, contrary to how it does work.

Here are some supporting quotes from the docs:

A typical use of this enumeration is to define whether two processes can simultaneously read from the same file. For example, if a file is opened and Read is specified, other users can open the file for reading but not for writing.

Here's another gem:

The following FileStream constructor opens an existing file and grants read-only access to other users (Read).

FileStream s2 = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);

Can anyone shed any light on this behavior. I'm testing this on .NET 4 % Windows XP.

Fluting answered 14/5, 2013 at 13:9 Comment(0)
G
21
 var fileStream2 = new FileStream(..., FileShare.Read)

This trips up lots of programmers. Everybody assumes that this added read sharing. It didn't, the original file access request already allowed reading and specifying it again doesn't change anything. Instead it denies write sharing. And that cannot work because somebody already got write access. And is using it, you cannot remove that right. So your request to access the file will fail.

You must include FileShare.Write.

Gridiron answered 14/5, 2013 at 22:30 Comment(2)
Thanks. So this misunderstanding is so prevalent that even the docs are wrong/misleading?Fluting
You mean include FileShare.Write on the second stream, right?Rammish
T
2

I think I've found the answer in the documentation for CreateFile.

In the discussion of the dwShareMode parameter, it says:

FILE_SHARE_READ 0x00000001 Enables subsequent open operations on a file or device to request read access. Otherwise, other processes cannot open the file or device if they request read access. If this flag is not specified, but the file or device has been opened for read access, the function fails.

FILE_SHARE_WRITE 0x00000002 Enables subsequent open operations on a file or device to request write access. Otherwise, other processes cannot open the file or device if they request write access. If this flag is not specified, but the file or device has been opened for write access or has a file mapping with write access, the function fails.

That fundamentally changes my understanding of how file sharing works.

Therron answered 14/5, 2013 at 18:43 Comment(0)
S
1

What actually happens, is that the fileStream2 can not change the subsequent access to a file which already is open for writing (or appending) by fileStream1.

fileStream2 will successfully open the file leaving a FileShare.Read as "legacy" for subsequent access only if there are no processes which already have Write file access to it. Even more, in our example we are talking about the same process. It wouldn't make too much sense to modify a file stream's properties from another file stream, wouldn't it?

Maybe the following comparison explains it even better:

// works
using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite))
using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read))
using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
}

// fails
using (var fileStream1 = new FileStream("test.file", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
using (var fileStream2 = new FileStream("test.file", FileMode.Append, FileAccess.Write, FileShare.Read))
using (var fileStream3 = new FileStream("test.file", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
}

In my opinion, the description phrase for FileShare.Read:

Allows subsequent opening of the file for reading.

should be read as

The subsequent access to the file is restricted only for reading, including the access of already existing locks.

[Update]

I haven't parsed through the code, but it seems that these two links could shed some light over the internal functioning of the constructor:

The internal FileStream ctor

The internal FileStream Init method

Stoma answered 14/5, 2013 at 14:5 Comment(5)
Still hasn't clicked, despite reading your answer several times. I find it hard to believe that the API docs are so completely and utterly wrong. eg. "For example, if a file is opened and Read is specified, other users can open the file for reading but not for writing." That's exactly what I tried to do in my example, but it doesn't work.Fluting
@KentBoogaart: I don't know if it is relevant (I don't understand either) but a key phrase might be "other users". A better test of "other users" would be in a multi-threaded test.Owens
@Chris: my real scenario is multi-threaded (attempting to gather logs on background diagnostic thread). In addition, the docs explicitly state "by this process or another process" when describing the sharing behavior.Fluting
Fair enough. I think on having looked at this a bit (though I need to get a copy of Refactor to see how true this is inside the .NET code) is that the FileShare parameter has to be compatible with all the previous FileAccess parameters. So in this case it fails because filestream2 is only willing to let other filestreams access it readonly but something alreayd has write permission so it won't work. Both answers here currently I think are trying to say this, just not that clearly...Owens
@Chris: Looking inside NET doesn't make much sense - it boils down to WinAPI calls so you'll just get to FileCreate or FileOpen call with FileShare passed as parameter. But in the rest - yes, that's exactly what I've wrote about, the problem is not with second call of contruct unable to open file, but with it's inability to obtain desired lock due to file being already open with incompatible settings by first one. And as with the Alex Filipovici help become clear - the only problem is slightly incorrect description in enum.Maryleemarylin
M
0

Fourth parameter you pass

share
A constant that determines how the file will be shared by processes.

determines in what mode others can open the file. So obviously - when you're trying to open the file with fileshare mode "read" and already have the same file open in Write mode - operation fails.

Maryleemarylin answered 14/5, 2013 at 13:15 Comment(5)
It's not third, it's fourth. And FileShare.Read Allows subsequent opening of the file for reading.Stoma
FileShare.Read - "Allows subsequent opening of file for reading" - but not for writing. You already have file open for writing what is forbidden with that share mode. What should the function do?Maryleemarylin
Btw - where did you get about "subsequent" - quote in my answer is from MSDN, where is nothing about subsequent there.Maryleemarylin
Here it is.Stoma
In description of FileStream itself there is nothing about only subsequent processes. Even more flags are originated from WinAPi and always worked with all processes. (If you forbid to modify file while you are reading it allowing to modify it to somebody who opened it before doesn't makes sense). So this is just not very good desciption in enum.Maryleemarylin

© 2022 - 2024 — McMap. All rights reserved.