Skip to content

Commit 2e9b0bb

Browse files
rm: move open DLLs to temp dir to allow dir to be deleted (#53456)
1 parent 98b3f72 commit 2e9b0bb

File tree

2 files changed

+51
-34
lines changed

2 files changed

+51
-34
lines changed

base/file.jl

+26-14
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ function mkpath(path::AbstractString; mode::Integer = 0o777)
249249
path
250250
end
251251

252+
# Files that were requested to be deleted but can't be by the current process
253+
# i.e. loaded DLLs on Windows
254+
delayed_delete_dir() = joinpath(tempdir(), "julia_delayed_deletes")
255+
252256
"""
253257
rm(path::AbstractString; force::Bool=false, recursive::Bool=false)
254258
@@ -270,33 +274,41 @@ Stacktrace:
270274
[...]
271275
```
272276
"""
273-
function rm(path::AbstractString; force::Bool=false, recursive::Bool=false)
277+
function rm(path::AbstractString; force::Bool=false, recursive::Bool=false, allow_delayed_delete::Bool=true)
278+
# allow_delayed_delete is used by Pkg.gc() but is otherwise not part of the public API
274279
if islink(path) || !isdir(path)
275280
try
276-
@static if Sys.iswindows()
277-
# is writable on windows actually means "is deletable"
278-
st = lstat(path)
279-
if ispath(st) && (filemode(st) & 0o222) == 0
280-
chmod(path, 0o777)
281-
end
282-
end
283281
unlink(path)
284282
catch err
285-
if force && isa(err, IOError) && err.code==Base.UV_ENOENT
286-
return
283+
if isa(err, IOError)
284+
force && err.code==Base.UV_ENOENT && return
285+
@static if Sys.iswindows()
286+
if allow_delayed_delete && err.code==Base.UV_EACCES && endswith(path, ".dll")
287+
# Loaded DLLs cannot be deleted on Windows, even with posix delete mode
288+
# but they can be moved. So move out to allow the dir to be deleted
289+
# TODO: Add a mechanism to delete these moved files after dlclose or process exit
290+
dir = mkpath(delayed_delete_dir())
291+
temp_path = tempname(dir, cleanup = false, suffix = string("_", basename(path)))
292+
@debug "Could not delete DLL most likely because it is loaded, moving to tempdir" path temp_path
293+
mv(path, temp_path)
294+
return
295+
end
296+
end
287297
end
288298
rethrow()
289299
end
290300
else
291301
if recursive
292302
try
293303
for p in readdir(path)
294-
rm(joinpath(path, p), force=force, recursive=true)
304+
try
305+
rm(joinpath(path, p), force=force, recursive=true)
306+
catch err
307+
(isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow()
308+
end
295309
end
296310
catch err
297-
if !(isa(err, IOError) && err.code==Base.UV_EACCES)
298-
rethrow(err)
299-
end
311+
(isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow()
300312
end
301313
end
302314
req = Libc.malloc(_sizeof_uv_fs)

base/loading.jl

+25-20
Original file line numberDiff line numberDiff line change
@@ -2902,26 +2902,10 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in
29022902
end
29032903

29042904
if cache_objects
2905-
try
2906-
rename(tmppath_so, ocachefile::String; force=true)
2907-
catch e
2908-
e isa IOError || rethrow()
2909-
isfile(ocachefile::String) || rethrow()
2910-
# Windows prevents renaming a file that is in use so if there is a Julia session started
2911-
# with a package image loaded, we cannot rename that file.
2912-
# The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that
2913-
# that cache file does not exist.
2914-
ocachename, ocacheext = splitext(ocachefile::String)
2915-
old_cachefiles = Set(readdir(cachepath))
2916-
num = 1
2917-
while true
2918-
ocachefile = ocachename * "_$num" * ocacheext
2919-
in(basename(ocachefile), old_cachefiles) || break
2920-
num += 1
2921-
end
2922-
# TODO: Risk for a race here if some other process grabs this name before us
2923-
cachefile = cachefile_from_ocachefile(ocachefile)
2924-
rename(tmppath_so, ocachefile::String; force=true)
2905+
ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile)
2906+
if ocachefile_new != ocachefile
2907+
cachefile = cachefile_from_ocachefile(ocachefile_new)
2908+
ocachefile = ocachefile_new
29252909
end
29262910
@static if Sys.isapple()
29272911
run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull())
@@ -2945,6 +2929,27 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in
29452929
end
29462930
end
29472931

2932+
function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0)
2933+
try
2934+
rename(tmppath_so, ocachefile; force=true)
2935+
catch e
2936+
e isa IOError || rethrow()
2937+
# If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup
2938+
# on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded
2939+
if !isfile(ocachefile) && e.code != Base.UV_EACCES
2940+
rethrow()
2941+
end
2942+
# Windows prevents renaming a file that is in use so if there is a Julia session started
2943+
# with a package image loaded, we cannot rename that file.
2944+
# The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that
2945+
# that cache file does not exist.
2946+
ocachename, ocacheext = splitext(ocachefile_orig)
2947+
ocachefile_unique = ocachename * "_$num" * ocacheext
2948+
ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1)
2949+
end
2950+
return ocachefile
2951+
end
2952+
29482953
function module_build_id(m::Module)
29492954
hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
29502955
return (UInt128(hi) << 64) | lo

0 commit comments

Comments
 (0)