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

  • Thread starter Thread starter Amit
  • Start date Start date
A

Amit

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 said:
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,

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 said:
Amit said:
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 said:
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
Amit said:
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 said:
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
 
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.
 
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
 
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 said:
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
 
Back
Top