# Acquiring locks in finalizers

**URL:** https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628
**Category:** General Usage
**Tags:** question, documentation, multithreading, garbage-collection
**Created:** [September 19, 2024, 11:08pm UTC](https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628 "2024-09-19T23:08:36Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![remysucre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/remysucre/32/209833_2.png) [@remysucre](https://discourse.julialang.org/u/remysucre)
#### Post date: [September 19, 2024, 11:08pm UTC](https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628/1 "2024-09-19T23:08:36Z")

</div>

The manual has an [example](https://docs.julialang.org/en/v1/manual/multi-threading/#Safe-use-of-Finalizers) with a finalizer that acquires a lock (without blocking). However, in the [Locks](https://docs.julialang.org/en/v1/devdocs/locks/#Locks) section it also says:

> The following are definitely leaf locks (level 1), and must not try to acquire any other lock:  
> …
> 
> - finalizers
> 
> …

So, is it actually safe for a finalizer to acquire locks?

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [September 20, 2024, 6:24am UTC](https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628/2 "2024-09-20T06:24:44Z")

</div>

The way this is done in the linked example (assuming you’re talking about point 2, where it talks about the Distributed stdlib) makes it ok in that very narrow context.

In general though, it’s not safe to acquire locks in a finalizer. Usually there’s room for a design that doesn’t require that too. Do you have an example where you need this?

---

<div class="post-metadata">

### Author: ![remysucre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/remysucre/32/209833_2.png) [@remysucre](https://discourse.julialang.org/u/remysucre)
#### Post date: [September 20, 2024, 4:09pm UTC](https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628/3 "2024-09-20T16:09:30Z")

</div>

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](https://github.com/Z3Prover/z3/blob/0c48a50d59f207e3f1dee38a02391fd21f038d5b/src/api/python/z3/z3.py#L350C1-L351C1) 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:

```julia
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?

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [September 20, 2024, 5:05pm UTC](https://discourse.julialang.org/t/acquiring-locks-in-finalizers/119628/4 "2024-09-20T17:05:56Z")

</div>

> [@Sukera](#):
>
> In general though, it’s not safe to acquire locks in a finalizer. Usually there’s room for a design that doesn’t require that too. Do you have an example where you need this?

In FFTW.jl, because destroying plans is not thread-safe, we jump through some hoops in the finalizer, which calls a function `maybe_destroy_plan`: [FFTW.jl/src/fft.jl at f888022d7a1ff78491abf8f33f1055cc52a68f0a · JuliaMath/FFTW.jl · GitHub](https://github.com/JuliaMath/FFTW.jl/blob/f888022d7a1ff78491abf8f33f1055cc52a68f0a/src/fft.jl#L351-L381)

My understanding is that you can use a `trylock` spinloop as long as you call `GC.safepoint()` in the loop [according to @jameson](https://github.com/JuliaMath/FFTW.jl/issues/163#issuecomment-852444447)?
