The dangers of automatic GC mode
BlitzMax Forums/BlitzMax Programming/The dangers of automatic GC mode
| ||
http://leadwerks.blogspot.com/2009/02/i-was-working-on-bullet-class-today-and.html |
| ||
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 |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
Are you using the new threaded GC? Are these things going on in different threads? |
| ||
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. |
| ||
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. |
| ||
The mesh is a field of the bullet. |
| ||
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. |
| ||
Does no one understand the concept of what I posted? |
| ||
Apparently not :-) |
| ||
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? |
| ||
yeah thats I thought too |
| ||
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. |
| ||
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 |
| ||
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... |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
@Brucey: Obviously I was talking at program ending! |
| ||
Okay. Just making sure ;-) One needs to be very specific in these forums, or people may misinterpret an answer. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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... |
| ||
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? |
| ||
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. |
| ||
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! ;) |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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... |
| ||
I stand corrected ;) |