Description
When returning values from tasks executed on another thread, those values are kept alive even though it should be possible to collect them:
julia> a = [2,2];
julia> finalizer(a) do _
Core.println("finalizing a")
end
julia> t = Threads.@spawn (println("returning a from thread $(Threads.threadid())"); a)
Task (runnable) @0x00007fffb73e8e80
returning a from thread 2
julia> dump(t)
Task
next: Nothing nothing
queue: Nothing nothing
storage: Nothing nothing
donenotify: Base.GenericCondition{Base.Threads.SpinLock}
waitq: Base.InvasiveLinkedList{Task}
head: Nothing nothing
tail: Nothing nothing
lock: Base.Threads.SpinLock
owned: Int64 0
result: Array{Int64}((2,)) [2, 2] # <----------------- the array a is being kept alive by the task
logstate: Nothing nothing
code: #3 (function of type var"#3#4")
_state: UInt8 0x01
sticky: Bool false
_isexception: Bool false
julia> wait(t)
julia> a = nothing
julia> t = nothing
julia> GC.gc(true)
# nothing is collected
Running under gdb
reveals that the task object is being kept alive in the thread's local storage:
$ gdb --args julia -t2
(gdb) run
julia> code from above
(gdb) call jl_(jl_all_tls_states[1].current_task)
Task(next=nothing, queue=nothing, storage=nothing,
donenotify=Base.GenericCondition{Base.Threads.SpinLock}(waitq=Base.InvasiveLinkedList{Task}(head=nothing, tail=nothing), lock=Base.Threads.SpinLock(owned=0)),
result=Array{Int64, (2,)}[2, 2], # <------------------- our array again
logstate=nothing, code=Main.var"#3", _state=0x01, sticky=false, _isexception=false)
Running another task on the same thread replaces that sate and allows collection of the array:
julia> # code from above
julia> t = Threads.@spawn println("doing nothing from thread $(Threads.threadid())")
Task (runnable) @0x00007fffb5b99e40
doing nothing from thread 2
julia> GC.gc(true)
finalizing a
As observed in JuliaGPU/CUDA.jl#866.