Skip to content

Commit 1bb49bd

Browse files
Registry: Add lock to lazy fields in RegistryInstance and PkgEntry (#4341)
1 parent dd5b8ae commit 1bb49bd

File tree

1 file changed

+100
-90
lines changed

1 file changed

+100
-90
lines changed

src/Registry/registry_instance.jl

Lines changed: 100 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -175,83 +175,89 @@ mutable struct PkgEntry
175175
const uuid::UUID
176176

177177
const in_memory_registry::Union{Dict{String, String}, Nothing}
178+
# Lock for thread-safe lazy loading
179+
const info_lock::ReentrantLock
178180
# Version.toml / (Compat.toml / Deps.toml):
179181
info::PkgInfo # lazily initialized
180182

181-
PkgEntry(path, registry_path, name, uuid, in_memory_registry) = new(path, registry_path, name, uuid, in_memory_registry #= undef =#)
183+
PkgEntry(path, registry_path, name, uuid, in_memory_registry) = new(path, registry_path, name, uuid, in_memory_registry, ReentrantLock() #= undef =#)
182184
end
183185

184186
registry_info(pkg::PkgEntry) = init_package_info!(pkg)
185187

186188
function init_package_info!(pkg::PkgEntry)
187-
# Already uncompressed the info for this package, return early
188-
isdefined(pkg, :info) && return pkg.info
189-
path = pkg.registry_path
190-
191-
d_p = parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Package.toml"))
192-
name = d_p["name"]::String
193-
name != pkg.name && error("inconsistent name in Registry.toml ($(name)) and Package.toml ($(pkg.name)) for pkg at $(path)")
194-
repo = get(d_p, "repo", nothing)::Union{Nothing, String}
195-
subdir = get(d_p, "subdir", nothing)::Union{Nothing, String}
196-
197-
# Versions.toml
198-
d_v = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Versions.toml")) ?
199-
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Versions.toml")) : Dict{String, Any}()
200-
version_info = Dict{VersionNumber, VersionInfo}(
201-
VersionNumber(k) =>
202-
VersionInfo(SHA1(v["git-tree-sha1"]::String), get(v, "yanked", false)::Bool) for (k, v) in d_v
203-
)
204-
205-
# Compat.toml
206-
compat_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Compat.toml")) ?
207-
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Compat.toml")) : Dict{String, Any}()
208-
compat = Dict{VersionRange, Dict{String, VersionSpec}}()
209-
for (v, data) in compat_data_toml
210-
data = data::Dict{String, Any}
211-
vr = VersionRange(v)
212-
d = Dict{String, VersionSpec}(dep => VersionSpec(vr_dep) for (dep, vr_dep::Union{String, Vector{String}}) in data)
213-
compat[vr] = d
214-
end
189+
# Thread-safe lazy loading with double-check pattern
190+
return @lock pkg.info_lock begin
191+
# Double-check: if another thread loaded while we were waiting for the lock
192+
isdefined(pkg, :info) && return pkg.info
193+
194+
path = pkg.registry_path
195+
196+
d_p = parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Package.toml"))
197+
name = d_p["name"]::String
198+
name != pkg.name && error("inconsistent name in Registry.toml ($(name)) and Package.toml ($(pkg.name)) for pkg at $(path)")
199+
repo = get(d_p, "repo", nothing)::Union{Nothing, String}
200+
subdir = get(d_p, "subdir", nothing)::Union{Nothing, String}
201+
202+
# Versions.toml
203+
d_v = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Versions.toml")) ?
204+
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Versions.toml")) : Dict{String, Any}()
205+
version_info = Dict{VersionNumber, VersionInfo}(
206+
VersionNumber(k) =>
207+
VersionInfo(SHA1(v["git-tree-sha1"]::String), get(v, "yanked", false)::Bool) for (k, v) in d_v
208+
)
215209

216-
# Deps.toml
217-
deps_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Deps.toml")) ?
218-
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Deps.toml")) : Dict{String, Any}()
219-
deps = Dict{VersionRange, Dict{String, UUID}}()
220-
for (v, data) in deps_data_toml
221-
data = data::Dict{String, Any}
222-
vr = VersionRange(v)
223-
d = Dict{String, UUID}(dep => UUID(uuid) for (dep, uuid::String) in data)
224-
deps[vr] = d
225-
end
226-
# All packages depend on julia
227-
deps[VersionRange()] = Dict("julia" => JULIA_UUID)
210+
# Compat.toml
211+
compat_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Compat.toml")) ?
212+
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Compat.toml")) : Dict{String, Any}()
213+
compat = Dict{VersionRange, Dict{String, VersionSpec}}()
214+
for (v, data) in compat_data_toml
215+
data = data::Dict{String, Any}
216+
vr = VersionRange(v)
217+
d = Dict{String, VersionSpec}(dep => VersionSpec(vr_dep) for (dep, vr_dep::Union{String, Vector{String}}) in data)
218+
compat[vr] = d
219+
end
228220

229-
# WeakCompat.toml
230-
weak_compat_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakCompat.toml")) ?
231-
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakCompat.toml")) : Dict{String, Any}()
232-
weak_compat = Dict{VersionRange, Dict{String, VersionSpec}}()
233-
for (v, data) in weak_compat_data_toml
234-
data = data::Dict{String, Any}
235-
vr = VersionRange(v)
236-
d = Dict{String, VersionSpec}(dep => VersionSpec(vr_dep) for (dep, vr_dep::Union{String, Vector{String}}) in data)
237-
weak_compat[vr] = d
238-
end
221+
# Deps.toml
222+
deps_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Deps.toml")) ?
223+
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "Deps.toml")) : Dict{String, Any}()
224+
deps = Dict{VersionRange, Dict{String, UUID}}()
225+
for (v, data) in deps_data_toml
226+
data = data::Dict{String, Any}
227+
vr = VersionRange(v)
228+
d = Dict{String, UUID}(dep => UUID(uuid) for (dep, uuid::String) in data)
229+
deps[vr] = d
230+
end
231+
# All packages depend on julia
232+
deps[VersionRange()] = Dict("julia" => JULIA_UUID)
233+
234+
# WeakCompat.toml
235+
weak_compat_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakCompat.toml")) ?
236+
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakCompat.toml")) : Dict{String, Any}()
237+
weak_compat = Dict{VersionRange, Dict{String, VersionSpec}}()
238+
for (v, data) in weak_compat_data_toml
239+
data = data::Dict{String, Any}
240+
vr = VersionRange(v)
241+
d = Dict{String, VersionSpec}(dep => VersionSpec(vr_dep) for (dep, vr_dep::Union{String, Vector{String}}) in data)
242+
weak_compat[vr] = d
243+
end
239244

240-
# WeakDeps.toml
241-
weak_deps_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakDeps.toml")) ?
242-
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakDeps.toml")) : Dict{String, Any}()
243-
weak_deps = Dict{VersionRange, Dict{String, UUID}}()
244-
for (v, data) in weak_deps_data_toml
245-
data = data::Dict{String, Any}
246-
vr = VersionRange(v)
247-
d = Dict{String, UUID}(dep => UUID(uuid) for (dep, uuid::String) in data)
248-
weak_deps[vr] = d
249-
end
245+
# WeakDeps.toml
246+
weak_deps_data_toml = custom_isfile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakDeps.toml")) ?
247+
parsefile(pkg.in_memory_registry, pkg.registry_path, joinpath(pkg.path, "WeakDeps.toml")) : Dict{String, Any}()
248+
weak_deps = Dict{VersionRange, Dict{String, UUID}}()
249+
for (v, data) in weak_deps_data_toml
250+
data = data::Dict{String, Any}
251+
vr = VersionRange(v)
252+
d = Dict{String, UUID}(dep => UUID(uuid) for (dep, uuid::String) in data)
253+
weak_deps[vr] = d
254+
end
250255

251-
@assert !isdefined(pkg, :info)
252-
pkg.info = PkgInfo(repo, subdir, version_info, compat, deps, weak_compat, weak_deps)
256+
@assert !isdefined(pkg, :info)
257+
pkg.info = PkgInfo(repo, subdir, version_info, compat, deps, weak_compat, weak_deps)
253258

254-
return pkg.info
259+
return pkg.info
260+
end
255261
end
256262

257263

@@ -277,6 +283,7 @@ mutable struct RegistryInstance
277283
path::String
278284
tree_info::Union{Base.SHA1, Nothing}
279285
compressed_file::Union{String, Nothing}
286+
const load_lock::ReentrantLock # Lock for thread-safe lazy loading
280287

281288
# Lazily loaded fields
282289
name::String
@@ -290,7 +297,7 @@ mutable struct RegistryInstance
290297

291298
# Inner constructor for lazy loading - leaves fields undefined
292299
function RegistryInstance(path::String, tree_info::Union{Base.SHA1, Nothing}, compressed_file::Union{String, Nothing})
293-
return new(path, tree_info, compressed_file)
300+
return new(path, tree_info, compressed_file, ReentrantLock())
294301
end
295302

296303
# Full constructor for when all fields are known
@@ -300,46 +307,49 @@ mutable struct RegistryInstance
300307
pkgs::Dict{UUID, PkgEntry}, in_memory_registry::Union{Nothing, Dict{String, String}},
301308
name_to_uuids::Dict{String, Vector{UUID}}
302309
)
303-
return new(path, tree_info, compressed_file, name, uuid, repo, description, pkgs, in_memory_registry, name_to_uuids)
310+
return new(path, tree_info, compressed_file, ReentrantLock(), name, uuid, repo, description, pkgs, in_memory_registry, name_to_uuids)
304311
end
305312
end
306313

307314
const REGISTRY_CACHE = Dict{String, Tuple{Base.SHA1, Bool, RegistryInstance}}()
308315

309316
@noinline function _ensure_registry_loaded_slow!(r::RegistryInstance)
310-
isdefined(r, :pkgs) && return r
317+
return @lock r.load_lock begin
318+
# Double-check pattern: if another thread loaded while we were waiting for the lock
319+
isdefined(r, :pkgs) && return r
311320

312-
if getfield(r, :compressed_file) !== nothing
313-
r.in_memory_registry = uncompress_registry(joinpath(dirname(getfield(r, :path)), getfield(r, :compressed_file)))
314-
else
315-
r.in_memory_registry = nothing
316-
end
321+
if getfield(r, :compressed_file) !== nothing
322+
r.in_memory_registry = uncompress_registry(joinpath(dirname(getfield(r, :path)), getfield(r, :compressed_file)))
323+
else
324+
r.in_memory_registry = nothing
325+
end
317326

318-
d = parsefile(r.in_memory_registry, getfield(r, :path), "Registry.toml")
319-
r.name = d["name"]::String
320-
r.uuid = UUID(d["uuid"]::String)
321-
r.repo = get(d, "repo", nothing)::Union{String, Nothing}
322-
r.description = get(d, "description", nothing)::Union{String, Nothing}
323-
324-
r.pkgs = Dict{UUID, PkgEntry}()
325-
for (uuid, info) in d["packages"]::Dict{String, Any}
326-
uuid = UUID(uuid::String)
327-
info::Dict{String, Any}
328-
name = info["name"]::String
329-
pkgpath = info["path"]::String
330-
pkg = PkgEntry(pkgpath, getfield(r, :path), name, uuid, r.in_memory_registry)
331-
r.pkgs[uuid] = pkg
332-
end
327+
d = parsefile(r.in_memory_registry, getfield(r, :path), "Registry.toml")
328+
r.name = d["name"]::String
329+
r.uuid = UUID(d["uuid"]::String)
330+
r.repo = get(d, "repo", nothing)::Union{String, Nothing}
331+
r.description = get(d, "description", nothing)::Union{String, Nothing}
332+
333+
r.pkgs = Dict{UUID, PkgEntry}()
334+
for (uuid, info) in d["packages"]::Dict{String, Any}
335+
uuid = UUID(uuid::String)
336+
info::Dict{String, Any}
337+
name = info["name"]::String
338+
pkgpath = info["path"]::String
339+
pkg = PkgEntry(pkgpath, getfield(r, :path), name, uuid, r.in_memory_registry)
340+
r.pkgs[uuid] = pkg
341+
end
333342

334-
r.name_to_uuids = Dict{String, Vector{UUID}}()
343+
r.name_to_uuids = Dict{String, Vector{UUID}}()
335344

336-
return r
345+
return r
346+
end
337347
end
338348

339349
# Property accessors that trigger lazy loading
340350
@inline function Base.getproperty(r::RegistryInstance, f::Symbol)
341351
if f === :name || f === :uuid || f === :repo || f === :description || f === :pkgs || f === :name_to_uuids
342-
isdefined(r, :pkgs) || _ensure_registry_loaded_slow!(r)
352+
_ensure_registry_loaded_slow!(r) # Takes a lock to ensure thread safety
343353
end
344354
return getfield(r, f)
345355
end

0 commit comments

Comments
 (0)