Skip to content

Commit c5b0a6c

Browse files
pidlock cache file precompilation (#49052)
1 parent 631d187 commit c5b0a6c

File tree

5 files changed

+82
-11
lines changed

5 files changed

+82
-11
lines changed

base/loading.jl

+40-2
Original file line numberDiff line numberDiff line change
@@ -1902,8 +1902,17 @@ function _require(pkg::PkgId, env=nothing)
19021902
@goto load_from_cache
19031903
end
19041904
# spawn off a new incremental pre-compile task for recursive `require` calls
1905-
cachefile = compilecache(pkg, path)
1906-
if isa(cachefile, Exception)
1905+
cachefile_or_module = maybe_cachefile_lock(pkg, path) do
1906+
# double-check now that we have lock
1907+
m = _require_search_from_serialized(pkg, path, UInt128(0))
1908+
m isa Module && return m
1909+
compilecache(pkg, path)
1910+
end
1911+
cachefile_or_module isa Module && return cachefile_or_module::Module
1912+
cachefile = cachefile_or_module
1913+
if isnothing(cachefile) # maybe_cachefile_lock returns nothing if it had to wait for another process
1914+
@goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search
1915+
elseif isa(cachefile, Exception)
19071916
if precompilableerror(cachefile)
19081917
verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
19091918
@logmsg verbosity "Skipping precompilation since __precompile__(false). Importing $pkg."
@@ -2805,6 +2814,35 @@ function show(io::IO, cf::CacheFlags)
28052814
print(io, ", opt_level = ", cf.opt_level)
28062815
end
28072816

2817+
# Set by FileWatching.__init__()
2818+
global mkpidlock_hook
2819+
global trymkpidlock_hook
2820+
global parse_pidfile_hook
2821+
2822+
# allows processes to wait if another process is precompiling a given source already
2823+
function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String)
2824+
if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook)
2825+
pidfile = string(srcpath, ".pidlock")
2826+
cachefile = invokelatest(trymkpidlock_hook, f, pidfile)
2827+
if cachefile === false
2828+
pid, hostname, age = invokelatest(parse_pidfile_hook, pidfile)
2829+
verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
2830+
if isempty(hostname) || hostname == gethostname()
2831+
@logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $pkg"
2832+
else
2833+
@logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $pkg"
2834+
end
2835+
# wait until the lock is available, but don't actually acquire it
2836+
# returning nothing indicates a process waited for another
2837+
return invokelatest(mkpidlock_hook, Returns(nothing), pidfile)
2838+
end
2839+
return cachefile
2840+
else
2841+
# for packages loaded before FileWatching.__init__()
2842+
f()
2843+
end
2844+
end
2845+
28082846
# returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey
28092847
# otherwise returns the list of dependencies to also check
28102848
@constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false)

stdlib/FileWatching/docs/src/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ A simple utility tool for creating advisory pidfiles (lock files).
2020

2121
```@docs
2222
mkpidlock
23+
trymkpidlock
2324
close(lock::LockMonitor)
2425
```
2526

stdlib/FileWatching/src/FileWatching.jl

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export
1818
PollingFileWatcher,
1919
FDWatcher,
2020
# pidfile:
21-
mkpidlock
21+
mkpidlock,
22+
trymkpidlock
2223

2324
import Base: @handle_as, wait, close, eventloop, notify_error, IOError,
2425
_sizeof_uv_poll, _sizeof_uv_fs_poll, _sizeof_uv_fs_event, _uv_hook_close, uv_error, _UVError,
@@ -462,6 +463,11 @@ function __init__()
462463
global uv_jl_fspollcb = @cfunction(uv_fspollcb, Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid}))
463464
global uv_jl_fseventscb_file = @cfunction(uv_fseventscb_file, Cvoid, (Ptr{Cvoid}, Ptr{Int8}, Int32, Int32))
464465
global uv_jl_fseventscb_folder = @cfunction(uv_fseventscb_folder, Cvoid, (Ptr{Cvoid}, Ptr{Int8}, Int32, Int32))
466+
467+
Base.mkpidlock_hook = mkpidlock
468+
Base.trymkpidlock_hook = trymkpidlock
469+
Base.parse_pidfile_hook = Pidfile.parse_pidfile
470+
465471
nothing
466472
end
467473

@@ -885,6 +891,6 @@ function poll_file(s::AbstractString, interval_seconds::Real=5.007, timeout_s::R
885891
end
886892

887893
include("pidfile.jl")
888-
import .Pidfile: mkpidlock
894+
import .Pidfile: mkpidlock, trymkpidlock
889895

890896
end

stdlib/FileWatching/src/pidfile.jl

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Pidfile
22

33

4-
export mkpidlock
4+
export mkpidlock, trymkpidlock
55

66
using Base:
77
IOError, UV_EEXIST, UV_ESRCH,
@@ -41,6 +41,16 @@ Optional keyword arguments:
4141
"""
4242
function mkpidlock end
4343

44+
"""
45+
trymkpidlock([f::Function], at::String, [pid::Cint, proc::Process]; kwopts...)
46+
47+
Like `mkpidlock` except returns `false` instead of waiting if the file is already locked.
48+
49+
!!! compat "Julia 1.10"
50+
This function requires at least Julia 1.10.
51+
52+
"""
53+
function trymkpidlock end
4454

4555
# mutable only because we want to add a finalizer
4656
mutable struct LockMonitor
@@ -95,6 +105,18 @@ function mkpidlock(at::String, proc::Process; kwopts...)
95105
return lock
96106
end
97107

108+
function trymkpidlock(args...; kwargs...)
109+
try
110+
mkpidlock(args...; kwargs..., wait=false)
111+
catch ex
112+
if ex isa PidlockedError
113+
return false
114+
else
115+
rethrow()
116+
end
117+
end
118+
end
119+
98120
"""
99121
Base.touch(::Pidfile.LockMonitor)
100122
@@ -192,8 +214,12 @@ function tryopen_exclusive(path::String, mode::Integer = 0o444)
192214
return nothing
193215
end
194216

217+
struct PidlockedError <: Exception
218+
msg::AbstractString
219+
end
220+
195221
"""
196-
open_exclusive(path::String; mode, poll_interval, stale_age) :: File
222+
open_exclusive(path::String; mode, poll_interval, wait, stale_age) :: File
197223
198224
Create a new a file for read-write advisory-exclusive access.
199225
If `wait` is `false` then error out if the lock files exist
@@ -218,7 +244,7 @@ function open_exclusive(path::String;
218244
file = tryopen_exclusive(path, mode)
219245
end
220246
if file === nothing
221-
error("Failed to get pidfile lock for $(repr(path)).")
247+
throw(PidlockedError("Failed to get pidfile lock for $(repr(path))."))
222248
else
223249
return file
224250
end

stdlib/FileWatching/test/pidfile.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,14 @@ end
180180
Base.errormonitor(rmtask)
181181

182182
t1 = time()
183-
@test_throws ErrorException open_exclusive("pidfile", wait=false)
183+
@test_throws Pidfile.PidlockedError open_exclusive("pidfile", wait=false)
184184
@test time()-t1 0 atol=1
185185

186186
sleep(1)
187187
@test !deleted
188188

189189
t1 = time()
190-
@test_throws ErrorException open_exclusive("pidfile", wait=false)
190+
@test_throws Pidfile.PidlockedError open_exclusive("pidfile", wait=false)
191191
@test time()-t1 0 atol=1
192192

193193
wait(rmtask)
@@ -246,7 +246,7 @@ end
246246
Base.errormonitor(waittask)
247247

248248
# mkpidlock with no waiting
249-
t = @elapsed @test_throws ErrorException mkpidlock("pidfile", wait=false)
249+
t = @elapsed @test_throws Pidfile.PidlockedError mkpidlock("pidfile", wait=false)
250250
@test t 0 atol=1
251251

252252
t = @elapsed lockf1 = mkpidlock(joinpath(dir, "pidfile"))
@@ -354,7 +354,7 @@ end
354354
@test lockf.update === nothing
355355

356356
sleep(1)
357-
t = @elapsed @test_throws ErrorException mkpidlock("pidfile-2", wait=false, stale_age=1, poll_interval=1, refresh=0)
357+
t = @elapsed @test_throws Pidfile.PidlockedError mkpidlock("pidfile-2", wait=false, stale_age=1, poll_interval=1, refresh=0)
358358
@test t 0 atol=1
359359

360360
sleep(5)

0 commit comments

Comments
 (0)