Skip to content

Commit efa9e73

Browse files
use FileWatching.mkpidlock for atomic usage and registry writes
1 parent 544bb89 commit efa9e73

File tree

5 files changed

+142
-48
lines changed

5 files changed

+142
-48
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ version = "1.8.0"
99
Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
1010
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
1111
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
12+
FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
1213
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
1314
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
1415
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,160 @@
11
# This file is machine-generated - editing it directly is not advised
22

3-
[[ArgTools]]
3+
julia_version = "1.8.0-DEV.1456"
4+
manifest_format = "2.0"
5+
project_hash = "136cd253a89b81157598e253c00b82898978de50"
6+
7+
[[deps.ArgTools]]
48
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
9+
version = "1.1.1"
510

6-
[[Artifacts]]
11+
[[deps.Artifacts]]
712
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
813

9-
[[Base64]]
14+
[[deps.Base64]]
1015
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
1116

12-
[[Dates]]
17+
[[deps.Dates]]
1318
deps = ["Printf"]
1419
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
1520

16-
[[Downloads]]
17-
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
18-
git-tree-sha1 = "5a19556930ddb432b1094e0796911c16282c517f"
21+
[[deps.Downloads]]
22+
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
1923
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
24+
version = "1.6.0"
25+
26+
[[deps.FileWatching]]
27+
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
2028

21-
[[InteractiveUtils]]
29+
[[deps.InteractiveUtils]]
2230
deps = ["Markdown"]
2331
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
2432

25-
[[JSON3]]
33+
[[deps.JSON3]]
2634
deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"]
2735
git-tree-sha1 = "961ef1c3e5c8a595d5bec270a9007429ef12ed10"
2836
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
2937
version = "1.5.1"
3038

31-
[[LibCURL]]
39+
[[deps.LibCURL]]
3240
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
3341
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
42+
version = "0.6.3"
3443

35-
[[LibCURL_jll]]
44+
[[deps.LibCURL_jll]]
3645
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
3746
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
47+
version = "7.73.0+4"
3848

39-
[[LibGit2]]
49+
[[deps.LibGit2]]
4050
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
4151
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
4252

43-
[[LibSSH2_jll]]
53+
[[deps.LibSSH2_jll]]
4454
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
4555
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
56+
version = "1.9.1+2"
4657

47-
[[Libdl]]
58+
[[deps.Libdl]]
4859
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
4960

50-
[[Logging]]
61+
[[deps.Logging]]
5162
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
5263

53-
[[Markdown]]
64+
[[deps.Markdown]]
5465
deps = ["Base64"]
5566
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
5667

57-
[[MbedTLS_jll]]
68+
[[deps.MbedTLS_jll]]
5869
deps = ["Artifacts", "Libdl"]
5970
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
71+
version = "2.24.0+2"
6072

61-
[[Mmap]]
73+
[[deps.Mmap]]
6274
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
6375

64-
[[MozillaCACerts_jll]]
76+
[[deps.MozillaCACerts_jll]]
6577
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
78+
version = "2020.7.22"
6679

67-
[[NetworkOptions]]
80+
[[deps.NetworkOptions]]
6881
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
82+
version = "1.2.0"
6983

70-
[[Parsers]]
84+
[[deps.Parsers]]
7185
deps = ["Dates"]
7286
git-tree-sha1 = "50c9a9ed8c714945e01cd53a21007ed3865ed714"
7387
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
7488
version = "1.0.15"
7589

76-
[[Pkg]]
77-
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
90+
[[deps.Pkg]]
91+
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
7892
path = "../.."
7993
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78e"
80-
version = "1.5.0"
94+
version = "1.8.0"
8195

82-
[[Printf]]
96+
[[deps.Printf]]
8397
deps = ["Unicode"]
8498
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
8599

86-
[[REPL]]
100+
[[deps.REPL]]
87101
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
88102
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
89103

90-
[[Random]]
91-
deps = ["Serialization"]
104+
[[deps.Random]]
105+
deps = ["SHA", "Serialization"]
92106
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
93107

94-
[[SHA]]
108+
[[deps.SHA]]
95109
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
110+
version = "0.7.0"
96111

97-
[[Scratch]]
112+
[[deps.Scratch]]
98113
deps = ["Dates"]
99114
git-tree-sha1 = "ad4b278adb62d185bbcb6864dc24959ab0627bf6"
100115
uuid = "6c6a2e73-6563-6170-7368-637461726353"
101116
version = "1.0.3"
102117

103-
[[Serialization]]
118+
[[deps.Serialization]]
104119
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
105120

106-
[[Sockets]]
121+
[[deps.Sockets]]
107122
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
108123

109-
[[StructTypes]]
124+
[[deps.StructTypes]]
110125
deps = ["Dates", "UUIDs"]
111126
git-tree-sha1 = "d94235fcdc4a09649f263365c5f7e4ed4ba6ed34"
112127
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
113128
version = "1.2.1"
114129

115-
[[TOML]]
130+
[[deps.TOML]]
116131
deps = ["Dates"]
117132
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
133+
version = "1.0.0"
118134

119-
[[Tar]]
135+
[[deps.Tar]]
120136
deps = ["ArgTools", "SHA"]
121137
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
138+
version = "1.10.0"
122139

123-
[[UUIDs]]
140+
[[deps.UUIDs]]
124141
deps = ["Random", "SHA"]
125142
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
126143

127-
[[Unicode]]
144+
[[deps.Unicode]]
128145
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
129146

130-
[[Zlib_jll]]
147+
[[deps.Zlib_jll]]
131148
deps = ["Libdl"]
132149
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
150+
version = "1.2.12+1"
133151

134-
[[nghttp2_jll]]
152+
[[deps.nghttp2_jll]]
135153
deps = ["Artifacts", "Libdl"]
136154
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
155+
version = "1.41.0+1"
137156

138-
[[p7zip_jll]]
157+
[[deps.p7zip_jll]]
139158
deps = ["Artifacts", "Libdl"]
140159
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
160+
version = "16.2.1+1"

src/Registry/Registry.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ using ..Pkg: depots1, printpkgstyle, stderr_f, isdir_nothrow, pathrepr, pkg_serv
55
GitTools, get_bool_env
66
using ..Pkg.PlatformEngines: download_verify_unpack, download, download_verify, exe7z
77
using UUIDs, LibGit2, TOML
8+
import FileWatching
89

910
include("registry_instance.jl")
1011

@@ -163,6 +164,8 @@ function download_registries(io::IO, regs::Vector{RegistrySpec}, depot::String=d
163164
populate_known_registries_with_urls!(regs)
164165
regdir = joinpath(depot, "registries")
165166
isdir(regdir) || mkpath(regdir)
167+
FileWatching.mkpidlock(joinpath(regdir, ".pid"), stale_age = 10) do
168+
# only allow one julia process to download and install registries at a time
166169
registry_urls = pkg_server_registry_urls()
167170
for reg in regs
168171
if reg.path !== nothing && reg.url !== nothing
@@ -250,6 +253,7 @@ function download_registries(io::IO, regs::Vector{RegistrySpec}, depot::String=d
250253
end
251254
end
252255
end
256+
end
253257
return nothing
254258
end
255259

@@ -348,6 +352,8 @@ function update(regs::Vector{RegistrySpec} = RegistrySpec[]; io::IO=stderr_f(),
348352
for reg in unique(r -> r.uuid, find_installed_registries(io, regs); seen=Set{UUID}())
349353
let reg=reg, errors=errors
350354
regpath = pathrepr(reg.path)
355+
FileWatching.mkpidlock(joinpath(dirname(dirname(reg.path)), ".$(reg.name).pid"), stale_age = 10) do
356+
# only allow one julia process to update a registry at a time
351357
let regpath=regpath
352358
if reg.tree_info !== nothing
353359
printpkgstyle(io, :Updating, "registry at " * regpath)
@@ -439,6 +445,7 @@ function update(regs::Vector{RegistrySpec} = RegistrySpec[]; io::IO=stderr_f(),
439445
end
440446
end
441447
end
448+
end
442449
end
443450
end
444451
if !isempty(errors)

src/Types.jl

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import ..Pkg, ..Registry
1515
import ..Pkg: GitTools, depots, depots1, logdir, set_readonly, safe_realpath, pkg_server, stdlib_dir, stdlib_path, isurl, stderr_f, RESPECT_SYSIMAGE_VERSIONS
1616
import Base.BinaryPlatforms: Platform
1717
using ..Pkg.Versions
18+
import FileWatching
1819

1920
import Base: SHA1
2021
using SHA
@@ -484,16 +485,31 @@ function write_env_usage(source_file::AbstractString, usage_filepath::AbstractSt
484485
# Ensure that log dir exists
485486
!ispath(logdir()) && mkpath(logdir())
486487

487-
# Generate entire entry as a string first
488-
entry = sprint() do io
489-
TOML.print(io, Dict(source_file => [Dict("time" => now())]))
490-
end
491-
492-
# Append entry to log file in one chunk
493488
usage_file = joinpath(logdir(), usage_filepath)
494-
open(usage_file, append=true) do io
495-
write(io, entry)
489+
timestamp = now()
490+
491+
## Atomically write usage file using process id locking
492+
FileWatching.mkpidlock(usage_file * ".pid", stale_age = 3) do
493+
usage = if isfile(usage_file)
494+
TOML.parsefile(usage_file)
495+
else
496+
Dict{String, Any}()
497+
end
498+
499+
# record new usage
500+
usage[source_file] = [Dict("time" => timestamp)]
501+
502+
# keep only latest usage info
503+
for k in keys(usage)
504+
times = map(d -> Dates.DateTime(d["time"]), usage[k])
505+
usage[k] = [Dict("time" => maximum(times))]
506+
end
507+
508+
open(usage_file, "w") do io
509+
TOML.print(io, usage, sorted=true)
510+
end
496511
end
512+
return
497513
end
498514

499515
function read_package(path::String)

test/pkg.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,56 @@ temp_pkg_dir() do project_path
363363
@test any(x -> startswith(x, manifest), keys(usage))
364364
end
365365

366+
@testset "test atomicity of write_env_usage with $(Sys.CPU_THREADS) parallel processes" begin
367+
tasks = Task[]
368+
iobs = IOBuffer[]
369+
Sys.CPU_THREADS == 1 && error("Cannot test for atomic usage log file interaction effectively with only Sys.CPU_THREADS=1")
370+
run(`$(Base.julia_cmd()) --project="$(pkgdir(Pkg))" -e "import Pkg"`) # to precompile Pkg given we're in a different depot
371+
flag_start_dir = tempdir() # once n=Sys.CPU_THREADS files are in here, the processes can proceed to the concurrent test
372+
flag_end_file = tempname() # use creating this file as a way to stop the processes early if an error happens
373+
for i in 1:Sys.CPU_THREADS
374+
iob = IOBuffer()
375+
t = @async run(pipeline(`$(Base.julia_cmd()[1]) --project="$(pkgdir(Pkg))"
376+
-e "import Pkg;
377+
Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true;
378+
Pkg.activate(temp = true);
379+
Pkg.add(\"Random\", io = devnull);
380+
touch(tempname(raw\"$flag_start_dir\")) # file marker that first part has finished
381+
while length(readdir(raw\"$flag_start_dir\")) < $(Sys.CPU_THREADS)
382+
# sync all processes to start at the same time
383+
sleep(0.1)
384+
end
385+
@async begin
386+
sleep(15)
387+
touch(raw\"$flag_end_file\")
388+
end
389+
i = 0
390+
while !isfile(raw\"$flag_end_file\")
391+
global i += 1
392+
try
393+
Pkg.Types.EnvCache()
394+
catch
395+
touch(raw\"$flag_end_file\")
396+
println(stderr, \"Errored after $i iterations\")
397+
rethrow()
398+
end
399+
yield()
400+
end"`,
401+
stderr = iob, stdout = devnull))
402+
push!(tasks, t)
403+
push!(iobs, iob)
404+
end
405+
for i in eachindex(tasks)
406+
try
407+
fetch(tasks[i]) # If any of these failed it will throw when fetched
408+
catch
409+
print(String(take!(iobs[i])))
410+
break
411+
end
412+
end
413+
@test any(istaskfailed, tasks) == false
414+
end
415+
366416
@testset "adding nonexisting packages" begin
367417
nonexisting_pkg = randstring(14)
368418
@test_throws PkgError Pkg.add(nonexisting_pkg)

0 commit comments

Comments
 (0)