diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index 8f48131f8d..145681f8df 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -582,6 +582,13 @@ internal static Delegate FindBuilder(MulticastDelegate mcd)
internal static long TimerCurrent() => DateTime.UtcNow.ToFileTimeUtc();
+ internal static long FastTimerCurrent() => Environment.TickCount;
+
+ internal static uint CalculateTickCountElapsed(long startTick, long endTick)
+ {
+ return (uint)(endTick - startTick);
+ }
+
internal static long TimerFromSeconds(int seconds)
{
long result = checked((long)seconds * TimeSpan.TicksPerSecond);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
index 7a5e37198b..026a437cb7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStatistics.cs
@@ -38,7 +38,7 @@ internal static ValueSqlStatisticsScope TimedScope(SqlStatistics statistics)
// internal values that are not exposed through properties
internal long _closeTimestamp;
internal long _openTimestamp;
- internal long _startExecutionTimestamp;
+ internal long? _startExecutionTimestamp;
internal long _startFetchTimestamp;
internal long _startNetworkServerTimestamp;
@@ -80,7 +80,7 @@ internal bool WaitForDoneAfterRow
internal void ContinueOnNewConnection()
{
- _startExecutionTimestamp = 0;
+ _startExecutionTimestamp = null;
_startFetchTimestamp = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
@@ -108,7 +108,7 @@ internal IDictionary GetDictionary()
{ "UnpreparedExecs", _unpreparedExecs },
{ "ConnectionTime", ADP.TimerToMilliseconds(_connectionTime) },
- { "ExecutionTime", ADP.TimerToMilliseconds(_executionTime) },
+ { "ExecutionTime", _executionTime },
{ "NetworkServerTime", ADP.TimerToMilliseconds(_networkServerTime) }
};
Debug.Assert(dictionary.Count == Count);
@@ -117,9 +117,9 @@ internal IDictionary GetDictionary()
internal bool RequestExecutionTimer()
{
- if (_startExecutionTimestamp == 0)
+ if (!_startExecutionTimestamp.HasValue)
{
- _startExecutionTimestamp = ADP.TimerCurrent();
+ _startExecutionTimestamp = ADP.FastTimerCurrent();
return true;
}
return false;
@@ -127,7 +127,7 @@ internal bool RequestExecutionTimer()
internal void RequestNetworkServerTimer()
{
- Debug.Assert(_startExecutionTimestamp != 0, "No network time expected outside execution period");
+ Debug.Assert(_startExecutionTimestamp.HasValue, "No network time expected outside execution period");
if (_startNetworkServerTimestamp == 0)
{
_startNetworkServerTimestamp = ADP.TimerCurrent();
@@ -137,10 +137,11 @@ internal void RequestNetworkServerTimer()
internal void ReleaseAndUpdateExecutionTimer()
{
- if (_startExecutionTimestamp > 0)
+ if (_startExecutionTimestamp.HasValue)
{
- _executionTime += (ADP.TimerCurrent() - _startExecutionTimestamp);
- _startExecutionTimestamp = 0;
+ uint elapsed = ADP.CalculateTickCountElapsed(_startExecutionTimestamp.Value, ADP.FastTimerCurrent());
+ _executionTime += elapsed;
+ _startExecutionTimestamp = null;
}
}
@@ -176,7 +177,7 @@ internal void Reset()
_unpreparedExecs = 0;
_waitForDoneAfterRow = false;
_waitForReply = false;
- _startExecutionTimestamp = 0;
+ _startExecutionTimestamp = null;
_startNetworkServerTimestamp = 0;
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
index 2336768391..89efdfc9be 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
@@ -62,6 +62,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TickCountElapsedTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TickCountElapsedTest.cs
new file mode 100644
index 0000000000..33f5c660eb
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TickCountElapsedTest.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using Microsoft.Data.Common;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.Tests
+{
+
+ ///
+ /// Tests for Environment.TickCount elapsed time calculation with wraparound handling.
+ ///
+ public sealed class TickCountElapsedTest
+ {
+ ///
+ /// Invokes the internal CalculateTickCountElapsed method to compute elapsed time between two tick counts.
+ ///
+ ///
+ ///
+ ///
+ internal static uint CalculateTickCountElapsed(long startTick, long endTick) {
+ var adpType = Assembly.GetAssembly(typeof(SqlConnection)).GetType("Microsoft.Data.Common.ADP");
+ return (uint) adpType.GetMethod("CalculateTickCountElapsed", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] {startTick, endTick});
+ }
+
+ ///
+ /// Verifies that normal elapsed time calculation works correctly.
+ ///
+ [Fact]
+ public void CalculateTickCountElapsed_NormalCase_ReturnsCorrectElapsed()
+ {
+ uint elapsed = CalculateTickCountElapsed(1000, 1500);
+ Assert.Equal(500u, elapsed);
+ }
+
+ ///
+ /// Verifies that wraparound from int.MaxValue to int.MinValue is handled correctly.
+ ///
+ [Fact]
+ public void CalculateTickCountElapsed_MaxWraparound_ReturnsOne()
+ {
+ uint elapsed = CalculateTickCountElapsed(int.MaxValue, int.MinValue);
+ Assert.Equal(1u, elapsed);
+ }
+
+ ///
+ /// Verifies that partial wraparound scenarios work correctly.
+ ///
+ [Theory]
+ [InlineData(2147483600, -2147483600, 96u)]
+ [InlineData(2147483647, -2147483647, 2u)]
+ public void CalculateTickCountElapsed_PartialWraparound_ReturnsCorrectElapsed(long start, long end, uint expected)
+ {
+ uint elapsed = CalculateTickCountElapsed(start, end);
+ Assert.Equal(expected, elapsed);
+ }
+
+ ///
+ /// Verifies that zero elapsed time returns zero.
+ ///
+ [Fact]
+ public void CalculateTickCountElapsed_ZeroElapsed_ReturnsZero()
+ {
+ uint elapsed = CalculateTickCountElapsed(1000, 1000);
+ Assert.Equal(0u, elapsed);
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
index 4d1dd14cfb..beb8df7992 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs
@@ -52,8 +52,6 @@ public static void Test(string srcConstr, string dstConstr, string dstTable)
Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive.");
Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive.");
- Assert.True((long)stats["ConnectionTime"] >= (long)stats["ExecutionTime"], "Connection Time is less than Execution Time.");
- Assert.True((long)stats["ExecutionTime"] >= (long)stats["NetworkServerTime"], "Execution Time is less than Network Server Time.");
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]);
DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]);