Problem with finalizers

I’m currently having some trouble with a finalizer cleaning up an object that is needed by another finalizer.

I have two objects, both of which contain pointers to some C object, one of which is a “context object” for the other (contains some information needed to clean the other up).

The first object R has a type that looks like this:

type SingularPolyRing
   ptr::some_c_thing1
end

The second object p say, has a type that looks like this:

type spoly
   ptr::some_c_thing2
   parent::SingularPolyRing

   function spoly(R::SingularPolyRing, p::some_c_thing2)
      z = new(p, R)
      finalizer(z, _spoly_cleanup)
      return z
   end
end

(Here _spoly_cleanup(z) calls some C function to clean up.)

Both objects have finalizers, but when julia is shut down, the finalizer for the first object R is always called before that of the second, even though the second object p contains a reference to the first object R!

I tried writing the finalizer for the spoly p as follows:

   function spoly(R::SingularPolyRing, p::some_c_thing2)
      z = new(some_c_thing2, R)
      finalizer(z, x -> _spoly_clear_fn(x, R))
      return z
   end

The reasoning is that surely the lambda/closure hangs onto a copy of R, preventing it from being cleaned up first. But alas, R is still cleaned up first, before _spoly_clear_fn gets called. I’ve verified this by putting print statements in the relevant C functions.

Is there a canonical way of preventing Julia from cleaning up an object that is needed to clean up another object?

Finalizers can be called at any time after the object is dead in any order.

Sure. But surely the object isn’t dead if something still has a reference to it.

Not if the one referencing it is another dead object.

So the solution seems to be that I need to maintain another reference to R somewhere. Presumably p is cleaned up because the REPL or some internal symbol table no longer has a reference to it.

Is there somewhere I can put R so that it is cleaned up after all the other global symbols, e.g. in Base or something like that?

No. It’s unclear what exactly is the behavior your need but maintaining a refcount manually can be a solution if that’s what the C library want.

On the C side, p is basically a tagged union. R is a context object that contains a lookup table of function pointers for all objects that are tagged unions like p. The “generic” delete function for p requires you to pass both p and R so that it can look up which delete function it is supposed to be calling to clean up p. It looks at the type tag for p to determine which function needs to be called.

Some libraries would just maintain a pointer to R inside p itself, but for extremely performance critical stuff, where memory usage is really important, p can’t hold a pointer to R and so you have to pass both p and R to delete. That means on the Julia side, the object R is needed to clean up p, which means you can’t clean up R first.

Anyway, I’ll see if I can find some information about maintaining refcounts. Perhaps a solution will suggest itself.

Just Googling refcount Julia doesn’t seem to be turning up anything relevant. Is the idea that you can somehow assign a refcount to finalized objects? Where would I find this?

It’s ok, I think I figured out what you mean. You meant to manually maintain a refcount to R from all objects p that depend on it. All the finalizer for R would do is decrease the refcount. And only when there are really zero references would the actual C delete function be called.

That should work ok, I think. I’ll give it a try.

Correct. <20 characters limit …>

This works fine. Thanks for the suggestion.

Just to document for others who find the thread, here is what I did:

  • Inside the Julia type for R I added an additional field “refcount”, which is initially set to 1.
  • The finaliser for R decrements the refcount field and if the refcount == 0 it calls the C delete function.
  • The finaliser for p first cleans up p by calling the relevant C delete function, then it calls the finaliser for R directly (which as mentioned decrements the refcount by one more and checks if it is time to clean up R).

I don’t mean to horn in here, but I think this could cause a problem if you have more than one reference to an R running around. Say, like the following
x = R() y=x

Is the reference count incremented at the y=x call? If not, couldn’t you end up with x being garbage-collected out from under you?

I think that’s ok. The finalizer for R can’t get called by the gc itself unless there are no references to it. That’s why the refcount starts at 1 and not zero. That final refcount is for R itself. In your example, only when x and y both die is the finalizer for the actual object called by the gc.

I checked that this seems to not be a problem, anyway.

We actually have to keep a global dictionary of all R’s anyway (since we need the R’s to be singletons), so the only way the final reference to R can be zeroed is if the dictionary gets cleaned up, which can only happen in our case upon exit from Julia. Still, I don’t think this is a problem even without the dictionary.