Calling Finalize from Running Finalizer

I haven’t found it in the documentation, so I wanted to have it confirmed/documented:

Assume you have two objects, both having a finalizer. One finalizer needs to be called before the other, because there is a dependency. Then you can’t simply call finalize of the other object in the finalizer. Is this correct?

This can probably best explained with an example:

cprintln(str) = ccall(:jl_safe_printf, Cvoid, (Cstring, ), str * "\n")

mutable struct A
    A() = finalizer(new()) do a
        cprintln("In finalizer A")

mutable struct B
    B(a) = finalizer(new()) do b
        cprintln("Begin finalizer B")
        cprintln("End finalizer B")

b = B(A())
b = nothing

results in

Begin finalizer B
End finalizer B
In finalizer A

So I assume the documentation for finalize is incorrect, because instead of

Immediately run finalizers registered for object x.

it should be probably something like (just guessing, please enter the correct wording):

Registers to run finalizers registered for object x as soon as possible (often immediately, but sometimes delayed for example while already running in another finalizer)

Several thoughts:

  1. You might not want to rely on GC to finalize.
  2. To delay finalization, you can reinstall the finalizer in finalize.
1 Like

If I remember correctly when we locate a group of unreachable objects we “detach” the finalizers and copy them into a separate list. Then we run all the finalizers in that list.

So when you call finalize(a) inside the finalizer there is no finalizer attached anymore and thus none to execute.

1 Like

Thanks, that explains the behavior. What wording do you recommend for the documentation to reflect that?

Thanks, I introduced a do-block syntax so that the finalizers only act as a (probably unnecessary) safeguard. Maybe I’ll get rid of them in the future, because they seem to be not without problems.

Meanwhile I registered both finalizers for the same object. Finalizers of different objects race (calling order is non-deterministic), while having multiple finalizers for the same object leads to deterministic behavior: The finalizer defined last will be called first which is what you normally want when freeing resources.

Coming from statically typed languages having an object of type B registering as a finalizer on an object of type A seems to be counter-intuitive, but works quite well (for a finalizer). This probably only makes sense if the lifetime of the objects is really coupled, which is the case in my real problem.

This would be the example to ensure that finalizer A is always running before finalizer B:

cprintln(str) = ccall(:jl_safe_printf, Cvoid, (Cstring, ), str * "\n")

mutable struct A; end
addfinalizer(a::A) = finalizer(a) do a
    cprintln("In finalizer A")

mutable struct B; end
addfinalizer(b::B, a::A) = finalizer(a) do b
    cprintln("In finalizer B")

a = A()
b = B()
addfinalizer(b, a)

a = b = nothing