Leaky Abstraction strikes again: FileStream.Lock() gotchas

First, if you don’t know the Law of Leaky Abstractions go on and read here (10 minutes well spent!).

.NET’s FileStream.Lock() is a handy method that lets you lock a section of a file (instead of locking it completely) so that other processes/threads cannot touch that part.

The usage is fairly simple: you specify from where to lock the file and how long is the section you want to protect. However, despite its simplicity, there are a couple of things it’s better to keep in mind or you’ll be scratching your head in front of the screen.

First: contrarily to what some articles say, this method locks part of the file for write but also for read access. Maybe those articles refer to an older framework version or whatever else, but a simple test seems to confirm that a process cannot read a part of a file that has been locked.

The second thing can be tricky.
Let’s make a simple experiment: we write 100 bytes in a new file and we lock everything except the very first byte. Then we launch another process that reads the first byte.

// first process:
using (var fs = new FileStream("myFile.txt",
    using (var bw = new BinaryWriter(fs))
        bw.Write(new byte[100]);
    // locks everything except the first byte
    fs.Lock(1, 99);
    fs.Unlock(1, 99);

// second process (first process is waiting at Console.ReadLine()):
using (var fs = new FileStream("myFile.txt",
    using (var br = new BinaryReader(fs))
        // read the first byte
        var b = br.ReadByte();

What happens? The second process throws an exception: The process cannot access the file because another process has locked a portion of the file .
Why? We didn’t try to access the locked portion, so this should not have happened!

At first you may believe that Lock() is buggy and locks the whole file. But this is not true, in fact Lock() works correctly.
The answer is in the FileStream’s buffer (I hear the “aha!”). In fact when you ask FileStream to read a single byte, he’s smart enough not to read a single byte but to fill its internal buffer (4K by default) to speed up the reading. So it tries to read into the locked part and fails.

Now that you know why this is happening you can more or less easily solve the problem depending on your situation: you may for ex. adjust your buffer size depending on the length of the chunks you are reading.

In the example above it’s enough to pass 1 as buffer length to the second process FileStream’s constructor (after line 21) to make it work (just to show the theory, not that this is a good practice!).

I really think that the FileStream abstraction should handle this case and avoid the “leak”, but the .NET framework guys are smart people and I bet there is a good reason if it doesn’t.