-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Android][libs] Introduce GetLocalUtcOffset temporary fast result #74459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a3f0e9f
a44084a
1edaeee
0712f57
96bf37f
582c359
9a2a8a2
f352b3a
f7dc8e9
32451c8
1a55a0c
61af7b7
50ff121
b1531b6
533ad1c
ef7729e
55fcccc
1cfc352
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // 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; | ||
| 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 | ||
| // 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 load | ||
| // the TimeZoneInfo Local cache implementation which loads AndroidTZData. | ||
| public static DateTimeOffset Now | ||
| { | ||
| get | ||
| { | ||
| DateTime utcDateTime = DateTime.UtcNow; | ||
|
|
||
| if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. | ||
| return ToLocalTime(utcDateTime, true); | ||
|
|
||
| if (s_startNewBackgroundThread) // The cache isn't loaded and no background thread has been created | ||
| { | ||
| lock (s_localUtcOffsetLock) | ||
| { | ||
| // 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 | ||
| s_androidTZDataLoaded = true; | ||
|
|
||
| lock (s_localUtcOffsetLock) | ||
| { | ||
| s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded | ||
| } | ||
| }); | ||
| s_loadAndroidTZData.IsBackground = true; | ||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); | ||
| if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default | ||
| return ToLocalTime(utcDateTime, true); | ||
|
|
||
| // 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); | ||
|
|
||
| return new DateTimeOffset(localTicks, offset); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,17 @@ 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[3]; | ||
| appctx_keys[0] = "RUNTIME_IDENTIFIER"; | ||
| appctx_keys[1] = "APP_CONTEXT_BASE_DIRECTORY"; | ||
| appctx_keys[2] = "System.TimeZoneInfo.LocalDateTimeOffset"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we considering this as "internal-use only"? Or would we expect developers to get this AppContext setting? With its current name, it kind of seems like an "official" setting. I wonder if we should rename it to better indicate that we don't expect others to use it. (and quite possibly we could remove it in the future if we refactor this code.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we want to set the MonoVMProperty on Xamarin-Android at all times. And to address #74459 (comment), I believe we would want to maintain alignment of AndroidAppBuilder in I think the only purpose for this property is to get the fast path local date time offset, but there was a concern whether we needed this to follow runtime config naming #74459 (comment). |
||
|
|
||
| const char* appctx_values[2]; | ||
| const char* appctx_values[3]; | ||
| appctx_values[0] = ANDROID_RUNTIME_IDENTIFIER; | ||
| appctx_values[1] = bundle_path; | ||
| 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); | ||
|
|
||
| char *file_name = RUNTIMECONFIG_BIN_FILE; | ||
| int str_len = strlen (bundle_path) + strlen (file_name) + 1; // +1 is for the "/" | ||
|
|
@@ -251,7 +255,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(3, appctx_keys, appctx_values); | ||
|
|
||
| mono_debug_init (MONO_DEBUG_FORMAT_MONO); | ||
| mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL); | ||
|
|
@@ -318,7 +322,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 +351,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) | ||
| { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.