Skip to content

.NET Memory Mapped File Bug

February 12, 2011

I need to track a large set of binary flags, and wish to keep them persistent across multiple runs of the application. The way that came to mind was to store a large bitmap, each bit representing the state of a flag, in a memory mapped file. By using a memory mapped file I can access the bits in memory, and automatically get the persistence to disk. I set up a memory map of size 64 MB, allowing me to store just over a half billion bits.

But when using this map, I accidentally stumbled upon what is likely a bug in .NET. After writing to it, attempting to dispose of the MemoryMappedViewAccessor results in an IOException.

It’s really easy to duplicate the bug with this simple example, which opens or creates a file for the memory map, and sets the second bit of a random byte within the file. The real scenario was more involved than this, but this contrived example distills the part that doesn’t work. The exception isn’t triggered every time, but inside a loop it will usually hit well before reaching 100 attempts.

using(MemoryMappedFile file = MemoryMappedFile.CreateFromFile("c:\temp\testmmap", FileMode.OpenOrCreate, "testmmap", 1L << 26))
using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor())
{
    long offset = rand.Next(1 << 26);
    byte val;
    accessor.Read(offset, out val);
    val |= 2;
    accessor.Write(offset, val);
}

When it reaches the closing brace, an exception is thrown with the message: “The process cannot access the file because another process has locked a portion of the file.” This exception is triggered by a Flush() call executed by the Dispose() method of MemoryMappedViewAccessor.

At first I assumed there was a bug in my program or that I used the classes incorrectly. Searching online led me to this link describing the same problem, with confirmation from Microsoft that it is indeed a bug.

The feedback from Microsoft offers several solutions. The first one, replacing the using block with a Flush() and Dispose(), did not work for me as I encounter the exception either way. Other solutions, wait and try again later, or try in a loop, just seems silly (although given that it doesn’t happen every time, likely to work). The remaining solution, catch the exception and just ignore if the code is “ERROR_LOCK_VIOLATION=33”, doesn’t seem practical because I don’t see any indication of this error code in the exception.

What I ended up doing was forgoing memory mapped files, and just seeking to find the locations to read or set in the file. Rewriting the above example this way looks like:

using(FileStream file = File.Open("c:\temp\testmmap", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    long pos= rand.Next(1 << 26);
    file.Seek(pos, SeekOrigin.Begin);
    int val = file.ReadByte();
    val |= 2;
    file.Seek(pos, SeekOrigin.Begin);
    file.WriteByte((byte)(val | mask));
}

This solution, while functionally the same, doesn’t feel quite right. I stuck with it because I’m not worried too much about performance (it’s not a time-critical application), and would rather keep the data directly in a file than try to periodically sync a heap-stored bitmap to disk. But typically memory mapping is used if the workload is a good match. The entire 64 MB bitmap is likely to fit in memory, and memory mapping allows me to manipulate it directly using random access, instead of going through file system calls. And considering that in this workload, different parts of the bitmap are likely to be hot at different times, the whole thing typically need not be loaded in physical memory anyway.

I am kind of surprised that this bug lurks in .NET. Memory mapping has existed on every other platform for as long as I can remember, and as demand paging is an essential feature for an OS, so it should just work. It’s crazy, no?

No comments yet

Leave a Reply

Your email address will not be published. Required fields are marked *