Skip to content

Commit dd03e48

Browse files
committed
Implemented Spinwaiting on sequence overflow (see #24). Moved MockTimeSource and new MockAutoIncrementingIntervalTimeSource to new namespace. Added spinwait to configuration. Added a bunch of constructor overloads. Upped versions.
1 parent f181c1d commit dd03e48

18 files changed

+291
-31
lines changed

IdGen.Configuration/AppConfigFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static IdGenerator GetFromConfig(string name)
3737
if (idgen != null)
3838
{
3939
var ts = idgen.TickDuration == TimeSpan.Zero ? defaulttimesource : new DefaultTimeSource(idgen.Epoch, idgen.TickDuration);
40-
return new IdGenerator(idgen.Id, new MaskConfig(idgen.TimestampBits, idgen.GeneratorIdBits, idgen.SequenceBits), ts);
40+
return new IdGenerator(idgen.Id, new MaskConfig(idgen.TimestampBits, idgen.GeneratorIdBits, idgen.SequenceBits), ts, idgen.UseSpinWait);
4141
}
4242

4343
throw new KeyNotFoundException();

IdGen.Configuration/IdGen.Configuration.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1313
<PackageProjectUrl>https://github.com/RobThree/IdGen</PackageProjectUrl>
1414
<PackageTags>idgen configuration</PackageTags>
15-
<PackageReleaseNotes></PackageReleaseNotes>
15+
<PackageReleaseNotes>Added spinwait option (see #24)</PackageReleaseNotes>
1616
<Description>Configuration support for IdGen</Description>
17-
<Version>1.0</Version>
17+
<Version>1.0.1</Version>
1818
<RootNamespace>IdGen.Configuration</RootNamespace>
1919
<PackageIcon>logo.png</PackageIcon>
2020
<PackageIconUrl />

IdGen.Configuration/IdGeneratorElement.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ private string StringEpoch
3939
set => this["epoch"] = value;
4040
}
4141

42+
/// <summary>
43+
/// Gets/sets the spinwait option of the <see cref="IdGeneratorElement"/>.
44+
/// </summary>
45+
[ConfigurationProperty("useSpinWait", IsRequired = false)]
46+
public bool UseSpinWait
47+
{
48+
get => (bool)this["useSpinWait"];
49+
set => this["useSpinWait"] = value;
50+
}
51+
4252
/// <summary>
4353
/// Gets/sets the Epoch of the <see cref="IdGeneratorElement"/>.
4454
/// </summary>

IdGen/IdGen.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1313
<PackageProjectUrl>https://github.com/RobThree/IdGen</PackageProjectUrl>
1414
<PackageTags>scalable unique id generator distributed</PackageTags>
15-
<PackageReleaseNotes>Fix concurrency issue (see PR #23)</PackageReleaseNotes>
15+
<PackageReleaseNotes>Added spinwait option (see #24)</PackageReleaseNotes>
1616
<Description>Twitter Snowflake-alike ID generator for .Net</Description>
17-
<Version>2.4</Version>
17+
<Version>2.4.1</Version>
1818
<RootNamespace>IdGen</RootNamespace>
1919
<PackageIcon>logo.png</PackageIcon>
2020
<PackageIconUrl />

IdGen/IdGenerator.cs

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Runtime.CompilerServices;
5+
using System.Threading;
56

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

21+
private const bool DEFAULTSPINWAIT = false;
22+
2023
private int _sequence = 0;
2124
private long _lastgen = -1;
2225
private readonly long _generatorId;
26+
private readonly bool _usespinwait;
2327

2428
private readonly long MASK_SEQUENCE;
2529
private readonly long MASK_TIME;
@@ -52,6 +56,17 @@ public class IdGenerator : IIdGenerator<long>
5256
/// </summary>
5357
public ITimeSource TimeSource { get; private set; }
5458

59+
/// <summary>
60+
/// Gets wether the <see cref="IdGenerator"/> uses <see cref="SpinWait">spinwaiting</see> when a
61+
/// sequenceoverflow occurs.
62+
/// </summary>
63+
/// <remarks>
64+
/// When true, the <see cref="IdGenerator"/> won't throw a <see cref="SequenceOverflowException"/> when a
65+
/// sequence overflows; rather it will wait (using a spinwait) for the next tick and then return a new Id.
66+
/// </remarks>
67+
public bool UseSpinWait => _usespinwait;
68+
69+
5570
/// <summary>
5671
/// Initializes a new instance of the <see cref="IdGenerator"/> class, 2015-01-01 0:00:00Z is used as default
5772
/// epoch and the <see cref="IdGen.MaskConfig.Default"/> value is used for the <see cref="MaskConfig"/>. The
@@ -60,7 +75,19 @@ public class IdGenerator : IIdGenerator<long>
6075
/// <param name="generatorId">The Id of the generator.</param>
6176
/// <exception cref="ArgumentOutOfRangeException">Thrown when GeneratorId exceeds maximum value.</exception>
6277
public IdGenerator(int generatorId)
63-
: this(generatorId, DefaultEpoch) { }
78+
: this(generatorId, DefaultEpoch, DEFAULTSPINWAIT) { }
79+
80+
/// <summary>
81+
/// Initializes a new instance of the <see cref="IdGenerator"/> class, 2015-01-01 0:00:00Z is used as default
82+
/// epoch and the <see cref="IdGen.MaskConfig.Default"/> value is used for the <see cref="MaskConfig"/>. The
83+
/// <see cref="DefaultTimeSource"/> is used to retrieve timestamp information.
84+
/// </summary>
85+
/// <param name="generatorId">The Id of the generator.</param>
86+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
87+
/// <exception cref="ArgumentOutOfRangeException">Thrown when GeneratorId exceeds maximum value.</exception>
88+
public IdGenerator(int generatorId, bool useSpinWait)
89+
: this(generatorId, DefaultEpoch, useSpinWait) { }
90+
6491

6592
/// <summary>
6693
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="IdGen.MaskConfig.Default"/>
@@ -73,7 +100,21 @@ public IdGenerator(int generatorId)
73100
/// Thrown when GeneratorId exceeds maximum value or epoch in future.
74101
/// </exception>
75102
public IdGenerator(int generatorId, DateTimeOffset epoch)
76-
: this(generatorId, epoch, MaskConfig.Default) { }
103+
: this(generatorId, epoch, MaskConfig.Default, DEFAULTSPINWAIT) { }
104+
105+
/// <summary>
106+
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="IdGen.MaskConfig.Default"/>
107+
/// value is used for the <see cref="MaskConfig"/>. The <see cref="DefaultTimeSource"/> is used to retrieve
108+
/// timestamp information.
109+
/// </summary>
110+
/// <param name="generatorId">The Id of the generator.</param>
111+
/// <param name="epoch">The Epoch of the generator.</param>
112+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
113+
/// <exception cref="ArgumentOutOfRangeException">
114+
/// Thrown when GeneratorId exceeds maximum value or epoch in future.
115+
/// </exception>
116+
public IdGenerator(int generatorId, DateTimeOffset epoch, bool useSpinWait)
117+
: this(generatorId, epoch, MaskConfig.Default, useSpinWait) { }
77118

78119
/// <summary>
79120
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
@@ -87,7 +128,22 @@ public IdGenerator(int generatorId, DateTimeOffset epoch)
87128
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
88129
/// </exception>
89130
public IdGenerator(int generatorId, MaskConfig maskConfig)
90-
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch)) { }
131+
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch), DEFAULTSPINWAIT) { }
132+
133+
/// <summary>
134+
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
135+
/// used to retrieve timestamp information.
136+
/// </summary>
137+
/// <param name="generatorId">The Id of the generator.</param>
138+
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
139+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
140+
/// <exception cref="ArgumentNullException">Thrown when maskConfig is null.</exception>
141+
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
142+
/// <exception cref="ArgumentOutOfRangeException">
143+
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
144+
/// </exception>
145+
public IdGenerator(int generatorId, MaskConfig maskConfig, bool useSpinWait)
146+
: this(generatorId, maskConfig, new DefaultTimeSource(DefaultEpoch), useSpinWait) { }
91147

92148
/// <summary>
93149
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
@@ -102,7 +158,23 @@ public IdGenerator(int generatorId, MaskConfig maskConfig)
102158
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
103159
/// </exception>
104160
public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig)
105-
: this(generatorId, maskConfig, new DefaultTimeSource(epoch)) { }
161+
: this(generatorId, maskConfig, new DefaultTimeSource(epoch), DEFAULTSPINWAIT) { }
162+
163+
/// <summary>
164+
/// Initializes a new instance of the <see cref="IdGenerator"/> class. The <see cref="DefaultTimeSource"/> is
165+
/// used to retrieve timestamp information.
166+
/// </summary>
167+
/// <param name="generatorId">The Id of the generator.</param>
168+
/// <param name="epoch">The Epoch of the generator.</param>
169+
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
170+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
171+
/// <exception cref="ArgumentNullException">Thrown when maskConfig is null.</exception>
172+
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
173+
/// <exception cref="ArgumentOutOfRangeException">
174+
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
175+
/// </exception>
176+
public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig, bool useSpinWait)
177+
: this(generatorId, maskConfig, new DefaultTimeSource(epoch), useSpinWait) { }
106178

107179
/// <summary>
108180
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
@@ -115,7 +187,22 @@ public IdGenerator(int generatorId, DateTimeOffset epoch, MaskConfig maskConfig)
115187
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
116188
/// </exception>
117189
public IdGenerator(int generatorId, ITimeSource timeSource)
118-
: this(generatorId, MaskConfig.Default, timeSource) { }
190+
: this(generatorId, MaskConfig.Default, timeSource, DEFAULTSPINWAIT) { }
191+
192+
/// <summary>
193+
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
194+
/// </summary>
195+
/// <param name="generatorId">The Id of the generator.</param>
196+
/// <param name="timeSource">The time-source to use when acquiring time data.</param>
197+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
198+
/// <exception cref="ArgumentNullException">Thrown when either maskConfig or timeSource is null.</exception>
199+
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
200+
/// <exception cref="ArgumentOutOfRangeException">
201+
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
202+
/// </exception>
203+
public IdGenerator(int generatorId, ITimeSource timeSource, bool useSpinWait)
204+
: this(generatorId, MaskConfig.Default, timeSource, useSpinWait) { }
205+
119206

120207
/// <summary>
121208
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
@@ -129,6 +216,21 @@ public IdGenerator(int generatorId, ITimeSource timeSource)
129216
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
130217
/// </exception>
131218
public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSource)
219+
: this(generatorId, maskConfig, timeSource, DEFAULTSPINWAIT) { }
220+
221+
/// <summary>
222+
/// Initializes a new instance of the <see cref="IdGenerator"/> class.
223+
/// </summary>
224+
/// <param name="generatorId">The Id of the generator.</param>
225+
/// <param name="maskConfig">The <see cref="MaskConfig"/> of the generator.</param>
226+
/// <param name="timeSource">The time-source to use when acquiring time data.</param>
227+
/// <param name="useSpinWait">Whether to use spinwaiting when a sequenceoverflow occurs.</param>
228+
/// <exception cref="ArgumentNullException">Thrown when either maskConfig or timeSource is null.</exception>
229+
/// <exception cref="InvalidOperationException">Thrown when maskConfig defines a non-63 bit bitmask.</exception>
230+
/// <exception cref="ArgumentOutOfRangeException">
231+
/// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future.
232+
/// </exception>
233+
public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSource, bool useSpinWait)
132234
{
133235
if (maskConfig == null)
134236
throw new ArgumentNullException(nameof(maskConfig));
@@ -160,6 +262,7 @@ public IdGenerator(int generatorId, MaskConfig maskConfig, ITimeSource timeSourc
160262
MaskConfig = maskConfig;
161263
TimeSource = timeSource;
162264
_generatorId = generatorId;
265+
_usespinwait = useSpinWait;
163266
}
164267

165268
/// <summary>
@@ -221,13 +324,21 @@ private long CreateIdImpl(out Exception exception)
221324
if (timestamp == _lastgen)
222325
{
223326
if (_sequence >= MASK_SEQUENCE)
224-
{
225-
exception = new SequenceOverflowException("Sequence overflow. Refusing to generate id for rest of tick");
226-
return -1;
327+
{
328+
if (_usespinwait)
329+
{
330+
SpinWait.SpinUntil(() => _lastgen != GetTicks());
331+
return CreateIdImpl(out exception); // Try again
332+
}
333+
else
334+
{
335+
exception = new SequenceOverflowException("Sequence overflow. Refusing to generate id for rest of tick");
336+
return -1;
337+
}
227338
}
228339
_sequence++;
229340
}
230-
else // If we're in a new(er) "timeslot", so we can reset the sequence and store the new(er) "timeslot"
341+
else // We're in a new(er) "timeslot", so we can reset the sequence and store the new(er) "timeslot"
231342
{
232343
_sequence = 0;
233344
_lastgen = timestamp;

IdGenDocumentation/Content/VersionHistory/v2.4.0.0.aml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
44
<introduction>
55
<para>
6-
Version 2.3.0.0 was released on 2020-04-18.
6+
Version 2.4.0.0 was released on 2020-04-18.
77
</para>
88
</introduction>
99

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<topic id="941bdc70-cff6-4a4d-80b6-509114890f14" revisionNumber="1">
3+
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
4+
<introduction>
5+
<para>
6+
Version 2.4.1.0 was released on 2020-07-02.
7+
</para>
8+
</introduction>
9+
10+
<section>
11+
<title>Changes in This Release</title>
12+
<content>
13+
14+
<list class="bullet">
15+
<listItem>
16+
<para>
17+
Implemented spinwait (see <externalLink>
18+
<linkText>#24</linkText>
19+
<linkUri>https://github.com/RobThree/IdGen/pull/24</linkUri>
20+
</externalLink>).
21+
</para>
22+
</listItem>
23+
</list>
24+
</content>
25+
</section>
26+
27+
<relatedTopics>
28+
<link xlink:href="4491ec46-0001-46c1-88b7-3dd2ee8472f3" />
29+
</relatedTopics>
30+
31+
</developerConceptualDocument>
32+
</topic>

0 commit comments

Comments
 (0)