So I’m wrapping a C library that has a Context type which owns the memory of many Expression types, where each Expression is reference counted. I want to call free and dec_ref on them via the finalizer, similar to how the python wrapper does it via __del__.
When both Context and some Expression are out of scope in Julia, the finalizers on them may be called in any order. However, calling dec_ref on Expression after calling free on Context will segfault, because freeing the context already deallocates the memory holding the expression.
To prevent the above, I need to make sure to never call dec_ref on Expression after calling free on Context, and also to make sure they are not called at the same time. For the latter I can use a lock, and the finalizers for the types will acquire the lock before freeing the memory. It’ll look like this:
mutable struct Context
ctx::C_context
finalized::Bool
lock::ReentrantLock
function Context(ctx::C_context)
c = new(ctx, false, ReentrantLock())
finalizer(finalize_ctx, c)
end
end
function finalize_ctx(c)
if islocked(c.lock) || !trylock(c.lock)
finalizer(finalize_ctx, c)
return nothing
end
try
c.finalized = true
C_del_context(c.ctx)
finally
unlock(c.lock)
end
end
mutable struct Expr <: AST
ctx::Context
ast::C_expr
function Expr(ctx::Context, ast::C_expr)
e = new(ctx, ast)
C_inc_ref(e.ctx, e.ast)
finalizer(finalize_expr, e)
end
end
function finalize_expr(e)
if !e.ctx.finalized # no need to free e if ctx is already freed
if islocked(e.ctx.lock) || !trylock(e.ctx.lock)
finalizer(finalize_expr, e)
return nothing
end
try
C_dec_ref(e.ctx, e.ast)
finally
unlock(e.ctx.lock)
end
end
end
Would this be OK?