Skip to content

Commit 1b2e6da

Browse files
committed
Teach PkgServer to serve multiple registry flavors
The storage server now exports two different "flavors" of registry declarations, `eager` and `conservative`: - `eager` registries are published as soon as possible, and should track upstream git repositories quite closely, (in general, it should be less than two minutes behind the state of the upstream git repository). The downside is that the storage server may not have processed some of the resources in the registry, so if you are a user behind a restrictive firewall and cannot download resources off of arbitrary servers, we suggest you use the `conservative` registry. - `conservative` registries are published only once the StorageServer has at least tried to process all resources (packages, artifacts, etc...) within a registry. It does not guarantee that all resources are downloadable (after all, there are some package versions that contain links to artifacts that simply do not exist) but it gives a "best effort" treehash to use. The downside is that in the event that a large number of new resources have been added to the registry recently, it may take a little while for the storage server to process them all, so the `conservative` registry flavor may lag behind real-time by a few hours. Pkg clients that ask for `/registries` are, by default, served a `conservative` registry, unless one of the following two conditions hold: - The Pkg client declares itself to be running CI (via one of the CI variables transmitted in the `Julia-Ci-Variables` header, see [0] for more), in which case we assume you likely want `eager`. - The Pkg client transmits a `Julia-Registry-Preference` header set to one of the valid flavors, which currently includes `eager` and `conservative`. Now that [1] has been merged into `Pkg.jl`, users can easily set their own preference header by simply defining the environment mapping `JULIA_PKG_SERVER_REGISTRY_PREFERENCE=eager`. [0] https://github.com/JuliaLang/Pkg.jl/blob/bb180de2802a8320241e10721c1ac4eefd3eb27d/src/PlatformEngines.jl#L200-L213 [1] JuliaLang/Pkg.jl#2770
1 parent 3d2b47c commit 1b2e6da

File tree

3 files changed

+51
-33
lines changed

3 files changed

+51
-33
lines changed

src/PkgServer.jl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ include("dynamic.jl")
2222
mutable struct RegistryMeta
2323
# Upstream registry URL (e.g. "https://github.com/JuliaRegistries/General")
2424
upstream_url::String
25-
# The latest hash we know about for this registry
26-
latest_hash::Union{Nothing,String}
25+
# The hash (for each flavor) for each registry
26+
hashes::Dict{String,String}
2727

2828
function RegistryMeta(url::String)
2929
# Check to ensure this path actually exists
@@ -36,7 +36,7 @@ mutable struct RegistryMeta
3636
if !endswith(url, ".git") && url_exists(git_url)
3737
url = git_url
3838
end
39-
return new(url, nothing)
39+
return new(url, Dict{String,String}())
4040
end
4141
end
4242

@@ -99,7 +99,8 @@ function start(;kwargs...)
9999

100100
# Update registries first thing
101101
@info("Performing initial registry update")
102-
update_registries()
102+
registry_flavor_list = ("eager", "conservative")
103+
update_registries.(registry_flavor_list)
103104
global last_registry_update = now()
104105

105106
# Experimental.@sync throws if _any_ of the tasks fail
@@ -109,7 +110,7 @@ function start(;kwargs...)
109110
sleep(1)
110111
@try_printerror begin
111112
forget_failures()
112-
update_registries()
113+
update_registries.(registry_flavor_list)
113114
last_registry_update = now()
114115
end
115116
end
@@ -166,8 +167,17 @@ function start(;kwargs...)
166167
return
167168
end
168169

169-
if resource == "/registries"
170-
open(joinpath(config.root, "static", "registries")) do io
170+
if occursin(r"^/registries(\.[a-z]+)?$", resource)
171+
# If they're asking for just "/registries", inspect headers to figure
172+
# out which registry flavor they actually want, and if none is given,
173+
# give them `conservative` by default, unless they are self-reporting
174+
# as a CI bot, in which case we'll always point them to `eager`.
175+
if resource == "/registries"
176+
ci = any([v == "t" for (k, v) in filter(!isempty, split(HTTP.header(http, "Julia-CI-Variables", ""), ";"))])
177+
flavor = HTTP.header(http, "Julia-Registry-Preference", ci ? "eager" : "conservative")
178+
resource = "/registries.$(flavor)"
179+
end
180+
open(joinpath(config.root, "static", basename(resource))) do io
171181
serve_file(http, io, "text/plain")
172182
end
173183
return

src/resource.jl

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ const resource_re = Regex("""
1010
""", "x")
1111

1212
"""
13-
get_registries(server)
13+
get_registries(server, flavor)
1414
1515
Interrogate a storage server for a list of registries, match the response against the
1616
registries we are paying attention to, return dict mapping from registry UUID to its
1717
latest treehash.
1818
"""
19-
function get_registries(server::AbstractString)
19+
function get_registries(server::AbstractString, flavor::AbstractString)
2020
regs = Dict{String,String}()
21-
response = HTTP.get("$server/registries", status_exception = false)
21+
response = HTTP.get("$server/registries.$(flavor)", status_exception = false)
2222
if response.status != 200
23-
@error("Failure to fetch /registries", server, response.status)
23+
@error("Failure to fetch /registries.$(flavor)", server, response.status)
2424
return regs
2525
end
2626
for line in eachline(IOBuffer(response.body))
@@ -30,7 +30,7 @@ function get_registries(server::AbstractString)
3030
uuid in keys(config.registries) || continue
3131
regs[uuid] = hash
3232
else
33-
@error("invalid response", server, resource="registries", line)
33+
@error("invalid response", server, resource="registries.$(flavor)", line)
3434
end
3535
end
3636
return regs
@@ -126,12 +126,12 @@ function verify_registry_hash(uuid::AbstractString, hash::AbstractString)
126126
return url === nothing || url_exists(url)
127127
end
128128

129-
function update_registries()
129+
function update_registries(flavor::String)
130130
# collect current registry hashes from servers
131131
regs = Dict(uuid => Dict{String,Vector{String}}() for uuid in keys(config.registries))
132132
servers = Dict(uuid => Vector{String}() for uuid in keys(config.registries))
133133
for server in config.storage_servers
134-
for (uuid, hash) in get_registries(server)
134+
for (uuid, hash) in get_registries(server, flavor)
135135
push!(get!(regs[uuid], hash, String[]), server)
136136
push!(servers[uuid], server)
137137
end
@@ -173,29 +173,33 @@ function update_registries()
173173
wait(dl_state.dl_task)
174174
end
175175

176-
if config.registries[uuid].latest_hash != hash
176+
if get(config.registries[uuid].hashes, flavor, "") != hash
177177
@info("new current registry hash", uuid, hash, hash_servers)
178178
changed = true
179179
end
180180

181181
# we've got a new registry hash to serve
182-
config.registries[uuid].latest_hash = hash
182+
config.registries[uuid].hashes[flavor] = hash
183183
break
184184
end
185185
end
186186

187-
# write new registry info to file
188-
registries_path = joinpath(config.root, "static", "registries")
189-
if changed || !isfile(registries_path)
190-
new_registries = joinpath(config.root, "temp", "registries.tmp." * randstring())
191-
mkpath(dirname(new_registries))
192-
open(new_registries, "w") do io
193-
for uuid in sort!(collect(keys(config.registries)))
194-
println(io, "/registry/$(uuid)/$(config.registries[uuid].latest_hash)")
187+
# write new registry info to file, for each flavor we publish
188+
for flavor in ("eager", "conservative")
189+
registries_path = joinpath(config.root, "static", "registries.$(flavor)")
190+
if changed || !isfile(registries_path)
191+
new_registries = joinpath(config.root, "temp", "registries.$(flavor).tmp." * randstring())
192+
mkpath(dirname(new_registries))
193+
open(new_registries, "w") do io
194+
for uuid in sort!(collect(keys(config.registries)))
195+
if haskey(config.registries[uuid].hashes, flavor)
196+
println(io, "/registry/$(uuid)/$(config.registries[uuid].hashes[flavor])")
197+
end
198+
end
195199
end
200+
mkpath(dirname(registries_path))
201+
mv(new_registries, registries_path; force=true)
196202
end
197-
mkpath(dirname(registries_path))
198-
mv(new_registries, registries_path; force=true)
199203
end
200204
return changed
201205
end

test/tests.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@ while true
5252
end
5353

5454
@testset "Direct HTTP requests" begin
55-
# Test that we get a sensical answer for /registries
56-
response = HTTP.get("$(server_url)/registries")
57-
@test response.status == 200
58-
registry_url = chomp(String(response.body))
59-
@test match(r"^/registry/([a-z0-9]+-){4}[a-z0-9]+/[a-z0-9]+$", registry_url) != nothing
60-
registry_uuid, registry_treehash = split(registry_url, "/")[3:4]
55+
# Test that we get a sensical answer for /registries{,.eager,.conservative}
56+
local registry_uuid, registry_treehash
57+
for registry_flavor in ("registries", "registries.eager", "registries.conservative")
58+
response = HTTP.get("$(server_url)/$(registry_flavor)")
59+
@test response.status == 200
60+
registry_url = chomp(String(response.body))
61+
@test match(r"^/registry/([a-z0-9]+-){4}[a-z0-9]+/[a-z0-9]+$", registry_url) !== nothing
62+
registry_uuid, registry_treehash = split(registry_url, "/")[3:4]
63+
end
6164

6265
# Test asking for that registry directly, unpacking it and verifying the treehash
6366
mktemp() do tarball_path, tarball_io
@@ -68,7 +71,8 @@ end
6871
end
6972

7073
# Verify that these files exist within the cache
71-
@test isfile(joinpath(cache_dir, "..", "static", "registries"))
74+
@test isfile(joinpath(cache_dir, "..", "static", "registries.eager"))
75+
@test isfile(joinpath(cache_dir, "..", "static", "registries.conservative"))
7276
@test isfile(joinpath(cache_dir, "registry", registry_uuid, registry_treehash))
7377

7478
# Next, hit the `/meta` endpoint, ensure that the version it reports matches with what we expect:

0 commit comments

Comments
 (0)