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")
end
end
mutable struct B
B(a) = finalizer(new()) do b
cprintln("Begin finalizer B")
finalize(a)
cprintln("End finalizer B")
end
end
b = B(A())
b = nothing
Base.GC.gc()
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)
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")
end
mutable struct B; end
addfinalizer(b::B, a::A) = finalizer(a) do b
cprintln("In finalizer B")
end
a = A()
b = B()
addfinalizer(b, a)
addfinalizer(a)
a = b = nothing
Base.GC.gc()