Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions lib/level-zero/cmdlist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,12 @@ mutable struct ZeImmediateCommandList <: AbstractZeCommandList
@warn "Leaking an immediate command list still busy after $(FINALIZER_SYNC_TIMEOUT_NS ÷ 1_000_000_000)s to avoid blocking finalization" maxlog = 1
return
end
zeCommandListDestroy(obj)
release(obj.context)
# mark destroyed: the stream registry can still reach this list, and
# synchronizing a destroyed handle crashes in the driver
# mark the list as destroyed before destroying it: it can still be reached and
# synchronized (see `destroyed`), which crashes in the driver on a destroyed handle
handle = obj.handle
obj.handle = ze_command_list_handle_t(C_NULL)
zeCommandListDestroy(handle)
release(obj.context)
end
obj
end
Expand Down
12 changes: 5 additions & 7 deletions lib/level-zero/cmdqueue.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,12 @@ mutable struct ZeCommandQueue
for (_, fence) in Iterators.flatten((obj.submitted, obj.spare, obj.failed))
zeFenceDestroy(fence)
end
zeCommandQueueDestroy(obj)
# mark the queue as destroyed before destroying it: it can still be reached and
# synchronized (see `destroyed`), which crashes in the driver on a destroyed handle
handle = obj.handle
obj.handle = ze_command_queue_handle_t(C_NULL)
zeCommandQueueDestroy(handle)
release(obj.context)
if LTS[]
# mark the queue as destroyed: it can still be weakly reachable (e.g. from
# the stream registry used by `synchronize_all_streams`), and synchronizing a
# destroyed handle crashes in the driver.
obj.handle = ze_command_queue_handle_t(C_NULL)
end
end
obj
end
Expand Down
12 changes: 10 additions & 2 deletions lib/level-zero/synchronization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ function check_done(res::ze_result_t)
throw_api_error(res)
end
end
# Lists and queues are only destroyed by their finalizers, which first wait for their work
# where the driver doesn't. They can still be waited for afterwards: an object finalized in the same GC cycle as one
# of them can make it reachable again (e.g. memory freed then recording its last user), and
# synchronizing a destroyed handle crashes in the driver.
destroyed(obj::Union{ZeImmediateCommandList, ZeCommandQueue}) = obj.handle == C_NULL

Base.isdone(list::ZeImmediateCommandList) =
check_done(unchecked_zeCommandListHostSynchronize(list, 0))
destroyed(list) || check_done(unchecked_zeCommandListHostSynchronize(list, 0))
Base.isdone(queue::ZeCommandQueue) =
check_done(unchecked_zeCommandQueueSynchronize(queue, 0))
destroyed(queue) || check_done(unchecked_zeCommandQueueSynchronize(queue, 0))

# the blocking synchronization (GC-safe, like all Level Zero calls)
gcsafe_synchronize(list::ZeImmediateCommandList) =
destroyed(list) ? RESULT_SUCCESS :
unchecked_zeCommandListHostSynchronize(list, typemax(UInt64))
gcsafe_synchronize(queue::ZeCommandQueue) =
destroyed(queue) ? RESULT_SUCCESS :
unchecked_zeCommandQueueSynchronize(queue, typemax(UInt64))
gcsafe_synchronize(event::ZeEvent) =
unchecked_zeEventHostSynchronize(event, typemax(UInt64))
Expand Down
14 changes: 14 additions & 0 deletions test/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,20 @@ end
synchronize()
@test Array(b) == ones(Float32, n_chunk)

# memory last used by a task that's done is collected together with that task's stream,
# whose command list then gets destroyed, and can be reused regardless. (the task is
# created in a function, as the testset would keep it alive.)
upload_in_task() = fetch(@async oneAPI.device_pointer(oneArray(ones(Float32, n_chunk))))
ptr = upload_in_task()
GC.gc(true)
ys = []
for _ in 1:64
push!(ys, oneArray(fill(2f0, n_chunk)))
oneAPI.device_pointer(last(ys)) == ptr && break
end
oneAPI.POOL[] && @test oneAPI.device_pointer(last(ys)) == ptr
@test all(y -> Array(y) == fill(2f0, n_chunk), ys)

# many small arrays keep their contents, also when unused memory is given back
xs = [oneArray(fill(Float32(i), i)) for i in 1:300]
for i in 1:2:300
Expand Down
Loading