Jump to content
  • Advertisement
Sign in to follow this  
Bacterius

Lua, collecting garbage on exception?

This topic is 1001 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

 

In our project we use Lua as application code which calls into modules implemented either in Lua or in a lower level language. This works very well for us, but throughout the project we have had some issues dealing with Lua errors robustly. Because our Lua scripts are application-level logic, they routinely manipulate userdata provided by the lower-level modules; many of these structures hold system resources and need to be quickly finalized after we are done with them. We have close() functions in our code to this end, but, of course, if a Lua error is thrown, this doesn't happen until the garbage collector decides to run (we have correct __gc metamethods) which may be never since the default GC strategy is to only collect when a certain amount of memory is used. This is, naturally, unacceptable.

 

We don't abort on error because our Lua scripts run an event loop; we catch Lua exception at the callback boundary, log it, and usually resume the loop afterwards; the program is terminated only in the case of a catastrophic error, or if something goes wrong before starting the event loop (e.g. parsing a config file, ...).

 

So a few weeks ago I had a seemingly simple but apparently effective idea: override pcall with an implementation which, in case of an error, triggers the garbage collector; as far as I can tell this provides very strong guarantees about the finalization of resources acquired within a failed Lua chunk; since we don't use globals, the only accidental way for these resources to escape finalization is for them to be referenced in upvalues or table/userdata parameters of that chunk before it fails, which is easy to, like, not do. Just keep them in locals and either return them as you would normally, or lift them outside the chunk through its upvalues at the very end.

 

The problem I can't seem to find any references to this pattern; are there any downsides to this? Because as far as I can tell this is a free lunch:

 

 - trivial to implement: override pcall, add lua_gc() calls on error. done

 - ensures that all failed resources are immediately finalized in case of failure

 - has zero overhead (and if no error occurs, our normal code path does the cleanup by itself)

 

Any thoughts? Any obvious defect I overlooked? Has anyone seen and/or done this before, and did it work for you?

 

Thanks!

 

EDIT: actually this isn't quite true; I did find exactly 2 references to this pattern, one on the Lua website (supposedly the vanilla Lua interpreter does the same thing in interactive mode when user input fails for some reason, but I can't find the code that does it in the Lua 5.3 distribution so maybe it doesn't do it anymore) and here: http://lua-users.org/lists/lua-l/2009-02/msg00191.html but it doesn't go into a lot of detail.

Edited by Bacterius

Share this post


Link to post
Share on other sites
Advertisement


- trivial to implement: override pcall, add lua_gc() calls on error. done

You don't override pcall. You just call it instead of lua_call. Exception will be caught, and error returned on lua stack.

 


- has zero overhead (and if no error occurs, our normal code path does the cleanup by itself)

Since you're running it in a loop, errors reported by pcall should only raise error flag.

It will be checked and reset (with lua_gc called) only once, right before main loop next iteration.

I doubt it's life-critical to release resources instantly. And sometimes it's not even possible at all, if failed coroutine involved, as mentioned in that thread you found on lua-l.

Share this post


Link to post
Share on other sites
vstrakh, I don't think you are getting Bacterius completely. It is entirely possible that a Lua object consumes only a tiny amount of Lua memory while consuming lots of application resources (for examples because it represents a large texture living on the GPU or a database). These resources can remain in garbage collector limbo for a long time if no extra action is taken.

Bacterius: it certainly sounds reasonable to me. In a game-centric situation I would consider not doing it with every pcall and just setting an error flag. A garbage collection could then be triggered at a convenient time (the next level loads, the user just paused the game, ...) but maybe profiling shows it's not even necessary.

Share this post


Link to post
Share on other sites


These resources can remain in garbage collector limbo for a long time if no extra action is taken.

I'm aware of that, and I didn't propose to ignore that.

And I think it's a bad practice to allow code to "crash" at undefined point, while simultaneously requiring that every object released that instant.

Share this post


Link to post
Share on other sites
But Bacterius does not necessarily want them to release 'that instant'. He simply wants to avoid releasing them at an arbitrary point in time (up to 'never', depending on how much Lua memory his event loop actually allocates during normal runs).

That said I do not find releasing resources at the point of an error to be unreasonable. After all, RAII in C++ does exactly that. That behavior is just not free out of the box in Lua, though what Bacterius plans should emulate it closely enough.

Share this post


Link to post
Share on other sites
We had a lot of trouble with a similar system a few years back, with native resources being kept alive by the Java garbage collector. It sounds like you have a little more control over the Lua garbage collector than we had in Java, but in the end we had to build out an entirely separate 'resource collector' to augment the garbage collector where native resources were referenced from Java.

Share this post


Link to post
Share on other sites

 

- trivial to implement: override pcall, add lua_gc() calls on error. done

You don't override pcall. You just call it instead of lua_call. Exception will be caught, and error returned on lua stack.
 
 
 

- has zero overhead (and if no error occurs, our normal code path does the cleanup by itself)

Since you're running it in a loop, errors reported by pcall should only raise error flag.
It will be checked and reset (with lua_gc called) only once, right before main loop next iteration.
I doubt it's life-critical to release resources instantly. And sometimes it's not even possible at all, if failed coroutine involved, as mentioned in that thread you found on lua-l.

 

 
Yes, I know how to use pcall; the idea is to "build in" this functionality into pcall so that Lua scripts can just use pcall transparently, instead of having duplicate error handling code on every pcall. We have several different callbacks each with their own pcall to wrap the Lua implementation of the callback (we are using libuv).

 

 

 


That said I do not find releasing resources at the point of an error to be unreasonable. After all, RAII in C++ does exactly that. That behavior is just not free out of the box in Lua, though what Bacterius plans should emulate it closely enough.

 

The original goal was exactly to emulate RAII; this is the best solution I have found so far that doesn't require manually tracking resources, invasive patching of the Lua distribution or other miscellaneous hacks.

 

 

 


We had a lot of trouble with a similar system a few years back, with native resources being kept alive by the Java garbage collector. It sounds like you have a little more control over the Lua garbage collector than we had in Java, but in the end we had to build out an entirely separate 'resource collector' to augment the garbage collector where native resources were referenced from Java.

 

I was fearing I would need to set something like this up but according to the Lua docs the garbage collector does in fact guarantee that calling it will free all dangling resources, so that should be enough control. But yeah if it's not enough or if it turns out a full garbage-collection cycle is problematic we could implement a GC only for our resources, though this would be more work and require cooperation from the modules that allocate the resources...

 


Bacterius: it certainly sounds reasonable to me. In a game-centric situation I would consider not doing it with every pcall and just setting an error flag. A garbage collection could then be triggered at a convenient time (the next level loads, the user just paused the game, ...) but maybe profiling shows it's not even necessary.

 

This is not for a game so we don't have any hard timing constraints, so we can afford to do pretty much whatever during exceptions! But yeah I definitely agree for a game you'd want to be careful with gratuitously calling the garbage collector like that. Although I haven't profiled yet but the overhead should be minimal with an incremental GC... maybe.

Edited by Bacterius

Share this post


Link to post
Share on other sites
In a game, incremental is a double edged sword. You might notice that a full collection takes too long, so you make it incremental and reduce the amount it collects per frae until you're within your frametime budget again. But then you might notice that on average, the amount being collected per frame is lower than the amount generated per frame, and now you eat up all the RAM and crash :lol:
In a game, the solution is to maniacally avoid generating garbage in the first place :(

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!