@@ -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 =# )
182184end
183185
184186registry_info (pkg:: PkgEntry ) = init_package_info! (pkg)
185187
186188function 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
255261end
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
305312end
306313
307314const 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
337347end
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)
345355end
0 commit comments