Unhandled exception in FileStream when writing to a full disk - bug in framework?

Discussion in 'ASP .Net' started by Amit, Jul 29, 2003.

  1. Amit

    Amit Guest

    Hello,

    I just ran into a weird problem with FileStream. The problem occurs in
    the following scenario:
    - I create an instance of FileStream and use it to write to a disk
    which have just a small amount of free space.
    - When the disk gets full, FileStream throws an IOException. I catch
    and process it.
    - My program continues executing. At some later point, I'm getting an
    unhandled IOException (althoug the whole code is wrapped in try-catch
    block).
    - After inspecting the call stack and the running threads I made the
    following observations:
    --- The second exception occurs in a thread which doesn't belong to my
    process.
    --- The call stack of the thread in which this exception occurs starts
    within the FileStream finalize method.

    By observing the calls in the call stack, I believe that what happen
    is as follows:

    When the disk gets full and the first exception (that I handle)
    occurs, the buffer of the FileStream object is not empty. However,
    since the object gets out of scope, the garbage collector tries to
    release it at some point. The garbage collector calls finalize, which
    calls dispose, and one of the calls in this sequense is a call to
    flush() that tries to write the buffer before killing the object.
    Since the disk is full, this call fails and an IOException is thrown
    from the garbage collector thread.

    I found no way to get around this, except from disabling garbage
    collection for the FileStream object, which IMHO is pretty ugly. If the
    garbage collector and FileStream always work in this way, i.e. the GC
    calls finalize, which causes flush to be called without knowing that
    the disk is already full and a previous write failed, then it seems
    like a bug in the framework.

    I would appreciate any comments,

    TIA,

    Amit

    P.s. Below is the code I've been using for demonstrating this problem:

    static void Main(string[] args)
    {
    try
    {
    FileStream fs =
    new FileStream(args[0], FileMode.Create);
    Console.WriteLine("starting to write...");
    while (true)
    fs.WriteByte(0xff);
    }
    catch (Exception e)
    {
    Console.WriteLine("Exception: {0}",
    e.Message);
    }
    Console.WriteLine("press any key to continue");
    Console.Read();


    }
     
    Amit, Jul 29, 2003
    #1
    1. Advertising

  2. Amit

    Alan Pretre Guest

    "Amit" <> wrote in message
    news:...
    > I found no way to get around this, except from disabling garbage
    > collection for the FileStream object, which IMHO is pretty ugly. If the
    > garbage collector and FileStream always work in this way, i.e. the GC
    > calls finalize, which causes flush to be called without knowing that
    > the disk is already full and a previous write failed, then it seems
    > like a bug in the framework.


    When you get the exception, can't you do fs.SetLength(0)? I haven't tried
    it since I don't have a spare drive to run it on at the moment. I
    rearranged your code. Let me know what happens.

    static void Main(string[] args) {
    try {
    FileStream fs = new FileStream(args[0], FileMode.Create);
    try {
    Console.WriteLine("starting to write...");
    while (true) fs.WriteByte(0xff);
    } catch (Exception e1) {
    Console.WriteLine("Exception: {0}", e1.Message);
    fs.SetLength(0);
    }
    } catch (Exception e2) {
    Console.WriteLine("Exception: {0}", e2.Message);
    }
    Console.WriteLine("press any key to continue");
    Console.Read();
    }

    -- Alan
     
    Alan Pretre, Jul 29, 2003
    #2
    1. Advertising

  3. Amit

    Amit Guest

    Alan,

    Sorry, this doesn't help. I tried it and it failed - SetLength itself
    throws an exception in this case, and if you catch and handle it, you
    still get the unhandled exception later.

    I also looked at SetLength code (in rotor), and it shows that
    SetLength tries to flush the buffer. This of course fails.

    I still thinks this is a bug in the framework - they should have
    maintain state so Finalize will know that previous writes failed and
    will not attempt to write the buffer.

    Any more ideas?

    Amit

    "Alan Pretre" <no@spam> wrote in message news:<O#>...
    > "Amit" <> wrote in message
    > news:...
    > > I found no way to get around this, except from disabling garbage
    > > collection for the FileStream object, which IMHO is pretty ugly. If the
    > > garbage collector and FileStream always work in this way, i.e. the GC
    > > calls finalize, which causes flush to be called without knowing that
    > > the disk is already full and a previous write failed, then it seems
    > > like a bug in the framework.

    >
    > When you get the exception, can't you do fs.SetLength(0)? I haven't tried
    > it since I don't have a spare drive to run it on at the moment. I
    > rearranged your code. Let me know what happens.
    >
    > static void Main(string[] args) {
    > try {
    > FileStream fs = new FileStream(args[0], FileMode.Create);
    > try {
    > Console.WriteLine("starting to write...");
    > while (true) fs.WriteByte(0xff);
    > } catch (Exception e1) {
    > Console.WriteLine("Exception: {0}", e1.Message);
    > fs.SetLength(0);
    > }
    > } catch (Exception e2) {
    > Console.WriteLine("Exception: {0}", e2.Message);
    > }
    > Console.WriteLine("press any key to continue");
    > Console.Read();
    > }
    >
    > -- Alan
     
    Amit, Jul 30, 2003
    #3
  4. Amit

    Dave Guest

    Can you dispose the object yourself when the exception occurs? This might
    cause it to throw the second exception immediately rather then when the
    finalizer runs.

    "Amit" <> wrote in message
    news:...
    > Alan,
    >
    > Sorry, this doesn't help. I tried it and it failed - SetLength itself
    > throws an exception in this case, and if you catch and handle it, you
    > still get the unhandled exception later.
    >
    > I also looked at SetLength code (in rotor), and it shows that
    > SetLength tries to flush the buffer. This of course fails.
    >
    > I still thinks this is a bug in the framework - they should have
    > maintain state so Finalize will know that previous writes failed and
    > will not attempt to write the buffer.
    >
    > Any more ideas?
    >
    > Amit
    >
    > "Alan Pretre" <no@spam> wrote in message

    news:<O#>...
    > > "Amit" <> wrote in message
    > > news:...
    > > > I found no way to get around this, except from disabling garbage
    > > > collection for the FileStream object, which IMHO is pretty ugly. If

    the
    > > > garbage collector and FileStream always work in this way, i.e. the GC
    > > > calls finalize, which causes flush to be called without knowing that
    > > > the disk is already full and a previous write failed, then it seems
    > > > like a bug in the framework.

    > >
    > > When you get the exception, can't you do fs.SetLength(0)? I haven't

    tried
    > > it since I don't have a spare drive to run it on at the moment. I
    > > rearranged your code. Let me know what happens.
    > >
    > > static void Main(string[] args) {
    > > try {
    > > FileStream fs = new FileStream(args[0], FileMode.Create);
    > > try {
    > > Console.WriteLine("starting to write...");
    > > while (true) fs.WriteByte(0xff);
    > > } catch (Exception e1) {
    > > Console.WriteLine("Exception: {0}", e1.Message);
    > > fs.SetLength(0);
    > > }
    > > } catch (Exception e2) {
    > > Console.WriteLine("Exception: {0}", e2.Message);
    > > }
    > > Console.WriteLine("press any key to continue");
    > > Console.Read();
    > > }
    > >
    > > -- Alan
     
    Dave, Jul 30, 2003
    #4
  5. Amit

    Amit Guest

    "Dave" <> wrote in message news:<>...
    > Can you dispose the object yourself when the exception occurs? This might
    > cause it to throw the second exception immediately rather then when the
    > finalizer runs.


    No, it won't help. Dispose will fail since it cannot write to the
    disk, but even if you do that and catch the second exception, Finalize
    will still try to dispose the file again. The point is, anything you
    do trying to release the file will fail, so the buffer will remain
    full. I would expect that the FileStream would "remeber" that it
    cannot write, and it won't, after the first failure, attempt
    writing again. However, apparently this is not how it works. There is
    nothing one can do, AFAIK, to prevent Finalize from trying to flush
    the file.

    Cheers,
    Amit
     
    Amit, Jul 31, 2003
    #5
  6. Amit

    Dave Guest

    It sounds like the current version has been poorly implemented. How about
    something silly....if you can reliably detect when this sort of failure
    occurs perhaps you can redirect the filestream to point to a file that it
    can always write to; a bit bucket that was always available. It's been a
    long time since this has come up but I seem to recall there used to be such
    a file. That might cause it to change its behavior during finalization.


    "Amit" <> wrote in message
    news:...
    > "Dave" <> wrote in message

    news:<>...
    > > Can you dispose the object yourself when the exception occurs? This

    might
    > > cause it to throw the second exception immediately rather then when the
    > > finalizer runs.

    >
    > No, it won't help. Dispose will fail since it cannot write to the
    > disk, but even if you do that and catch the second exception, Finalize
    > will still try to dispose the file again. The point is, anything you
    > do trying to release the file will fail, so the buffer will remain
    > full. I would expect that the FileStream would "remeber" that it
    > cannot write, and it won't, after the first failure, attempt
    > writing again. However, apparently this is not how it works. There is
    > nothing one can do, AFAIK, to prevent Finalize from trying to flush
    > the file.
    >
    > Cheers,
    > Amit
     
    Dave, Jul 31, 2003
    #6
  7. Amit

    Amit Guest

    I poked around a little bit, and I believe there are two separate
    problems here:

    1. I looked at the rotor FileStream code, and it seems that once you
    get the "not enough space on disk" exception, FileStream do not
    provide any means for closing the file and releasing the underlying
    WIN32 handle. So even if you find a way to catch the exception,
    handles might leak (even if the CLR would swalloe the exception, see
    my next point).

    2. It seems that all exceptions occuring in Finalizers should be
    swallowed by the CLR. You may look at
    http://blogs.gotdotnet.com/cbrumme/CategoryView.aspx/CLR
    at the "Unhandled Exceptions" section. In the case we are discussing,
    this is apparently not the way it happens.


    Anyway, I found a workaround by calling FileStream.Close() from within
    a try/catch block, and if an exception occurs I call
    CG.SuppressFinalize for the FileStream object. That way I do not get
    unhandled exceptions, but I believe that I lose a leaking WIN32
    hanlde. I just don't have enough time to mess with it anymore.

    Cheers,

    Amit


    "Dave" <> wrote in message news:<>...
    > It sounds like the current version has been poorly implemented. How about
    > something silly....if you can reliably detect when this sort of failure
    > occurs perhaps you can redirect the filestream to point to a file that it
    > can always write to; a bit bucket that was always available. It's been a
    > long time since this has come up but I seem to recall there used to be such
    > a file. That might cause it to change its behavior during finalization.
    >
    >
     
    Amit, Aug 4, 2003
    #7
  8. Amit

    Dave Guest

    It looks like a bug and/or a design deficiency in the FileStream class - it
    seems like one of those problems that will get improved on as the runtime
    becomes more mature. I'm very familiar with Chris's weblog - swallowing
    exceptions in the finalizer thread makes sense, but they should be reported
    as a bug in the object being finalized.

    Closing the file object in a catch or finally block seems like good
    programming practice anyway - files are one of those sensitive system
    objects that need careful handling. I'm surprised you have to use
    GC.SuppressFinalize on it after you have closed it - I would have expected
    it to be able to retain the state info necessary to remember that it was
    already closed.

    Good luck with the rest of your project.

    Dave



    "Amit" <> wrote in message
    news:...
    > I poked around a little bit, and I believe there are two separate
    > problems here:
    >
    > 1. I looked at the rotor FileStream code, and it seems that once you
    > get the "not enough space on disk" exception, FileStream do not
    > provide any means for closing the file and releasing the underlying
    > WIN32 handle. So even if you find a way to catch the exception,
    > handles might leak (even if the CLR would swalloe the exception, see
    > my next point).
    >
    > 2. It seems that all exceptions occuring in Finalizers should be
    > swallowed by the CLR. You may look at
    > http://blogs.gotdotnet.com/cbrumme/CategoryView.aspx/CLR
    > at the "Unhandled Exceptions" section. In the case we are discussing,
    > this is apparently not the way it happens.
    >
    >
    > Anyway, I found a workaround by calling FileStream.Close() from within
    > a try/catch block, and if an exception occurs I call
    > CG.SuppressFinalize for the FileStream object. That way I do not get
    > unhandled exceptions, but I believe that I lose a leaking WIN32
    > hanlde. I just don't have enough time to mess with it anymore.
    >
    > Cheers,
    >
    > Amit
    >
    >
    > "Dave" <> wrote in message

    news:<>...
    > > It sounds like the current version has been poorly implemented. How

    about
    > > something silly....if you can reliably detect when this sort of failure
    > > occurs perhaps you can redirect the filestream to point to a file that

    it
    > > can always write to; a bit bucket that was always available. It's been a
    > > long time since this has come up but I seem to recall there used to be

    such
    > > a file. That might cause it to change its behavior during finalization.
    > >
    > >
     
    Dave, Aug 4, 2003
    #8
  9. Amit

    Alan Pretre Guest

    "Amit" <> wrote in message
    news:...
    > 1. I looked at the rotor FileStream code, and it seems that once you
    > get the "not enough space on disk" exception, FileStream do not
    > provide any means for closing the file and releasing the underlying
    > WIN32 handle. So even if you find a way to catch the exception,
    > handles might leak (even if the CLR would swalloe the exception, see
    > my next point).


    You do have access to the underlying OS handle, fs.Handle. Perhaps you
    could use P/Invoke to close the handle to prevent leaks.

    -- Alan
     
    Alan Pretre, Aug 4, 2003
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jas Shultz
    Replies:
    0
    Views:
    1,001
    Jas Shultz
    Dec 3, 2003
  2. Ola
    Replies:
    0
    Views:
    566
  3. Tedka
    Replies:
    2
    Views:
    2,720
    Mr. Dot Net
    Jul 19, 2004
  4. Scott

    FileStream Writing Problems

    Scott, Aug 6, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    426
    =?Utf-8?B?U2hhd24gSw==?=
    Aug 6, 2004
  5. Warren Tang
    Replies:
    1
    Views:
    923
    Warren Tang
    Sep 23, 2008
Loading...

Share This Page