Acquiring locks in finalizers

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?