diff --git a/src/array.jl b/src/array.jl index 569da017..3fd539b1 100644 --- a/src/array.jl +++ b/src/array.jl @@ -570,7 +570,10 @@ function GPUArrays.derive(::Type{T}, a::oneArray, dims::Dims{N}, offset::Int) wh Base.elsize(a) == 0 || error("Cannot derive a singleton array from non-singleton inputs") end offset = a.offset + offset * sizeof(T) - oneArray{T,N}(a.data, dims; a.maxsize, offset) + # The derived array constructor copies `a.data`, but merely loading that field does not + # keep `a` alive. Without this preserve, `a` may be finalized between the field load and + # the DataRef copy, causing its finalizer to mark the reference as freed. + GC.@preserve a oneArray{T,N}(a.data, dims; a.maxsize, offset) end diff --git a/test/array.jl b/test/array.jl index aadc8928..1ca66453 100644 --- a/test/array.jl +++ b/test/array.jl @@ -43,6 +43,41 @@ end @test Array(xs) == [0,1,0] end +@testset "derived array lifetime" begin + parent = oneArray{UInt8}(undef, 1) + + # Construct through an ephemeral derived array, whose finalizer shares the same + # reference-counted allocation with the returned view. + function ephemeral_derived_view(parent) + intermediate = @view parent[:] + @view intermediate[:] + end + + derived = ephemeral_derived_view(parent) + @test derived isa oneArray{UInt8} + + # Exercise finalization while deriving. Without preserving the immediate parent in + # GPUArrays.derive, its finalizer can mark the DataRef as freed before it is copied. + if Threads.nthreads() > 1 + stop_gc = Threads.Atomic{Bool}(false) + gc_task = Threads.@spawn while !stop_gc[] + GC.gc(false) + yield() + end + try + Threads.@threads for _ in 1:min(Threads.nthreads(), 4) + for _ in 1:100 + a = ephemeral_derived_view(parent) + oneAPI.unsafe_free!(a) + end + end + finally + stop_gc[] = true + wait(gc_task) + end + end +end + @testset "reinterpret of view with non-aligned offset" begin # reinterpreting a view to a larger element type where the byte offset # is not a multiple of the new element size