none
ThreadAbortException in ASP.Net 4 leaking file handle? RRS feed

  • Question

  • Occasionally if our file server is slow and the page doesn't finish by its timeout ASP.Net will hit it with a ThreadAbortException. If that happens inside the Win32Native.CreateFile it's leaving the file handle locked until we do iisreset.

    Is this a flaw in .NET? Is there anything we can do about this short of bad ideas like raising the timeout to some giant number... I don't think ThreadAbort.Reset would help because the damage is already done and I don't even have the file handle returned from FileStream to close it myself.

    --

    at Microsoft.Win32.Win32Native.CreateFile(String lpFileName, Int32 dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, Int32 dwFlagsAndAttributes, IntPtr hTemplateFile)

    at Microsoft.Win32.Win32Native.SafeCreateFile(String lpFileName, Int32 dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, Int32 dwFlagsAndAttributes, IntPtr hTemplateFile)

    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, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)

    at System.IO.FileStream..ctor(String path, FileMode mode)

    Wednesday, April 25, 2012 9:07 PM

Answers

  • "If you don't have an object to dispose, how is the handle freed?"

    Normally the finalizer should cleanup the SafeHandle. But of course, that happens only after a GC and until then... the file stays locked.

    "To be honest, it looks more like a bug in Win API's CreateFile()"

    Can't be, Thread.Abort doesn't abort native code, it waits until the native code returns. And because safe handles are used I expect that a SafeHandle is created before abort happens. After all that's one of the reasons safe handles were created in the first place.

    In any case there's nothing the OP can do about this except avoiding the abort by increasing the ASP.NET timeout. It's not the first time I see a report about a problem caused by ASP.NET's thread aborts, once I saw something that looked like a corrupted hashtable. It's funny that ASP.NET tries to keep the server healthy by preventing requests from running too long and it ends up leaking resources and even corrupting the appdomain.

    Thursday, April 26, 2012 5:49 PM
    Moderator

All replies

    1. Wrong forum.  Post your ASP.net questions @ http://forums.asp.net.
    2. If you have trouble with leaked resources, program the right way:  Encapsulate the resource in a class that implements the disposable pattern (IDisposable) and always use instances of such class inside a using clause.

    Jose R. MCP

    Thursday, April 26, 2012 5:36 AM
  • @Sideout: It would be better if you report this as a bug on Connect: https://connect.microsoft.com/VisualStudio

    @Shweta Jain & webJose: Even if this happens in ASP.NET (mainly because ASP.NET is a common user of Thread.Abort) that doesn't mean this problem is specific to ASP.NET. The main problem is that .NET has code that is not thread abort safe.

    Thursday, April 26, 2012 8:01 AM
    Moderator
  • This is not a bug.  It just how .net works.  The .net framework is garbage-collected.  You are using a FileStream object outside a using clause.  If the thread aborts, the object is left untouched and to the mercy of garbage collection, just like any other .net object.  Given enough time, the GC will dispose of this stray FileStream object and the file will be freed.  Want a guarantee that this object is properly collected?  Use it inside a using clause.  I don't see a bug here anywhere.

    Jose R. MCP

    Thursday, April 26, 2012 4:02 PM
  • That would have been my hope, but this is not the case even inside a using().  You'll see the IL decompile version shows that the try/catch is AFTER the "new FileStream" and what I'm saying is that if the code is hit by ThreadAbortException (by ASP.Net timeout handler not me doing anything to call Thread.Abort) the FileStream will NOT be disposed and like I said it would not even return a reference to me if I did try/catch new FileStream explicitly because it failed in the constructor so I'm totally out of luck for any possible way I could safely handle this.  Please see the stack trace I posted.

    public static void RunSnippet()

    {   

      using (FileStream fileStream = new FileStream("c:\\sniptext.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite))   

      {       

        fileStream.WriteByte(0);   

      }

    }

    Decompile to IL has the FileStream constructor outside of try/finally.  I’ve confimed that opening a file stream and then just doing nothing WILL leave it open even if you don’t write to it.

    .method public hidebysig static

        void RunSnippet () cil managed

    {

        .locals init (

            [0] class [mscorlib]System.IO.FileStream fileStream

        )

        IL_0000: ldstr "c:\\sniptext.txt"

        IL_0005: ldc.i4.4

        IL_0006: ldc.i4.3

        IL_0007: newobj instance void [mscorlib]System.IO.FileStream::.ctor(string,  valuetype [mscorlib]System.IO.FileMode,  valuetype [mscorlib]System.IO.FileAccess)

        IL_000c: stloc.0

        .try

        {

            IL_000d: ldloc.0

            IL_000e: ldc.i4.0

            IL_000f: callvirt instance void [mscorlib]System.IO.Stream::WriteByte(uint8)

            IL_0014: leave.s IL_0020

        }

        finally

        {

            IL_0016: ldloc.0

            IL_0017: brfalse.s IL_001f

            IL_0019: ldloc.0

            IL_001a: callvirt instance void [mscorlib]System.IDisposable::Dispose()

            IL_001f: endfinally

        }

        IL_0020: ret

    }


    • Edited by Sideout Thursday, April 26, 2012 5:06 PM
    Thursday, April 26, 2012 4:36 PM
  • Ah, I see.  I was thinking afterwards the constructor.  If you don't have an object to dispose, how is the handle freed?  That's the thing I didn't understand the first time.  Now I do see the bug.  To be honest, it looks more like a bug in Win API's CreateFile() than it is a bug for .net.  Looks like CreateFile() may be succeeding somehow (because the file is open), but it never returns and the thread is aborted.


    Jose R. MCP

    Thursday, April 26, 2012 4:55 PM
  • If no object gets created, then no object can be placed in the finalizer queue to be finalized.

    Do you have a way to avoid aborting threads?

    Thursday, April 26, 2012 5:07 PM
  • No we cannot avoid the aborting and we are not directly aborting them.  The build in ASP.Net timeout handler is aborting them and it's in our interest for safety and scalability to NOT leave threads running an inordinately long time if the file server isn't responding or it starts affecting the performance of the rest of the app if to too many threads are tied up.

    Thursday, April 26, 2012 5:26 PM
  • "If you don't have an object to dispose, how is the handle freed?"

    Normally the finalizer should cleanup the SafeHandle. But of course, that happens only after a GC and until then... the file stays locked.

    "To be honest, it looks more like a bug in Win API's CreateFile()"

    Can't be, Thread.Abort doesn't abort native code, it waits until the native code returns. And because safe handles are used I expect that a SafeHandle is created before abort happens. After all that's one of the reasons safe handles were created in the first place.

    In any case there's nothing the OP can do about this except avoiding the abort by increasing the ASP.NET timeout. It's not the first time I see a report about a problem caused by ASP.NET's thread aborts, once I saw something that looked like a corrupted hashtable. It's funny that ASP.NET tries to keep the server healthy by preventing requests from running too long and it ends up leaking resources and even corrupting the appdomain.

    Thursday, April 26, 2012 5:49 PM
    Moderator