Multithreaded program hangs without explict GC.gc()

Coming in late here, but I think this is a textbook case of what the docs for GC.safepoint are talking about. The solution is to insert a safepoint within the loop in f_wait (not within test), as follows:

using Base.Threads
using ProgressBars

function f_wait(a, b)
    while !a[]
        GC.safepoint()
    end
    return a[] && b[]
end;

function test(n, x = Atomic{Bool}(false), y = Atomic{Bool}(false))
    for i in ProgressBar(1:n)
        x[] = y[] = false
        t_wx = @spawn f_wait(x, y);
        t_wy = @spawn f_wait(y, x);
        x[] = y[] = true

        wait.([t_wx, t_wy])
    end
    return true
end

test(100000)

In more detail: The problem is that f_wait has a potentially infinite loop with no allocations, IO, or task switches, hence no implicit GC safepoints, thus blocking GC for a potentially infinite time. Meanwhile, your test function performs an allocation when it creates the t_wy task, and this happens after t_wx has been scheduled, but before its termination condition x[] is set to true. Whenever this particular allocation triggers a GC run, you have a deadlock—the main thread is waiting for every other thread to reach a safepoint so the GC can do its sweep, while t_wx is waiting for the main thread to set x[] to true, never encountering a safepoint during the wait. The solution is to introduce a safepoint explicitly as shown.

3 Likes