www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - another gc question

reply Toaster <wb sapo.pt> writes:
Hello,

I have a top-to-bottom structure and I need to make sure, when one
object is deleted, the object will delete all objects downstream in
the structure. I implemented this kind of linked list:


class Top {

	Middle list;

	~this()
	{
		while(list) {
			Middle m = list;
			list = null;
			delete m;
		}
	}
}

class Middle {
	
	Top parent;

	Middle next;
	Middle previous;
	
	Bottom list;

	this(Top parent)
	{
		this.parent = parent;
		next = parent.list;
		if(next) next.previous = this;
		parent.list = this;
	}

	~this()
	{
		while(list) {
			Bottom b = list;
			list = null;
			delete b;
		}

		if(previous) {
			previous.next = next;
			if(next) next.previous = previous;
		} else {
			parent.list = next;
			if(next) next.previous = null;
		}
	}
}



class Bottom {
	
	Middle  parent;
	
	Bottom next;
	Bottom previous;
	
	this(Middle parent)
	{
		this.parent = parent;
		next = parent.list;
		if(next) next.previous = this;
		parent.list = this;
	}

	~this()
	{
		if(previous) {
			previous.next = next;
			if(next) next.previous = previous;
		} else {
			parent.list = next;
			if(next) next.previous = null;
		}
	}
}


I hope this makes sure for:

- No Middle objects and Bottom objects are collectable as long as
their Top object has a valid reference because they all have
references in the parent's list

- With the Top object out of scope, when the gc kicks in it picks one
unpredictable object out of the structure and calls it's destructor
(that's how I understood it), the destructor will delete recursively
the whole part of the structure that depends on it, then it removes
itself from the parent's list. If the gc collects the Top object
first, the whole structure is gone.

I tried it out and appearently it works. I am still asking because I
am not aware of the internal workings of «delete» and the gc. For
instance, would there be any problem for the gc if a destructor
deletes manually an object the gc has flagged collectable before
calling that destructor? Any comments would be appreciated.
Sep 15 2004
next sibling parent "Ben Hinkle" <bhinkle mathworks.com> writes:
"Toaster" <wb sapo.pt> wrote in message
news:phdgk0h6akqeuu33lfkc0jnn40k67q761m 4ax.com...
 Hello,

 I have a top-to-bottom structure and I need to make sure, when one
 object is deleted, the object will delete all objects downstream in
 the structure. I implemented this kind of linked list:


 class Top {

 Middle list;

 ~this()
 {
 while(list) {
 Middle m = list;
 list = null;
 delete m;
 }
 }
 }

 class Middle {

 Top parent;

 Middle next;
 Middle previous;

 Bottom list;

 this(Top parent)
 {
 this.parent = parent;
 next = parent.list;
 if(next) next.previous = this;
 parent.list = this;
 }

 ~this()
 {
 while(list) {
 Bottom b = list;
 list = null;
 delete b;
 }

 if(previous) {
 previous.next = next;
 if(next) next.previous = previous;
 } else {
 parent.list = next;
 if(next) next.previous = null;
 }
 }
 }



 class Bottom {

 Middle  parent;

 Bottom next;
 Bottom previous;

 this(Middle parent)
 {
 this.parent = parent;
 next = parent.list;
 if(next) next.previous = this;
 parent.list = this;
 }

 ~this()
 {
 if(previous) {
 previous.next = next;
 if(next) next.previous = previous;
 } else {
 parent.list = next;
 if(next) next.previous = null;
 }
 }
 }


 I hope this makes sure for:

 - No Middle objects and Bottom objects are collectable as long as
 their Top object has a valid reference because they all have
 references in the parent's list

 - With the Top object out of scope, when the gc kicks in it picks one
 unpredictable object out of the structure and calls it's destructor
 (that's how I understood it), the destructor will delete recursively
 the whole part of the structure that depends on it, then it removes
 itself from the parent's list. If the gc collects the Top object
 first, the whole structure is gone.

 I tried it out and appearently it works. I am still asking because I
 am not aware of the internal workings of «delete» and the gc. For
 instance, would there be any problem for the gc if a destructor
 deletes manually an object the gc has flagged collectable before
 calling that destructor? Any comments would be appreciated.

I don't understand the motivation for worrying about this. I wouldn't have any destructors at all. If the list becomes garbage all the nodes will be collected in due time - not just one randomly chosen node. The GC calls destructors on anything about to be collected and the random part is that the nodes get collected in random order.
Sep 15 2004
prev sibling parent reply Arcane Jill <Arcane_member pathlink.com> writes:
In article <phdgk0h6akqeuu33lfkc0jnn40k67q761m 4ax.com>, Toaster says...
Hello,

I have a top-to-bottom structure and I need to make sure, when one
object is deleted, the object will delete all objects downstream in
the structure. 

I realise this is a really dumb question, but ... have you tried just not bothering to delete anything at all, ever? I only ask because deletion seems to be a rare requirement in D. Jill
Sep 15 2004
parent reply Toaster <wb sapo.pt> writes:
I realise this is a really dumb question, but ... have you tried just not
bothering to delete anything at all, ever? I only ask because deletion seems to
be a rare requirement in D.

Sorry, I forgot about the purpose of this: the real objects holds windows handles, and the destructor of each class calls the API to free that handle. But, if the Top object's handle is freed by the API call, all Middle and Bottom handles get invalidated. So, any call to a Middle destructor AFTER a call to it's parent Top destructor means "Access violation" because the API doesn't check if the handle is valid. So I need to make sure that a Bottom destructor never gets called after it's parents have been destroyed. I do this by making sure that the Top or Middle destructors delete ALL dependent destructors before freeing their own handle. So the middle constructor would look like this: ~this() { while(list) { Bottom b = list; list = null; delete b; /* child destructors free all child handles */ } freeHandle(); /* it's safe to free the own handle now */ if(previous) { previous.next = next; if(next) next.previous = previous; } else { parent.list = next; if(next) next.previous = null; } }
Sep 15 2004
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Toaster wrote:
<snip>
 Sorry, I forgot about the purpose of this:  the real objects holds
 windows handles, and the destructor of each class calls the API to
 free that handle. But, if the Top object's handle is freed by the API
 call, all Middle and Bottom handles get invalidated.

Surely, if the handles are invalidated then Windows has automatically freed them at that point. What are they handles of, for that matter?
 So, any call to a
 Middle destructor AFTER a call to it's parent Top destructor means
 "Access violation" because the API doesn't check if the handle is
 valid. 
 So I need to make sure that a Bottom destructor never gets called
 after it's parents have been destroyed. I do this by making sure that
 the Top or Middle destructors delete ALL dependent destructors before
 freeing their own handle.

A possibility I can see is to use std.gc.addRange and removeRange to pin down each object's parent. Another is to do all freeing of handles in the Top destructor, and do away with the others. Stewart. -- My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Sep 15 2004
parent reply Toaster <wb sapo.pt> writes:
On Wed, 15 Sep 2004 16:28:38 +0100, Stewart Gordon
<smjg_1998 yahoo.com> wrote:

Surely, if the handles are invalidated then Windows has automatically 
freed them at that point.  What are they handles of, for that matter?

they are odbc handles henv, hdbc, hstmt
A possibility I can see is to use std.gc.addRange and removeRange to pin 
down each object's parent.

Another is to do all freeing of handles in the Top destructor, and do 
away with the others.

I would like to stick with that implementation because besides freeing all handles in the correct order by gc it allows for this: Top top = new Top; Middle m1 = new Middle(top); Middle m2 = new Middle(top); Bottom b1_1 = new Bottom(middle); /* create a whole bunch of objects and their associated handles and work with them */ delete m4; /* delete m4 and all b4_* objects and free their handles in the correct order */ delete top; /*delete all objects and free all handles in the correct order */ /* all objects are deleted, but after deleting top I don't need them anyway. */ My only concern is if some day before release 1.0 the manual won't say "delete cannot be used from inside a class destructor".
Sep 15 2004
next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Toaster wrote:

<snip>
 they are odbc handles henv, hdbc, hstmt

Well, I know practically nothing about ODBC, so I can't really comment. <snip>
 I would like to stick with that implementation because besides freeing
 all handles in the correct order by gc it allows for this:

 My only concern is if some day before release 1.0 the manual won't say
 "delete cannot be used from inside a class destructor".

To be honest, I'm not sure about your use of delete. I don't know if the GC is allowed to queue objects for destruction, to the effect that delete on an already queued object would have no effect. Possibly better would be to create a .dispose() method in each class, which would call .dispose on the child objects, free its own handle and then set a 'disposed' flag. Each class's destructor would, of course, call its own .dispose. Of course, you'd check the flag - whether you'd do this in .dispose or in the destructor would depend depend on whether you want a slight increase in efficiency or protection against disposing something twice. Stewart. -- My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Sep 15 2004
parent Toaster <wb sapo.pt> writes:
On Wed, 15 Sep 2004 18:02:59 +0100, Stewart Gordon
<smjg_1998 yahoo.com> wrote:


Possibly better would be to create a .dispose() method in each class, 
which would call .dispose on the child objects, free its own handle and 
then set a 'disposed' flag.  Each class's destructor would, of course, 
call its own .dispose.  Of course, you'd check the flag - whether you'd 
do this in .dispose or in the destructor would depend depend on whether 
you want a slight increase in efficiency or protection against disposing 
something twice.

I see what you mean, you can propagate downstream in another function that just frees the handle, that would be the best choice IF delete is not allowed in destructors. If delete can be used in destructors, then there's no point of doing so: You would have to do the checks you described and clutter up the code; the lists would have to be maintained anyway, and the objects sit waiting for collection. I still want to stick to the implementation: - I know, if the object exists, the handle is still valid. On bug, instead of a reference to an object with a handle invalidated by the parent, I have a reference to a deleted object and get it debugged at once. - I don't want to write more code if I don't have to. Currently I delete one object and this deletes a whole tree of objects rendered useless (not just the handles), giving back the memory to the gc that can use it right away for new objects without having to run a collection. Isn't that an advantage? Since I found nothing in documentation about delete not being allowed in destructors, I keep it for now. I just hoped somebody could tell me for sure.
Sep 15 2004
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
In article <6gpgk0difrkkrlr0djhkkakjegdme8hfha 4ax.com>, Toaster says...
On Wed, 15 Sep 2004 16:28:38 +0100, Stewart Gordon
<smjg_1998 yahoo.com> wrote:
Surely, if the handles are invalidated then Windows has automatically 
freed them at that point.  What are they handles of, for that matter?

they are odbc handles henv, hdbc, hstmt

Why allow the destruction of a connection object to invalidate all query objects open through that connection? I would be more inclined to do something like this: # class ConnHandle { # public: # this( HDBC h ) { handle = h; } # ~this() { CloseHandle( handle ); } # HDBC handle; # } # # class Statement { # public: # this( ConnHandle h ) { conn = h; } # private: # ConnHandle conn; # } # # class Connection { # public: # this( ... ) { handle = new ConnHandle( ... ); } # Statement open() { return new Statement( handle ); } # private: # ConnHandle handle; # } So the connection object can be deleted, but the connection itself is held open until all statements using that connection are deleted as well. This may mean connections lingering for an indeterminate amount of time, but this shouldn't be a serious issue. The alternative would be to have ConnHandle be reference counted, but the basic design would be the same. Sean
Sep 15 2004
parent reply Toaster <wb sapo.pt> writes:
On Wed, 15 Sep 2004 18:57:31 +0000 (UTC), Sean Kelly <sean f4.ca>
wrote:

Why allow the destruction of a connection object to invalidate all query objects
open through that connection?  I would be more inclined to do something like
this:

I don't do that. You can invalidate any query (bottom) object without invalidating the connection (middle) object. To be specific, if a connection object's destructor is called, it first disconnect()s from the data source, then it frees the hdbc. If I don't finish and free the hstmt's prior to that, the disconnect() may or may not be sucessful; if not the hdbc stays connected even after destruction of it's container object, probably forever until the program exits. But by disconnecting and freeing the hdbc, all derived hstmt handles become invalid. Now, if the gc destructs the connection object first, then the result object, the result object's hstmt will have been invalidated by the API - access violation. This can't happen if I delete all result objects prior, getting all hstmt freed, and free the hdbc then. I think it's appropriate to do so, I just copy the behaviour of the API to the classes: closing a connection renders all statements useless.
Sep 15 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <9j6hk0ppqjpisak9pepv5cq5t09ad8nfac 4ax.com>, Toaster says...
On Wed, 15 Sep 2004 18:57:31 +0000 (UTC), Sean Kelly <sean f4.ca>
wrote:

Why allow the destruction of a connection object to invalidate all query objects
open through that connection?  I would be more inclined to do something like
this:

I don't do that. You can invalidate any query (bottom) object without invalidating the connection (middle) object. To be specific, if a connection object's destructor is called, it first disconnect()s from the data source, then it frees the hdbc. If I don't finish and free the hstmt's prior to that, the disconnect() may or may not be sucessful; if not the hdbc stays connected even after destruction of it's container object, probably forever until the program exits. But by disconnecting and freeing the hdbc, all derived hstmt handles become invalid. Now, if the gc destructs the connection object first, then the result object, the result object's hstmt will have been invalidated by the API - access violation. This can't happen if I delete all result objects prior, getting all hstmt freed, and free the hdbc then. I think it's appropriate to do so, I just copy the behaviour of the API to the classes: closing a connection renders all statements useless.

What I was suggesting was just to have the connection handle managed separately from the connection object itself. Though I suppose these are different approaches. You want any statements using a connection to be closed and (I assume) exist as skeletons if the connection is closed--perhaps throwing a ConnectionClosed exception if the user tries to manipulate the statement--while I want the connection to remain open so long as there are any statements using it, whether or not the connection object hsa been deleted. I'm not sure that either is any more correct than the other, though I know which I prefer :) Sean
Sep 15 2004
parent Toaster <wb sapo.pt> writes:
On Wed, 15 Sep 2004 21:10:48 +0000 (UTC), Sean Kelly <sean f4.ca>
wrote:

What I was suggesting was just to have the connection handle managed separately
from the connection object itself.  Though I suppose these are different
approaches.  You want any statements using a connection to be closed and (I
assume) exist as skeletons if the connection is closed--perhaps throwing a
ConnectionClosed exception if the user tries to manipulate the statement--while
I want the connection to remain open so long as there are any statements using
it, whether or not the connection object hsa been deleted.  I'm not sure that
either is any more correct than the other, though I know which I prefer :)


Sean

This would be another way, but it's not what I want. Whenever the code closes a connection or frees an environment, I want all depending objects to disappear. So the user doesn't have to figure out why his broken object doesn't work - there IS no broken object, just an invalid reference, it's that simple. I think the user can check for that in D, and what I have seen from my own bugs is that D returns the access violation in an exception. Anyway,in this case this is not a runtime error in a finished application, usually this is an implementation error and no checking should be necessary in the debugged final code. So you don't have to implement in each and every method of the object the "if the connection is closed then don't call the API, throw an exception instead". And the user doesn't call any bottom.isTheParentStillAlive() functions, he just does assert(bottom); that's all.
Sep 15 2004