Repository: JuliaLang/julia
File: base/file.jl (Julia)

Code before:
# Files that were requested to be deleted but can't be by the current process
# i.e. loaded DLLs on Windows
delayed_delete_list() = joinpath(tempdir(), "julia_delayed_deletes.txt")

"""
    rm(path::AbstractString; force::Bool=false, recursive::Bool=false)

Delete the file, link, or empty directory at the given path. If `force=true` is passed, a
non-existing path is not treated as error. If `recursive=true` is passed and the path is a
directory, then all contents are removed recursively.

# Examples
```jldoctest
julia> mkpath("my/test/dir");

julia> rm("my", recursive=true)

julia> rm("this_file_does_not_exist", force=true)

julia> rm("this_file_does_not_exist")
ERROR: IOError: unlink("this_file_does_not_exist"): no such file or directory (ENOENT)
Stacktrace:
[...]
```
"""
function rm(path::AbstractString; force::Bool=false, recursive::Bool=false)
    if islink(path) || !isdir(path)
        try
            unlink(path)
        catch err
            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 renamed. Do so temporarily, until later cleanup by Pkg.gc()
                        temp_path = tempname(dirname(path), cleanup = false, suffix = string("_", basename(path)))
                        # ensure that temp_path is on the same drive as path to avoid issue #59589
                        @debug "Could not delete DLL most likely because it is loaded, moving to a temporary path" path temp_path
                        Base.open(delayed_delete_list(), "a") do io
                            println(io, abspath(temp_path)) # record the temporary path for Pkg.gc()
                        end
                        rename(path, temp_path) # do not call mv which could recursively call rm(path)
                        return
                    end
                end
            end
            rethrow()
        end
    else
        if recursive

Review comment (suggestion):
I do not think we want this behavior for all `rm()` calls; I think we should continue to only do this behavior if `allow_delayed_delete=true`, since this is a very heavyweight and unusual behavior.  In particular, getting `UV_EACCES` can happen if the file you're attempting to delete doesn't belong to your user.  We don't want to just blindly re-try those deletes later, we only want to do that if the caller has explicitly said that this is an operation they expect to succeed (Which is the case when `Pkg` is calling into here).

Code after:
# Files that were requested to be deleted but can't be by the current process,
# i.e. loaded DLLs on Windows, are listed in the directory below
delayed_delete_ref() = joinpath(tempdir(), "julia_delayed_deletes_ref")

"""
    rm(path::AbstractString; force::Bool=false, recursive::Bool=false)

Delete the file, link, or empty directory at the given path. If `force=true` is passed, a
non-existing path is not treated as error. If `recursive=true` is passed and the path is a
directory, then all contents are removed recursively.

# Examples
```jldoctest
julia> mkpath("my/test/dir");

julia> rm("my", recursive=true)

julia> rm("this_file_does_not_exist", force=true)

julia> rm("this_file_does_not_exist")
ERROR: IOError: unlink("this_file_does_not_exist"): no such file or directory (ENOENT)
Stacktrace:
[...]
```
"""
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)
        catch err
            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")
                        delayed_delete_dll(path)
                        return
                    end
                end
            end
            rethrow()
        end
    else
        if recursive
            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