The dangers of automatic GC mode

BlitzMax Forums/BlitzMax Programming/The dangers of automatic GC mode

JoshK(Posted 2009) [#1]
http://leadwerks.blogspot.com/2009/02/i-was-working-on-bullet-class-today-and.html


slenkar(Posted 2009) [#2]
you could have removed the bullet and mesh from all lists when it is destroyed in the game,
then the mesh just goes away by itself eventually

then the mesh is never tested for shadows or light emittance because it is not on any lists,
the bullet is not tested for collisions because it is not on any lists either


JoshK(Posted 2009) [#3]
The fact I didn't use a link for the temporary mesh update list does not matter. I could have gotten the same error even if the FreeEntity() method removed it from the list, because the bullet Delete() method might be called at any time.


Gabriel(Posted 2009) [#4]
It's isn't clear to me what relationship there is between Mesh and TBullet. I assume Mesh is derived from Entity, and that - in this instance - the Entity which TBullet holds as a field is a Mesh, but I'm not positive about that. However, it's also not clear how your bullet is going out of scope in the middle of looping through entities. Are you using the new threaded GC? Are these things going on in different threads?


JoshK(Posted 2009) [#5]
Your assumptions are correct.

I don't rely on GC to free entities themselves because their relationships with each other are far too complicated for the default GC. Parents reference children, children reference parents, each entity is stored in a global list, as well as scene graph lists.

The whole point of what I wrote is that the bullet ISN'T going out of scope during the entity loop. It goes out of scope at some point before that, and the GC just happens to collect it sometimes during that loop, causing the crash. But you don't really know when the GC will collect something.


REDi(Posted 2009) [#6]
Are you using the new threaded GC? Are these things going on in different threads?



JoshK(Posted 2009) [#7]
I am using BlitzMax version 1.30, so no threading or new GC.

It's not a bug. It makes sense it is working this way. But it is something weird I never really thought about before, and something to keep in mind when writing complex apps.

This can be totally avoided just by using manual GC mode.


Brucey(Posted 2009) [#8]
The bullet will never get "out of scope" if it is a Field of an existing object.

I've never used manual GC mode, and I believe I use some very complex circular relationships. Never been an issue.


JoshK(Posted 2009) [#9]
The mesh is a field of the bullet.


Otus(Posted 2009) [#10]
I don't rely on GC to free entities themselves because their relationships with each other are far too complicated for the default GC. Parents reference children, children reference parents, each entity is stored in a global list, as well as scene graph lists.

If the relationships get that complicated, it might be a good time to ask if it's necessary. Maybe it is, but sounds like there might be some unnecessary complexity in the design.

Other than that, the way to handle your problem might be another list for to-be-freed objects that gets emptied once a frame. That lets the GC free some memory in the middle of loops, but makes sure global lists maintain their state.


JoshK(Posted 2009) [#11]
Does no one understand the concept of what I posted?


Brucey(Posted 2009) [#12]
Apparently not :-)


Gabriel(Posted 2009) [#13]

The whole point of what I wrote is that the bullet ISN'T going out of scope during the entity loop. It goes out of scope at some point before that, and the GC just happens to collect it sometimes during that loop, causing the crash. But you don't really know when the GC will collect something.


That's what I thought initially, but if that was the case, it shouldn't be in any lists by the time it's collected. Why is the mesh still in the MeshUpdateList after the TBullet which "owns" it has gone out of scope? Surely when the bullet goes out of scope it removes it's mesh from the MeshUpdateList?


slenkar(Posted 2009) [#14]
yeah thats I thought too


JoshK(Posted 2009) [#15]
The point is that when the bullet goes out of scope, GC is not instantaneous. It does not occur the moment the bullet goes out of scope. It occurs at some random point in the program when the compiler feels like it.

I will try to make an example to demonstrate this.


JoshK(Posted 2009) [#16]
Here's an example:
SuperStrict

Framework brl.linkedlist
Import brl.math
Import brl.system
Import brl.standardio

Type TFoo
	
	Field thing:TThing
	
	Method New()
		thing=New TThing
	EndMethod	
	
	Method Delete()
		thing.Free()
		thing=Null
	EndMethod

EndType

Type TThing
	
	Global list:TList=New TList
	
	Field link:TLink
	
	Method New()
		link=list.addlast(Self)
	EndMethod
	
	Method Free()
		link.remove()
		link=Null
	EndMethod
	
	Method Update()
		Local n:Int
		For n=1 To 1000000
			Sqr(4534543)
		Next
	EndMethod
	
EndType


Local foo:TFoo
Local thing:TThing
Local n:Int
Local i:Int

For n=1 To 1000

	For i=1 To 100
		foo=New TFoo
	Next
	foo=Null
	
	For thing=EachIn TThing.list
		Print TThing.list.count()
		RuntimeError "This should not be happening"
	Next

Next



ziggy(Posted 2009) [#17]
The Delete method (as stated in documentation) is not instant and may even not be called for some objects (if the program ends, it does not call the Delete method for any pending collection. What one would call pending finalizers).
Maybe whe should need another method (like delete or new) called when the object goes out of the GC scope? Just a thouhgt...


ziggy(Posted 2009) [#18]
I've just cheked it, and now the documentation on the Delete method sais: Reserved for future expansion. So not sure how reliable this may be.


Kurator(Posted 2009) [#19]
quite interesting,

first i thought the static list is filled with "null" elements, but there aren't any "null" entries

[Edit1] I think ziggy is right, delete() is not executed all the time, modified your example - in 1.000.000 instances it is executed 999.834 times

[Edit2] If i put some delays inside, after 3000 ms the Delete is executed 1.000.000 times


Brucey(Posted 2009) [#20]
The Delete method ... may even not be called for some objects

It's always called when an object is GC'd. It isn't some random occurrence.

If you are talking about when the program ends, then sure, everything simply terminates, since the idea here is that you don't need to GC if in a moment nothing will exist.
If you need to Save state or something, you write an OnEnd and do something there.


ziggy(Posted 2009) [#21]
@Brucey: Obviously I was talking at program ending!


Brucey(Posted 2009) [#22]
Okay. Just making sure ;-)

One needs to be very specific in these forums, or people may misinterpret an answer.


JoshK(Posted 2009) [#23]
It's the automatic GC that has an element of randomness, not the Delete method itself. Just because an object goes out of scope does not mean the automatic GC will instantly collect it before the program performs anything else.

Again, this isn't an error or a problem, more just something to be aware of.


Brucey(Posted 2009) [#24]
Just because an object goes out of scope does not mean the automatic GC will instantly collect it before the program performs anything else.

That's right, and you design with that in mind.
I design my modules with that in mind. A framework frees an instance, I free the BlitzMax reference, knowing full well that at some point, my object will be GC'd, but once I know it's going, I don't care about it any more.
If you don't Free/unlink something which should be freed, you'll run into the issues you've run into.


slenkar(Posted 2009) [#25]
on your example what does link.remove() do?
I assume it doesnt remove the TThing from the global TThing list,

which is what I would do if wanted to get rid of it.


TaskMaster(Posted 2009) [#26]
Leadwerks, you are using the Delete method to remove another instance of an object. That is your mistake. The Delete method is called when the GC happens to clean the object, not immediately. Never expect the Delete method to clean up other objects for you in a timely manner.

If you want it to happen like that, then you are going to need a method in the Foo to call, to have it cleanup its Things, before you let it go out of scope.

This is not a bug, you are just using the Delete method to do something it is not designed to do. You want it to fire immediately, and it does not.


REDi(Posted 2009) [#27]
Calling GCCollect is best avoided as much a possible, its better IMHO to add a free method to the TFoo type... (modified from your example)



But if you really don't want the added api, then adding a GCCollect (before you examine the list) is the only solution.


dmaz(Posted 2009) [#28]
why do we keep rehashing this? this has been discussed many times before. don't use Delete()


TaskMaster(Posted 2009) [#29]
There is nothing wrong with using Delete, as long as you just use it to its limitations.


ziggy(Posted 2009) [#30]
In one application I used this dirty fix to 'emulate' the GC.Collect of .net, to ensure everything is collected and the Delete methods are called.
Function Collection(Generations:Int = 2000)
	Local MA:Int = GCMemAlloced()
	Local Iteration:Int = 0
	GCCollect()
	While ma <> GCMemAlloced() And iteration < Generations
		GCCollect()
		ma = GCMemAlloced()
		Iteration:+1
	Wend
End Function

It is very dirty, becouse, if the Delete method of objects generate the same amount of garbage that was available before the collection, this functino could leave pending object to be collected. It is likely to never happen, but you know...

This is just an idea, I've posted it here just to see if someone can come with a better solution. Obviously, the best way (the only way IMHO) to deal with this is desigining the application considerating how the max garbage collector works, instead of making this kind of dirty tweaks, but sometimes it is interesting to play arround...


plash(Posted 2009) [#31]
why do we keep rehashing this? this has been discussed many times before. don't use Delete()
There is nothing wrong with using Delete, as long as you just use it to its limitations.
Eeek! What are its exact limitations?


Brucey(Posted 2009) [#32]
The only limitation that I know of, is on program termination, the GC does not run through and collect everything, the program simply terminates.
That means, that if you rely on Delete() to store something, the chances are it would not get called when the program terminates.

Otherwise, Delete appears to work as expected - it is called when the Object is GC'd.
I use it extensively, everywhere, and have no issues with it.


plash(Posted 2009) [#33]
The only limitation that I know of, is on program termination, the GC does not run through and collect everything, the program simply terminates.
Oh, phew! ;)


TaskMaster(Posted 2009) [#34]
Right.

It may not be called if the program terminates.

And

It does not get called immediately when an object goes out of scope or is unreferenced. It is called when the GC cleans up the object.


dmaz(Posted 2009) [#35]
also, it only works as expected when it's actually GC'd. calling GCCollect() manually does not guarantee that an object *you* think should be GC'd will acutally be GC'd.


REDi(Posted 2009) [#36]
also, it only works as expected when it's actually GC'd. calling GCCollect() manually does not guarantee that an object *you* think should be GC'd will acutally be GC'd.

But if you Null out an object and call GCCollect it will always be collected.


dmaz(Posted 2009) [#37]
But if you Null out an object and call GCCollect it will always be collected.

no, that's not the case... again, this has been discussed at length. a simple search of the forum shows this. you can not count on GCCollect always collecting something that you think is ready for collection even though in most cases it will.

http://www.blitzbasic.com/Community/posts.php?topic=73386#820353
http://www.blitzmax.com/Community/posts.php?topic=63550#709423
and others...


REDi(Posted 2009) [#38]
I stand corrected ;)