From c71bab16a55a09fb3153eb04d1a6bc5714f39e0d Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 27 May 2024 09:30:45 -0500 Subject: [PATCH] Move TimeZoneCache to a separate file (#466) --- src/TimeZones.jl | 5 ++- src/types/timezone.jl | 77 -------------------------------------- src/types/timezonecache.jl | 73 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 79 deletions(-) create mode 100644 src/types/timezonecache.jl diff --git a/src/TimeZones.jl b/src/TimeZones.jl index 4162962b..b8eb6c51 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -33,7 +33,7 @@ export TimeZone, @tz_str, istimezone, FixedTimeZone, VariableTimeZone, ZonedDate _scratch_dir() = @get_scratch!("build") -const _COMPILED_DIR = Ref{String}() +const _COMPILED_DIR = Ref{String}(TZJData.ARTIFACT_DIR) # TimeZone types used to disambiguate the context of a DateTime # abstract type UTC <: TimeZone end # Already defined in the Dates stdlib @@ -53,9 +53,10 @@ include("indexable_generator.jl") include("class.jl") include("utcoffset.jl") +include(joinpath("types", "timezone.jl")) include(joinpath("types", "fixedtimezone.jl")) include(joinpath("types", "variabletimezone.jl")) -include(joinpath("types", "timezone.jl")) +include(joinpath("types", "timezonecache.jl")) include(joinpath("types", "zoneddatetime.jl")) include(joinpath("tzfile", "TZFile.jl")) include(joinpath("tzjfile", "TZJFile.jl")) diff --git a/src/types/timezone.jl b/src/types/timezone.jl index 0f7760c7..26dd330d 100644 --- a/src/types/timezone.jl +++ b/src/types/timezone.jl @@ -1,80 +1,3 @@ -# Use a separate cache for FixedTimeZone (which is `isbits`) so the container is concretely -# typed and we avoid allocating a FixedTimeZone every time we get one from the cache. -struct TimeZoneCache - ftz::Dict{String,Tuple{FixedTimeZone,Class}} - vtz::Dict{String,Tuple{VariableTimeZone,Class}} - lock::ReentrantLock - initialized::Threads.Atomic{Bool} -end - -TimeZoneCache() = TimeZoneCache(Dict(), Dict(), ReentrantLock(), Threads.Atomic{Bool}(false)) - -# Retains the compiled tzdata in memory. Read-only access to the cache is thread-safe and -# any changes to this structure can result in inconsistent behaviour. Do not access this -# object directly, instead use `get` to access the cache content. -const _TZ_CACHE = TimeZoneCache() - -function Base.copy!(dst::TimeZoneCache, src::TimeZoneCache) - copy!(dst.ftz, src.ftz) - copy!(dst.vtz, src.vtz) - dst.initialized[] = src.initialized[] - return dst -end - -function reload!(cache::TimeZoneCache, compiled_dir::AbstractString=_COMPILED_DIR[]) - empty!(cache.ftz) - empty!(cache.vtz) - - walk_tz_dir(compiled_dir) do name, path - tz, class = open(TZJFile.read, path, "r")(name) - - if tz isa FixedTimeZone - cache.ftz[name] = (tz, class) - elseif tz isa VariableTimeZone - cache.vtz[name] = (tz, class) - else - error("Unhandled TimeZone class encountered: $(typeof(tz))") - end - end - - !isempty(cache.ftz) && !isempty(cache.vtz) || error("Cache remains empty after loading") - - return cache -end - -function Base.get(body::Function, cache::TimeZoneCache, name::AbstractString) - if !cache.initialized[] - lock(cache.lock) do - if !cache.initialized[] - _initialize() - reload!(cache) - cache.initialized[] = true - end - end - end - - return get(cache.ftz, name) do - get(cache.vtz, name) do - body() - end - end -end - -function _initialize() - # Write out our compiled tzdata representations into a scratchspace - desired_version = TZData.tzdata_version() - - _COMPILED_DIR[] = if desired_version == TZJData.TZDATA_VERSION - TZJData.ARTIFACT_DIR - else - TZData.build(desired_version, _scratch_dir()) - end - - return nothing -end - -_reload_tz_cache(compiled_dir::AbstractString) = reload!(_TZ_CACHE, compiled_dir) - """ TimeZone(str::AbstractString) -> TimeZone diff --git a/src/types/timezonecache.jl b/src/types/timezonecache.jl new file mode 100644 index 00000000..750a038f --- /dev/null +++ b/src/types/timezonecache.jl @@ -0,0 +1,73 @@ +# Use a separate cache for FixedTimeZone (which is `isbits`) so the container is concretely +# typed and we avoid allocating a FixedTimeZone every time we get one from the cache. +struct TimeZoneCache + ftz::Dict{String,Tuple{FixedTimeZone,Class}} + vtz::Dict{String,Tuple{VariableTimeZone,Class}} + lock::ReentrantLock + initialized::Threads.Atomic{Bool} +end + +TimeZoneCache() = TimeZoneCache(Dict(), Dict(), ReentrantLock(), Threads.Atomic{Bool}(false)) + +# Retains the compiled tzdata in memory. Read-only access to the cache is thread-safe and +# any changes to this structure can result in inconsistent behaviour. Do not access this +# object directly, instead use `get` to access the cache content. +const _TZ_CACHE = TimeZoneCache() + +function Base.copy!(dst::TimeZoneCache, src::TimeZoneCache) + copy!(dst.ftz, src.ftz) + copy!(dst.vtz, src.vtz) + dst.initialized[] = src.initialized[] + return dst +end + +function reload!(cache::TimeZoneCache, compiled_dir::AbstractString=_COMPILED_DIR[]) + empty!(cache.ftz) + empty!(cache.vtz) + + walk_tz_dir(compiled_dir) do name, path + tz, class = open(TZJFile.read, path, "r")(name) + + if tz isa FixedTimeZone + cache.ftz[name] = (tz, class) + elseif tz isa VariableTimeZone + cache.vtz[name] = (tz, class) + else + error("Unhandled TimeZone class encountered: $(typeof(tz))") + end + end + + !isempty(cache.ftz) && !isempty(cache.vtz) || error("Cache remains empty after loading") + + return cache +end + +function Base.get(body::Function, cache::TimeZoneCache, name::AbstractString) + if !cache.initialized[] + lock(cache.lock) do + if !cache.initialized[] + _build() + reload!(cache) + cache.initialized[] = true + end + end + end + + return get(cache.ftz, name) do + get(cache.vtz, name) do + body() + end + end +end + +# Build specific tzdata version if specified by `JULIA_TZ_VERSION` +function _build() + desired_version = TZData.tzdata_version() + if desired_version != TZJData.TZDATA_VERSION + _COMPILED_DIR[] = TZData.build(desired_version, _scratch_dir()) + end + + return nothing +end + +_reload_tz_cache(compiled_dir::AbstractString) = reload!(_TZ_CACHE, compiled_dir)