www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Static destructors called before class instance destructors

reply rkreis <elite01 gmx.de> writes:
Good day.

Using derelict, I noticed bad behavior: The app segfaulted inside of 
destructors. I later found out that derelict unloaded libraries and the 
destructor, freeing resources, tried to call such unloaded functions. 
This happens because a static destructor unloads all libraries. A simple 
test case shows that static destructors can be called before all 
instances are destructed. If an instance destructor then uses resources 
freed by the static destructor, things become problematic.

import std.stdio;

class Dude {
     private static char[] eek;

     public this() {
         writefln("Dude ctor");
     }

     public ~this() {
         writefln("Dude dtor - eek is '%s'", eek);
     }

     public static this() {
         writefln("Dude static ctor");
         eek = "Everything up and running";
     }

     public static ~this() {
         writefln("Dude static dtor");
         eek = "You're doomed";
     }
}

void main() {
     Dude d = new Dude();
}
outputs
Dude static ctor
Dude ctor
Dude static dtor
Dude dtor - eek is 'You're doomed'

with GDC 0.23 and DMD 1.018. This shows that the instance destructor 
accesses a static variable that has been modified by the static 
destructor. I can't think of any workaround.
In my opinion, it would be better if static destructors were only called 
after all unreachable class instances have been destructed. That 
wouldn't break static destructors accessing static references to class 
instances.
If one of those classes also depends on the static destructor not having 
run, that's a circular dependency to which I can't find a satisfying 
solution. For those cases, I suggest the current behavior, or a compile 
time error (which is hard to implement, I believe). Anyway, easily 
accessing static variables after the static destructor has run doesn't 
sound right.

See you soon.
Jul 21 2007
parent reply Sean Kelly <sean f4.ca> writes:
rkreis wrote:
 Good day.
 
 Using derelict, I noticed bad behavior: The app segfaulted inside of 
 destructors. I later found out that derelict unloaded libraries and the 
 destructor, freeing resources, tried to call such unloaded functions. 
 This happens because a static destructor unloads all libraries. A simple 
 test case shows that static destructors can be called before all 
 instances are destructed. If an instance destructor then uses resources 
 freed by the static destructor, things become problematic.
 
 import std.stdio;
 
 class Dude {
     private static char[] eek;
 
     public this() {
         writefln("Dude ctor");
     }
 
     public ~this() {
         writefln("Dude dtor - eek is '%s'", eek);
     }
 
     public static this() {
         writefln("Dude static ctor");
         eek = "Everything up and running";
     }
 
     public static ~this() {
         writefln("Dude static dtor");
         eek = "You're doomed";
     }
 }
 
 void main() {
     Dude d = new Dude();
 }
 outputs
 Dude static ctor
 Dude ctor
 Dude static dtor
 Dude dtor - eek is 'You're doomed'

Funny, I've recently been trying to decide how to handle this exact situation. When a D application shuts down, unreachable objects may/will be collected then static dtors are run. After that, there may still be valid D objects floating around, either which the GC incorrectly thought were reachable or that were reachable through static references. Should these objects never be finalized? This would certainly be the easier/cleaner approach to take, but I'm not sure how I feel about leaving objects un-finalized. Sean
Jul 22 2007
next sibling parent rkreis <elite01 gmx.de> writes:
Sean Kelly schrieb:
 rkreis wrote:
 Good day.

 Using derelict, I noticed bad behavior: The app segfaulted inside of 
 destructors. I later found out that derelict unloaded libraries and 
 the destructor, freeing resources, tried to call such unloaded 
 functions. This happens because a static destructor unloads all 
 libraries. A simple test case shows that static destructors can be 
 called before all instances are destructed. If an instance destructor 
 then uses resources freed by the static destructor, things become 
 problematic.

 import std.stdio;

 class Dude {
     private static char[] eek;

     public this() {
         writefln("Dude ctor");
     }

     public ~this() {
         writefln("Dude dtor - eek is '%s'", eek);
     }

     public static this() {
         writefln("Dude static ctor");
         eek = "Everything up and running";
     }

     public static ~this() {
         writefln("Dude static dtor");
         eek = "You're doomed";
     }
 }

 void main() {
     Dude d = new Dude();
 }
 outputs
 Dude static ctor
 Dude ctor
 Dude static dtor
 Dude dtor - eek is 'You're doomed'

Funny, I've recently been trying to decide how to handle this exact situation. When a D application shuts down, unreachable objects may/will be collected then static dtors are run. After that, there may still be valid D objects floating around, either which the GC incorrectly thought were reachable or that were reachable through static references. Should these objects never be finalized? This would certainly be the easier/cleaner approach to take, but I'm not sure how I feel about leaving objects un-finalized. Sean

I vote against silently leaving them unfinalized just because they might depend on static variables. Maybe print a (runtime) warning or error (like unhandled switch cases), or just finalize them, making it the developer's job to care that objects still reachable after the static destructors have run won't depend on static variables. Either way, I would like all unreachable objects to be collected before static destructors are run. And I'd also like to see more opinions/suggestions on this. Have a nice day
Jul 22 2007
prev sibling parent reply Tristam MacDonald <swiftcoder gmail.com> writes:
Sean Kelly Wrote:

 rkreis wrote:
 Good day.
 
 Using derelict, I noticed bad behavior: The app segfaulted inside of 
 destructors. I later found out that derelict unloaded libraries and the 
 destructor, freeing resources, tried to call such unloaded functions. 
 This happens because a static destructor unloads all libraries. A simple 
 test case shows that static destructors can be called before all 
 instances are destructed. If an instance destructor then uses resources 
 freed by the static destructor, things become problematic.
 
 import std.stdio;
 
 class Dude {
     private static char[] eek;
 
     public this() {
         writefln("Dude ctor");
     }
 
     public ~this() {
         writefln("Dude dtor - eek is '%s'", eek);
     }
 
     public static this() {
         writefln("Dude static ctor");
         eek = "Everything up and running";
     }
 
     public static ~this() {
         writefln("Dude static dtor");
         eek = "You're doomed";
     }
 }
 
 void main() {
     Dude d = new Dude();
 }
 outputs
 Dude static ctor
 Dude ctor
 Dude static dtor
 Dude dtor - eek is 'You're doomed'

Funny, I've recently been trying to decide how to handle this exact situation. When a D application shuts down, unreachable objects may/will be collected then static dtors are run. After that, there may still be valid D objects floating around, either which the GC incorrectly thought were reachable or that were reachable through static references. Should these objects never be finalized? This would certainly be the easier/cleaner approach to take, but I'm not sure how I feel about leaving objects un-finalized. Sean

DO they actually get finalised at the moment? I seem to have noticed that a lot of destructors aren't run at program termination, unless fullCollect() is explicitly called.
Jul 22 2007
parent Sean Kelly <sean f4.ca> writes:
Tristam MacDonald wrote:
 Sean Kelly Wrote:
 
 rkreis wrote:
 Good day.

 Using derelict, I noticed bad behavior: The app segfaulted inside of 
 destructors. I later found out that derelict unloaded libraries and the 
 destructor, freeing resources, tried to call such unloaded functions. 
 This happens because a static destructor unloads all libraries. A simple 
 test case shows that static destructors can be called before all 
 instances are destructed. If an instance destructor then uses resources 
 freed by the static destructor, things become problematic.

 import std.stdio;

 class Dude {
     private static char[] eek;

     public this() {
         writefln("Dude ctor");
     }

     public ~this() {
         writefln("Dude dtor - eek is '%s'", eek);
     }

     public static this() {
         writefln("Dude static ctor");
         eek = "Everything up and running";
     }

     public static ~this() {
         writefln("Dude static dtor");
         eek = "You're doomed";
     }
 }

 void main() {
     Dude d = new Dude();
 }
 outputs
 Dude static ctor
 Dude ctor
 Dude static dtor
 Dude dtor - eek is 'You're doomed'

situation. When a D application shuts down, unreachable objects may/will be collected then static dtors are run. After that, there may still be valid D objects floating around, either which the GC incorrectly thought were reachable or that were reachable through static references. Should these objects never be finalized? This would certainly be the easier/cleaner approach to take, but I'm not sure how I feel about leaving objects un-finalized. Sean

DO they actually get finalised at the moment? I seem to have noticed that a lot of destructors aren't run at program termination, unless fullCollect() is explicitly called.

Not in Tango right now, no. But I may at least perform a normal collection prior to the static dtors to clean up all the stuff that really is no longer being referenced. Sean
Jul 23 2007