AsyncFinalizers.jl
AsyncFinalizers
— ModuleAsyncFinalizers
AsyncFinalizers.jl extends finalizer
for
Allowing executing arbitrary code, including I/O, upon garbage collection of a given object.
Safe and unsafe APIs for avoiding escaping and thus "resurrecting" the object that would be collected otherwise.
For more information, see the documentation.
For how it works internally, see Implementation strategy.
API
AsyncFinalizers.onfinalize
: likefinalizer
but allows I/OAsyncFinalizers.unsafe_unwrap
: unwrap theshim
wrapper (see below)
Example
julia> using AsyncFinalizers
julia> mutable struct RefInt
value::Int
end
julia> object = RefInt(42);
julia> AsyncFinalizers.onfinalize(object) do shim
# Unpack `shim` of the finalized `object`. I/O is not allowed here.
value = shim.value
# Return a thunk:
return function ()
# Arbitrary I/O is possible here:
println("RefInt(", value, ") is finalized")
end
end;
julia> object = nothing
julia> GC.gc(); sleep(0.1)
RefInt(42) is finalized
Note that the callback passed to AsyncFinalizers.onfinalize
receives a shim
wrapper and not the original object
itself. To get the original object wrapped in shim
, use AsyncFinalizers.unsafe_unwrap
.
Implementation strategy
AsyncFinalizers.jl works internally by a background worker task that processes queued async finalizers (returned as thunks from the "on-finalize" callback registered using AsyncFinalizers.onfinalize
) and a queue with lock-free put!
called from the standard finalizer (the callback passed to Base.finalize
). Since put!
is lock-free in the "strict" sense (modulo GC), put!
called in the standard finalizer can always eventually make forward progress independent of the state of the worker task at which it encounters the GC safepoint.
AsyncFinalizers.onfinalize
— FunctionAsyncFinalizers.onfinalize(finalizer_factory, object)
Register asynchronous finalizer for an object
.
The callback function finalizer_factory
is a function with the following signature
finalizer_factory(shim) -> async_finalizer
i.e., finalizer_factory
is a function that takes shim
and returns async_finalizer
where shim
is an object that wraps the object
and provides the same properties and async_finalizer
is nothing
or a callable that does not take any argument. The shim
is valid only until finalizer_factory
returns and not inside async_finalizer
. As such, finalizer_factory
must destruct shim
and only capture the fields required for async_finalizer
.
Use AsyncFinalizers.unsafe_unwrap
to unwrap shim
and obtain the original object
. However, it is the user's responsibility to ensure that async_finalizer
does not capture object
.
The code executed in finalizer_factory
should be as minimal as possible. In particular, no I/O is allowed inside of finalizer_factory
.
AsyncFinalizers.unsafe_unwrap
— FunctionAsyncFinalizers.unsafe_unwrap(shim) -> object
Unwrap a shim
and obtain the original object. The user is responsible for ensuring that object
does not escape.