Skip to content

Commit

Permalink
Run thread-safety checks when Julia is using threads (JuliaTime#387)
Browse files Browse the repository at this point in the history
* Run thread-safety checks when Julia is using threads

* Use 8-threads specifically

* Reset cache for thread-safety checks
  • Loading branch information
omus authored and kpamnany committed May 5, 2023
1 parent d16f0b6 commit 8731d0a
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 63 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ jobs:
TZDATA_VERSION: 2016j # Matches tzdata version used in tests
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
env:
JULIA_NUM_THREADS: "8" # Can probably use "auto" here instead
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v2
with:
Expand Down
10 changes: 9 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@ include("helpers.jl")
include("rounding.jl")
include("parse.jl")
include("plotting.jl")
include("thread-safety.jl")

is_apple_silicon = first(Sys.cpu_info()).model == "Apple M1"
if is_apple_silicon && Threads.nthreads() == 8
@info "Thread safety tests can hang on Apple Silicon when using 8-threads. Use 7-threads instead"
elseif Threads.nthreads() == 1
@info "Skipping thread safety check as Julia was started with a single thread"
else
include("thread-safety.jl")
end

# Note: Run the build tests last to ensure that re-compiling the time zones files
# doesn't interfere with other tests.
Expand Down
105 changes: 43 additions & 62 deletions test/thread-safety.jl
Original file line number Diff line number Diff line change
@@ -1,72 +1,53 @@
# Test that TimeZones.jl can be safely used in a multithreaded environment.
# Note that the number of threads being used cannot be changed dynamically, so
# this test file spawns a new julia process running with multiple threads.

using Test
@testset "Thread safety" begin
# Reset the cache before running the thread-safety tests to increase the chance of an
# issue occurring when multiple threads load data at the same time.
TimeZones._reset_tz_cache()

@testset "Multithreaded TimeZone brute force test" begin
function create_zdt(year, month, day, tz_name)
ZonedDateTime(DateTime(year, month, day), TimeZone(tz_name))
end
function cycle_zdts()
return [
try
create_zdt(year, month, day, tz_name)
catch e
# Ignore ZonedDateTimes that aren't valid
e isa Union{ArgumentError,AmbiguousTimeError,NonExistentTimeError} || rethrow()
nothing
end
for year in 2000:2020
for month in 1:5
for day in 10:15
for tz_name in timezone_names()
]
end

const program = """
using TimeZones
using Test
outputs = Channel(Inf)
@sync begin
for _ in 1:15
Threads.@spawn begin
put!(outputs, cycle_zdts())
end
end
end
close(outputs)

@assert Threads.nthreads() > 1 "This system does not support multiple threads, so the thread-safety tests cannot be run."
tzs = collect(outputs)

@testset "Multithreaded TimeZone brute force test" begin
function create_zdt(year, month, day, tz_name)
ZonedDateTime(DateTime(year, month, day), TimeZone(tz_name))
end
function cycle_zdts()
return [
try
create_zdt(year, month, day, tz_name)
catch e
# Ignore ZonedDateTimes that aren't valid
e isa Union{ArgumentError,AmbiguousTimeError,NonExistentTimeError} || rethrow()
nothing
end
for year in 2000:2020
for month in 1:5
for day in 10:15
for tz_name in timezone_names()
]
# Test that every Task produced the same result
allsame(x) = all(y -> y == first(x), x)
@test allsame(tzs)
end

outputs = Channel(Inf)
@sync begin
for _ in 1:15
@testset "Interleaved compile() and TimeZone construction" begin
@sync for i in 1:20
if (i % 5 == 0)
TimeZones.TZData.compile()
end
Threads.@spawn begin
put!(outputs, cycle_zdts())
TimeZone("US/Eastern", TimeZones.Class(:LEGACY))
end
end
end
close(outputs)
tzs = collect(outputs)
# Test that every Task produced the same result
allsame(x) = all(y -> y == first(x), x)
@test allsame(tzs)
end
#----------------------------------------------------
@testset "Interleaved compile() and TimeZone construction" begin
@sync for i in 1:20
if (i % 5 == 0)
TimeZones.TZData.compile()
end
Threads.@spawn begin
TimeZone("US/Eastern", TimeZones.Class(:LEGACY))
end
end
end
"""

@info "Running Thread Safety tests"
@testset "Multithreaded TimeZone construction" begin
# Workaround for Apple Silicon hanging when using 8 threads
nthreads = first(Sys.cpu_info()).model == "Apple M1" ? 7 : 8

withenv("JULIA_NUM_THREADS" => nthreads) do
run(`$(Base.julia_cmd()) --proj -E $(program)`)
end
end

0 comments on commit 8731d0a

Please sign in to comment.