|
1 | | -const TIME_ZONE_CACHE = Dict{String,Tuple{TimeZone,Class}}() |
| 1 | +# Thread-local TimeZone cache, which caches time zones _per thread_, allowing thread-safe |
| 2 | +# caching. Note that this means the cache will grow in size, and may store redundant objects |
| 3 | +# accross multiple threads, but this extra space usage allows for fast, lock-free access |
| 4 | +# to the cache, while still being thread-safe. |
| 5 | +const THREAD_TZ_CACHES = Vector{Dict{String,Tuple{TimeZone,Class}}}() |
| 6 | + |
| 7 | +# Based upon the thread-safe Global RNG implementation in the Random stdlib: |
| 8 | +# https://github.com/JuliaLang/julia/blob/e4fcdf5b04fd9751ce48b0afc700330475b42443/stdlib/Random/src/RNGs.jl#L369-L385 |
| 9 | +@inline _tz_cache() = _tz_cache(Threads.threadid()) |
| 10 | +@noinline function _tz_cache(tid::Int) |
| 11 | + 0 < tid <= length(THREAD_TZ_CACHES) || _tz_cache_length_assert() |
| 12 | + if @inbounds isassigned(THREAD_TZ_CACHES, tid) |
| 13 | + @inbounds cache = THREAD_TZ_CACHES[tid] |
| 14 | + else |
| 15 | + cache = eltype(THREAD_TZ_CACHES)() |
| 16 | + @inbounds THREAD_TZ_CACHES[tid] = cache |
| 17 | + end |
| 18 | + return cache |
| 19 | +end |
| 20 | +@noinline _tz_cache_length_assert() = @assert false "0 < tid <= length(THREAD_TZ_CACHES)" |
| 21 | + |
| 22 | +function _reset_tz_cache() |
| 23 | + # ensures that we didn't save a bad object |
| 24 | + resize!(empty!(THREAD_TZ_CACHES), Threads.nthreads()) |
| 25 | +end |
2 | 26 |
|
3 | 27 | """ |
4 | 28 | TimeZone(str::AbstractString) -> TimeZone |
@@ -43,7 +67,7 @@ TimeZone(::AbstractString, ::Class) |
43 | 67 | function TimeZone(str::AbstractString, mask::Class=Class(:DEFAULT)) |
44 | 68 | # Note: If the class `mask` does not match the time zone we'll still load the |
45 | 69 | # information into the cache to ensure the result is consistent. |
46 | | - tz, class = get!(TIME_ZONE_CACHE, str) do |
| 70 | + tz, class = get!(_tz_cache(), str) do |
47 | 71 | tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) |
48 | 72 |
|
49 | 73 | if isfile(tz_path) |
@@ -98,12 +122,12 @@ function istimezone(str::AbstractString, mask::Class=Class(:DEFAULT)) |
98 | 122 | end |
99 | 123 |
|
100 | 124 | # Perform more expensive checks against pre-compiled time zones |
101 | | - tz, class = get(TIME_ZONE_CACHE, str) do |
| 125 | + tz, class = get(_tz_cache(), str) do |
102 | 126 | tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) |
103 | 127 |
|
104 | 128 | if isfile(tz_path) |
105 | 129 | # Cache the data since we're already performing the deserialization |
106 | | - TIME_ZONE_CACHE[str] = open(deserialize, tz_path, "r") |
| 130 | + _tz_cache()[str] = open(deserialize, tz_path, "r") |
107 | 131 | else |
108 | 132 | nothing, Class(:NONE) |
109 | 133 | end |
|
0 commit comments