www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: TDPL: Manual invocation of destructor

reply bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:
 class File {
      FILE * fp;
      this() { fp = fopen("/tmp/temporary"); }
      ~this() { fclose(fp); }
 }

I am not expert enough about all this topic, so I try to keep myself out of this discussion. Recently I have read this:
issues with finalizers. The main problem with finalizers and garbage collection
is that finalizers generally try to reclaim a non-memory resource (such as a
specific file, a file handle, or a network socket) based purely on a memory
reclamation policy (generally triggered by later memory allocations). Generally
those other resource exhaust well before memory does, then you're out of the
resource stuck waiting for garbage collection to notice a particular finalizer
needs to be run. A better general rule of thumb is "don't ever use finalizers".
Instead always call close inside a finally.<

This means that putting something like a fclose(fp) inside the ~this() is wrong, you need to add a specific method to that File class to ask for the closing of the file, because you generally can't rely on the GC for this, because a GC is free to even never collect objects, if there is enough RAM. In my opinion it's correct to put something like a fclose(fp) inside the ~this() only if you are sure this struct/class will be always allocated on the stack, so its destructor will always be called deterministically when the scope ends (a reference counting strategy too seems acceptable, because it is deterministic). In a GC-based language you can't assume your destructors are run, so your destructors usually need to be empty, and you need to add other methods to free explicitly and manually (or with a scope(exit)) all the resources that aren't RAM. Please take this cum grano salis, I am not an expert on this. Bye, bearophile
Aug 10 2010
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 08:27:19 -0400, bearophile <bearophileHUGS lycos.com>  
wrote:

 Andrei Alexandrescu:
 class File {
      FILE * fp;
      this() { fp = fopen("/tmp/temporary"); }
      ~this() { fclose(fp); }
 }

I am not expert enough about all this topic, so I try to keep myself out of this discussion. Recently I have read this:
 issues with finalizers. The main problem with finalizers and garbage  
 collection is that finalizers generally try to reclaim a non-memory  
 resource (such as a specific file, a file handle, or a network socket)  
 based purely on a memory reclamation policy (generally triggered by  
 later memory allocations). Generally those other resource exhaust well  
 before memory does, then you're out of the resource stuck waiting for  
 garbage collection to notice a particular finalizer needs to be run. A  
 better general rule of thumb is "don't ever use finalizers". Instead  
 always call close inside a finally.<

This means that putting something like a fclose(fp) inside the ~this() is wrong, you need to add a specific method to that File class to ask for the closing of the file, because you generally can't rely on the GC for this, because a GC is free to even never collect objects, if there is enough RAM. In my opinion it's correct to put something like a fclose(fp) inside the ~this() only if you are sure this struct/class will be always allocated on the stack, so its destructor will always be called deterministically when the scope ends (a reference counting strategy too seems acceptable, because it is deterministic). In a GC-based language you can't assume your destructors are run, so your destructors usually need to be empty, and you need to add other methods to free explicitly and manually (or with a scope(exit)) all the resources that aren't RAM. Please take this cum grano salis, I am not an expert on this.

destructors are for the purpose of clearing out resources that are not provided by the GC. Not having it in the destructor is a bad thing. The thing is, if you *don't* call it in the destructor, and require someone to manually call your explicit method, then someone will forget to make that call, and your resource stays open forever. It also means you have to call your method when going out of scope. By clearing that resource in the destructor, and allowing a way to manually call that destructor, the class works in all three situations: manual resource management (via clear), scoped destruction, and GC destruction. I think in time, the GC may be associated with it's own thread, and may run on a schedule vs. having to wait for a memory allocation to run. When this happens, it's more likely you can rely on the GC to free the resources in a reasonable amount of time. -Steve
Aug 10 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Steven Schveighoffer:

 By clearing that resource in the destructor, and allowing a way to  
 manually call that destructor, the class works in all three situations:   
 manual resource management (via clear), scoped destruction, and GC  
 destruction.

From what I have seen so far: 1) the semantics of clear() is a mess 2) objects don't get deallocated deterministically when they go out of scope 3) and the GC is not deterministic, you can only partially hope your destructor will be called at program end, in some random order. And sometimes your object destructors will not be called even at the end of the program. The only thing I see good here is to use a scope(exit) to close the file: auto f = new File(...); scope(exit) f.close(); ... While clear, scoped destruction of the object, and GC destruction don't seem enough in this situation, or any other situation where there you have a resource that is not RAM.
 I think in time, the GC may be associated with it's own thread, and may  
 run on a schedule vs. having to wait for a memory allocation to run.  When  
 this happens, it's more likely you can rely on the GC to free the  
 resources in a reasonable amount of time.

I'll believe that only when I see it. Even one of the most advanced GC around, the one in the Sun JVM, doesn't act as deterministically as you say (maybe the G1 now does, I am not sure). Instead of a clear() function it can be added to classes and structs an optional standard method (like .clear()) that when called performs the cleaning of the object, frees all resources (but not the RAM used by the object instance), puts references to null, etc. The deallocation of the RAM is fully left to the GC later. An class that defines clear() keeps the extra bit of state to tell apart the living or dead state of the object, plus an extra assert in the class invariant. This is just an idea, probably not too much good :-) Bye, bearophile
Aug 10 2010
parent Don <nospam nospam.com> writes:
bearophile wrote:
 Steven Schveighoffer:
 
 By clearing that resource in the destructor, and allowing a way to  
 manually call that destructor, the class works in all three situations:   
 manual resource management (via clear), scoped destruction, and GC  
 destruction.

From what I have seen so far: 1) the semantics of clear() is a mess 2) objects don't get deallocated deterministically when they go out of scope 3) and the GC is not deterministic, you can only partially hope your destructor will be called at program end, in some random order. And sometimes your object destructors will not be called even at the end of the program. The only thing I see good here is to use a scope(exit) to close the file: auto f = new File(...); scope(exit) f.close(); ... While clear, scoped destruction of the object, and GC destruction don't seem enough in this situation, or any other situation where there you have a resource that is not RAM.

I completely agree. Everything I've read about finalizers indicates that it's a completely broken concept. It seems as though it was initially envisioned (in Java, in the early documents of C#, etc) as equivalent to destructors. Apparently it took some time to realize that the value in destructors comes almost entirely from the deterministic lifetime. Instead, finalizers seem to be equivalent to registering a callback with the runtime. Their main legitimate use seems to be for contract programming (though I've never heard it described in that way): at the moment of garbage collection, check that the destructor has actually been run.
 
 
 I think in time, the GC may be associated with it's own thread, and may  
 run on a schedule vs. having to wait for a memory allocation to run.  When  
 this happens, it's more likely you can rely on the GC to free the  
 resources in a reasonable amount of time.

I'll believe that only when I see it. Even one of the most advanced GC around, the one in the Sun JVM, doesn't act as deterministically as you say (maybe the G1 now does, I am not sure). Instead of a clear() function it can be added to classes and structs an optional standard method (like .clear()) that when called performs the cleaning of the object, frees all resources (but not the RAM used by the object instance), puts references to null, etc. The deallocation of the RAM is fully left to the GC later. An class that defines clear() keeps the extra bit of state to tell apart the living or dead state of the object, plus an extra assert in the class invariant. This is just an idea, probably not too much good :-) Bye, bearophile

Aug 12 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 12 Aug 2010 14:46:00 -0400, Don <nospam nospam.com> wrote:

 bearophile wrote:
 Steven Schveighoffer:

 By clearing that resource in the destructor, and allowing a way to   
 manually call that destructor, the class works in all three  
 situations:   manual resource management (via clear), scoped  
 destruction, and GC  destruction.

1) the semantics of clear() is a mess 2) objects don't get deallocated deterministically when they go out of scope 3) and the GC is not deterministic, you can only partially hope your destructor will be called at program end, in some random order. And sometimes your object destructors will not be called even at the end of the program. The only thing I see good here is to use a scope(exit) to close the file: auto f = new File(...); scope(exit) f.close(); ... While clear, scoped destruction of the object, and GC destruction don't seem enough in this situation, or any other situation where there you have a resource that is not RAM.

I completely agree. Everything I've read about finalizers indicates that it's a completely broken concept. It seems as though it was initially envisioned (in Java, in the early documents of C#, etc) as equivalent to destructors. Apparently it took some time to realize that the value in destructors comes almost entirely from the deterministic lifetime. Instead, finalizers seem to be equivalent to registering a callback with the runtime.

No, it's purpose is to be a last-ditch effort to clean up unmanaged resources. It's kind of like a safety net. Essentially, if you are relying on the GC to destroy your object, and you haven't cleaned up all the resources in that object, you don't want those resources to leak. Once the object is destroyed, the resource is leaked if it's not cleaned up. Is allowing the destructor to clean up your resource a program error? It's up to the developer to decide that. D still lacks a standard way of deterministic destruction. Clear doesn't work because it still calls the destructor, and the destructor must assume it's non-deterministic.
 Their main legitimate use seems to be for contract programming (though  
 I've never heard it described in that way): at the moment of garbage  
 collection, check that the destructor has actually been run.

It can be set up this way, just throw an exception if the resource isn't cleaned up instead of silently cleaning up the mess in your destructor. -Steve
Aug 12 2010