Skip to content

Timeuuid #68

Closed
wants to merge 3 commits into from
View
1 src/FluentCassandra.csproj
@@ -253,6 +253,7 @@
<Compile Include="Thrift\Transport\TTransportException.cs" />
<Compile Include="Thrift\Transport\TTransportFactory.cs" />
<Compile Include="TimestampHelper.cs" />
+ <Compile Include="TimeUUIDHelper.cs" />
<Compile Include="Types\AsciiType.cs" />
<Compile Include="Types\AsciiTypeConverter.cs" />
<Compile Include="System\Numerics\BigDecimal.cs" />
View
114 src/GuidGenerator.cs
@@ -7,7 +7,7 @@ namespace FluentCassandra
/// Used for generating UUID based on RFC 4122.
/// </summary>
/// <seealso href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace</seealso>
- public static partial class GuidGenerator
+ public static class GuidGenerator
{
// number of bytes in guid
public const int ByteArraySize = 16;
@@ -28,17 +28,24 @@ public static partial class GuidGenerator
private const byte NodeByte = 10;
// offset to move from 1/1/0001, which is 0-time for .NET, to gregorian 0-time of 10/15/1582
- private static readonly DateTimeOffset GregorianCalendarStart = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero);
+ private static DateTimeOffset GregorianCalendarStart = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero);
+
+ private static long TicksFromLastGuid = TimeUUIDHelper.UtcNow().Ticks;
+ private static object Semaphore = new object();
+
+ public static byte[] CurrentSequence = new byte[2];
+
+ private static readonly Random random = new Random();
+
- // random clock sequence and node
public static byte[] DefaultNode { get; set; }
static GuidGenerator()
{
DefaultNode = new byte[6];
- var random = new Random();
- random.NextBytes(DefaultNode);
+ random.NextBytes(DefaultNode);
+ random.NextBytes(CurrentSequence);
}
public static GuidVersion GetVersion(this Guid guid)
@@ -47,17 +54,10 @@ public static GuidVersion GetVersion(this Guid guid)
return (GuidVersion)((bytes[VersionByte] & 0xFF) >> VersionByteShift);
}
- private static byte[] ClockSequence
- {
- get
- {
- var ts = Stopwatch.GetTimestamp();
- return new byte[] {
- (byte)(ts),
- (byte)(ts >> 0x8)
- };
- }
- }
+ public static void GenerateNewClockSequence()
+ {
+ random.NextBytes(CurrentSequence);
+ }
public static DateTimeOffset GetDateTimeOffset(Guid guid)
{
@@ -91,19 +91,42 @@ public static DateTime GetUtcDateTime(Guid guid)
return GetDateTimeOffset(guid).UtcDateTime;
}
+ public static int GetClockSequence(Guid guid)
+ {
+ byte[] clockSequenceBytes = new byte[2];
+
+ Array.Copy(guid.ToByteArray(), 8, clockSequenceBytes, 0, 2);
+
+ return BitConverter.ToInt16(clockSequenceBytes, 0);
+ }
+
public static Guid GenerateTimeBasedGuid()
- {
- return GenerateTimeBasedGuid(DateTimePrecise.UtcNowOffset, ClockSequence, DefaultNode);
+ {
+ lock (Semaphore)
+ {
+ DateTimeOffset offset = TimeUUIDHelper.UtcNow();
+
+ long ticksForThisGuid = offset.Ticks;
+
+ if (ticksForThisGuid <= TicksFromLastGuid) //time has stopped or moved backward since last generation
+ {
+ GenerateNewClockSequence();
+ }
+
+ TicksFromLastGuid = ticksForThisGuid;
+
+ return GenerateTimeBasedGuid(offset, CurrentSequence, DefaultNode);
+ }
}
- public static Guid GenerateTimeBasedGuid(DateTime dateTime)
+ public static Guid GenerateTimeBasedGuid(DateTime dateTime)
{
- return GenerateTimeBasedGuid(dateTime, ClockSequence, DefaultNode);
+ return GenerateTimeBasedGuid(dateTime, CurrentSequence, DefaultNode);
}
public static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime)
{
- return GenerateTimeBasedGuid(dateTime, ClockSequence, DefaultNode);
+ return GenerateTimeBasedGuid(dateTime, CurrentSequence, DefaultNode);
}
public static Guid GenerateTimeBasedGuid(DateTime dateTime, byte[] clockSequence, byte[] node)
@@ -113,40 +136,41 @@ public static Guid GenerateTimeBasedGuid(DateTime dateTime, byte[] clockSequence
public static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime, byte[] clockSequence, byte[] node)
{
- if (clockSequence == null)
- throw new ArgumentNullException("clockSequence");
- if (node == null)
- throw new ArgumentNullException("node");
+ if (clockSequence == null)
+ throw new ArgumentNullException("clockSequence");
- if (clockSequence.Length != 2)
- throw new ArgumentOutOfRangeException("clockSequence", "The clockSequence must be 2 bytes.");
+ if (node == null)
+ throw new ArgumentNullException("node");
- if (node.Length != 6)
- throw new ArgumentOutOfRangeException("node", "The node must be 6 bytes.");
+ if (clockSequence.Length != 2)
+ throw new ArgumentOutOfRangeException("clockSequence", "The clockSequence must be 2 bytes.");
+ if (node.Length != 6)
+ throw new ArgumentOutOfRangeException("node", "The node must be 6 bytes.");
- long ticks = (dateTime - GregorianCalendarStart).Ticks;
- byte[] guid = new byte[ByteArraySize];
- byte[] timestamp = BitConverter.GetBytes(ticks);
+ long ticks = (dateTime - GregorianCalendarStart).Ticks;
+
+ byte[] guid = new byte[ByteArraySize];
+ byte[] timestamp = BitConverter.GetBytes(ticks);
- // copy node
- Array.Copy(node, 0, guid, NodeByte, Math.Min(6, node.Length));
+ // copy node
+ Array.Copy(node, 0, guid, NodeByte, Math.Min(6, node.Length));
- // copy clock sequence
- Array.Copy(clockSequence, 0, guid, GuidClockSequenceByte, Math.Min(2, clockSequence.Length));
+ // copy clock sequence
+ Array.Copy(clockSequence, 0, guid, GuidClockSequenceByte, Math.Min(2, clockSequence.Length));
- // copy timestamp
- Array.Copy(timestamp, 0, guid, TimestampByte, Math.Min(8, timestamp.Length));
+ // copy timestamp
+ Array.Copy(timestamp, 0, guid, TimestampByte, Math.Min(8, timestamp.Length));
- // set the variant
- guid[VariantByte] &= (byte)VariantByteMask;
- guid[VariantByte] |= (byte)VariantByteShift;
+ // set the variant
+ guid[VariantByte] &= (byte) VariantByteMask;
+ guid[VariantByte] |= (byte) VariantByteShift;
- // set the version
- guid[VersionByte] &= (byte)VersionByteMask;
- guid[VersionByte] |= (byte)((byte)GuidVersion.TimeBased << VersionByteShift);
+ // set the version
+ guid[VersionByte] &= (byte) VersionByteMask;
+ guid[VersionByte] |= (byte) ((byte) GuidVersion.TimeBased << VersionByteShift);
- return new Guid(guid);
+ return new Guid(guid);
}
}
}
View
13 src/TimeUUIDHelper.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace FluentCassandra
+{
+ public class TimeUUIDHelper
+ {
+ /// <summary>
+ /// Allows for substitution for Unit Tests
+ /// </summary>
+ public static Func<DateTimeOffset> UtcNow = () => DateTimePrecise.UtcNowOffset ;
+
+ }
+}
View
84 test/FluentCassandra.Sandbox/Program.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using FluentCassandra.Connections;
using FluentCassandra.Types;
using FluentCassandra.Linq;
@@ -13,7 +14,10 @@ internal class Program
{
public static readonly string KeyspaceName = ConfigurationManager.AppSettings["TestKeySpace"];
public static readonly Server Server = new Server(ConfigurationManager.AppSettings["TestServer"]);
-
+
+ private static HashSet<Guid> uuids = new HashSet<Guid>();
+ private static HashSet<byte[]> clockSequences = new HashSet<byte[]>();
+
#region Setup
@@ -460,36 +464,86 @@ private static void BigDecimalTest()
}
}
#endregion
-
+
+ #region TimeBasedUUIDGenerationTest
+
+ private static void TimeBasedUUIDGenerationTest()
+ {
+ //make it look like the clock reverted once in a while
+ TimeUUIDHelper.UtcNow = () =>
+ {
+ DateTimeOffset time = DateTimePrecise.UtcNowOffset;
+ if (time.Ticks % 100 == 0)
+ {
+ return time.AddTicks(-10);
+ }
+
+ return time;
+ };
+
+ TaskFactory factory = new TaskFactory();
+ Task[] tasks = new Task[50];
+
+ for (int i = 0; i < tasks.Length; i++)
+ {
+ Task t = factory.StartNew(TestCollidingTimeUUID);
+ tasks[i] = t;
+ }
+
+ Task.WaitAll(tasks);
+
+ Console.WriteLine("TimeBasedUUIDGenerationTest Complete");
+ }
+
+
+ private static void TestCollidingTimeUUID()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ Guid timeUUID = GuidGenerator.GenerateTimeBasedGuid();
+
+ if (uuids.Add(timeUUID)) continue;
+
+ Console.WriteLine("duplicate found at iteration:" + i);
+ break;
+ }
+ }
+
+ #endregion
+
private static void Main(string[] args)
{
- SetupKeyspace();
+ TimeBasedUUIDGenerationTest();
+
+ //SetupKeyspace();
- CreateFirstPost();
+ //CreateFirstPost();
- CreateSecondPost();
+ //CreateSecondPost();
- ReadFirstPost();
+ //ReadFirstPost();
- ReadAllPosts();
+ //ReadAllPosts();
- UpdateFirstPost();
+ //UpdateFirstPost();
- ReadFirstPost();
+ //ReadFirstPost();
- CreateComments();
+ //CreateComments();
- CreateColumnFamilyWithUUIDOperator();
+ //CreateColumnFamilyWithUUIDOperator();
- CreateColumnFamilyWithTimestampOperator();
+ //CreateColumnFamilyWithTimestampOperator();
- ReadComments();
+ //ReadComments();
- TombstoneTest();
+ //TombstoneTest();
- BigDecimalTest();
+ //BigDecimalTest();
Console.Read();
}
+
+
}
}
View
211 test/FluentCassandra.Tests/GuidGeneratorTest.cs
@@ -4,91 +4,126 @@
namespace FluentCassandra
{
-
- public class GuidGeneratorTest
- {
- [Fact]
- public void Type1Check()
- {
- // arrange
- var expected = GuidVersion.TimeBased;
- var guid = GuidGenerator.GenerateTimeBasedGuid();
-
- // act
- var actual = guid.GetVersion();
-
- // assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void SanityType1Check()
- {
- // arrange
- var expected = GuidVersion.TimeBased;
- var guid = Guid.NewGuid();
-
- // act
- var actual = guid.GetVersion();
-
- // assert
- Assert.NotEqual(expected, actual);
- }
-
- [Fact]
- public void GetDateTimeUnspecified()
- {
- // arrange
- var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Unspecified);
- var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
-
- // act
- var actual = GuidGenerator.GetDateTime(guid).ToLocalTime();
-
- // assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void GetDateTimeLocal()
- {
- // arrange
- var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Local);
- var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
-
- // act
- var actual = GuidGenerator.GetLocalDateTime(guid);
-
- // assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void GetDateTimeUtc()
- {
- // arrange
- var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Utc);
- var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
-
- // act
- var actual = GuidGenerator.GetUtcDateTime(guid);
-
- // assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void GetDateTimeOffset()
- {
- // arrange
- var expected = new DateTimeOffset(1980, 3, 14, 12, 23, 42, 112, TimeSpan.Zero);
- var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
-
- // act
- var actual = GuidGenerator.GetDateTimeOffset(guid);
-
- // assert
- Assert.Equal(expected, actual);
- }
- }
-}
+
+ public class GuidGeneratorTest
+ {
+ [Fact]
+ public void Type1Check()
+ {
+ // arrange
+ var expected = GuidVersion.TimeBased;
+ var guid = GuidGenerator.GenerateTimeBasedGuid();
+
+ // act
+ var actual = guid.GetVersion();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void SanityType1Check()
+ {
+ // arrange
+ var expected = GuidVersion.TimeBased;
+ var guid = Guid.NewGuid();
+
+ // act
+ var actual = guid.GetVersion();
+
+ // assert
+ Assert.NotEqual(expected, actual);
+ }
+
+ [Fact]
+ public void GetDateTimeUnspecified()
+ {
+ // arrange
+ var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Unspecified);
+ var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
+
+ // act
+ var actual = GuidGenerator.GetDateTime(guid).ToLocalTime();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void GetDateTimeLocal()
+ {
+ // arrange
+ var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Local);
+ var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
+
+ // act
+ var actual = GuidGenerator.GetLocalDateTime(guid);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void GetDateTimeUtc()
+ {
+ // arrange
+ var expected = new DateTime(1980, 3, 14, 12, 23, 42, 112, DateTimeKind.Utc);
+ var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
+
+ // act
+ var actual = GuidGenerator.GetUtcDateTime(guid);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void GetDateTimeOffset()
+ {
+ // arrange
+ var expected = new DateTimeOffset(1980, 3, 14, 12, 23, 42, 112, TimeSpan.Zero);
+ var guid = GuidGenerator.GenerateTimeBasedGuid(expected);
+
+ // act
+ var actual = GuidGenerator.GetDateTimeOffset(guid);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void DoesNotCreateDuplicateWhenTimeHasNotPassed()
+ {
+ // arrange
+ DateTimeOffset currentTime = DateTimeOffset.UtcNow;
+
+ TimeUUIDHelper.UtcNow = () => currentTime; //make sure all calls will return the same time
+ Guid firstGuid = GuidGenerator.GenerateTimeBasedGuid();
+
+ // act
+ Guid secondGuid = GuidGenerator.GenerateTimeBasedGuid();
+
+ // assert
+ Assert.True(firstGuid != secondGuid, "first: " + firstGuid + " second: " + secondGuid);
+ //Assert.NotEqual(firstGuid,secondGuid);
+ }
+
+ [Fact]
+ public void ClockSequenceChangesWhenTimeMovesBackward()
+ {
+ // arrange
+ DateTimeOffset currentTime = DateTimeOffset.UtcNow;
+
+ TimeUUIDHelper.UtcNow = () => currentTime;
+ Guid firstGuid = GuidGenerator.GenerateTimeBasedGuid();
+
+ // act
+ TimeUUIDHelper.UtcNow = () => currentTime.AddTicks(-1); //make sure clock went backwards
+ Guid secondGuid = GuidGenerator.GenerateTimeBasedGuid();
+
+ // assert
+ //clock sequence is not equal
+ Assert.NotEqual(GuidGenerator.GetClockSequence(firstGuid), GuidGenerator.GetClockSequence(secondGuid));
+ }
+ }
+}
Something went wrong with that request. Please try again.