Expert Texture Home Contact me About Subscribe Digipede Connect on LinkedIn rwandering on Twitter rwandering on FriendFeed

rwandering.net

The blogged wandering of Robert W. Anderson

Dan’s Worst .NET Bug

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 . . .

  1. Check the .NET versions / are they somehow different between our machines?
  2. See if this is a known bug.  Connect.microsoft.com, here I come.
  3. 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: , , , , , , ,

    Trackback

3 Comments »

    Tom wrote @ November 3rd, 2008 at 11:24 am

I think I’ve run into this, too. Did you find a workaround other than sleeping for a second?

    Robert W. Anderson wrote @ November 4th, 2008 at 11:07 am

Tom,

I never did get any further with this. Other things took priority. This is a question I should have gotten answered at the PDC. When I get a chance, I’ll ping some people on the Microsoft product teams to see if I can get an answer.

Cheers,
Robert

    Tom wrote @ December 16th, 2008 at 4:03 pm

I found a workaround for this. By calling GC.Collect and GC.WaitForPendingFinalilzers every so often, that was enough to clean up and prevent the crash. I think what was happening was that the system is allocating handles faster than it can free them, never getting around to doing so. Which meant that the file system ran out of file handles, thus the IO Exception. It’s cheesy, but it worked for me!

Your comment

HTML-Tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>