# Doubly linked objects preventing garbage collection

**URL:** <https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326>\
**Category:** General Usage\
**Created:** [October 23, 2023, 7:22pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326 "2023-10-23T19:22:54Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![f.ij](https://avatars.discourse-cdn.com/v4/letter/f/8491ac/32.png) [@f.ij](https://discourse.julialang.org/u/f.ij)\
**Post date:** [October 23, 2023, 7:22pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/1 "2023-10-23T19:22:54Z")

</div>

I’m trying to manage some async tasks (usually timers) that correspond to structs I create. Sometimes I want to delete all my data and start over, which is when I want the timers to close. I thought it would be a good idea to create a finalizing function that closes the timers. In my program I’m using doubly linked objects since the ordering of my program is hierarchical. It seems that this somehow prevents the structs from getting garbage collected? This is an example:

```julia
abstract type AFoo end

mutable struct Foo <: AFoo
    i::Int
    t::Timer
    ref::ABar

    function Foo(i, t, ref)
        f = new(i, t, ref)
        finalizer(destructor, f)
        return f
    end
end

mutable struct Bar
    i::Int
    t::Timer
    ref::AFoo

    function Bar(i)
        tfoo = Timer((t) -> println("foo"), 0, interval = 2)
        tbar = Timer((t) -> println("bar"), 1, interval = 2)
        b = new(i)
        b.t = tbar
        b.ref = Foo(i, b, tfoo)
        finalizer(destructor, b)
        return b
    end
end

function destructor(f::Foo)
    close(f.t)
end

function destructor(b::Bar)
    close(b.t)
end

b = Bar(1)
b = nothing
GC.gc()

```

The tasks will keep printing.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [October 23, 2023, 8:29pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/2 "2023-10-23T20:29:19Z")

</div>

What version of Julia of are you running?

Your example currently does not execute as is. Here’s my attempted fix.

```julia
abstract type AFoo end
abstract type ABar end

mutable struct Foo <: AFoo
    i::Int
    t::Timer
    ref::ABar

    function Foo(i, t, ref)
        f = new(i, t, ref)
        finalizer(destructor, f)
        return f
    end
end

mutable struct Bar <: ABar
    i::Int
    t::Timer
    ref::AFoo

    function Bar(i)
        tfoo = Timer((t) -> println("foo"), 0, interval = 2)
        tbar = Timer((t) -> println("bar"), 1, interval = 2)
        b = new(i)
        b.t = tbar
        b.ref = Foo(i, tfoo, b)
        finalizer(destructor, b)
        return b
    end
end

function destructor(f::Foo)
    @async println("Destructing Foo")
    close(f.t)
end

function destructor(b::Bar)
    @async println("Destructing Bar")
    close(b.t)
end

b = Bar(1)
sleep(4)
b = nothing
GC.gc()
sleep(4)

```

If I put this into a file `test.jl` and then run `julia test.jl` I get the following output:

```julia
$ julia test.jl
foo
bar
foo
bar
foo
Destructing Bar
Destructing Foo

```

---

<div class="post-metadata">

**Author:** ![f.ij](https://avatars.discourse-cdn.com/v4/letter/f/8491ac/32.png) [@f.ij](https://discourse.julialang.org/u/f.ij)\
**Post date:** [October 24, 2023, 3:42pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/3 "2023-10-24T15:42:19Z")

</div>

Sorry I indeed changed some things before posting to make the code shorter and more readable, I should’ve tested it. Now testing it myself I cannot reproduce the problem anymore. Perhaps in my own program the problem lies somewhere else and I’m keeping a reference somewhere that I’m forgetting. Is there a way to check what objects hold references to some given object?

---

<div class="post-metadata">

**Author:** ![f.ij](https://avatars.discourse-cdn.com/v4/letter/f/8491ac/32.png) [@f.ij](https://discourse.julialang.org/u/f.ij)\
**Post date:** [October 24, 2023, 4:00pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/4 "2023-10-24T16:00:11Z")

</div>

Ah I see what happened. I first had the timer hold some reference to a field in `Bar`. This prevents the finalizer from being run and the task being closed, even if there are no available references to b anywhere else than in the task. The timers I’m using hold references to their structs, so I guess trying to close them using finalizers is just not the right way.

```julia
abstract type AFoo end
abstract type ABar end

mutable struct Foo <: AFoo
    i::Int
    t::Timer
    ref::ABar

    function Foo(i, t, ref)
        f = new(i, t, ref)
        finalizer(destructor, f)
        return f
    end
end

mutable struct Bar <: ABar
    i::Int
    t::Timer
    ref::AFoo

    function Bar(i)
        tfoo = Timer((t) -> println("foo"), 0, interval = 2)
       
        b = new(i)

        tbar = Timer((t) -> println("bar $(b.i)"), 1, interval = 2)
        b.t = tbar
        b.ref = Foo(i, tfoo, b)
        finalizer(destructor, b)
        return b
    end
end

function destructor(f::Foo)
    @async println("Destructing Foo")
    close(f.t)
end

function destructor(b::Bar)
    @async println("Destructing Bar")
    close(b.t)
end

b = Bar(1)
sleep(4)
b = nothing
GC.gc()
sleep(4)

```

Here the finalizers aren’t run.

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [October 24, 2023, 4:05pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/5 "2023-10-24T16:05:11Z")

</div>

You can try again with `GC.gc(true)` to force a full garbage collection. But generally you should never rely on finalizers to run. Recent thread on a similar topic:

> [@Can I mimick finalizing for immutables / is an immutable file handler unfeasible?](https://discourse.julialang.org/t/can-i-mimick-finalizing-for-immutables-is-an-immutable-file-handler-unfeasible/104687/1):
>
> I can’t think of other uses for destructors/finalizers in a language without manual deallocations, so I’m using file-closing as an example. It seems to me that I could implement open to open a file and construct an immutable file descriptor, then implement a close to use the file descriptor to close the file. However, my concern is that the file descriptor isn’t handled by the garbage collector, so unlike the typical IOStreams with finalizer(close, x), the file won’t be automatically closed when…

Memory issue can also arise from different things. Julia 1.9 changed GC heuristics and as a result many people experience memory issues. If you haven’t already you should give Julia [a hint with --heap-size-hint](https://github.com/JuliaLang/julia/blob/bed2cd540a11544ed4be381d471bbf590f0b745e/NEWS.md?plain=1#L50C51-L50C51) and see if things improve.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [October 24, 2023, 10:52pm UTC](https://discourse.julialang.org/t/doubly-linked-objects-preventing-garbage-collection/105326/6 "2023-10-24T22:52:29Z")

</div>

> [@abraemer](#):
>
> You can try again with `GC.gc(true)` to force a full garbage collection.

`GC.gc()` by default does `GC.gc(true)`, so OP is doing that already. You’re right it’s probably a bad idea to rely on finalizers to wrap anything up besides freeing the parts of memory the GC can’t track directly. In fact, the more efficiently you allocate, the more delayed the automatic GC runs and finalizers. Manually running `GC.gc()` often enough to undo that delay will ruin performance.

> [@f.ij](#):
>
> Ah I see what happened. I first had the timer hold some reference to a field in `Bar`. This prevents the finalizer from being closed, even if there is not available reference elsewhere to `b`. The timers I’m using hold references to their structs, so I guess this is not a valid way of doing it.

~~I’m a little puzzled actually, if `b=nothing` removes all live references to the mutable `Foo`, `Bar`, and `Timer` instances, shouldn’t those be collected? GC is supposed to handle circular references too. Is this some tasks rule that I should know?~~ Never mind I just checked and reread the post, the scheduled `Timer`s kept running even in the first MWE where the issue doesn’t occur. It’s simply that scheduled tasks don’t need a variable in the main thread to keep them live; for example, `@async while true println("foo"); sleep(1) end` runs forever, and it’s not assigned to a variable to begin with. Circular references are only collected if none of them are live, so it’s bad to make a task one of them.
