Last Thurday, Dan posted Worst .NET Bug I’ve Ever Seen. This post was the result of a Digipede customer support incident resulting from this .NET behavior. We had some trouble tracking it down. The unexpected exceptions were bad enough — and the deceptive exception message text made it worse.
He posted code to reproduce the problem on a single thread (a tight loop of open, write, close). The actual code in question included no looping, but multiple threads following a fairly common pattern (i.e., open temp file, write, close, delete, rename). The code was properly synchronized, but it still threw exceptions. To keep this simple, I’ll stick with Dan’s simple version:
while (true) { using (Stream sw = File.Open(strFileName, FileMode.Create)) { using (BinaryWriter bw = new BinaryWriter(sw)) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(bw.BaseStream, this); } } }
Loop this code on one or more threads and you may eventually get a System.IO.IOException
. I say may because I can’t reproduce it, but Dan can and so can one of our customers.
Given many caveats (e.g., the paths are valid, the proper permissions exist, the file is not opened elsewhere, etc.), this code should never throw an exception. The unmanaged resources should be released when the Stream.Dispose method is called. The file should be closed. The types of exceptions that our customer (and Dan) got all follow from the fact that sometimes the file isn’t closed. Add a Thread.Sleep(1000) after the using block and the problem goes away.
So, is this a .NET bug or a Windows bug, or just expected behavior?
The first thing I did was take Dan’s simple loop and write it using the Windows Platform SDK (i.e., using the Windows API, not .NET). I left out the delete/rename part of the pattern to make it like Dan’s tight loop. The code looks like this:
while (true) { HANDLE hFile = CreateFile(fileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("Could not open file (error %d)\n", GetLastError()); break;; } byte buffer[1024]; DWORD writtenBytes; if (!WriteFile(hFile, buffer, sizeof(buffer), &writtenBytes, NULL)) { printf("Could not write file (error %d)\n", GetLastError()); break; } if (!CloseHandle(hFile)) { printf("Could not close file (error %d)\n", GetLastError()); break; } }
This code works fine on Dan’s machine (i.e., never exits).
Next steps
So, yes, this appears to be a problem in .NET. My next steps are to . . .
- Check the .NET versions / are they somehow different between our machines?
- See if this is a known bug. Connect.microsoft.com, here I come.
- Question my assumptions.
To this last point, does .NET (or Windows) not guarantee that a file is closed when the last handle is released? If this is somehow true . . . how much code have you written that assumes that it does? Me, a lot. OK, not a huge amount, but enough that I’m really surprised this hasn’t come up before.
I’ll follow up on this post when I get to the bottom of it. I also have some complaints about the content of some of the exceptions. In the meantime, any reader want to try to reproduce this?
[tags].NET, .NET3.5, .NET2.0, CLR, BCL, Bug, Microsoft, Connect[/tags]