Hanging when playing around with tasks

I was playing around with tasks and gathering return values from tasks that are opened and closed at will by a user. I wrote the following and I’m experience hangs.

mutable struct Process
    task::Union{Nothing, Task}
    updates::Int
    @atomic run::Bool
end

function quit(p::Process)
    @atomic p.run = false
    if !isnothing(p.task)
        if istaskstarted(p.task)
            wait(p.task)
        end
    end
    p.updates = 0
    return p
end

function fib(n)
    if n <= 1 return 1 end
    return fib(n - 1) + fib(n - 2)
end

function threadedloop(p)
    n = 0
    while p.run
        n = fib(n)
        p.updates += 1
        GC.safepoint()
    end
    return n
end

Process() = Process(nothing, 0, true)

@inline function createtask(p, func)
    println("Running task")
    @atomic p.run = true
    println("Task assigned")
    p.task = Threads.@spawn func(p)
    println("Returning task")
    return p.task
end
processes = [Process() for i in 1:7]
println("Making processes")
begin 
    for i in 1:7
        println(i)
        createtask(processes[i], threadedloop)
    end
end
quit.(processes)

For me this will often hang just after the print statement “Returning task”. Any ideas why? I’m on julia 1.10b3 but it also hangs with 1.9.3. This is a minimal example, but in my own code I experience the hangs when using the quit function, where it will hang on the wait function. Now I don’t even get there. It does seem to have something to do with the atomic variable I’m using. This is probably not the “right” way of doing things, but nonetheless, I’m confused as to why it makes Julia hang.

I’m not sure that is sufficient - I think you want yield here instead, to allow other tasks to run as well.

Sorry, I should’ve mentioned I tried that and found yielding also causes the same behavior. Also, I want the loops to be as tight as possible, so I prefer not to yield.

How many threads are you starting Julia with?

Currently 8.

Ok, starting julia with -t 8 and pasting your example into the REPL indeed makes it hang after the first Returning task is printed. My guess is that this is because you’re scheduling the tasks on the same threadpool as the REPL itself runs on - it doesn’t hang with -t 8,1 (so 1 interactive thread for the REPL, 8 for other tasks).

Okay but that seems like a bug then, no?

In my program I’m actually running the quit statement from an interface (GLMakie), which causes hanging. Though, I’m not sure wether this is an asynchronous function call from the same thread of the REPL or from another thread. I’ll check whether it also helps for me.

I tried it and it indeed seems to work fine now, thanks for the help. However, I still feel this shouldn’t be necessary to not make it hang, right?