Skip to content

Commit

Permalink
[release/7.0] [Android][libs] Introduce DateTimeOffset.Now temporary …
Browse files Browse the repository at this point in the history
…fast result (#74965)

Backport of #74459 to release/7.0

Introduces fast path for DateTimeOffset.Now on Android that allows us to load tzdata in the background. This improves performance from around 227ms to 21.81ms.
  • Loading branch information
github-actions[bot] authored Sep 12, 2022
1 parent e8649de commit 54e978c
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeKind.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.NonAndroid.cs" Condition="'$(TargetsAndroid)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.Android.cs" Condition="'$(TargetsAndroid)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\DayOfWeek.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DBNull.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Decimal.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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
{
// 0 == in process of being loaded, 1 == loaded
private static volatile int s_androidTZDataLoaded = -1;

// 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 == 1) // The background thread finished, the cache is loaded.
{
return ToLocalTime(utcDateTime, true);
}

object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset");
if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default
{
// no need to create the thread, load tzdata now
s_androidTZDataLoaded = 1;
return ToLocalTime(utcDateTime, true);
}

// The cache isn't loaded yet.
if (Interlocked.CompareExchange(ref s_androidTZDataLoaded, 0, -1) == -1)
{
new Thread(() =>
{
try
{
// 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
}
finally
{
s_androidTZDataLoaded = 1;
}
}) { IsBackground = true }.Start();
}

// 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
Expand Up @@ -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<DateTimeOffset>,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.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);
}
Expand Down
18 changes: 16 additions & 2 deletions src/tasks/AndroidAppBuilder/Templates/MonoRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class MonoRunner extends Instrumentation
{
Expand Down Expand Up @@ -88,7 +91,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 = getLocalDateTimeOffset();
return initRuntime(filesDir, cacheDir, testResultsDir, entryPointLibName, args, localDateTimeOffset);
}

@Override
Expand Down Expand Up @@ -149,7 +153,17 @@ static void unzipAssets(Context context, String toPath, String zipName) {
}
}

static native int initRuntime(String libsDir, String cacheDir, String testResultsDir, String entryPointLibName, String[] args);
static int getLocalDateTimeOffset() {
if (android.os.Build.VERSION.SDK_INT >= 26) {
return OffsetDateTime.now().getOffset().getTotalSeconds();
}
else {
int offsetInMillis = Calendar.getInstance().getTimeZone().getRawOffset();
return offsetInMillis / 1000;
}
}

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);
}
16 changes: 10 additions & 6 deletions src/tasks/AndroidAppBuilder/Templates/monodroid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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";

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 "/"
Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit 54e978c

Please sign in to comment.