From a3f0e9f758f28b1fb380c57bd7188a58e9cfe05c Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Mon, 1 Aug 2022 11:03:26 -0400 Subject: [PATCH 01/17] Avoid ICU loading via StringComparison.Ordinal --- .../src/System/TimeZoneInfo.Unix.Android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index e03c67beaa58d..f0065b0428741 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -107,7 +107,7 @@ private static int ParseGMTNumericZone(string name) { return new TimeZoneInfo(id, TimeSpan.FromSeconds(0), id, name, name, null, disableDaylightSavingTime:true); } - if (name.StartsWith("GMT", StringComparison.Ordinal)) + if (name[0] == 'G' && name[1] == 'M' && name[2] == 'T') { return new TimeZoneInfo(id, TimeSpan.FromSeconds(ParseGMTNumericZone(name)), id, name, name, null, disableDaylightSavingTime:true); } From a44084a386e441581bfb355115b1b6ac4c276c33 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 11 Aug 2022 16:25:45 -0400 Subject: [PATCH 02/17] Pass OffsetDateTime and monotonic clock through monovm --- .../Templates/MonoRunner.java | 7 +++-- .../AndroidAppBuilder/Templates/monodroid.c | 28 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java b/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java index 700d95e123f07..4bd5a68d41623 100644 --- a/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java +++ b/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; public class MonoRunner extends Instrumentation { @@ -88,7 +90,8 @@ public static int initialize(String entryPointLibName, String[] args, Context co unzipAssets(context, filesDir, "assets.zip"); Log.i("DOTNET", "MonoRunner initialize,, entryPointLibName=" + entryPointLibName); - return initRuntime(filesDir, cacheDir, testResultsDir, entryPointLibName, args); + int localDateTimeOffset = OffsetDateTime.now().getOffset().getTotalSeconds(); + return initRuntime(filesDir, cacheDir, testResultsDir, entryPointLibName, args, localDateTimeOffset); } @Override @@ -149,7 +152,7 @@ static void unzipAssets(Context context, String toPath, String zipName) { } } - static native int initRuntime(String libsDir, String cacheDir, String testResultsDir, String entryPointLibName, String[] args); + static native int initRuntime(String libsDir, String cacheDir, String testResultsDir, String entryPointLibName, String[] args, int local_date_time_offset); static native int setEnv(String key, String value); } diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 1d8d3f1bd529c..754e89b6123a6 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -203,7 +203,7 @@ cleanup_runtime_config (MonovmRuntimeConfigArguments *args, void *user_data) } int -mono_droid_runtime_init (const char* executable, int managed_argc, char* managed_argv[]) +mono_droid_runtime_init (const char* executable, int managed_argc, char* managed_argv[], int local_date_time_offset) { // NOTE: these options can be set via command line args for adb or xharness, see AndroidSampleApp.csproj @@ -225,13 +225,29 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed // TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES - const char* appctx_keys[2]; + const char* appctx_keys[4]; appctx_keys[0] = "RUNTIME_IDENTIFIER"; appctx_keys[1] = "APP_CONTEXT_BASE_DIRECTORY"; + appctx_keys[2] = "LOCAL_DATE_TIME_OFFSET"; + appctx_keys[3] = "KERNEL_MONOTONIC_CLOCK"; - const char* appctx_values[2]; + const char* appctx_values[4]; appctx_values[0] = ANDROID_RUNTIME_IDENTIFIER; appctx_values[1] = bundle_path; + char buffer_one[32]; + snprintf (buffer_one, sizeof(buffer_one), "%d", local_date_time_offset); + appctx_values[2] = strdup (buffer_one); + char buffer_two[64]; + struct timespec ts; + int result = clock_gettime (CLOCK_MONOTONIC, &ts); + int64_t kernel_monotonic_clock; + if (result == 0) + { + kernel_monotonic_clock = (int64_t)(ts.tv_sec * (int64_t)(1000000000)) + (int64_t)(ts.tv_nsec); + snprintf (buffer_two, sizeof(buffer_two), "%ld", kernel_monotonic_clock); + } + const char* mitch_two = strdup (buffer_two); + appctx_values[3] = mitch_two; char *file_name = RUNTIMECONFIG_BIN_FILE; int str_len = strlen (bundle_path) + strlen (file_name) + 1; // +1 is for the "/" @@ -251,7 +267,7 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed free (file_path); } - monovm_initialize(2, appctx_keys, appctx_values); + monovm_initialize(4, appctx_keys, appctx_values); mono_debug_init (MONO_DEBUG_FORMAT_MONO); mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL); @@ -318,7 +334,7 @@ Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstrin } int -Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_testresults_dir, jstring j_entryPointLibName, jobjectArray j_args) +Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_testresults_dir, jstring j_entryPointLibName, jobjectArray j_args, long current_local_time) { char file_dir[2048]; char cache_dir[2048]; @@ -347,7 +363,7 @@ Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_ managed_argv[i + 1] = (*env)->GetStringUTFChars(env, j_arg, NULL); } - int res = mono_droid_runtime_init (executable, managed_argc, managed_argv); + int res = mono_droid_runtime_init (executable, managed_argc, managed_argv, current_local_time); for (int i = 0; i < args_len; ++i) { From 1edaeeee37066ad392ab19100248994ee53b0797 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 11 Aug 2022 16:27:06 -0400 Subject: [PATCH 03/17] WIP Leverage monovm_initialize_preparsed --- src/mono/mono/mini/monovm.c | 2 ++ .../mono/jit/details/mono-private-unstable-types.h | 6 ++++++ src/tasks/AndroidAppBuilder/Templates/monodroid.c | 10 +++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/monovm.c b/src/mono/mono/mini/monovm.c index 144594276f218..686da9ad38e95 100644 --- a/src/mono/mono/mini/monovm.c +++ b/src/mono/mono/mini/monovm.c @@ -18,6 +18,7 @@ static MonoCoreTrustedPlatformAssemblies *trusted_platform_assemblies; static MonoCoreLookupPaths *native_lib_paths; static MonoCoreLookupPaths *app_paths; static MonoCoreLookupPaths *platform_resource_roots; +static MonoCoreLocalTime *local_time; static void mono_core_trusted_platform_assemblies_free (MonoCoreTrustedPlatformAssemblies *a) @@ -220,6 +221,7 @@ monovm_initialize_preparsed (MonoCoreRuntimeProperties *parsed_properties, int p trusted_platform_assemblies = parsed_properties->trusted_platform_assemblies; app_paths = parsed_properties->app_paths; native_lib_paths = parsed_properties->native_dll_search_directories; + local_time = parsed_properties->local_time; mono_loader_install_pinvoke_override (parsed_properties->pinvoke_override); finish_initialization (); diff --git a/src/native/public/mono/jit/details/mono-private-unstable-types.h b/src/native/public/mono/jit/details/mono-private-unstable-types.h index 64398893c56b7..41b1b6092d96e 100644 --- a/src/native/public/mono/jit/details/mono-private-unstable-types.h +++ b/src/native/public/mono/jit/details/mono-private-unstable-types.h @@ -45,10 +45,16 @@ typedef struct { char **dirs; } MonoCoreLookupPaths; +typedef struct { + uint64_t current_local_time; + uint64_t kernel_monotonic_clock; +} MonoCoreLocalTime; + typedef struct { MonoCoreTrustedPlatformAssemblies *trusted_platform_assemblies; MonoCoreLookupPaths *app_paths; MonoCoreLookupPaths *native_dll_search_directories; + MonoCoreLocalTime *local_time; PInvokeOverrideFn pinvoke_override; } MonoCoreRuntimeProperties; diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 754e89b6123a6..7d9c65c017468 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -249,6 +249,14 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed const char* mitch_two = strdup (buffer_two); appctx_values[3] = mitch_two; + MonoCoreLocalTime mclt = { + local_date_time_offset, + kernel_monotonic_clock, + }; + MonoCoreRuntimeProperties mcrp = { + &mclt, + }; + char *file_name = RUNTIMECONFIG_BIN_FILE; int str_len = strlen (bundle_path) + strlen (file_name) + 1; // +1 is for the "/" char *file_path = (char *)malloc (sizeof (char) * (str_len +1)); // +1 is for the terminating null character @@ -267,7 +275,7 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed free (file_path); } - monovm_initialize(4, appctx_keys, appctx_values); + monovm_initialize_preparsed(&mcrp, 4, appctx_keys, appctx_values); mono_debug_init (MONO_DEBUG_FORMAT_MONO); mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL); From 0712f573f27105d6b09172324c4e5dd6f0563dad Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 11 Aug 2022 17:28:52 -0400 Subject: [PATCH 04/17] Initial perf improvement implementation --- .../src/System/TimeZoneInfo.Unix.Android.cs | 41 +++++++++++++++++++ .../System/TimeZoneInfo.Unix.NonAndroid.cs | 7 ++++ .../src/System/TimeZoneInfo.Win32.cs | 7 ++++ .../src/System/TimeZoneInfo.cs | 7 ---- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index f0065b0428741..c200637775063 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -142,6 +142,47 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return Utc; } + private static TimeSpan? _localUtcOffset; + private static object _localUtcOffsetLock = new(); + // Shortcut for TimeZoneInfo.Local.GetUtcOffset + internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + if (_localUtcOffset != null) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + + if (_localUtcOffset == null) + { + lock (_localUtcOffsetLock) + { + if (_localUtcOffset == null) + { + Thread loadAndroidTZData = new Thread(() => { + CachedData cachedData = s_cachedData; + _localUtcOffset = cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + }); + loadAndroidTZData.IsBackground = true; + loadAndroidTZData.Start(); + Thread.Sleep(1000); + } + } + } + + object? localDateTimeOffset = AppContext.GetData("LOCAL_DATE_TIME_OFFSET"); + int localDateTimeOffsetSeconds; + if (localDateTimeOffset != null) + localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); + else + { + throw new Exception ("LOCAL_DATE_TIME_OFFSET NOT SET"); + } + long localDateTimeOffsetTicks = localDateTimeOffsetSeconds * 10000000; // 10^7 ticks per second + TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); + return offset; + } + private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs index a9d6c462610fe..c9dc759a65d43 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs @@ -24,6 +24,13 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return GetLocalTimeZoneFromTzFile(); } + // Shortcut for TimeZoneInfo.Local.GetUtcOffset + internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e) { value = null; diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index c0774303f3f91..473f1667707d3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -76,6 +76,13 @@ public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule? rule) } } + // Shortcut for TimeZoneInfo.Local.GetUtcOffset + internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + /// /// Returns a cloned array of AdjustmentRule objects /// diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 236442359f011..34586ef1bb304 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -312,13 +312,6 @@ public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) => public TimeSpan GetUtcOffset(DateTime dateTime) => GetUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); - // Shortcut for TimeZoneInfo.Local.GetUtcOffset - internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } - /// /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. /// From 96bf37faeed907999679d9bad2f0a4cfe4a30efc Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 18 Aug 2022 16:19:53 -0400 Subject: [PATCH 05/17] Cleanup GetLocalUtcOffset --- .../src/System/TimeZoneInfo.Unix.Android.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index c200637775063..72e1984f5ba7a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -144,6 +144,7 @@ private static TimeZoneInfo GetLocalTimeZoneCore() private static TimeSpan? _localUtcOffset; private static object _localUtcOffsetLock = new(); + private static Thread? _loadAndroidTZData; // Shortcut for TimeZoneInfo.Local.GetUtcOffset internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) { @@ -153,19 +154,24 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); } - if (_localUtcOffset == null) + if (_localUtcOffset == null && _loadAndroidTZData == null) { lock (_localUtcOffsetLock) { - if (_localUtcOffset == null) + if (_localUtcOffset != null) { - Thread loadAndroidTZData = new Thread(() => { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + if (_loadAndroidTZData == null) + { + _loadAndroidTZData = new Thread(() => { CachedData cachedData = s_cachedData; _localUtcOffset = cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + Thread.Sleep(1000); }); - loadAndroidTZData.IsBackground = true; - loadAndroidTZData.Start(); - Thread.Sleep(1000); + _loadAndroidTZData.IsBackground = true; + _loadAndroidTZData.Start(); } } } From 582c3595a270149c57804f06ba1b2e2af09bed3c Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Tue, 23 Aug 2022 13:30:31 -0400 Subject: [PATCH 06/17] Remove Kernel Monotonic Time app context key --- .../src/System/TimeZoneInfo.Unix.Android.cs | 12 +++------ .../jit/details/mono-private-unstable-types.h | 1 - .../AndroidAppBuilder/Templates/monodroid.c | 25 +++++-------------- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 72e1984f5ba7a..98364fc38bd4e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -177,14 +177,10 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption } object? localDateTimeOffset = AppContext.GetData("LOCAL_DATE_TIME_OFFSET"); - int localDateTimeOffsetSeconds; - if (localDateTimeOffset != null) - localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); - else - { - throw new Exception ("LOCAL_DATE_TIME_OFFSET NOT SET"); - } - long localDateTimeOffsetTicks = localDateTimeOffsetSeconds * 10000000; // 10^7 ticks per second + if (localDateTimeOffset == null) + throw new Exception("LOCAL_DATE_TIME_OFFSET NOT SET"); + long localDateTimeOffsetTicks = Convert.ToInt32(localDateTimeOffset) * 10000000; // 10^7 ticks per second + TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); return offset; } diff --git a/src/native/public/mono/jit/details/mono-private-unstable-types.h b/src/native/public/mono/jit/details/mono-private-unstable-types.h index 41b1b6092d96e..fddaf391c024c 100644 --- a/src/native/public/mono/jit/details/mono-private-unstable-types.h +++ b/src/native/public/mono/jit/details/mono-private-unstable-types.h @@ -47,7 +47,6 @@ typedef struct { typedef struct { uint64_t current_local_time; - uint64_t kernel_monotonic_clock; } MonoCoreLocalTime; typedef struct { diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 7d9c65c017468..32318c366d219 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -225,33 +225,20 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed // TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES - const char* appctx_keys[4]; + const char* appctx_keys[3]; appctx_keys[0] = "RUNTIME_IDENTIFIER"; appctx_keys[1] = "APP_CONTEXT_BASE_DIRECTORY"; appctx_keys[2] = "LOCAL_DATE_TIME_OFFSET"; - appctx_keys[3] = "KERNEL_MONOTONIC_CLOCK"; - const char* appctx_values[4]; + const char* appctx_values[3]; appctx_values[0] = ANDROID_RUNTIME_IDENTIFIER; appctx_values[1] = bundle_path; - char buffer_one[32]; - snprintf (buffer_one, sizeof(buffer_one), "%d", local_date_time_offset); - appctx_values[2] = strdup (buffer_one); - char buffer_two[64]; - struct timespec ts; - int result = clock_gettime (CLOCK_MONOTONIC, &ts); - int64_t kernel_monotonic_clock; - if (result == 0) - { - kernel_monotonic_clock = (int64_t)(ts.tv_sec * (int64_t)(1000000000)) + (int64_t)(ts.tv_nsec); - snprintf (buffer_two, sizeof(buffer_two), "%ld", kernel_monotonic_clock); - } - const char* mitch_two = strdup (buffer_two); - appctx_values[3] = mitch_two; + char local_date_time_offset_buffer[32]; + snprintf (local_date_time_offset_buffer, sizeof(local_date_time_offset_buffer), "%d", local_date_time_offset); + appctx_values[2] = strdup (local_date_time_offset_buffer); MonoCoreLocalTime mclt = { local_date_time_offset, - kernel_monotonic_clock, }; MonoCoreRuntimeProperties mcrp = { &mclt, @@ -275,7 +262,7 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed free (file_path); } - monovm_initialize_preparsed(&mcrp, 4, appctx_keys, appctx_values); + monovm_initialize_preparsed(&mcrp, 3, appctx_keys, appctx_values); mono_debug_init (MONO_DEBUG_FORMAT_MONO); mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL); From 9a2a8a26ff5683ab0ecd92e1f56617c9f8d9dd40 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Tue, 23 Aug 2022 14:59:25 -0400 Subject: [PATCH 07/17] Cleanup GetLocalUtcOffset --- .../src/System/TimeZoneInfo.Unix.Android.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 98364fc38bd4e..05b6490a0ce65 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -142,32 +142,39 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return Utc; } + private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + private static TimeSpan? _localUtcOffset; private static object _localUtcOffsetLock = new(); private static Thread? _loadAndroidTZData; // Shortcut for TimeZoneInfo.Local.GetUtcOffset + // On Android, loading AndroidTZData while obtaining cachedData.Local is expensive for startup. + // We introduce a fast result for GetLocalUtcOffset that relies on the date time offset being + // passed into monovm_initialize(_preparsed) from Java in seconds as LOCAL_DATE_TIME_OFFSET. + // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. + // The fast path is initially used, and we start a background thread to get cachedData.Local internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) { - if (_localUtcOffset != null) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } + if (_localUtcOffset != null) // The background thread finished, the cache is loaded. + return GetCacheLocalUtcOffset(dateTime, flags); - if (_localUtcOffset == null && _loadAndroidTZData == null) + if (_localUtcOffset == null && _loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created { lock (_localUtcOffsetLock) { + // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, + // once the lock is available, check for a cache and background thread. if (_localUtcOffset != null) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } + return GetCacheLocalUtcOffset(dateTime, flags); + if (_loadAndroidTZData == null) { _loadAndroidTZData = new Thread(() => { - CachedData cachedData = s_cachedData; - _localUtcOffset = cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + _localUtcOffset = GetCacheLocalUtcOffset(dateTime, flags); Thread.Sleep(1000); }); _loadAndroidTZData.IsBackground = true; @@ -178,9 +185,9 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption object? localDateTimeOffset = AppContext.GetData("LOCAL_DATE_TIME_OFFSET"); if (localDateTimeOffset == null) - throw new Exception("LOCAL_DATE_TIME_OFFSET NOT SET"); - long localDateTimeOffsetTicks = Convert.ToInt32(localDateTimeOffset) * 10000000; // 10^7 ticks per second + return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default + long localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); return offset; } From f352b3a91ddef33ef02acfdc0a22867986b6bf12 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 24 Aug 2022 11:09:29 -0400 Subject: [PATCH 08/17] Sleep background thread first, then start loading AndroidTZData --- .../src/System/TimeZoneInfo.Unix.Android.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 05b6490a0ce65..e2c80ad34f965 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -174,8 +174,8 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption if (_loadAndroidTZData == null) { _loadAndroidTZData = new Thread(() => { - _localUtcOffset = GetCacheLocalUtcOffset(dateTime, flags); Thread.Sleep(1000); + _localUtcOffset = GetCacheLocalUtcOffset(dateTime, flags); }); _loadAndroidTZData.IsBackground = true; _loadAndroidTZData.Start(); From f7dc8e9e4445db5fcaf091e9410a841b946403ed Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 24 Aug 2022 14:30:42 -0400 Subject: [PATCH 09/17] Conform Runtime Config Property name --- .../src/System/TimeZoneInfo.Unix.Android.cs | 2 +- src/tasks/AndroidAppBuilder/Templates/monodroid.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index e2c80ad34f965..96de253b3f8ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -183,7 +183,7 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption } } - object? localDateTimeOffset = AppContext.GetData("LOCAL_DATE_TIME_OFFSET"); + object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); if (localDateTimeOffset == null) return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 32318c366d219..ef28a01fbe3ff 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -228,7 +228,7 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed const char* appctx_keys[3]; appctx_keys[0] = "RUNTIME_IDENTIFIER"; appctx_keys[1] = "APP_CONTEXT_BASE_DIRECTORY"; - appctx_keys[2] = "LOCAL_DATE_TIME_OFFSET"; + appctx_keys[2] = "System.TimeZoneInfo.LocalDateTimeOffset"; const char* appctx_values[3]; appctx_values[0] = ANDROID_RUNTIME_IDENTIFIER; From 32451c8133595d40d9aaf1a24c92a2a4a4d71129 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 25 Aug 2022 11:45:43 -0400 Subject: [PATCH 10/17] Revert "WIP Leverage monovm_initialize_preparsed" This reverts commit 1edaeeee37066ad392ab19100248994ee53b0797. --- src/mono/mono/mini/monovm.c | 2 -- .../mono/jit/details/mono-private-unstable-types.h | 5 ----- src/tasks/AndroidAppBuilder/Templates/monodroid.c | 9 +-------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/mono/mono/mini/monovm.c b/src/mono/mono/mini/monovm.c index 686da9ad38e95..144594276f218 100644 --- a/src/mono/mono/mini/monovm.c +++ b/src/mono/mono/mini/monovm.c @@ -18,7 +18,6 @@ static MonoCoreTrustedPlatformAssemblies *trusted_platform_assemblies; static MonoCoreLookupPaths *native_lib_paths; static MonoCoreLookupPaths *app_paths; static MonoCoreLookupPaths *platform_resource_roots; -static MonoCoreLocalTime *local_time; static void mono_core_trusted_platform_assemblies_free (MonoCoreTrustedPlatformAssemblies *a) @@ -221,7 +220,6 @@ monovm_initialize_preparsed (MonoCoreRuntimeProperties *parsed_properties, int p trusted_platform_assemblies = parsed_properties->trusted_platform_assemblies; app_paths = parsed_properties->app_paths; native_lib_paths = parsed_properties->native_dll_search_directories; - local_time = parsed_properties->local_time; mono_loader_install_pinvoke_override (parsed_properties->pinvoke_override); finish_initialization (); diff --git a/src/native/public/mono/jit/details/mono-private-unstable-types.h b/src/native/public/mono/jit/details/mono-private-unstable-types.h index fddaf391c024c..64398893c56b7 100644 --- a/src/native/public/mono/jit/details/mono-private-unstable-types.h +++ b/src/native/public/mono/jit/details/mono-private-unstable-types.h @@ -45,15 +45,10 @@ typedef struct { char **dirs; } MonoCoreLookupPaths; -typedef struct { - uint64_t current_local_time; -} MonoCoreLocalTime; - typedef struct { MonoCoreTrustedPlatformAssemblies *trusted_platform_assemblies; MonoCoreLookupPaths *app_paths; MonoCoreLookupPaths *native_dll_search_directories; - MonoCoreLocalTime *local_time; PInvokeOverrideFn pinvoke_override; } MonoCoreRuntimeProperties; diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index ef28a01fbe3ff..a016353a0b4f3 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -237,13 +237,6 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed snprintf (local_date_time_offset_buffer, sizeof(local_date_time_offset_buffer), "%d", local_date_time_offset); appctx_values[2] = strdup (local_date_time_offset_buffer); - MonoCoreLocalTime mclt = { - local_date_time_offset, - }; - MonoCoreRuntimeProperties mcrp = { - &mclt, - }; - char *file_name = RUNTIMECONFIG_BIN_FILE; int str_len = strlen (bundle_path) + strlen (file_name) + 1; // +1 is for the "/" char *file_path = (char *)malloc (sizeof (char) * (str_len +1)); // +1 is for the terminating null character @@ -262,7 +255,7 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed free (file_path); } - monovm_initialize_preparsed(&mcrp, 3, appctx_keys, appctx_values); + monovm_initialize(3, appctx_keys, appctx_values); mono_debug_init (MONO_DEBUG_FORMAT_MONO); mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL); From 1a55a0cdc3a296ca2912c755c192afe99928c688 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 25 Aug 2022 12:12:45 -0400 Subject: [PATCH 11/17] Address feedback --- .../src/System/TimeZoneInfo.Unix.Android.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 96de253b3f8ae..3b3eedcce529b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -107,7 +107,7 @@ private static int ParseGMTNumericZone(string name) { return new TimeZoneInfo(id, TimeSpan.FromSeconds(0), id, name, name, null, disableDaylightSavingTime:true); } - if (name[0] == 'G' && name[1] == 'M' && name[2] == 'T') + if (name.Length >= 3 && name[0] == 'G' && name[1] == 'M' && name[2] == 'T') { return new TimeZoneInfo(id, TimeSpan.FromSeconds(ParseGMTNumericZone(name)), id, name, name, null, disableDaylightSavingTime:true); } @@ -144,13 +144,13 @@ private static TimeZoneInfo GetLocalTimeZoneCore() private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); } - private static TimeSpan? _localUtcOffset; - private static object _localUtcOffsetLock = new(); - private static Thread? _loadAndroidTZData; + private static bool s_androidTZDataLoaded; + private static object s_localUtcOffsetLock = new(); + private static Thread? s_loadAndroidTZData; // Shortcut for TimeZoneInfo.Local.GetUtcOffset // On Android, loading AndroidTZData while obtaining cachedData.Local is expensive for startup. // We introduce a fast result for GetLocalUtcOffset that relies on the date time offset being @@ -159,26 +159,33 @@ private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOp // The fast path is initially used, and we start a background thread to get cachedData.Local internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) { - if (_localUtcOffset != null) // The background thread finished, the cache is loaded. + if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. + { + s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded return GetCacheLocalUtcOffset(dateTime, flags); + } - if (_localUtcOffset == null && _loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created + if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created { - lock (_localUtcOffsetLock) + lock (s_localUtcOffsetLock) { // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, // once the lock is available, check for a cache and background thread. - if (_localUtcOffset != null) + if (s_androidTZDataLoaded) + { + s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded return GetCacheLocalUtcOffset(dateTime, flags); - - if (_loadAndroidTZData == null) + } + if (s_loadAndroidTZData == null) { - _loadAndroidTZData = new Thread(() => { + s_loadAndroidTZData = new Thread(() => { Thread.Sleep(1000); - _localUtcOffset = GetCacheLocalUtcOffset(dateTime, flags); + CachedData cachedData = s_cachedData; + _ = cachedData.Local; + s_androidTZDataLoaded = true; }); - _loadAndroidTZData.IsBackground = true; - _loadAndroidTZData.Start(); + s_loadAndroidTZData.IsBackground = true; + s_loadAndroidTZData.Start(); } } } @@ -187,7 +194,7 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption if (localDateTimeOffset == null) return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default - long localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); + int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); return offset; } From 61af7b7cfc70b69af18d9e17e67c4e147b1d90be Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Fri, 26 Aug 2022 11:54:20 -0400 Subject: [PATCH 12/17] Fix thread clearing --- .../src/System/TimeZoneInfo.Unix.Android.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 3b3eedcce529b..4398a16d1931b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -160,10 +160,7 @@ private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOp internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) { if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. - { - s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded return GetCacheLocalUtcOffset(dateTime, flags); - } if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created { @@ -172,17 +169,19 @@ internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOption // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, // once the lock is available, check for a cache and background thread. if (s_androidTZDataLoaded) - { - s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded return GetCacheLocalUtcOffset(dateTime, flags); - } + if (s_loadAndroidTZData == null) { s_loadAndroidTZData = new Thread(() => { Thread.Sleep(1000); CachedData cachedData = s_cachedData; _ = cachedData.Local; - s_androidTZDataLoaded = true; + lock (s_localUtcOffsetLock) + { + s_androidTZDataLoaded = true; + s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded + } }); s_loadAndroidTZData.IsBackground = true; s_loadAndroidTZData.Start(); From 50ff121a1895eb80bd93d501c553ead8ff476836 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Fri, 26 Aug 2022 19:53:03 -0400 Subject: [PATCH 13/17] Move implementation to DateTimeOffset.Now --- .../System.Private.CoreLib.Shared.projitems | 2 + .../src/System/DateTimeOffset.Android.cs | 80 +++++++++++++++++++ .../src/System/DateTimeOffset.NonAndroid.cs | 12 +++ .../src/System/DateTimeOffset.cs | 6 +- .../src/System/TimeZoneInfo.Unix.Android.cs | 56 ------------- .../System/TimeZoneInfo.Unix.NonAndroid.cs | 7 -- .../src/System/TimeZoneInfo.Win32.cs | 7 -- .../src/System/TimeZoneInfo.cs | 7 ++ 8 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6f3a16e613636..87ecf7027d0d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -254,6 +254,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs new file mode 100644 index 0000000000000..b3fffcad37099 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; + +namespace System +{ + public readonly partial struct DateTimeOffset + { + private static bool s_androidTZDataLoaded; + private static readonly object s_localUtcOffsetLock = new(); + private static Thread? s_loadAndroidTZData; + + // TryGetNow is a helper function on Android responsible for: + // 1) quickly returning a fast path offset when first called + // 2) starting a background thread to pursue the original implementation + // + // On Android, loading AndroidTZData is expensive for startup performance. + // The fast result relies on `System.TimeZoneInfo.LocalDateTimeOffset` being set + // in the App Context's properties as the appropriate local date time offset from UTC. + // monovm_initialize(_preparsed) can be leveraged to do so. + // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. + // So, on first call, we return the fast path and start a background thread to pursue the original + // implementation which eventually loads AndroidTZData. + + private static TimeSpan TryGetNow() + { + if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. + return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); + + if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created + { + lock (s_localUtcOffsetLock) + { + // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, + // once the lock is available, check for a cache and background thread. + if (s_androidTZDataLoaded) + return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); + + if (s_loadAndroidTZData == null) + { + s_loadAndroidTZData = new Thread(() => { + Thread.Sleep(1000); + _ = TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); + lock (s_localUtcOffsetLock) + { + s_androidTZDataLoaded = true; + s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded + } + }); + s_loadAndroidTZData.IsBackground = true; + s_loadAndroidTZData.Start(); + } + } + } + + object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); + if (localDateTimeOffset == null) + return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); // If no offset property provided through monovm app context, default + + int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); + TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); + return offset; + } + + public static DateTimeOffset Now + { + get + { + DateTime utcDateTime = DateTime.UtcNow; + TimeSpan offset = TryGetNow(); + long localTicks = utcDateTime.Ticks + offset.Ticks; + if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) + throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); + + return new DateTimeOffset(localTicks, offset); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs new file mode 100644 index 0000000000000..d85c0a0c38d4d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System +{ + public readonly partial struct DateTimeOffset + { + // Returns a DateTimeOffset representing the current date and time. The + // resolution of the returned value depends on the system timer. + public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 3f8feead885a7..b2289d1377ace 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -34,7 +34,7 @@ namespace System [StructLayout(LayoutKind.Auto)] [Serializable] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public readonly struct DateTimeOffset + public readonly partial struct DateTimeOffset : IComparable, ISpanFormattable, IComparable, @@ -321,10 +321,6 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _dateTime = _dateTime.AddMicroseconds(microsecond); } - // Returns a DateTimeOffset representing the current date and time. The - // resolution of the returned value depends on the system timer. - public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); - public static DateTimeOffset UtcNow { get diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs index 4398a16d1931b..d9b46074b8e35 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.Android.cs @@ -142,62 +142,6 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return Utc; } - private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } - - private static bool s_androidTZDataLoaded; - private static object s_localUtcOffsetLock = new(); - private static Thread? s_loadAndroidTZData; - // Shortcut for TimeZoneInfo.Local.GetUtcOffset - // On Android, loading AndroidTZData while obtaining cachedData.Local is expensive for startup. - // We introduce a fast result for GetLocalUtcOffset that relies on the date time offset being - // passed into monovm_initialize(_preparsed) from Java in seconds as LOCAL_DATE_TIME_OFFSET. - // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. - // The fast path is initially used, and we start a background thread to get cachedData.Local - internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. - return GetCacheLocalUtcOffset(dateTime, flags); - - if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created - { - lock (s_localUtcOffsetLock) - { - // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, - // once the lock is available, check for a cache and background thread. - if (s_androidTZDataLoaded) - return GetCacheLocalUtcOffset(dateTime, flags); - - if (s_loadAndroidTZData == null) - { - s_loadAndroidTZData = new Thread(() => { - Thread.Sleep(1000); - CachedData cachedData = s_cachedData; - _ = cachedData.Local; - lock (s_localUtcOffsetLock) - { - s_androidTZDataLoaded = true; - s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded - } - }); - s_loadAndroidTZData.IsBackground = true; - s_loadAndroidTZData.Start(); - } - } - } - - object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); - if (localDateTimeOffset == null) - return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default - - int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); - TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); - return offset; - } - private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs index c9dc759a65d43..a9d6c462610fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs @@ -24,13 +24,6 @@ private static TimeZoneInfo GetLocalTimeZoneCore() return GetLocalTimeZoneFromTzFile(); } - // Shortcut for TimeZoneInfo.Local.GetUtcOffset - internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } - private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e) { value = null; diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index 473f1667707d3..c0774303f3f91 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -76,13 +76,6 @@ public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule? rule) } } - // Shortcut for TimeZoneInfo.Local.GetUtcOffset - internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } - /// /// Returns a cloned array of AdjustmentRule objects /// diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 34586ef1bb304..236442359f011 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -312,6 +312,13 @@ public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) => public TimeSpan GetUtcOffset(DateTime dateTime) => GetUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); + // Shortcut for TimeZoneInfo.Local.GetUtcOffset + internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + /// /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. /// From b1531b6c2552aeb732edce81d763fb6f66fb4e2f Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 31 Aug 2022 01:05:15 -0400 Subject: [PATCH 14/17] Address feedback --- .../src/System/DateTimeOffset.Android.cs | 12 ++++++------ .../src/System/DateTimeOffset.NonAndroid.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs index b3fffcad37099..634dbd9e902b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -11,7 +11,7 @@ public readonly partial struct DateTimeOffset private static readonly object s_localUtcOffsetLock = new(); private static Thread? s_loadAndroidTZData; - // TryGetNow is a helper function on Android responsible for: + // TryGetNowOffset is a helper function on Android responsible for: // 1) quickly returning a fast path offset when first called // 2) starting a background thread to pursue the original implementation // @@ -23,7 +23,7 @@ public readonly partial struct DateTimeOffset // So, on first call, we return the fast path and start a background thread to pursue the original // implementation which eventually loads AndroidTZData. - private static TimeSpan TryGetNow() + private static TimeSpan TryGetNowOffset() { if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); @@ -40,7 +40,7 @@ private static TimeSpan TryGetNow() if (s_loadAndroidTZData == null) { s_loadAndroidTZData = new Thread(() => { - Thread.Sleep(1000); + Thread.Sleep(1000); // Delay the background thread to avoid impacting startup, if it still coincides, startup is already perceived as slow _ = TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); lock (s_localUtcOffsetLock) { @@ -55,8 +55,8 @@ private static TimeSpan TryGetNow() } object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); - if (localDateTimeOffset == null) - return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); // If no offset property provided through monovm app context, default + if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default + return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); @@ -68,7 +68,7 @@ public static DateTimeOffset Now get { DateTime utcDateTime = DateTime.UtcNow; - TimeSpan offset = TryGetNow(); + TimeSpan offset = TryGetNowOffset(); long localTicks = utcDateTime.Ticks + offset.Ticks; if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs index d85c0a0c38d4d..511e40ef6c8d1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.NonAndroid.cs @@ -3,10 +3,10 @@ namespace System { - public readonly partial struct DateTimeOffset - { - // Returns a DateTimeOffset representing the current date and time. The - // resolution of the returned value depends on the system timer. - public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); - } + public readonly partial struct DateTimeOffset + { + // Returns a DateTimeOffset representing the current date and time. The + // resolution of the returned value depends on the system timer. + public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); + } } From 533ad1cb52669ac9292483bf0194e00459f5f6f1 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 31 Aug 2022 09:18:40 -0400 Subject: [PATCH 15/17] Consolidate into DateTimeOffset.Now and avoid calling DateTime.UtcNow multiple times --- .../src/System/DateTimeOffset.Android.cs | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs index 634dbd9e902b4..fff4bdf1882d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -11,64 +11,60 @@ public readonly partial struct DateTimeOffset private static readonly object s_localUtcOffsetLock = new(); private static Thread? s_loadAndroidTZData; - // TryGetNowOffset is a helper function on Android responsible for: - // 1) quickly returning a fast path offset when first called - // 2) starting a background thread to pursue the original implementation + // Now on Android does the following + // 1) quickly returning a fast path result when first called if the right AppContext data element is set + // 2) starting a background thread to load TimeZoneInfo local cache // // On Android, loading AndroidTZData is expensive for startup performance. // The fast result relies on `System.TimeZoneInfo.LocalDateTimeOffset` being set // in the App Context's properties as the appropriate local date time offset from UTC. // monovm_initialize(_preparsed) can be leveraged to do so. // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. - // So, on first call, we return the fast path and start a background thread to pursue the original - // implementation which eventually loads AndroidTZData. - - private static TimeSpan TryGetNowOffset() + // So, on first call, we return the fast path and start a background thread to load + // the TimeZoneInfo Local cache implementation which loads AndroidTZData. + public static DateTimeOffset Now { - if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. - return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); - - if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created + get { - lock (s_localUtcOffsetLock) - { - // GetLocalUtcOffset may be called multiple times before a cache is loaded and a background thread is running, - // once the lock is available, check for a cache and background thread. - if (s_androidTZDataLoaded) - return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); + DateTime utcDateTime = DateTime.UtcNow; + + if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. + return ToLocalTime(utcDateTime, true); - if (s_loadAndroidTZData == null) + if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created + { + lock (s_localUtcOffsetLock) { - s_loadAndroidTZData = new Thread(() => { - Thread.Sleep(1000); // Delay the background thread to avoid impacting startup, if it still coincides, startup is already perceived as slow - _ = TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); - lock (s_localUtcOffsetLock) - { - s_androidTZDataLoaded = true; - s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded - } - }); - s_loadAndroidTZData.IsBackground = true; - s_loadAndroidTZData.Start(); + // Now may be called multiple times before a cache is loaded and a background thread is running, + // once the lock is available, check for a cache and background thread. + if (s_androidTZDataLoaded) + return ToLocalTime(utcDateTime, true); + + if (s_loadAndroidTZData == null) + { + s_loadAndroidTZData = new Thread(() => { + // Delay the background thread to avoid impacting startup, if it still coincides after 1s, startup is already perceived as slow + Thread.Sleep(1000); + _ = TimeZoneInfo.Local; // Load AndroidTZData + lock (s_localUtcOffsetLock) + { + s_androidTZDataLoaded = true; + s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded + } + }); + s_loadAndroidTZData.IsBackground = true; + s_loadAndroidTZData.Start(); + } } } - } - - object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); - if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default - return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); - int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); - TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); - return offset; - } + object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); + if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default + return ToLocalTime(utcDateTime, true); - public static DateTimeOffset Now - { - get - { - DateTime utcDateTime = DateTime.UtcNow; - TimeSpan offset = TryGetNowOffset(); + // Fast path obtained offset incorporated into ToLocalTime(DateTime.UtcNow, true) logic + int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); + TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); long localTicks = utcDateTime.Ticks + offset.Ticks; if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); From ef7729e9e2dd2262562ab81e9411539811eff896 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Wed, 31 Aug 2022 23:40:29 -0400 Subject: [PATCH 16/17] Introduce small performance gains Leverage a boolean flag instead of && Start the background thread to avoid deadlocking --- .../src/System/DateTimeOffset.Android.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs index fff4bdf1882d8..55abcaef3df8a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -10,6 +10,7 @@ public readonly partial struct DateTimeOffset private static bool s_androidTZDataLoaded; private static readonly object s_localUtcOffsetLock = new(); private static Thread? s_loadAndroidTZData; + private static bool s_startNewBackgroundThread = true; // Now on Android does the following // 1) quickly returning a fast path result when first called if the right AppContext data element is set @@ -31,7 +32,7 @@ public static DateTimeOffset Now if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. return ToLocalTime(utcDateTime, true); - if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created + if (s_startNewBackgroundThread) // The cache isn't loaded and no background thread has been created { lock (s_localUtcOffsetLock) { @@ -43,21 +44,29 @@ public static DateTimeOffset Now if (s_loadAndroidTZData == null) { s_loadAndroidTZData = new Thread(() => { + // We only want to start one background thread + s_startNewBackgroundThread = false; + // Delay the background thread to avoid impacting startup, if it still coincides after 1s, startup is already perceived as slow Thread.Sleep(1000); + _ = TimeZoneInfo.Local; // Load AndroidTZData + s_androidTZDataLoaded = true; + lock (s_localUtcOffsetLock) { - s_androidTZDataLoaded = true; s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded } }); s_loadAndroidTZData.IsBackground = true; - s_loadAndroidTZData.Start(); } } + + if (s_startNewBackgroundThread) + s_loadAndroidTZData.Start(); } + object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default return ToLocalTime(utcDateTime, true); From 1cfc352fff7a2cf418846bef176eb14c5d0bec38 Mon Sep 17 00:00:00 2001 From: mdh1418 Date: Thu, 1 Sep 2022 11:47:27 -0400 Subject: [PATCH 17/17] Bump boolean flag outside of background thread body --- .../src/System/DateTimeOffset.Android.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs index 55abcaef3df8a..c78dadcdfc3bd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.Android.cs @@ -44,9 +44,6 @@ public static DateTimeOffset Now if (s_loadAndroidTZData == null) { s_loadAndroidTZData = new Thread(() => { - // We only want to start one background thread - s_startNewBackgroundThread = false; - // Delay the background thread to avoid impacting startup, if it still coincides after 1s, startup is already perceived as slow Thread.Sleep(1000); @@ -63,7 +60,14 @@ public static DateTimeOffset Now } if (s_startNewBackgroundThread) + { + // Because Start does not block the calling thread, + // setting the boolean flag to false immediately after should + // prevent two calls to DateTimeOffset.Now in quick succession + // from both reaching here. s_loadAndroidTZData.Start(); + s_startNewBackgroundThread = false; + } }