@@ -43,10 +43,18 @@ _process_exists(pid) = ccall(:uv_kill, Cint, (Cint, Cint), pid, 0) != Base.UV_ES
4343# The record of a lock whose holder died on this host, or `nothing`. A remote
4444# host, a reused live pid, or a foreign format leaves the lock to its holder.
4545function _dead_build_lock_record (path, dead_age)
46+ # Read through libuv, as stdlib Pidfile does: on Windows it opens with
47+ # delete sharing, so this read cannot block a reclaimer's rename. An
48+ # IOStream `open` there denies it, failing that rename with EBUSY.
4649 record, age = try
47- open (io -> (read (io, String), time () - mtime (io)), path)
50+ file = Base. Filesystem. open (path, Base. Filesystem. JL_O_RDONLY)
51+ try
52+ (read (file, String), time () - mtime (file))
53+ finally
54+ close (file)
55+ end
4856 catch err
49- err isa SystemError && err. errnum == Libc . ENOENT && return nothing
57+ err isa Base . IOError && err. code == Base . UV_ENOENT && return nothing
5058 rethrow ()
5159 end
5260 age > dead_age || return nothing
@@ -69,30 +77,34 @@ function _reclaim_dead_build_lock(path, dead_age)
6977 try
7078 record = _dead_build_lock_record (path, dead_age)
7179 record === nothing && return false
72- _remove_build_lock (path)
80+ _remove_build_lock (path) || return false
7381 @warn " StanBlocks reclaimed a build lock whose holder no longer exists" path record
7482 true
7583 finally
7684 close (guard)
7785 end
7886end
7987
80- # Only a non-cooperating process can remove the file under the guard; its
81- # absence is then already the outcome a reclaim needs.
88+ # Whether the lock file is gone. Only a non-cooperating process can remove it
89+ # under the guard; its absence is then already the outcome a reclaim needs.
8290function _remove_build_lock (path)
8391 # Windows reserves a deleted name while any handle is open; move it aside.
8492 if Sys. iswindows ()
8593 aside = string (path, ' .' , getpid (), ' .' , time_ns (), " .deleted" )
8694 try
87- _rename_build_file (path, aside)
95+ _retry_windows_rename (path, aside, Base . UV_EBUSY )
8896 catch err
89- err isa Base. IOError && err. code == Base. UV_ENOENT && return nothing
97+ err isa Base. IOError || rethrow ()
98+ err. code == Base. UV_ENOENT && return true
99+ # A reader without delete sharing (an older StanBlocks, a virus
100+ # scanner) still has the dead file open: leave it for a later round.
101+ err. code == Base. UV_EBUSY && return false
90102 rethrow ()
91103 end
92104 path = aside
93105 end
94106 rm (path; force= true )
95- nothing
107+ true
96108end
97109
98110"""
@@ -177,20 +189,24 @@ function _rename_build_file(src, dst)
177189 nothing
178190end
179191
192+ # Windows readers opened without delete sharing briefly prevent a rename: of
193+ # the source with EBUSY, of a replaced destination with EACCES. Retry only that
194+ # platform's `code` for up to 1.6 s, then rethrow the last error.
195+ function _retry_windows_rename (src, dst, code)
196+ delays = Base. ExponentialBackOff (n= 10 , first_delay= 0.01 ,
197+ max_delay= 0.25 , factor= 2.0 , jitter= 0.0 )
198+ Base. retry (_rename_build_file; delays,
199+ check= (_, err) -> Sys. iswindows () && err isa Base. IOError && err. code == code)(src, dst)
200+ end
201+
180202function _atomic_build_write (path, value)
181203 mkpath (dirname (path))
182204 tmp, io = mktemp (dirname (path))
183205 try
184206 write (io, value)
185207 close (io)
186- # Windows readers opened without delete sharing temporarily prevent
187- # replacement. Retry only that platform's access-denied error; keep
188- # the old file visible and propagate a persistent failure unchanged.
189- delays = Base. ExponentialBackOff (n= 10 , first_delay= 0.01 ,
190- max_delay= 0.25 , factor= 2.0 , jitter= 0.0 )
191- Base. retry (_rename_build_file; delays,
192- check= (_, err) -> Sys. iswindows () && err isa Base. IOError &&
193- err. code == Base. UV_EACCES)(tmp, path)
208+ # Keep the old file visible and propagate a persistent failure unchanged.
209+ _retry_windows_rename (tmp, path, Base. UV_EACCES)
194210 finally
195211 isopen (io) && close (io)
196212 ispath (tmp) && rm (tmp)
0 commit comments