Skip to content
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

[Android][libs] Introduce GetLocalUtcOffset temporary fast result #74459

Merged
merged 18 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[0] == 'G' && name[1] == 'M' && name[2] == 'T')
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
{
return new TimeZoneInfo(id, TimeSpan.FromSeconds(ParseGMTNumericZone(name)), id, name, name, null, disableDaylightSavingTime:true);
}
Expand Down Expand Up @@ -142,6 +142,56 @@ 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);
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
}

private static TimeSpan? _localUtcOffset;
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
private static object _localUtcOffsetLock = new();
private static Thread? _loadAndroidTZData;
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
// 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) // The background thread finished, the cache is loaded.
return GetCacheLocalUtcOffset(dateTime, flags);

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)
return GetCacheLocalUtcOffset(dateTime, flags);

if (_loadAndroidTZData == null)
{
_loadAndroidTZData = new Thread(() => {
_localUtcOffset = GetCacheLocalUtcOffset(dateTime, flags);
Thread.Sleep(1000);
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
});
_loadAndroidTZData.IsBackground = true;
_loadAndroidTZData.Start();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grendello what are your thoughts here on starting a Thread?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thread idea was my suggestion and I think it's going to work quite OK, provided it's a bare thread as above and not part of a threadpool or TPL in general. It shouldn't hurt startup performance too much, although what I'd do would be to let it sleep first for 1 second and then start loading the TZ. The idea is that it should start after the main startup sequence of the app, and 1s should be enough time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 1s isn't enough time, is this going to make launching the app even slower?

mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

object? localDateTimeOffset = AppContext.GetData("LOCAL_DATE_TIME_OFFSET");
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
if (localDateTimeOffset == null)
return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default

long localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset);
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds);
return offset;
}

private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e)
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// Returns a cloned array of AdjustmentRule objects
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/mini/monovm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ typedef struct {
char **dirs;
} MonoCoreLookupPaths;

typedef struct {
uint64_t current_local_time;
} MonoCoreLocalTime;
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved

typedef struct {
MonoCoreTrustedPlatformAssemblies *trusted_platform_assemblies;
MonoCoreLookupPaths *app_paths;
MonoCoreLookupPaths *native_dll_search_directories;
MonoCoreLocalTime *local_time;
PInvokeOverrideFn pinvoke_override;
} MonoCoreRuntimeProperties;

Expand Down
7 changes: 5 additions & 2 deletions src/tasks/AndroidAppBuilder/Templates/MonoRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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();
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
return initRuntime(filesDir, cacheDir, testResultsDir, entryPointLibName, args, localDateTimeOffset);
}

@Override
Expand Down Expand Up @@ -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);
}
23 changes: 17 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,24 @@ 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] = "LOCAL_DATE_TIME_OFFSET";

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);

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 "/"
Expand All @@ -251,7 +262,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_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);
Expand Down Expand Up @@ -318,7 +329,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 +358,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