From 610783777c1a6b273289cb7f1cc494f640671b9e Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Wed, 26 Mar 2025 14:55:48 +1300 Subject: [PATCH 1/3] perf: do not initialize _LOCAL_TZ in __init__ --- src/JuliaHub.jl | 11 ++++------- src/utils.jl | 23 +++++++++++++++-------- test/utils.jl | 10 ++++++---- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/JuliaHub.jl b/src/JuliaHub.jl index 4121e06d8..1b4955a0f 100644 --- a/src/JuliaHub.jl +++ b/src/JuliaHub.jl @@ -13,6 +13,10 @@ import TOML import URIs import UUIDs +# We cache the local timezone in a global, so that we don't have to call +# TimeZones.localzone() every time we do a TZ operation. However, we only +# populate this when we actually call _localtz(). We used to do this in __init_, +# but that caused a noticeable startup lag. const _LOCAL_TZ = Ref{Dates.TimeZone}() include("utils.jl") @@ -31,13 +35,6 @@ include("jobs/logging.jl") include("jobs/logging-kafka.jl") include("jobs/logging-legacy.jl") -function __init__() - # We'll only attempt to determine the local timezone once, when the package loads, - # and store the result in a global. This way all timestamps will have consistent timezones - # even if something in the environment changes. - _LOCAL_TZ[] = _localtz() -end - # JuliaHub.jl follows the convention that all private names are # prefixed with an underscore. function _find_public_names() diff --git a/src/utils.jl b/src/utils.jl index e08561bdc..3d5cde543 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -378,7 +378,7 @@ end _utc2localtz(timestamp::Number) = _utc2localtz(Dates.unix2datetime(timestamp)) function _utc2localtz(datetime_utc::Dates.DateTime)::TimeZones.ZonedDateTime datetimez_utc = TimeZones.ZonedDateTime(datetime_utc, TimeZones.tz"UTC") - return TimeZones.astimezone(datetimez_utc, _LOCAL_TZ[]) + return TimeZones.astimezone(datetimez_utc, _localtz()) end # Special version of _utc2localtz to handle integer ms timestamp function _ms_utc2localtz(timestamp::Integer)::TimeZones.ZonedDateTime @@ -427,17 +427,24 @@ function _parse_tz(timestamp_str::AbstractString; msg::Union{AbstractString, Not end throw(JuliaHubError(errmsg)) end - return TimeZones.astimezone(timestamp, _LOCAL_TZ[]) + return TimeZones.astimezone(timestamp, _localtz()) end # It's quite easy to make TimeZones.localzone() fail and throw. # So this wraps it, and adds a UTC fallback (which seems like the sensible # default) in the case where somehow the local timezone is not configured properly. -function _localtz() - try - TimeZones.localzone() - catch e - @debug "Unable to determine local timezone" exception = (e, catch_backtrace()) - TimeZones.tz"UTC" +function _localtz()::Dates.TimeZone + global _LOCAL_TZ + if isassigned(_LOCAL_TZ) + return _LOCAL_TZ[] + else + tz = try + TimeZones.localzone() + catch e + @debug "Unable to determine local timezone" exception = (e, catch_backtrace()) + TimeZones.tz"UTC" + end + _LOCAL_TZ[] = tz + return tz end end diff --git a/test/utils.jl b/test/utils.jl index 8d004e69b..cbb6c9bc4 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -52,26 +52,28 @@ end end @testset "_parse_tz" begin + @test JuliaHub._localtz() isa Dates.TimeZone @test isassigned(JuliaHub._LOCAL_TZ) + @test JuliaHub._localtz() === JuliaHub._LOCAL_TZ[] let t = JuliaHub._parse_tz("2022-10-12T05:30:31.1+00:00") @test t isa TimeZones.ZonedDateTime - @test t.timezone == JuliaHub._LOCAL_TZ[] + @test t.timezone == JuliaHub._localtz() @test Dates.millisecond(t) == 100 end let t = JuliaHub._parse_tz("2022-10-12T05:30:31.12+00:00") @test t isa TimeZones.ZonedDateTime - @test t.timezone == JuliaHub._LOCAL_TZ[] + @test t.timezone == JuliaHub._localtz() @test Dates.millisecond(t) == 120 end let t = JuliaHub._parse_tz("2022-10-12T05:30:31.123+00:00") @test t isa TimeZones.ZonedDateTime - @test t.timezone == JuliaHub._LOCAL_TZ[] + @test t.timezone == JuliaHub._localtz() @test Dates.millisecond(t) == 123 end @test_throws JuliaHub.JuliaHubError JuliaHub._parse_tz("2022-10-12T05:30:31.+00:00") let t = JuliaHub._parse_tz("2022-10-12T05:30:31+00:00") @test t isa TimeZones.ZonedDateTime - @test t.timezone == JuliaHub._LOCAL_TZ[] + @test t.timezone == JuliaHub._localtz() @test Dates.millisecond(t) == 0 end @test_throws JuliaHub.JuliaHubError JuliaHub._parse_tz("") From d5d5059cd4d34f260653d7c3820371cc979b9f43 Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Wed, 26 Mar 2025 15:06:49 +1300 Subject: [PATCH 2/3] add to _authenticate --- src/authentication.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/authentication.jl b/src/authentication.jl index cb879b761..23f125fe8 100644 --- a/src/authentication.jl +++ b/src/authentication.jl @@ -259,6 +259,15 @@ end function _authenticate( server_uri::URIs.URI; force::Bool, maxcount::Integer, hook::Union{Base.Callable, Nothing} ) + # So this is a bit weird, but we want to ensure that the global _LOCAL_TZ[] is initialized + # in a somewhat reliable way. Generally, constructing the authentication object is the first + # thing that you do in a session, so we just call _localtz() here, even though we don't + # need it. This will ensure that the _LOCAL_TZ[] timezone object "cache" is populated + # as soon as you start using JuliaHub.jl, but _not_ when you load it, due to the effect + # that has on load time -- this function is pretty heavy, so the _localtz() call is not + # significant anyway. + _localtz() + isnothing(hook) || PkgAuthentication.register_open_browser_hook(hook) try # _authenticate either returns a valid token, or throws From 9beb0629044db8f96537ff5a070e2fea92249863 Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Wed, 26 Mar 2025 15:09:09 +1300 Subject: [PATCH 3/3] comments --- src/utils.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 3d5cde543..4ebb209b4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -430,14 +430,16 @@ function _parse_tz(timestamp_str::AbstractString; msg::Union{AbstractString, Not return TimeZones.astimezone(timestamp, _localtz()) end -# It's quite easy to make TimeZones.localzone() fail and throw. -# So this wraps it, and adds a UTC fallback (which seems like the sensible -# default) in the case where somehow the local timezone is not configured properly. +# This function is internally used where we need to pass the local timezone +# for datetime printing or parsing functions. function _localtz()::Dates.TimeZone global _LOCAL_TZ if isassigned(_LOCAL_TZ) return _LOCAL_TZ[] else + # It's quite easy to make TimeZones.localzone() fail and throw. + # So this wraps it, and adds a UTC fallback (which seems like the sensible + # default) in the case where somehow the local timezone is not configured properly. tz = try TimeZones.localzone() catch e