22using System . Collections ;
33using System . Collections . Generic ;
44using System . Runtime . CompilerServices ;
5+ using System . Threading ;
56
67namespace 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 ;
0 commit comments