Skip to content

Commit

Permalink
Implemented Spinwaiting on sequence overflow (see #24). Moved MockTim…
Browse files Browse the repository at this point in the history
…eSource and new MockAutoIncrementingIntervalTimeSource to new namespace. Added spinwait to configuration. Added a bunch of constructor overloads. Upped versions.
  • Loading branch information
RobThree committed Jul 2, 2020
1 parent f181c1d commit dd03e48
Show file tree
Hide file tree
Showing 18 changed files with 291 additions and 31 deletions.
2 changes: 1 addition & 1 deletion IdGen.Configuration/AppConfigFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static IdGenerator GetFromConfig(string name)
if (idgen != null)
{
var ts = idgen.TickDuration == TimeSpan.Zero ? defaulttimesource : new DefaultTimeSource(idgen.Epoch, idgen.TickDuration);
return new IdGenerator(idgen.Id, new MaskConfig(idgen.TimestampBits, idgen.GeneratorIdBits, idgen.SequenceBits), ts);
return new IdGenerator(idgen.Id, new MaskConfig(idgen.TimestampBits, idgen.GeneratorIdBits, idgen.SequenceBits), ts, idgen.UseSpinWait);
}

throw new KeyNotFoundException();
Expand Down
4 changes: 2 additions & 2 deletions IdGen.Configuration/IdGen.Configuration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/RobThree/IdGen</PackageProjectUrl>
<PackageTags>idgen configuration</PackageTags>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageReleaseNotes>Added spinwait option (see #24)</PackageReleaseNotes>
<Description>Configuration support for IdGen</Description>
<Version>1.0</Version>
<Version>1.0.1</Version>
<RootNamespace>IdGen.Configuration</RootNamespace>
<PackageIcon>logo.png</PackageIcon>
<PackageIconUrl />
Expand Down
10 changes: 10 additions & 0 deletions IdGen.Configuration/IdGeneratorElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ private string StringEpoch
set => this["epoch"] = value;
}

/// <summary>
/// Gets/sets the spinwait option of the <see cref="IdGeneratorElement"/>.
/// </summary>
[ConfigurationProperty("useSpinWait", IsRequired = false)]
public bool UseSpinWait
{
get => (bool)this["useSpinWait"];
set => this["useSpinWait"] = value;
}

/// <summary>
/// Gets/sets the Epoch of the <see cref="IdGeneratorElement"/>.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions IdGen/IdGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/RobThree/IdGen</PackageProjectUrl>
<PackageTags>scalable unique id generator distributed</PackageTags>
<PackageReleaseNotes>Fix concurrency issue (see PR #23)</PackageReleaseNotes>
<PackageReleaseNotes>Added spinwait option (see #24)</PackageReleaseNotes>
<Description>Twitter Snowflake-alike ID generator for .Net</Description>
<Version>2.4</Version>
<Version>2.4.1</Version>
<RootNamespace>IdGen</RootNamespace>
<PackageIcon>logo.png</PackageIcon>
<PackageIconUrl />
Expand Down
129 changes: 120 additions & 9 deletions IdGen/IdGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;

namespace IdGen
{
Expand All @@ -17,9 +18,12 @@ public class IdGenerator : IIdGenerator<long>
/// </summary>
public static readonly DateTime DefaultEpoch = new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc);

private const bool DEFAULTSPINWAIT = false;

private int _sequence = 0;
private long _lastgen = -1;
private readonly long _generatorId;
private readonly bool _usespinwait;

private readonly long MASK_SEQUENCE;
private readonly long MASK_TIME;
Expand Down Expand Up @@ -52,6 +56,17 @@ public class IdGenerator : IIdGenerator<long>
/// </summary>
public ITimeSource TimeSource { get; private set; }

/// <summary>
/// Gets wether the <see cref="IdGenerator"/> uses <see cref="SpinWait">spinwaiting</see> when a
/// sequenceoverflow occurs.
/// </summary>
/// <remarks>
/// When true, the <see cref="IdGenerator"/> won't throw a <see cref="SequenceOverflowException"/> when a
/// sequence overflows; rather it will wait (using a spinwait) for the next tick and then return a new Id.
/// </remarks>
public bool UseSpinWait => _usespinwait;


/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class, 2015-01-01 0:00:00Z is used as default
/// epoch and the <see cref="IdGen.MaskConfig.Default"/> value is used for the <see cref="MaskConfig"/>. The
Expand All @@ -60,7 +75,19 @@ public class IdGenerator : IIdGenerator<long>
/// <param name="generatorId">The Id of the generator.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when GeneratorId exceeds maximum value.</exception>
public IdGenerator(int generatorId)
: this(generatorId, DefaultEpoch) { }
: this(generatorId, DefaultEpoch, DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class, 2015-01-01 0:00:00Z is used as default
/// epoch and the <see cref="IdGen.MaskConfig.Default"/> value is used for the <see cref="MaskConfig"/>. The
/// <see cref="DefaultTimeSource"/> is used to retrieve timestamp information.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when GeneratorId exceeds maximum value.</exception>
public IdGenerator(int generatorId, bool useSpinWait)
: this(generatorId, DefaultEpoch, useSpinWait) { }


/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="IdGen.MaskConfig.Default"/>
Expand All @@ -73,7 +100,21 @@ public IdGenerator(int generatorId)
/// Thrown when GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, DateTimeOffset epoch)
: this(generatorId, epoch, MaskConfig.Default) { }
: this(generatorId, epoch, MaskConfig.Default, DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="IdGen.MaskConfig.Default"/>
/// value is used for the <see cref="MaskConfig"/>. The <see cref="DefaultTimeSource"/> is used to retrieve
/// timestamp information.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="epoch">The Epoch of the generator.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, DateTimeOffset epoch, bool useSpinWait)
: this(generatorId, epoch, MaskConfig.Default, useSpinWait) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
Expand All @@ -87,7 +128,22 @@ public IdGenerator(int generatorId, DateTimeOffset epoch)
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, MaskConfig maskConfig)
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch)) { }
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch), DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
/// used to retrieve timestamp information.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentNullException">Thrown when maskConfig is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, MaskConfig maskConfig, bool useSpinWait)
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch), useSpinWait) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
Expand All @@ -102,7 +158,23 @@ public IdGenerator(int generatorId, MaskConfig maskConfig)
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig)
: this(generatorId, maskConfig, new DefaultTimeSource(epoch)) { }
: this(generatorId, maskConfig, new DefaultTimeSource(epoch), DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
/// used to retrieve timestamp information.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="epoch">The Epoch of the generator.</param>
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentNullException">Thrown when maskConfig is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig, bool useSpinWait)
: this(generatorId, maskConfig, new DefaultTimeSource(epoch), useSpinWait) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
Expand All @@ -115,7 +187,22 @@ public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig)
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, ITimeSource timeSource)
: this(generatorId, MaskConfig.Default, timeSource) { }
: this(generatorId, MaskConfig.Default, timeSource, DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="timeSource">The time-source to use when acquiring time data.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentNullException">Thrown when either maskConfig or timeSource is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, ITimeSource timeSource, bool useSpinWait)
: this(generatorId, MaskConfig.Default, timeSource, useSpinWait) { }


/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
Expand All @@ -129,6 +216,21 @@ public IdGenerator(int generatorId, ITimeSource timeSource)
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSource)
: this(generatorId, maskConfig, timeSource, DEFAULTSPINWAIT) { }

/// <summary>
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
/// </summary>
/// <param name="generatorId">The Id of the generator.</param>
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
/// <param name="timeSource">The time-source to use when acquiring time data.</param>
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
/// <exception cref="ArgumentNullException">Thrown when either maskConfig or timeSource is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
/// </exception>
public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSource, bool useSpinWait)
{
if (maskConfig == null)
throw new ArgumentNullException(nameof(maskConfig));
Expand Down Expand Up @@ -160,6 +262,7 @@ public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSourc
MaskConfig = maskConfig;
TimeSource = timeSource;
_generatorId = generatorId;
_usespinwait = useSpinWait;
}

/// <summary>
Expand Down Expand Up @@ -221,13 +324,21 @@ private long CreateIdImpl(out Exception exception)
if (timestamp == _lastgen)
{
if (_sequence >= MASK_SEQUENCE)
{
exception = new SequenceOverflowException("Sequence overflow. Refusing to generate id for rest of tick");
return -1;
{
if (_usespinwait)
{
SpinWait.SpinUntil(() => _lastgen != GetTicks());
return CreateIdImpl(out exception); // Try again
}
else
{
exception = new SequenceOverflowException("Sequence overflow. Refusing to generate id for rest of tick");
return -1;
}
}
_sequence++;
}
else // If we're in a new(er) "timeslot", so we can reset the sequence and store the new(er) "timeslot"
else // We're in a new(er) "timeslot", so we can reset the sequence and store the new(er) "timeslot"
{
_sequence = 0;
_lastgen = timestamp;
Expand Down
2 changes: 1 addition & 1 deletion IdGenDocumentation/Content/VersionHistory/v2.4.0.0.aml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>
Version 2.3.0.0 was released on 2020-04-18.
Version 2.4.0.0 was released on 2020-04-18.
</para>
</introduction>

Expand Down
32 changes: 32 additions & 0 deletions IdGenDocumentation/Content/VersionHistory/v2.4.1.0.aml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="941bdc70-cff6-4a4d-80b6-509114890f14" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>
Version 2.4.1.0 was released on 2020-07-02.
</para>
</introduction>

<section>
<title>Changes in This Release</title>
<content>

<list class="bullet">
<listItem>
<para>
Implemented spinwait (see <externalLink>
<linkText>#24</linkText>
<linkUri>https://github.com/RobThree/IdGen/pull/24</linkUri>
</externalLink>).
</para>
</listItem>
</list>
</content>
</section>

<relatedTopics>
<link xlink:href="4491ec46-0001-46c1-88b7-3dd2ee8472f3" />
</relatedTopics>

</developerConceptualDocument>
</topic>
Loading

0 comments on commit dd03e48

Please sign in to comment.