Finalizer and atexit for C library wrappers

I have created a module that wraps a large C library. I need to use object finalizers to run teardown functions from the C library for each Julia object I create that’s holding onto structs from the C library.

I addition: the C library needs a final teardown function called before the program exits. It seems natural to do this in an atexit() that’s registered in my module’s __init()__.

Unfortunately: atexit() methods are called before finalizers! This leads to a segfault in my module due to the finalizers calling into the torn-down C library.

What’s the right way to handle this in Julia? Am I going to need to have my own “object registry” that I manually go through and finalize() in my atexit() routine?

Thanks for any insights!

It’s possible to add post julia finalizer C atexit functions but the thing you can do in it will be very limited (after all, a lot of julia runtime are finalized at that point).

The simplest solution I can think of is to keep a reference count of the library and keep a global/module level pinning object. The refcount should be increased when you construct any object and decreased when the object is finalized (in the finalizer). With this, when the last object is finalized (note that this may not be the module level pinning object since the finalizing order is undefined) you can be sure that julia is done with the library and the library can be finalized.

Hmmm - I can see it… but that’s quite a bit of pain.

What about manually invoking GC during my atexit() callback?

At the point where atexit() is being called… all of these objects should be unreferenced and GCable. A manual GC call should run all of the finalizers I would think…

Let me try it…

Not necessarily. There can still be global references.

We’ve had to do something like this for the LibGit2 module in Base. See #20124 for the changes that were made.