diff --git a/base/file.jl b/base/file.jl index 6156bafacdc47..157951c6bc5bf 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) @@ -270,20 +274,26 @@ 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 - @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 - return + if isa(err, IOError) + force && err.code==Base.UV_ENOENT && return + @static if Sys.iswindows() + 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 + dir = mkpath(delayed_delete_dir()) + 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 + end + end end rethrow() end @@ -291,12 +301,14 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) if recursive try for p in readdir(path) - rm(joinpath(path, p), force=force, recursive=true) + try + rm(joinpath(path, p), force=force, recursive=true) + catch err + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() + end end catch err - if !(isa(err, IOError) && err.code==Base.UV_EACCES) - rethrow(err) - end + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() end end req = Libc.malloc(_sizeof_uv_fs) diff --git a/base/loading.jl b/base/loading.jl index 1cff14203b291..e055eb3003c9e 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2902,26 +2902,10 @@ 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) + 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()) @@ -2945,6 +2929,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 = 0) + 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