From 14482503fe08033cdc18171ee5ed3d5fdc933176 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 24 Feb 2024 13:26:24 -0500 Subject: [PATCH 1/8] remove readonly set given libuv handles it --- base/file.jl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/base/file.jl b/base/file.jl index 6156bafacdc47..a3fa658c5de22 100644 --- a/base/file.jl +++ b/base/file.jl @@ -273,13 +273,6 @@ Stacktrace: function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) if islink(path) || !isdir(path) try - @static if Sys.iswindows() - # is writable on windows actually means "is deletable" - st = lstat(path) - if ispath(st) && (filemode(st) & 0o222) == 0 - chmod(path, 0o777) - end - end unlink(path) catch err if force && isa(err, IOError) && err.code==Base.UV_ENOENT From 1a59f69aeea80e5a7111b47c3128e8c52c1bdade Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 24 Feb 2024 13:27:05 -0500 Subject: [PATCH 2/8] don't stop early if a file cannot be deleted in recursive mode --- base/file.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/file.jl b/base/file.jl index a3fa658c5de22..7b1f8534342b6 100644 --- a/base/file.jl +++ b/base/file.jl @@ -282,13 +282,13 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) end else if recursive - try - for p in readdir(path) + for p in readdir(path) + try rm(joinpath(path, p), force=force, recursive=true) - end - catch err - if !(isa(err, IOError) && err.code==Base.UV_EACCES) - rethrow(err) + catch err + if !(isa(err, IOError) && err.code==Base.UV_EACCES) + rethrow(err) + end end end end From 2124655bb08427fdf5a26539bae0e17215e6b2fe Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 24 Feb 2024 13:27:27 -0500 Subject: [PATCH 3/8] win: move DLLs that cannot be deleted to temp dir --- base/file.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/base/file.jl b/base/file.jl index 7b1f8534342b6..5b13a4df54ef7 100644 --- a/base/file.jl +++ b/base/file.jl @@ -275,8 +275,16 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) try unlink(path) catch err - if force && isa(err, IOError) && err.code==Base.UV_ENOENT - return + if isa(err, IOError) + force && err.code==Base.UV_ENOENT && return + @static if Sys.iswindows() + if err.code==Base.UV_EACCES && endswith(path, ".dll") + # Loaded DLLs cannot be deleted on Windows, even with posix delete mode + # but they can be moved. So move out to allow the dir to be deleted + # and DLL deleted via the temp dir cleanup on next reboot + mv(path, tempname() * "_" * basename(path)) + end + end end rethrow() end From 02fc817e3efdb7890ced39f137c309cc274a5b2e Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 24 Feb 2024 15:23:44 -0500 Subject: [PATCH 4/8] rework ocachefile name uniquing --- base/loading.jl | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 1cff14203b291..499d24684c979 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2902,27 +2902,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end if cache_objects - try - rename(tmppath_so, ocachefile::String; force=true) - catch e - e isa IOError || rethrow() - isfile(ocachefile::String) || rethrow() - # Windows prevents renaming a file that is in use so if there is a Julia session started - # with a package image loaded, we cannot rename that file. - # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that - # that cache file does not exist. - ocachename, ocacheext = splitext(ocachefile::String) - old_cachefiles = Set(readdir(cachepath)) - num = 1 - while true - ocachefile = ocachename * "_$num" * ocacheext - in(basename(ocachefile), old_cachefiles) || break - num += 1 - end - # TODO: Risk for a race here if some other process grabs this name before us - cachefile = cachefile_from_ocachefile(ocachefile) - rename(tmppath_so, ocachefile::String; force=true) - end + ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile) @static if Sys.isapple() run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull()) end @@ -2945,6 +2925,27 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end end +function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 1) + try + rename(tmppath_so, ocachefile; force=true) + catch e + e isa IOError || rethrow() + # If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup + # on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded + if !isfile(ocachefile) && e.code != Base.UV_EACCES + rethrow() + end + # Windows prevents renaming a file that is in use so if there is a Julia session started + # with a package image loaded, we cannot rename that file. + # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that + # that cache file does not exist. + ocachename, ocacheext = splitext(ocachefile_orig) + ocachefile_unique = ocachename * "_$num" * ocacheext + ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1) + end + return ocachefile +end + function module_build_id(m::Module) hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m) return (UInt128(hi) << 64) | lo From b07d1e1fb9ad3969397ec95dd7a5f38d136cba98 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sat, 24 Feb 2024 19:47:02 -0500 Subject: [PATCH 5/8] move to fixed temp dir. Add debug log --- base/file.jl | 7 +++++-- base/loading.jl | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/base/file.jl b/base/file.jl index 5b13a4df54ef7..6a6a3824df362 100644 --- a/base/file.jl +++ b/base/file.jl @@ -281,8 +281,11 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) if err.code==Base.UV_EACCES && endswith(path, ".dll") # Loaded DLLs cannot be deleted on Windows, even with posix delete mode # but they can be moved. So move out to allow the dir to be deleted - # and DLL deleted via the temp dir cleanup on next reboot - mv(path, tempname() * "_" * basename(path)) + # TODO: Add a mechanism to delete these moved files after dlclose or process exit + dir = mkpath(joinpath(tempdir(), "julia_delayed_deletes")) + temp_path = tempname(dir, cleanup = false) * "_" * basename(path) + @debug "Could not delete DLL most likely because it is loaded, moving to tempdir" path temp_path + mv(path, temp_path) end end end diff --git a/base/loading.jl b/base/loading.jl index 499d24684c979..e055eb3003c9e 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2902,7 +2902,11 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end if cache_objects - ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile) + ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile) + if ocachefile_new != ocachefile + cachefile = cachefile_from_ocachefile(ocachefile_new) + ocachefile = ocachefile_new + end @static if Sys.isapple() run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull()) end @@ -2925,7 +2929,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end end -function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 1) +function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0) try rename(tmppath_so, ocachefile; force=true) catch e From 8b4c9c448c91affcebfa13c684ff50d37a6f448c Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 25 Feb 2024 19:18:25 -0500 Subject: [PATCH 6/8] fixes --- base/file.jl | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/base/file.jl b/base/file.jl index 6a6a3824df362..1b42e412e3858 100644 --- a/base/file.jl +++ b/base/file.jl @@ -249,6 +249,10 @@ function mkpath(path::AbstractString; mode::Integer = 0o777) path end +# Files that were requested to be deleted but can't be by the current process +# i.e. loaded DLLs on Windows +delayed_delete_dir() = joinpath(tempdir(), "julia_delayed_deletes") + """ rm(path::AbstractString; force::Bool=false, recursive::Bool=false) @@ -282,10 +286,11 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) # Loaded DLLs cannot be deleted on Windows, even with posix delete mode # but they can be moved. So move out to allow the dir to be deleted # TODO: Add a mechanism to delete these moved files after dlclose or process exit - dir = mkpath(joinpath(tempdir(), "julia_delayed_deletes")) + dir = mkpath(delayed_delete_dir()) temp_path = tempname(dir, cleanup = false) * "_" * basename(path) @debug "Could not delete DLL most likely because it is loaded, moving to tempdir" path temp_path mv(path, temp_path) + return end end end @@ -293,14 +298,16 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) end else if recursive - for p in readdir(path) - try - rm(joinpath(path, p), force=force, recursive=true) - catch err - if !(isa(err, IOError) && err.code==Base.UV_EACCES) - rethrow(err) + try + for p in readdir(path) + try + rm(joinpath(path, p), force=force, recursive=true) + catch err + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() end end + catch err + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() end end req = Libc.malloc(_sizeof_uv_fs) From c28d3fbd8617f916b740a9943b74bbdc769d44eb Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 26 Feb 2024 12:11:04 -0500 Subject: [PATCH 7/8] add way to disable delayed delete for Pkg.gc() to use --- base/file.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/file.jl b/base/file.jl index 1b42e412e3858..04d66b5ad6a1c 100644 --- a/base/file.jl +++ b/base/file.jl @@ -274,7 +274,8 @@ Stacktrace: [...] ``` """ -function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) +function rm(path::AbstractString; force::Bool=false, recursive::Bool=false, allow_delayed_delete::Bool=true) + # allow_delayed_delete is used by Pkg.gc() but is otherwise not part of the public API if islink(path) || !isdir(path) try unlink(path) @@ -282,7 +283,7 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) if isa(err, IOError) force && err.code==Base.UV_ENOENT && return @static if Sys.iswindows() - if err.code==Base.UV_EACCES && endswith(path, ".dll") + if allow_delayed_delete && err.code==Base.UV_EACCES && endswith(path, ".dll") # Loaded DLLs cannot be deleted on Windows, even with posix delete mode # but they can be moved. So move out to allow the dir to be deleted # TODO: Add a mechanism to delete these moved files after dlclose or process exit From 1cc1cb82a1c2c49e572f00c687e12197fa61462b Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 26 Feb 2024 12:46:38 -0500 Subject: [PATCH 8/8] use tempname with suffix for uniqueness --- base/file.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/file.jl b/base/file.jl index 04d66b5ad6a1c..157951c6bc5bf 100644 --- a/base/file.jl +++ b/base/file.jl @@ -288,7 +288,7 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false, allo # but they can be moved. So move out to allow the dir to be deleted # TODO: Add a mechanism to delete these moved files after dlclose or process exit dir = mkpath(delayed_delete_dir()) - temp_path = tempname(dir, cleanup = false) * "_" * basename(path) + temp_path = tempname(dir, cleanup = false, suffix = string("_", basename(path))) @debug "Could not delete DLL most likely because it is loaded, moving to tempdir" path temp_path mv(path, temp_path) return