From a95270e7df15a30294f078e48bda33b957aef57c Mon Sep 17 00:00:00 2001 From: Justin Willmert Date: Tue, 23 Apr 2024 10:10:20 -0500 Subject: [PATCH] Set finalizer on underlying `Memory` object in Julia 1.11+ In Julia 1.11+ with the new `Memory` object, the memory is not actually owned by the constructed `Array` and therefore the finalizer may trigger too early when the underlying memory is transfered from an `Array` to a different kind of owner (such as `IOBuffer`). Fix this by conditionally checking for the Julia 1.11+ behavior, and instead setting the finalizer on the `Memory`, if necessary. Duplicates the improvement made to the Mmap stdlib's implementation: JuliaLang/julia#54210 --- src/mmap.jl | 3 ++- test/runtests.jl | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/mmap.jl b/src/mmap.jl index d58b392..daea6a5 100644 --- a/src/mmap.jl +++ b/src/mmap.jl @@ -80,7 +80,8 @@ function _mmap(::Type{Array{T}}, dims::NTuple{N,Integer}, ptr = _sys_mmap(C_NULL, mmaplen, prot, flags, fd, Int64(offset) - page_pad) aptr = convert(Ptr{T}, ptr + page_pad) array = unsafe_wrap(Array{T,N}, aptr, dims) - finalizer(_ -> _sys_unmap!(ptr, mmaplen), array) + finalizer(_ -> _sys_unmap!(ptr, mmaplen), + @static VERSION >= v"1.11.0-DEV" && hasfield(Array, :ref) ? array.ref.mem : array) return array end function _mmap(::Type{Array{T}}, len::Int, prot::MmapProtection, flags::MmapFlags, diff --git a/test/runtests.jl b/test/runtests.jl index 92a64f8..8728157 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -248,3 +248,33 @@ end @test A == UnixMmap.msync!(A, UnixMmap.MS_ASYNC) end end + +@testset "Julia Memory-backed arrays" begin + # See JuliaLang/julia#54128 and JuliaLang/julia#54210 + mktempdir() do tdir + cd(tdir) do + # generate and store some random data in a file + open("rand.dat", "w") do io + write(io, rand(UInt8, 1024)) + end + + # read the contents of the file twice + # 1st: read as a plain array + plain = open("rand.dat", "r") do io + read(io) + end + # 2nd: read via a memory mapped array, wrapped in an IOBuffer. + # The IOBuffer is important in order to lose the original Array wrapper of + # the underlying Memory on Julia 1.11+ + mapbuf = open("rand.dat", "r") do io + IOBuffer(mmap(io, Vector{UInt8})) + end + + # Force a garbage collection + GC.gc(true) + # Then check that the plain array matches what can be retrieved from the IOBuffer + # If the underlying memory map has been freed, this should result in a segfault. + @test plain == take!(mapbuf) + end + end +end