Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How final are the finalizers? #623

Open
plajjan opened this issue Mar 16, 2024 · 2 comments
Open

How final are the finalizers? #623

plajjan opened this issue Mar 16, 2024 · 2 comments

Comments

@plajjan
Copy link
Contributor

plajjan commented Mar 16, 2024

I'd like to make use of the GC_finalizer function to run a cleanup function for my actors in the Acton programming language when they are being garbage collected. This is particularly useful for like IO actors that needs to close sockets and similar.

Acton uses libuv for IO and runs a worker thread per CPU core and a libuv loop per worker thread so we have many different libuv loops. Libuv is generally not thread safe, so all functions that operate on a particular socket associated with a particular libuv loop should also be run on the correct thread. We normally handle this through actor affinity, so a particular actor is pinned to a particular worker thread. Thus, the cleanup function I want to run from GC_finalizer isn't free to run wherever, it should be executed on a particular thread. How does the GC actually run finalizer functions? Which thread? Is the finalizer really final, like will the object be unconditionally collected after the finalizer function has finished running? I've been reading some of the docs but I haven't been able to answer this just yet, apologies if I've missed something obvious?

I'd really like to be able to instruct the GC if it's actually OK to collect an object or not so I get a chance of cleaning up. Something like this would be my ideal:

  • GC notices object (actor 42) have no references and can be collected, but it has finalizer set
  • thus GC runs finalizer function for actor 42's and it schedules actor 42's __cleanup__ method to run, running an actors method means placing a message on the message queue of actor 42 - and so we get a new reference to actor 42 via this message on the message queue (and the message queue is of course reachable from the root set), so all of a sudden actor 42 is not eligible for garbage collection any more
  • I don't want the GC to actually collect the object actor 42 at this point
  • RTS worker thread that owns actors 42 picks up the work from the actors message box and runs the __cleanup__ method
  • __cleanup__ function cleans up IO etc and then places a marker on actor 42, e.g. "cleanup_done=true"
  • the GC notices actor 42 object have no references and can be collected, since it has a finalizer, which it runs
  • the finalizer checks if cleanup_done==true and then lets the GC clean up the actor

Is something like this possible?

@ivmai
Copy link
Owner

ivmai commented Mar 17, 2024

/cc @hboehm

@plajjan
Copy link
Contributor Author

plajjan commented Mar 17, 2024

Another thought was to run the actual cleanup code in the finalizer. If the finalizer runs inside stop-the-world I guess the lack of thread safety might be OK with libuv since the normal libuv loops will be stopped.. But I don't trust the fully because I think the libuv threads might keep stuff on their stack so coming in and modifying the libuv loop structure by running socket cleanup from another thread during GC stop-the-world feels risky.

When are the finalizer functions run?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants