diff options
author | Jan Tattermusch <jtattermusch@google.com> | 2015-07-22 16:21:57 -0700 |
---|---|---|
committer | Jan Tattermusch <jtattermusch@google.com> | 2015-07-23 18:27:16 -0700 |
commit | f6410f54bc5426217f09a57dc9deaea2acf3d1d5 (patch) | |
tree | 44ae8862e4a58be67c6a3267a085ba12c50e6da7 | |
parent | a1b820e981187036835ae0ff43b7b5a17af8e131 (diff) |
robust conversion from Timespec to DateTime and tests
-rw-r--r-- | src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj | 2 | ||||
-rw-r--r-- | src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs | 158 | ||||
-rw-r--r-- | src/csharp/Grpc.Core.Tests/TimespecTest.cs | 101 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Internal/Timespec.cs | 128 | ||||
-rw-r--r-- | src/csharp/ext/grpc_csharp_ext.c | 12 |
5 files changed, 264 insertions, 137 deletions
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index 927954c448..7070802d0f 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -44,13 +44,13 @@ <Compile Include="ClientServerTest.cs" /> <Compile Include="ServerTest.cs" /> <Compile Include="GrpcEnvironmentTest.cs" /> - <Compile Include="TimespecTest.cs" /> <Compile Include="PInvokeTest.cs" /> <Compile Include="Internal\MetadataArraySafeHandleTest.cs" /> <Compile Include="Internal\CompletionQueueSafeHandleTest.cs" /> <Compile Include="Internal\CompletionQueueEventTest.cs" /> <Compile Include="Internal\ChannelArgsSafeHandleTest.cs" /> <Compile Include="ChannelOptionsTest.cs" /> + <Compile Include="Internal\TimespecTest.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs new file mode 100644 index 0000000000..69b94bb393 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs @@ -0,0 +1,158 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Runtime.InteropServices; +using Grpc.Core.Internal; +using NUnit.Framework; + +namespace Grpc.Core.Internal.Tests +{ + public class TimespecTest + { + [Test] + public void Now() + { + var timespec = Timespec.Now; + } + + [Test] + public void InfFuture() + { + var timespec = Timespec.InfFuture; + } + + [Test] + public void InfPast() + { + var timespec = Timespec.InfPast; + } + + [Test] + public void TimespecSizeIsNativeSize() + { + Assert.AreEqual(Timespec.NativeSize, Marshal.SizeOf(typeof(Timespec))); + } + + [Test] + public void ToDateTime() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + new Timespec(IntPtr.Zero, 0).ToDateTime()); + + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50), + new Timespec(new IntPtr(10), 5000).ToDateTime()); + + Assert.AreEqual(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc), + new Timespec(new IntPtr(1437452508), 0).ToDateTime()); + + // before epoch + Assert.AreEqual(new DateTime(1969, 12, 31, 23, 59, 55, DateTimeKind.Utc).AddTicks(10), + new Timespec(new IntPtr(-5), 1000).ToDateTime()); + } + + [Test] + public void ToDateTime_RoundUp() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddTicks(1), + new Timespec(IntPtr.Zero, 99).ToDateTime()); + } + + [Test] + public void ToDateTime_WrongInputs() + { + Assert.Throws(typeof(InvalidOperationException), + () => new Timespec(new IntPtr(0), -2).ToDateTime()); + Assert.Throws(typeof(InvalidOperationException), + () => new Timespec(new IntPtr(0), 1000 * 1000 * 1000).ToDateTime()); + Assert.Throws(typeof(InvalidOperationException), + () => new Timespec(new IntPtr(0), 0, GPRClockType.Monotonic).ToDateTime()); + } + + [Test] + public void ToDateTime_ReturnsUtc() + { + Assert.AreEqual(DateTimeKind.Utc, new Timespec(new IntPtr(1437452508), 0).ToDateTime().Kind); + Assert.AreNotEqual(DateTimeKind.Unspecified, new Timespec(new IntPtr(1437452508), 0).ToDateTime().Kind); + } + + [Test] + public void ToDateTime_Infinity() + { + Assert.AreEqual(DateTime.MaxValue, Timespec.InfFuture.ToDateTime()); + Assert.AreEqual(DateTime.MinValue, Timespec.InfPast.ToDateTime()); + } + + [Test] + public void ToDateTime_OverflowGivesMaxOrMinVal() + { + // we can only get overflow in ticks arithmetic on 64-bit + if (IntPtr.Size == 8) + { + var timespec = new Timespec(new IntPtr(long.MaxValue - 100), 0); + Assert.AreNotEqual(Timespec.InfFuture, timespec); + Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime()); + + Assert.AreEqual(DateTime.MinValue, new Timespec(new IntPtr(long.MinValue + 100), 0).ToDateTime()); + } + else + { + Console.WriteLine("Test cannot be run on this platform, skipping the test."); + } + } + + [Test] + public void ToDateTime_OutOfRangeGivesMaxOrMinVal() + { + // we can only get out of range on 64-bit, on 32 bit the max + // timestamp is ~ Jan 19 2038, which is far within range of DateTime + // same case for min value. + if (IntPtr.Size == 8) + { + // DateTime range goes up to year 9999, 20000 years from now should + // be out of range. + long seconds = 20000L * 365L * 24L * 3600L; + + var timespec = new Timespec(new IntPtr(seconds), 0); + Assert.AreNotEqual(Timespec.InfFuture, timespec); + Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime()); + + Assert.AreEqual(DateTime.MinValue, new Timespec(new IntPtr(-seconds), 0).ToDateTime()); + } + else + { + Console.WriteLine("Test cannot be run on this platform, skipping the test"); + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/TimespecTest.cs deleted file mode 100644 index a34b407a01..0000000000 --- a/src/csharp/Grpc.Core.Tests/TimespecTest.cs +++ /dev/null @@ -1,101 +0,0 @@ -#region Copyright notice and license - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#endregion - -using System; -using System.Runtime.InteropServices; -using Grpc.Core.Internal; -using NUnit.Framework; - -namespace Grpc.Core.Internal.Tests -{ - public class TimespecTest - { - [Test] - public void Now() - { - var timespec = Timespec.Now; - } - - [Test] - public void InfFuture() - { - var timespec = Timespec.InfFuture; - } - - [Test] - public void TimespecSizeIsNativeSize() - { - Assert.AreEqual(Timespec.NativeSize, Marshal.SizeOf(typeof(Timespec))); - } - - [Test] - public void ToDateTime() - { - Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), - new Timespec(IntPtr.Zero, 0).ToDateTime()); - - Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50), - new Timespec(new IntPtr(10), 5000).ToDateTime()); - - Assert.AreEqual(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc), - new Timespec(new IntPtr(1437452508), 0).ToDateTime()); - } - - [Test] - public void Add() - { - var t = new Timespec { tv_sec = new IntPtr(12345), tv_nsec = 123456789 }; - var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10)); - Assert.AreEqual(result.tv_sec, new IntPtr(12355)); - Assert.AreEqual(result.tv_nsec, 123456789); - } - - [Test] - public void Add_Nanos() - { - var t = new Timespec { tv_sec = new IntPtr(12345), tv_nsec = 123456789 }; - var result = t.Add(TimeSpan.FromTicks(10)); - Assert.AreEqual(result.tv_sec, new IntPtr(12345)); - Assert.AreEqual(result.tv_nsec, 123456789 + 1000); - } - - [Test] - public void Add_NanosOverflow() - { - var t = new Timespec { tv_sec = new IntPtr(12345), tv_nsec = 999999999 }; - var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10 + 10)); - Assert.AreEqual(result.tv_sec, new IntPtr(12356)); - Assert.AreEqual(result.tv_nsec, 999); - } - } -} diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs index da2819f14d..32a9c93f77 100644 --- a/src/csharp/Grpc.Core/Internal/Timespec.cs +++ b/src/csharp/Grpc.Core/Internal/Timespec.cs @@ -32,6 +32,8 @@ using System; using System.Runtime.InteropServices; using System.Threading; +using Grpc.Core.Utils; + namespace Grpc.Core.Internal { /// <summary> @@ -40,32 +42,40 @@ namespace Grpc.Core.Internal [StructLayout(LayoutKind.Sequential)] internal struct Timespec { - const int NanosPerSecond = 1000 * 1000 * 1000; - const int NanosPerTick = 100; + const long NanosPerSecond = 1000 * 1000 * 1000; + const long NanosPerTick = 100; + const long TicksPerSecond = NanosPerSecond / NanosPerTick; static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); [DllImport("grpc_csharp_ext.dll")] - static extern Timespec gprsharp_now(); + static extern Timespec gprsharp_now(GPRClockType clockType); + + [DllImport("grpc_csharp_ext.dll")] + static extern Timespec gprsharp_inf_future(GPRClockType clockType); [DllImport("grpc_csharp_ext.dll")] - static extern Timespec gprsharp_inf_future(); + static extern Timespec gprsharp_inf_past(GPRClockType clockType); [DllImport("grpc_csharp_ext.dll")] static extern int gprsharp_sizeof_timespec(); - public Timespec(IntPtr tv_sec, int tv_nsec) + public Timespec(IntPtr tv_sec, int tv_nsec) : this(tv_sec, tv_nsec, GPRClockType.Realtime) + { + } + + public Timespec(IntPtr tv_sec, int tv_nsec, GPRClockType clock_type) { this.tv_sec = tv_sec; this.tv_nsec = tv_nsec; - this.clock_type = GPRClockType.Realtime; + this.clock_type = clock_type; } // NOTE: on linux 64bit sizeof(gpr_timespec) = 16, on windows 32bit sizeof(gpr_timespec) = 8 // so IntPtr seems to have the right size to work on both. - public System.IntPtr tv_sec; - public int tv_nsec; - public GPRClockType clock_type; + private System.IntPtr tv_sec; + private int tv_nsec; + private GPRClockType clock_type; /// <summary> /// Timespec a long time in the future. @@ -74,54 +84,108 @@ namespace Grpc.Core.Internal { get { - return gprsharp_inf_future(); + return gprsharp_inf_future(GPRClockType.Realtime); } } - public static Timespec Now + /// <summary> + /// Timespec a long time in the past. + /// </summary> + public static Timespec InfPast { get { - return gprsharp_now(); + return gprsharp_inf_past(GPRClockType.Realtime); } } - - public DateTime ToDateTime() + + /// <summary> + /// Return Timespec representing the current time. + /// </summary> + public static Timespec Now { - return UnixEpoch.AddTicks(tv_sec.ToInt64() * (NanosPerSecond / NanosPerTick) + tv_nsec / NanosPerTick); + get + { + return gprsharp_now(GPRClockType.Realtime); + } } - internal static int NativeSize + /// <summary> + /// Seconds since unix epoch. + /// </summary> + public IntPtr TimevalSeconds { get { - return gprsharp_sizeof_timespec(); + return tv_sec; } } /// <summary> - /// Creates a GPR deadline from current instant and given timeout. + /// The nanoseconds part of timeval. /// </summary> - /// <returns>The from timeout.</returns> - public static Timespec DeadlineFromTimeout(TimeSpan timeout) + public int TimevalNanos { - if (timeout == Timeout.InfiniteTimeSpan) + get { - return Timespec.InfFuture; + return tv_nsec; } - return Timespec.Now.Add(timeout); } + + /// <summary> + /// Converts Timespec to DateTime. + /// Timespec needs to be of type GPRClockType.Realtime and needs to represent a legal value. + /// DateTime has lower resolution (100ns), so rounding can occurs. + /// Value are always rounded up to the nearest DateTime value in the future. + /// + /// For Timespec.InfFuture or if timespec is after the largest representable DateTime, DateTime.MaxValue is returned. + /// For Timespec.InfPast or if timespec is before the lowest representable DateTime, DateTime.MinValue is returned. + /// + /// Unless DateTime.MaxValue or DateTime.MinValue is returned, the resulting DateTime is always in UTC + /// (DateTimeKind.Utc) + /// </summary> + public DateTime ToDateTime() + { + Preconditions.CheckState(tv_nsec >= 0 && tv_nsec < NanosPerSecond); + Preconditions.CheckState(clock_type == GPRClockType.Realtime); + + // fast path for InfFuture + if (this.Equals(InfFuture)) + { + return DateTime.MaxValue; + } + + // fast path for InfPast + if (this.Equals(InfPast)) + { + return DateTime.MinValue; + } - public Timespec Add(TimeSpan timeSpan) + try + { + // convert nanos to ticks, round up to the nearest tick + long ticksFromNanos = tv_nsec / NanosPerTick + ((tv_nsec % NanosPerTick != 0) ? 1 : 0); + long ticksTotal = checked(tv_sec.ToInt64() * TicksPerSecond + ticksFromNanos); + return UnixEpoch.AddTicks(ticksTotal); + } + catch (OverflowException) + { + // ticks out of long range + return tv_sec.ToInt64() > 0 ? DateTime.MaxValue : DateTime.MinValue; + } + catch (ArgumentOutOfRangeException) + { + // resulting date time would be larger than MaxValue + return tv_sec.ToInt64() > 0 ? DateTime.MaxValue : DateTime.MinValue; + } + } + + internal static int NativeSize { - long nanos = (long)tv_nsec + (timeSpan.Ticks % TimeSpan.TicksPerSecond) * NanosPerTick; - long overflow_sec = (nanos > NanosPerSecond) ? 1 : 0; - - Timespec result; - result.tv_nsec = (int)(nanos % NanosPerSecond); - result.tv_sec = new IntPtr(tv_sec.ToInt64() + (timeSpan.Ticks / TimeSpan.TicksPerSecond) + overflow_sec); - result.clock_type = GPRClockType.Realtime; - return result; + get + { + return gprsharp_sizeof_timespec(); + } } } } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 682521446f..1cc44ebda7 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -433,10 +433,16 @@ grpcsharp_channel_args_destroy(grpc_channel_args *args) { /* Timespec */ -GPR_EXPORT gpr_timespec GPR_CALLTYPE gprsharp_now(void) { return gpr_now(GPR_CLOCK_REALTIME); } +GPR_EXPORT gpr_timespec GPR_CALLTYPE gprsharp_now(gpr_clock_type clock_type) { + return gpr_now(clock_type); +} + +GPR_EXPORT gpr_timespec GPR_CALLTYPE gprsharp_inf_future(gpr_clock_type clock_type) { + return gpr_inf_future(clock_type); +} -GPR_EXPORT gpr_timespec GPR_CALLTYPE gprsharp_inf_future(void) { - return gpr_inf_future(GPR_CLOCK_REALTIME); +GPR_EXPORT gpr_timespec GPR_CALLTYPE gprsharp_inf_past(gpr_clock_type clock_type) { + return gpr_inf_past(clock_type); } GPR_EXPORT gpr_int32 GPR_CALLTYPE gprsharp_sizeof_timespec(void) { |