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

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:

Advertisements

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

Advertisements

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

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

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

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

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

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

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

Share This Page

Welcome to The Coding Forums!

Welcome to the Coding Forums, the place to chat about anything related to programming and coding languages.

Please join our friendly community by clicking the button below - it only takes a few seconds and is totally free. You'll be able to ask questions about coding or chat with the community and help others.
Sign up now!