diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj index 1b0204588a2f8a..2a6c8528372c3a 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -291,7 +291,6 @@ - Common\Interop\Windows\OleAut32\Interop.VariantClear.cs diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs deleted file mode 100644 index 1f77180f47870f..00000000000000 --- a/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System -{ - public readonly partial struct DateTime - { - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool ValidateSystemTime(Interop.Kernel32.SYSTEMTIME* time, bool localTime); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool FileTimeToSystemTime(long fileTime, FullSystemTime* time); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime* time); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe bool SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* time, long* fileTime); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe long GetSystemTimeAsFileTime(); - } -} diff --git a/src/coreclr/src/classlibnative/bcltype/system.cpp b/src/coreclr/src/classlibnative/bcltype/system.cpp index 3660c14963df99..bf688e1da6ce2d 100644 --- a/src/coreclr/src/classlibnative/bcltype/system.cpp +++ b/src/coreclr/src/classlibnative/bcltype/system.cpp @@ -30,69 +30,12 @@ #include "array.h" #include "eepolicy.h" -#ifndef TARGET_UNIX -typedef void(WINAPI *pfnGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime); -extern pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime; - -void WINAPI InitializeGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime) -{ - pfnGetSystemTimeAsFileTime func = NULL; - - HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll")); - if (hKernel32 != NULL) - { - func = (pfnGetSystemTimeAsFileTime)GetProcAddress(hKernel32, "GetSystemTimePreciseAsFileTime"); - if (func != NULL) - { - // GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on - // misconfigured systems, it's possible for the "precise" time to be inaccurate: - // https://github.com/dotnet/runtime/issues/9014 - // If it's inaccurate, though, we expect it to be wildly inaccurate, so as a - // workaround/heuristic, we get both the "normal" and "precise" times, and as - // long as they're close, we use the precise one. This workaround can be removed - // when we better understand what's causing the drift and the issue is no longer - // a problem or can be better worked around on all targeted OSes. - - FILETIME systemTimeResult; - ::GetSystemTimeAsFileTime(&systemTimeResult); - - FILETIME preciseSystemTimeResult; - func(&preciseSystemTimeResult); - - LONG64 systemTimeLong100ns = (LONG64)((((ULONG64)systemTimeResult.dwHighDateTime) << 32) | (ULONG64)systemTimeResult.dwLowDateTime); - LONG64 preciseSystemTimeLong100ns = (LONG64)((((ULONG64)preciseSystemTimeResult.dwHighDateTime) << 32) | (ULONG64)preciseSystemTimeResult.dwLowDateTime); - - const INT32 THRESHOLD_100NS = 1000000; // 100ms - if (abs(preciseSystemTimeLong100ns - systemTimeLong100ns) > THRESHOLD_100NS) - { - // Too much difference. Don't use GetSystemTimePreciseAsFileTime. - func = NULL; - } - } - } - if (func == NULL) - { - func = &::GetSystemTimeAsFileTime; - } - - InterlockedCompareExchangeT(&g_pfnGetSystemTimeAsFileTime, func, &InitializeGetSystemTimeAsFileTime); - - g_pfnGetSystemTimeAsFileTime(lpSystemTimeAsFileTime); -} - -pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime = &InitializeGetSystemTimeAsFileTime; -#endif // TARGET_UNIX - +#ifdef TARGET_UNIX FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime) { FCALL_CONTRACT; - INT64 timestamp; -#ifndef TARGET_UNIX - g_pfnGetSystemTimeAsFileTime((FILETIME*)×tamp); -#else GetSystemTimeAsFileTime((FILETIME*)×tamp); -#endif #if BIGENDIAN timestamp = (INT64)(((UINT64)timestamp >> 32) | ((UINT64)timestamp << 32)); @@ -101,88 +44,8 @@ FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime) return timestamp; } FCIMPLEND; - - -#ifndef TARGET_UNIX - -FCIMPL1(VOID, SystemNative::GetSystemTimeWithLeapSecondsHandling, FullSystemTime *time) -{ - FCALL_CONTRACT; - INT64 timestamp; - - g_pfnGetSystemTimeAsFileTime((FILETIME*)×tamp); - - if (::FileTimeToSystemTime((FILETIME*)×tamp, &(time->systemTime))) - { - // to keep the time precision - time->hundredNanoSecond = timestamp % 10000; // 10000 is the number of 100-nano seconds per Millisecond - } - else - { - ::GetSystemTime(&(time->systemTime)); - time->hundredNanoSecond = 0; - } - - if (time->systemTime.wSecond > 59) - { - // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation. - // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds - time->systemTime.wSecond = 59; - time->systemTime.wMilliseconds = 999; - time->hundredNanoSecond = 9999; - } -} -FCIMPLEND; - -FCIMPL2(FC_BOOL_RET, SystemNative::FileTimeToSystemTime, INT64 fileTime, FullSystemTime *time) -{ - FCALL_CONTRACT; - if (::FileTimeToSystemTime((FILETIME*)&fileTime, (LPSYSTEMTIME) time)) - { - // to keep the time precision - time->hundredNanoSecond = fileTime % 10000; // 10000 is the number of 100-nano seconds per Millisecond - if (time->systemTime.wSecond > 59) - { - // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation. - // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds - time->systemTime.wSecond = 59; - time->systemTime.wMilliseconds = 999; - time->hundredNanoSecond = 9999; - } - FC_RETURN_BOOL(TRUE); - } - FC_RETURN_BOOL(FALSE); -} -FCIMPLEND; - -FCIMPL2(FC_BOOL_RET, SystemNative::ValidateSystemTime, SYSTEMTIME *time, CLR_BOOL localTime) -{ - FCALL_CONTRACT; - - if (localTime) - { - SYSTEMTIME st; - FC_RETURN_BOOL(::TzSpecificLocalTimeToSystemTime(NULL, time, &st)); - } - else - { - FILETIME timestamp; - FC_RETURN_BOOL(::SystemTimeToFileTime(time, ×tamp)); - } -} -FCIMPLEND; - -FCIMPL2(FC_BOOL_RET, SystemNative::SystemTimeToFileTime, SYSTEMTIME *time, INT64 *pFileTime) -{ - FCALL_CONTRACT; - - BOOL ret = ::SystemTimeToFileTime(time, (LPFILETIME) pFileTime); - FC_RETURN_BOOL(ret); -} -FCIMPLEND; #endif // TARGET_UNIX - FCIMPL0(UINT32, SystemNative::GetTickCount) { FCALL_CONTRACT; diff --git a/src/coreclr/src/classlibnative/bcltype/system.h b/src/coreclr/src/classlibnative/bcltype/system.h index 5bbf73d4a1048d..1315f0bdcb512e 100644 --- a/src/coreclr/src/classlibnative/bcltype/system.h +++ b/src/coreclr/src/classlibnative/bcltype/system.h @@ -43,13 +43,9 @@ class SystemNative public: // Functions on the System.Environment class -#ifndef TARGET_UNIX - static FCDECL1(VOID, GetSystemTimeWithLeapSecondsHandling, FullSystemTime *time); - static FCDECL2(FC_BOOL_RET, ValidateSystemTime, SYSTEMTIME *time, CLR_BOOL localTime); - static FCDECL2(FC_BOOL_RET, FileTimeToSystemTime, INT64 fileTime, FullSystemTime *time); - static FCDECL2(FC_BOOL_RET, SystemTimeToFileTime, SYSTEMTIME *time, INT64 *pFileTime); -#endif // TARGET_UNIX +#ifdef TARGET_UNIX static FCDECL0(INT64, __GetSystemTimeAsFileTime); +#endif // TARGET_UNIX static FCDECL0(UINT32, GetTickCount); static FCDECL0(UINT64, GetTickCount64); diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 87389732f8d2e0..a4e7debdf25662 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -132,13 +132,9 @@ FCFuncStart(gDiagnosticsStackTrace) FCFuncEnd() FCFuncStart(gDateTimeFuncs) -#if !defined(TARGET_UNIX) - FCFuncElement("GetSystemTimeWithLeapSecondsHandling", SystemNative::GetSystemTimeWithLeapSecondsHandling) - FCFuncElement("ValidateSystemTime", SystemNative::ValidateSystemTime) - FCFuncElement("FileTimeToSystemTime", SystemNative::FileTimeToSystemTime) - FCFuncElement("SystemTimeToFileTime", SystemNative::SystemTimeToFileTime) -#endif // TARGET_UNIX +#if defined(TARGET_UNIX) FCFuncElement("GetSystemTimeAsFileTime", SystemNative::__GetSystemTimeAsFileTime) +#endif // TARGET_UNIX FCFuncEnd() FCFuncStart(gEnvironmentFuncs) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs index ac4e1fe6be1168..6c5266ae34f144 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs @@ -1,30 +1,172 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; namespace System { public readonly partial struct DateTime { - internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds(); + private static LeapSecondCache s_leapSecondCache = new LeapSecondCache(); // dummy value, will be invalidated on first call + + private static unsafe delegate* unmanaged[Stdcall] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr(); + + private static unsafe delegate* unmanaged[Stdcall] GetGetSystemTimeAsFileTimeFnPtr() + { + IntPtr kernel32Lib = NativeLibrary.Load("kernel32.dll", typeof(DateTime).Assembly, DllImportSearchPath.System32); + IntPtr pfnGetSystemTime = NativeLibrary.GetExport(kernel32Lib, "GetSystemTimeAsFileTime"); // will never fail + + if (NativeLibrary.TryGetExport(kernel32Lib, "GetSystemTimePreciseAsFileTime", out IntPtr pfnGetSystemTimePrecise)) + { + // If GetSystemTimePreciseAsFileTime exists, we'd like to use it. However, on + // misconfigured systems, it's possible for the "precise" time to be inaccurate: + // https://github.com/dotnet/runtime/issues/9014 + // If it's inaccurate, though, we expect it to be wildly inaccurate, so as a + // workaround/heuristic, we get both the "normal" and "precise" times, and as + // long as they're close, we use the precise one. This workaround can be removed + // when we better understand what's causing the drift and the issue is no longer + // a problem or can be better worked around on all targeted OSes. + + // TODO: Once https://github.com/dotnet/runtime/issues/38134 is resolved, + // the calls below should suppress the GC transition. + + ulong filetimeStd, filetimePrecise; + ((delegate* unmanaged[Stdcall])pfnGetSystemTime)(&filetimeStd); + ((delegate* unmanaged[Stdcall])pfnGetSystemTimePrecise)(&filetimePrecise); + + if (Math.Abs((long)(filetimeStd - filetimePrecise)) <= 100 * TicksPerMillisecond) + { + pfnGetSystemTime = pfnGetSystemTimePrecise; // use the precise version + } + } + + return (delegate* unmanaged[Stdcall])pfnGetSystemTime; + } public static unsafe DateTime UtcNow { get { - if (s_systemSupportsLeapSeconds) + // The OS tick count and .NET's tick count are slightly different. The OS tick + // count is the *absolute* number of 100-ns intervals which have elapsed since + // January 1, 1601 (UTC). Due to leap second handling, the number of ticks per + // day is variable. Dec. 30, 2016 had 864,000,000,000 ticks (a standard 24-hour + // day), but Dec. 31, 2016 had 864,010,000,000 ticks due to leap second insertion. + // In .NET, *every* day is assumed to have exactly 24 hours (864,000,000,000 ticks). + // This means that per the OS, midnight Dec. 31, 2016 + 864 bn ticks = Dec. 31, 2016 23:59:60, + // but per .NET, midnight Dec. 31, 2016 + 864 bn ticks = Jan. 1, 2017 00:00:00. + // + // We can query the OS and have it deconstruct the tick count into (yyyy-mm-dd hh:mm:ss), + // constructing a new DateTime object from these components, but this is slow. + // So instead we'll rely on the fact that leap seconds only ever adjust the day + // by +1 or -1 second, and only at the very end of the day. That is, time rolls + // 23:59:58 -> 00:00:00 (negative leap second) or 23:59:59 -> 23:59:60 (positive leap + // second). Thus we assume that each day has at least 23 hr 59 min 59 sec, or + // 863,990,000,000 ticks. + // + // We take advantage of this by caching what the OS believes the tick count is at + // the beginning of the month vs. what .NET believes the tick count is at the beginning + // of the month. When the OS returns a tick count to us, if it's before 23:59:59 on the + // last day of the month, then we know that there's no way for a leap second to have been + // inserted or removed, and we can short-circuit the leap second handling logic by + // performing a quick addition and returning immediately. + // + // This relies on: + // a) Leap seconds only ever taking place on the last day of the month + // (historically these are Jun. & Dec., but we support any month); + // b) Leap seconds only ever occurring at 23:59:59; + // c) At most a single leap second being added or removed per month; and + // d) Windows never inserting a historical leap second (into the past) once the + // process is up and running (future insertions are ok). + + // TODO: Once https://github.com/dotnet/runtime/issues/38134 is resolved, + // the call below should suppress the GC transition. + + ulong osTicks; + s_pfnGetSystemTimeAsFileTime(&osTicks); + + // If the OS doesn't support leap second handling, short-circuit everything. + + if (!s_systemSupportsLeapSeconds) + { + return new DateTime((osTicks + FileTimeOffset) | KindUtc); + } + + return GetUtcNowFromOSTicksLeapSecondsAwareCached(osTicks); + } + } + + // This method is extracted into its own helper so that unit tests + // can call into it via reflection. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static DateTime GetUtcNowFromOSTicksLeapSecondsAwareCached(ulong osTicks) + { + Debug.Assert(s_systemSupportsLeapSeconds, "Shouldn't call this helper if OS doesn't support leap seconds."); + + // If our cache has been populated and we're within its validity period, rely + // solely on the cache. We use unsigned arithmetic below so that if the system + // clock is set backward, the resulting value will integer underflow and cause + // a cache miss, forcing us down the slow path. + + LeapSecondCache cache = s_leapSecondCache; + ulong deltaTicksFromStartOfMonth = osTicks - cache.OSFileTimeTicksAtStartOfMonth; + if (deltaTicksFromStartOfMonth < cache.CacheValidityPeriodInTicks) + { + return new DateTime(deltaTicksFromStartOfMonth + cache.DotNetDateDataAtStartOfMonth); // includes UTC marker + } + + // Move uncommon fallback pack into its own subroutine so that register + // allocation in get_UtcNow can stay as optimized as possible. + + return Fallback(osTicks); + static DateTime Fallback(ulong osTicks) + { + // If we reached this point, one of the following is true: + // a) the cache hasn't yet been initialized; or + // b) the month has changed since the last call to UtcNow; or + // c) the current time is 23:59:59 or 23:59:60 on the last day of the month. + // + // In cases (a) and (b), we'll update the cache. In case (c), we + // pessimistically assume we might be inside a leap second, so we + // won't update the cache. + + DateTime utcNow = FromFileTimeLeapSecondsAware((long)osTicks); + DateTime oneSecondBeforeMonthEnd = new DateTime(utcNow.Year, utcNow.Month, DaysInMonth(utcNow.Year, utcNow.Month), 23, 59, 59, DateTimeKind.Utc); + + if (utcNow < oneSecondBeforeMonthEnd) { - FullSystemTime time; - GetSystemTimeWithLeapSecondsHandling(&time); - return CreateDateTimeFromSystemTime(in time); + // It's not yet 23:59:59 on the last day of the month, so update the cache. + // It's ok for multiple threads to do this concurrently as long as the write + // to the static field is published *after* the cache's instance fields have + // been populated. + + DateTime startOfMonth = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc); + Debug.Assert(startOfMonth <= utcNow); + + Volatile.Write(ref s_leapSecondCache, new LeapSecondCache + { + OSFileTimeTicksAtStartOfMonth = osTicks - (ulong)(utcNow.Ticks - startOfMonth.Ticks), + DotNetDateDataAtStartOfMonth = startOfMonth._dateData, + CacheValidityPeriodInTicks = (ulong)(oneSecondBeforeMonthEnd.Ticks - startOfMonth.Ticks) + }); } - return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset)) | KindUtc); + return utcNow; } } + private sealed class LeapSecondCache + { + internal ulong OSFileTimeTicksAtStartOfMonth; // Windows FILETIME value + internal ulong DotNetDateDataAtStartOfMonth; // DateTime._dateData + internal ulong CacheValidityPeriodInTicks; // 100-ns intervals from StartOfMonth (inclusive) to cache expiration (exclusive) + } + + internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds(); + internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) { DateTime dt = new DateTime(year, month, day); @@ -111,34 +253,6 @@ internal FullSystemTime(long ticks) } } -#if !CORECLR - internal static readonly bool s_systemSupportsPreciseSystemTime = SystemSupportsPreciseSystemTime(); - - private static unsafe bool SystemSupportsPreciseSystemTime() - { - if (Environment.IsWindows8OrAbove) - { - // GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on - // misconfigured systems, it's possible for the "precise" time to be inaccurate: - // https://github.com/dotnet/runtime/issues/9014 - // If it's inaccurate, though, we expect it to be wildly inaccurate, so as a - // workaround/heuristic, we get both the "normal" and "precise" times, and as - // long as they're close, we use the precise one. This workaround can be removed - // when we better understand what's causing the drift and the issue is no longer - // a problem or can be better worked around on all targeted OSes. - - long systemTimeResult; - Interop.Kernel32.GetSystemTimeAsFileTime(&systemTimeResult); - - long preciseSystemTimeResult; - Interop.Kernel32.GetSystemTimePreciseAsFileTime(&preciseSystemTimeResult); - - return Math.Abs(preciseSystemTimeResult - systemTimeResult) <= 100 * TicksPerMillisecond; - } - - return false; - } - private static unsafe bool ValidateSystemTime(Interop.Kernel32.SYSTEMTIME* time, bool localTime) { if (localTime) @@ -172,43 +286,9 @@ private static unsafe bool FileTimeToSystemTime(long fileTime, FullSystemTime* t return false; } - private static unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime* time) - { - if (!FileTimeToSystemTime(GetSystemTimeAsFileTime(), time)) - { - Interop.Kernel32.GetSystemTime(&time->systemTime); - time->hundredNanoSecond = 0; - if (time->systemTime.Second > 59) - { - // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation. - // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds - time->systemTime.Second = 59; - time->systemTime.Milliseconds = 999; - time->hundredNanoSecond = 9999; - } - } - } - private static unsafe bool SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* time, long* fileTime) { return Interop.Kernel32.SystemTimeToFileTime(time, fileTime) != Interop.BOOL.FALSE; } - - private static unsafe long GetSystemTimeAsFileTime() - { - long timestamp; - - if (s_systemSupportsPreciseSystemTime) - { - Interop.Kernel32.GetSystemTimePreciseAsFileTime(×tamp); - } - else - { - Interop.Kernel32.GetSystemTimeAsFileTime(×tamp); - } - - return timestamp; - } -#endif } }