Destructors are useless?

M

Michi Henning

I've been having problem with destructors in the context of having ported C# code
developed under .NET to Mono. What happens is that, on a dual-CPU machine, various
parts of the code crash randomly (and rarely). This always happens during
process shutdown, after some thread has called System.Environment.Exit(). Clearly,
some sort of race condition.

Note that what follows only applies to destructors that are called when the process
shuts down -- there is no problem with destructors that get called by the GC when
the process is executing normally.

After a lot of debugging, I tracked it down to a destructor along the following
lines:

~SomeClass
{
if (!_destroyed)
{
System.Console.Error.WriteLine("Forgot to destroy SomeClass");
}
}

Under Mono, the attempt to write to the console crashes the process because, by the time
destructor is called by the garbage collector, the I/O subsystem has been partially
garbage collected already, and the process dies with a NullPointerException somewhere
in the guts of the I/O subsystem.

Not a problem with .NET: in .NET, the console isn't destroyed until after everything
else is destroyed. (For details, see:
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
There is a comment about two-thirds of the way down the page by Brian Grunkemeyer to that effect.)

Except that I can't rely on this because, as far as I can see, the spec doesn't guarantee that
the console will hang around during process shutdown, so it's not portable code.

Then I started reading the spec a bit more and found that destruction order is not guaranteed and
that, if A refers to B, it's entirely possible for B to be finalized before A. So, that got me to
thinking about what is actually legal to do from within a destructor, if that destructor may be
called during process shutdown. Here is a list of things that are *not* legal to do:

- I cannot dereference anything. If I do, the memory for the object that is reference is guaranteed
to still be there. However, that object may have been finalized already and, as a result, may no
long be in working order if I call a method on it.

- I cannot call a static method on anything. The static method itself is guaranteed to be there. However, the
implementation of the static method may depend on another static object that has been finalized already.
See previous point.

- I cannot safely invoke a virtual method on my own object. That's because my own object may have a derived
part, and that derived part may have been finalized already, and the virtual method may end up using
something in the derived part that conceptually no longer exists.

So, that doesn't leave a lot I can do in a destructor, as far as I can see. Here is what I can do safely:

I can assign or read any of my own data members, and any of the accessible data members of my base class.
That's about it.

So, I can assign null to all my data members that have reference type, just to be nice to the GC. But that
really isn't all that essential in most circumstances.

I can read my own data members. To what purpose? Well, to assert that my program state is still in fine
shape, of course:

~SomeClass()
{
System.Diagnostics.Assert(_myMember != null);
System.Diagnostics.Assert(_myOtherMember == null);
}

Oops. I can't safely call a static method, because whatever the implementation of the static method uses may
have been finalized already. Just as I can't safely write to the console, I can't safely assert either.

Of course, I have no real control over when destructors are called. In particular, I have not control
over what destructors run when some thread calls Exit(). As a result, these restrictions apply to *all*
destructors, not just those of static objects.

Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can't do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.


Mystified,

Michi.
 
D

Dmytro Lapshyn [MVP]

It is recommended to avoid using finalizers (they are actually called
'finalizers', not 'desctructors') wherever possible. If you need to release
unmanaged resources, implement IDisposable and follow the IDisposable
implementation design pattern recommended by Microsoft. If your class does
not use anything to be disposed, do not use the finalizer at all.

Still, you can put the finalizer to a good use implementing the Microsoft's
disposable object design pattern. In the pattern, the finalizer is the last
chance to release managed resources owned by the instance.
 
T

Tom Porterfield

Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can't do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.

If you are writing all managed code, then this statement is essentially it.
Within a pure managed environment this is no need to write a destructor.
Destructors are useful for releasing unmanaged resources. The garbage
collector has no specific knowledge on how to clean up an unmanaged
resource. Your class that encapsulates an unmanaged resource does have
that knowledge. So in your destructor you would take the responsibility
for making sure any unmanaged resources are properly released. Let the GC
handle your managed resources.

Also note that adding a destructor unnecessarily to an object can have a
negative impact on performance. An object with a destructor requires at
least two garbage collections. When the GC runs it reclaims the memory for
inaccessible objects without destructors. It cannot, at that time, collect
the inaccessible objects that do have destructors. Instead it places them
in a list of objects that are marked as ready for finalization. It then
calls the finalize method (destructor) on the objects in that list and then
removes them from the list. The next time GC runs it can them collect
these objects as they are now no longer in the list of objects that are
ready for finalization. So the fact that you have added a destructor to
your object only increases the chances that it will hang around longer than
other objects.
 
H

Helge Jensen

If you are writing all managed code, then this statement is essentially it.

Well,... not quite, but perhaps that why you write "essentially"?
Within a pure managed environment this is no need to write a destructor.

Destructors can be quite usefull for checking that appropriate action
has been done on objects. For example you can check whether Dispose()
has been called.

The really usefull information: who forgot to call Dispose, is of course
not available, but I have a nice MixIn class that I can use to record
the stacktrace when the object is created and that is definatly also
usefull to know. It gives you a place to start looking.

While you may not be able to do much with the knowledge in your
finalizer, you can certainly throw an exception (the program *IS*
broken,... it forgot to call Dispose()) and the runtime may do
"good-things" with it. In debug mode the Visual IDE pops up a
messagebox, alerting you to a bug which would otherwise go unnoticed.

You can have your Dispose() call "GC.SuppressFinalize(this)" and avoid
invocation of the destructor.
 
M

Michi Henning

Tom Porterfield said:
If you are writing all managed code, then this statement is essentially it.

Yes, it sure looks like it.
Within a pure managed environment this is no need to write a destructor.
Destructors are useful for releasing unmanaged resources. The garbage
collector has no specific knowledge on how to clean up an unmanaged
resource. Your class that encapsulates an unmanaged resource does have
that knowledge. So in your destructor you would take the responsibility
for making sure any unmanaged resources are properly released. Let the GC
handle your managed resources.

Sure, I understand all that. A destructor can't do anything that relies on
another
object. In other words, a destructor can't dereference anything, and it cannot
even call a static function. Fine, I accept that. So what are destructors good
for?
Releasing unmanaged resources is one thing. (If I can accept that unmanaged
resources may not be released promptly.) But the *one thing* I still would
like to use destructors for is to check whether my program is still in a sane
state. In other words, I'd like to write assertions about my own member
variables. Unfortunately, I can't even do that: if the assertion holds, no
problem;
but, if the assertion fails, the mere act of having it fail is sufficient to
cause
undefined behavior. So, damned if I do, and damned if don't: if I don't assert,
my program has a bug and will go and do undefined things. If I do assert, the
act
of detecting that my program has a bug causes my program to go and do
undefined things. Talk about being caught between a rock and a hard place...

It is interesting that Java doesn't have this problem. On process exit, Java
guarantees
that destructors (or finalizers, if you prefer) will *not* run. This means that
it's perfectly
safe to put assertions into Java finalizers: while the process is alive and
running, the
assertions are checked whenever the GC decides to garbage collect an object.
And, if the process is exiting, destructors don't run at all, meaning that
there are
no issues with respect to destruction order. Of course, that means that
assertions
don't run during process shut down, but at least my process gets to shut down
without crashing.

And, of course, the idea that C# destructors are good for releasing unmanaged
resources seems flawed, too. After all, how many realistic programs are there
that can release unmanged resources without having to enrol the aid of some
other
managed resource? For example, in my particular case, the program crashed
because
of a destructor that looked like this:

class SomeClass
{
~SomeClass()
{
if(_destroyed)
{
_logger.warning("Forgot to destroy SomeClass");
}
}

private Logger _logger;
}

What I'm suggesting here is that, for realistic programs, chances are pretty
slim
that a destructor can clean up an unmanaged resource without having to ask for
help from some managed object. But, of course, as soon as that is the case,
the destructor cannot be used anymore. I'm left to conclude that destructors
are so useless that I can't even use them to make assertions about my own
member variables, let alone use them to reclaim unmanaged resources.

It truly would seem appropriate to remove destructors from the language
altogether. At least, that way, people wouldn't have their illusions shattered.

Of course, the alternative would be to not run destructors on process shutdown,
like Java does, and the problem would go away...

Cheers,

Michi.
 
M

Michi Henning

The really usefull information: who forgot to call Dispose, is of course
not available, but I have a nice MixIn class that I can use to record
the stacktrace when the object is created and that is definatly also
usefull to know. It gives you a place to start looking.

While you may not be able to do much with the knowledge in your
finalizer, you can certainly throw an exception (the program *IS*
broken,... it forgot to call Dispose()) and the runtime may do
"good-things" with it. In debug mode the Visual IDE pops up a
messagebox, alerting you to a bug which would otherwise go unnoticed.

Hmmm... The specification says that, if a destructor throws, the exception
is ignored and execution of that destructor is aborted. Given that you cannot
safely assert in a destructor or write to the console, it seems unlikely that
you could safely record a stack trace: after all, the classes you have to use
to record the stack trace may have long since been finalized. And, if you
can record the stack trace, throwing the exception is useless because that
exception won't go anywhere (that's guaranteed by the spec). Ergo,
destructors are useless during process shutdown. And, because I cannot
control when constructors run, they are useless altogether.

Cheers,

Michi.
 
G

Guest

What I'm suggesting here is that, for realistic programs, chances are pretty
slim
that a destructor can clean up an unmanaged resource without having to ask for
help from some managed object. But, of course, as soon as that is the case,
the destructor cannot be used anymore. I'm left to conclude that destructors
are so useless that I can't even use them to make assertions about my own
member variables, let alone use them to reclaim unmanaged resources.

cannot make assertions hardly means can't reclaim unmanaged resources. the
framework itself already showed that realistic programs can free unmanaged
resources with finalizers. your illusion is the problem here, really. doesn't
do what you want it to do only means it's useless to you.
 
D

Dilip

Michi said:
It is interesting that Java doesn't have this problem. On process exit, Java
guarantees
that destructors (or finalizers, if you prefer) will *not* run. This means that
it's perfectly
safe to put assertions into Java finalizers: while the process is alive and
running, the
assertions are checked whenever the GC decides to garbage collect an object.
And, if the process is exiting, destructors don't run at all, meaning that
there are
no issues with respect to destruction order. Of course, that means that
assertions
don't run during process shut down, but at least my process gets to shut down
without crashing.

Ok -- so how about this:

class someclass
{
~someclass()
{
try
{
System.Diagnostics.Assert(_myMemVar == null);
}
catch(ObjectDisposedException)
{
// you can't do much here anyway
}
}
}
 
H

Helge Jensen

Michi said:
"Helge Jensen" <[email protected]> wrote in message
Hmmm... The specification says that, if a destructor throws, the exception
is ignored and execution of that destructor is aborted. Given that you cannot

So conforming runtimes should ignore the exception, that way semantics
is preserved.
safely assert in a destructor or write to the console, it seems unlikely that
you could safely record a stack trace: after all, the classes you have to use

It works well in practice, especially in debug mode.
to record the stack trace may have long since been finalized. And, if you

But i still have a trace indicating the stack that allocated the object
that wasn't Dispose'ed, that's a whole lot better than knowing nothing,
or just it's type.
can record the stack trace, throwing the exception is useless because that
exception won't go anywhere (that's guaranteed by the spec). Ergo,

It goes straight into a message-dialog in debug mode. I put the
stack-trace in the exception-message and that gives a really good place
to start bug-hunting.
destructors are useless during process shutdown. And, because I cannot
control when constructors run, they are useless altogether.

Try rereading my posting, i didn't say they were usefull for semantics,
but for finding bugs. For example the places where Dispose() has been
forgotten.
 
M

Michi Henning

Ok -- so how about this:

class someclass
{
~someclass()
{
try
{
System.Diagnostics.Assert(_myMemVar == null);
}
catch(ObjectDisposedException)
{
// you can't do much here anyway
}
}
}

No, don't think so. Under Mono, the code crashes with a NullPointerException
if the assertion fails somewhere inside the assert. A far as I can see, the
problem
is that, by the time my finalizer runs, stdout and stderr have been closed
already
and further calls to assert crash the process.

Cheers,

Michi.
 
M

Michi Henning

Helge Jensen said:
cannot

So conforming runtimes should ignore the exception, that way semantics
is preserved.
use

It works well in practice, especially in debug mode.


But i still have a trace indicating the stack that allocated the object
that wasn't Dispose'ed, that's a whole lot better than knowing nothing,
or just it's type.

I think we are missing each other here. I'm not disputing that a stack trace
would be useful. It clearly would be. (Just as an assert would be.) But,
by the time a finalizer runs as a result of a process calling Exit(), the run
time may well be in a state where the act of trying to get a stack trace
crashes the process. In other words, it's not safe to do try and do
this from within a finalizer.

Cheers,

Michi.
 
D

Dilip

Michi said:
I think we are missing each other here. I'm not disputing that a stack trace
would be useful. It clearly would be. (Just as an assert would be.) But,
by the time a finalizer runs as a result of a process calling Exit(), the run
time may well be in a state where the act of trying to get a stack trace
crashes the process. In other words, it's not safe to do try and do
this from within a finalizer.

Michi

I think you answered your own question. There is a school of thought
in the .NET community that pretty much agree with your assessment --
that a finalizer is almost completely useless. Brent Rector, who is
pretty well known in the .NET/MSFT circles, clearly shares your view:
http://www.developer.com/net/csharp/article.php/2233111

My suggestion is -- why don't you tell tell us exactly what you want to
do in a finalizer in your specific case? If we deal with concretes
instead of hypotheticals we may be able to reason why that is either
possible or not possible.

thanks!
--Dilip
 
M

Mattias Sjögren

It is interesting that Java doesn't have this problem. On process exit, Java
guarantees
that destructors (or finalizers, if you prefer) will *not* run. This means that
it's perfectly
safe to put assertions into Java finalizers: while the process is alive and
running, the
assertions are checked whenever the GC decides to garbage collect an object.
And, if the process is exiting, destructors don't run at all, meaning that
there are
no issues with respect to destruction order. Of course, that means that
assertions
don't run during process shut down, but at least my process gets to shut down
without crashing.


If that's the behavior you want, how about

~SomeClass()
{
if ( !Environment.HasShutdownStared ) {
Debug.Assert( ... );
}
}




Mattias
 
M

Michi Henning

Mattias Sjögren said:
If that's the behavior you want, how about

~SomeClass()
{
if ( !Environment.HasShutdownStared ) {
Debug.Assert( ... );
}
}

I just stumbled across this too, this looks interesting -- I'll experiment with
that, thanks!

Cheers,

Michi.
 
M

Michi Henning

Dilip said:
Michi

I think you answered your own question. There is a school of thought
in the .NET community that pretty much agree with your assessment --
that a finalizer is almost completely useless. Brent Rector, who is
pretty well known in the .NET/MSFT circles, clearly shares your view:
http://www.developer.com/net/csharp/article.php/2233111

My suggestion is -- why don't you tell tell us exactly what you want to
do in a finalizer in your specific case? If we deal with concretes
instead of hypotheticals we may be able to reason why that is either
possible or not possible.

Fair enough :)

I don't want to do all that much, really. My finalizers are side-effect
free as far as the application is concerned. All I really want to do
is be able to safely assert within a finalizer and, if the assertion fails,
get a reliable stack trace instead of a crash. The only other requirement
is that the finalizer must be able to obtain a lock on its own object because
I'm running multi-threaded, but I don't see any problem there. So, the
typical scenario is:

class SomeClass()
{
// Lots of member functions here...

public void someMethod()
{
lock(this)
{
_mem3 == 99;
// ...
}
}

~SomeClass()
{
lock(this)
{
Debug.Assert(_mem1 == null);
Debug.Assert(_mem2 != null);
Debug.Assert(_mem3 == 99);
// etc...
}
}
private someType _mem1;
private someType _mem2;
private int _mem3;
}

Ideally, I'd like the assertions to be tested not just during normal
program execution, but also when the process goes away. Apparently,
that's impossible because just about anything I do from within a finalizer
may end up crashing the process, depending on the order in which things
are finalized.

I'm going to experiment with testing for whether process
shutdown has started to protect the finalizer from doing someting potentially
lethal. The downside of this is that assertions won't be checked during process
shutdown. And it's not clear to me yet whether that approach is guaranteed
free from race conditions. For example, what happens if I'm multi-threaded,
and a thread calls Exit() while some other thread is half-way through a
finalizer,
and GC kicks in in parallel? On a multi-CPU machine, the GC thread will run
truly asynchronously to application threads. So, potentially, I think the
sequence
of events could be:

~SomeClass()
{
lock(this)
{
// Test whether process is shutting down and skip assertions in that
case
if(!processIsGoingAway)
{
Debug.Assert(_mem1 == null);
// Some other thread calls Exit() and GC kicks in here and
finalizes a pile
// of things, including whatever machinery is used by
Debug.Assert()
// This thread is suspended for the time being.

// This thread is scheduled again. While this thread was asleep,
the condition
// !processIsGoingAway has changed and no longer holds.

Debug.Assert(_mem1 != null); // BANG!!!

// I have a bug in my code, and _mem1 isn't non-null. But the
assert
// crashes my process because the GC, meanwhile, has finalized the
// infrastructure that is needed by Assert().
}
}
}

I think the above is entirely possible. Worse, it's likely to be rare, so the
problem
may go undetected for a long time.

Cheers,

Michi.
 
M

Michi Henning

Mattias Sjögren said:
If that's the behavior you want, how about

~SomeClass()
{
if ( !Environment.HasShutdownStared ) {
Debug.Assert( ... );
}
}

Unfortunately, I don't see how I can make that thread-safe --
HasShutDownStarted may change value half-way through
the execution of some finalizer.

Cheers,

Michi.
 
H

Helge Jensen

Michi said:
I think we are missing each other here.
Possibly.

I'm not disputing that a stack trace
would be useful. It clearly would be. (Just as an assert would be.) But,
by the time a finalizer runs as a result of a process calling Exit(), the run
time may well be in a state where the act of trying to get a stack trace

The stack trace was obtained when the object was created and stored in
the object, roughly as:

class Foo: IDisposable {
StackTrace t; // Construction captures stack-trace
bool disposed;
...
~Foo() {
if ( !disposed )
throw new MissingDispose(t);
}
}

Obtaining the stack-trace at finalization time isn't very usefull, the
finalizer is run by the finalization thread.
crashes the process. In other words, it's not safe to do try and do
this from within a finalizer.

The GC *can* invoke finalizers in undefined order, so you better not
rely on the functionality of an object that has a finalizer, inside your
finalizer.

In http://www.developer.com/net/csharp/article.php/2233111, this is
conservatively generalized to:

"you cannot, in general, use any member reference variables of an
object in its Finalize method."

Which is true, but not in the strict sense -- the "in general" saves
that. You can reference a member, but don't rely on any behaviour of it
that it's (possible) finalizer changes.
 
D

Dmytro Lapshyn [MVP]

Michi,
No, don't think so. Under Mono, the code crashes with a
NullPointerException
if the assertion fails somewhere inside the assert. A far as I can see,
the

Can it just be that Mono is non-conformant to the specification and it is a
particular Mono problem?
Are you able to repro this faulty behavior on Microsoft .NET ?
 
H

Helge Jensen

Michi Henning wrote:

Isn't this a bug in the MONO runtime?

Debug.Assert throws, the mono-runtime catches that and logs it to stderr
(i guess) but stderr is closed.
 
M

Michi Henning

Michi,
Can it just be that Mono is non-conformant to the specification and it is a
particular Mono problem?
Are you able to repro this faulty behavior on Microsoft .NET ?

No, not on .NET. Part of the problem may be that I don't have a dual-CPU
machine to run .NET on. With Linux/Mono, the problem only shows up
on a dual-CPU machine, but not when I boot that machine in single-CPU
mode.

I know that .NET special-cases the Console object so it's possible
to print something from a finalizer without running the risk of crashing.
But, as far as I can see, that guarantee is not made by the spec, only
by the .NET implementation. Hence, the Mono behavior is, strictly
speaking, legal.

If someone could point me to a guarantee in the spec as to the
usability of the Console from a finalizer, I'd be grateful!

Cheers,

Michi.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top