Skip to content

Commit 8f7542e

Browse files
committed
Fixed MassTransit#6124 - NewId.Next(n) was using the wrong algorithm for Guid generation
1 parent b617f57 commit 8f7542e

4 files changed

Lines changed: 154 additions & 22 deletions

File tree

src/MassTransit.Abstractions/NewId/INewIdGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public interface INewIdGenerator
1111

1212
Guid NextGuid();
1313

14+
ArraySegment<Guid> NextGuid(Guid[] ids, int index, int count);
15+
1416
ArraySegment<Guid> NextSequentialGuid(Guid[] ids, int index, int count);
1517

1618
Guid NextSequentialGuid();

src/MassTransit.Abstractions/NewId/NewId.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ namespace MassTransit
66
using NewIdFormatters;
77
using NewIdProviders;
88
#if NET6_0_OR_GREATER
9-
using System.Diagnostics;
109
using System.Runtime.Intrinsics.X86;
1110
using System.Runtime.Intrinsics;
1211
using System.Runtime.InteropServices;
1312
#endif
1413

14+
// We need to target netstandard2.0, so keep using ref parameter.
15+
// CS9191: The 'ref' modifier for argument 2 corresponding to 'in' parameter is equivalent to 'in'. Consider using 'in' instead.
16+
#pragma warning disable CS9191
1517

16-
// We need to target netstandard2.0, so keep using ref parameter.
17-
// CS9191: The 'ref' modifier for argument 2 corresponding to 'in' parameter is equivalent to 'in'. Consider using 'in' instead.
18-
#pragma warning disable CS9191
1918

2019
/// <summary>
2120
/// A NewId is a type that fits into the same space as a Guid/Uuid/unique identifier,
@@ -30,10 +29,10 @@ namespace MassTransit
3029
{
3130
public static readonly NewId Empty = new NewId(0, 0, 0, 0);
3231

33-
static readonly INewIdFormatter BraceFormatter = new DashedHexFormatter('{', '}');
34-
static readonly INewIdFormatter DashedHexFormatter = new DashedHexFormatter();
35-
static readonly INewIdFormatter HexFormatter = new HexFormatter();
36-
static readonly INewIdFormatter ParenFormatter = new DashedHexFormatter('(', ')');
32+
static readonly DashedHexFormatter BraceFormatter = new DashedHexFormatter('{', '}');
33+
static readonly DashedHexFormatter DashedHexFormatter = new DashedHexFormatter();
34+
static readonly HexFormatter HexFormatter = new HexFormatter();
35+
static readonly DashedHexFormatter ParenFormatter = new DashedHexFormatter('(', ')');
3736

3837
static INewIdGenerator? _generator;
3938
static ITickProvider? _tickProvider;
@@ -477,7 +476,7 @@ public static Guid[] NextGuid(int count)
477476
{
478477
var ids = new Guid[count];
479478

480-
_getGenerator().NextSequentialGuid(ids, 0, count);
479+
_getGenerator().NextGuid(ids, 0, count);
481480

482481
return ids;
483482
}
@@ -491,7 +490,7 @@ public static Guid[] NextGuid(int count)
491490
/// <returns></returns>
492491
public static ArraySegment<Guid> NextGuid(Guid[] ids, int index, int count)
493492
{
494-
return _getGenerator().NextSequentialGuid(ids, index, count);
493+
return _getGenerator().NextGuid(ids, index, count);
495494
}
496495

497496
/// <summary>
@@ -512,6 +511,30 @@ public static Guid NextSequentialGuid()
512511
return _getGenerator().NextSequentialGuid();
513512
}
514513

514+
/// <summary>
515+
/// Generate an array of NewIds
516+
/// </summary>
517+
/// <param name="count">The number of NewIds to generate</param>
518+
/// <returns></returns>
519+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
520+
public static Guid[] NextSequentialGuid(int count)
521+
{
522+
var ids = new Guid[count];
523+
524+
_getGenerator().NextSequentialGuid(ids, 0, count);
525+
526+
return ids;
527+
}
528+
529+
/// <summary>
530+
/// Generate an array of NewIds, and return it as a Guid in sequential format
531+
/// </summary>
532+
/// <returns></returns>
533+
public static ArraySegment<Guid> NextSequentialGuid(Guid[] ids, int index, int count)
534+
{
535+
return _getGenerator().NextSequentialGuid(ids, index, count);
536+
}
537+
515538
#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
516539
[MethodImpl(MethodImplOptions.AggressiveInlining)]
517540
static void FromGuid(in Guid guid, out NewId newId)

src/MassTransit.Abstractions/NewId/NewIdGenerator.cs

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ public Guid NextGuid()
9292
// swapping high and low byte, because SQL-server is doing the wrong ordering otherwise
9393
var sequenceSwapped = ((sequence << 8) | ((sequence >> 8) & 0x00FF)) & 0xFFFF;
9494

95-
#if NET6_0_OR_GREATER
95+
#if NET6_0_OR_GREATER
9696
if (Ssse3.IsSupported && BitConverter.IsLittleEndian)
9797
{
9898
var vec = Vector128.Create((int)a, b, _c, _d | sequenceSwapped);
9999
var result = Ssse3.Shuffle(vec.AsByte(), Vector128.Create((byte)12, 13, 14, 15, 8, 9, 10, 11, 5, 4, 3, 2, 1, 0, 7, 6));
100100
return Unsafe.As<Vector128<byte>, Guid>(ref result);
101101
}
102-
#endif
102+
#endif
103103

104104
var d = (byte)(b >> 8);
105105
var e = (byte)b;
@@ -128,9 +128,9 @@ public Guid NextSequentialGuid()
128128
var sequence = _sequence++;
129129

130130
var a = _a;
131-
#if NET6_0_OR_GREATER
131+
#if NET6_0_OR_GREATER
132132
var v = _b;
133-
#endif
133+
#endif
134134
var b = (short)(_b >> 16);
135135
var c = (short)_b;
136136

@@ -140,14 +140,14 @@ public Guid NextSequentialGuid()
140140
// swapping high and low byte, because SQL-server is doing the wrong ordering otherwise
141141
var sequenceSwapped = ((sequence << 8) | ((sequence >> 8) & 0x00FF)) & 0xFFFF;
142142

143-
#if NET6_0_OR_GREATER
143+
#if NET6_0_OR_GREATER
144144
if (Ssse3.IsSupported && BitConverter.IsLittleEndian)
145145
{
146146
var vec = Vector128.Create((int)a, v, _c, _d | sequenceSwapped);
147147
var result = Ssse3.Shuffle(vec.AsByte(), Vector128.Create((byte)0, 1, 2, 3, 6, 7, 4, 5, 11, 10, 9, 8, 15, 14, 13, 12));
148148
return Unsafe.As<Vector128<byte>, Guid>(ref result);
149149
}
150-
#endif
150+
#endif
151151

152152
var d = (byte)(_gc >> 8);
153153
var e = (byte)_gc;
@@ -190,16 +190,91 @@ public ArraySegment<NewId> Next(NewId[] ids, int index, int count)
190190
return new ArraySegment<NewId>(ids, index, count);
191191
}
192192

193+
public ArraySegment<Guid> NextGuid(Guid[] ids, int index, int count)
194+
{
195+
if (index + count > ids.Length)
196+
throw new ArgumentOutOfRangeException(nameof(count));
197+
198+
var ticks = _tickProvider.Ticks;
199+
200+
var a = _a;
201+
var b = _b;
202+
203+
var lockTaken = false;
204+
_spinLock.Enter(ref lockTaken);
205+
206+
if (ticks > _lastTick)
207+
UpdateTimestamp(ticks);
208+
209+
var d = (byte)(b >> 8);
210+
var e = (byte)b;
211+
var f = (byte)(a >> 24);
212+
var g = (byte)(a >> 16);
213+
var h = (byte)(a >> 8);
214+
var i = (byte)a;
215+
var j = (byte)(b >> 24);
216+
var k = (byte)(b >> 16);
217+
218+
var limit = index + count;
219+
for (var offset = index; offset < limit; offset++)
220+
{
221+
if (_sequence == 65535) // we are about to rollover, so we need to increment ticks
222+
{
223+
UpdateTimestamp(_lastTick + 1);
224+
225+
if (a != _a)
226+
{
227+
a = _a;
228+
f = (byte)(a >> 24);
229+
g = (byte)(a >> 16);
230+
h = (byte)(a >> 8);
231+
i = (byte)a;
232+
}
233+
234+
if (b != _b)
235+
{
236+
b = _b;
237+
d = (byte)(b >> 8);
238+
e = (byte)b;
239+
j = (byte)(b >> 24);
240+
k = (byte)(b >> 16);
241+
}
242+
}
243+
244+
var sequence = _sequence++;
245+
246+
// swapping high and low byte, because SQL-server is doing the wrong ordering otherwise
247+
var sequenceSwapped = ((sequence << 8) | ((sequence >> 8) & 0x00FF)) & 0xFFFF;
248+
249+
#if NET6_0_OR_GREATER
250+
if (Ssse3.IsSupported && BitConverter.IsLittleEndian)
251+
{
252+
var vec = Vector128.Create((int)a, b, _c, _d | sequenceSwapped);
253+
var result = Ssse3.Shuffle(vec.AsByte(), Vector128.Create((byte)12, 13, 14, 15, 8, 9, 10, 11, 5, 4, 3, 2, 1, 0, 7, 6));
254+
ids[offset] = Unsafe.As<Vector128<byte>, Guid>(ref result);
255+
continue;
256+
}
257+
#endif
258+
259+
ids[offset] = new Guid(_d | sequenceSwapped, _gb, _gc, d, e, f, g, h, i, j, k);
260+
}
261+
262+
if (lockTaken)
263+
_spinLock.Exit();
264+
265+
return new ArraySegment<Guid>(ids, index, count);
266+
}
267+
193268
public ArraySegment<Guid> NextSequentialGuid(Guid[] ids, int index, int count)
194269
{
195270
if (index + count > ids.Length)
196271
throw new ArgumentOutOfRangeException(nameof(count));
197272

198273
var ticks = _tickProvider.Ticks;
199274

200-
#if NET6_0_OR_GREATER
275+
#if NET6_0_OR_GREATER
201276
var v = _b;
202-
#endif
277+
#endif
203278
var a = _a;
204279
var b = (short)(_b >> 16);
205280
var c = (short)_b;
@@ -222,9 +297,9 @@ public ArraySegment<Guid> NextSequentialGuid(Guid[] ids, int index, int count)
222297
{
223298
UpdateTimestamp(_lastTick + 1);
224299

225-
#if NET6_0_OR_GREATER
300+
#if NET6_0_OR_GREATER
226301
v = _b;
227-
#endif
302+
#endif
228303
a = _a;
229304
b = (short)(_b >> 16);
230305
c = (short)_b;
@@ -235,15 +310,15 @@ public ArraySegment<Guid> NextSequentialGuid(Guid[] ids, int index, int count)
235310
// swapping high and low byte, because SQL-server is doing the wrong ordering otherwise
236311
var sequenceSwapped = ((sequence << 8) | ((sequence >> 8) & 0x00FF)) & 0xFFFF;
237312

238-
#if NET6_0_OR_GREATER
313+
#if NET6_0_OR_GREATER
239314
if (Ssse3.IsSupported && BitConverter.IsLittleEndian)
240315
{
241316
var vec = Vector128.Create((int)a, v, _c, _d | sequenceSwapped);
242317
var result = Ssse3.Shuffle(vec.AsByte(), Vector128.Create((byte)0, 1, 2, 3, 6, 7, 4, 5, 11, 10, 9, 8, 15, 14, 13, 12));
243318
ids[offset] = Unsafe.As<Vector128<byte>, Guid>(ref result);
244319
continue;
245320
}
246-
#endif
321+
#endif
247322

248323
var h = (byte)((_d | sequenceSwapped) >> 24);
249324
var i = (byte)((_d | sequenceSwapped) >> 16);

tests/MassTransit.Abstractions.Tests/NewId/NewId_Specs.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,38 @@ public void Should_generate_sequential_ids_quickly()
166166
}
167167
}
168168

169+
[Test]
170+
public void Should_be_using_the_correct_algorithm()
171+
{
172+
NewId.SetTickProvider(new StopwatchTickProvider());
173+
174+
var first = NewId.NextGuid();
175+
Guid[] next = NewId.NextGuid(3);
176+
177+
for (var i = 0; i < next.Length - 1; i++)
178+
{
179+
Assert.That(next[i].ToString().Substring(0, 4), Is.EqualTo(first.ToString().Substring(0, 4)));
180+
Assert.That(next[i].ToString().Substring(6), Is.EqualTo(first.ToString().Substring(6)));
181+
Assert.That(int.Parse(next[i].ToString().Substring(4, 2)), Is.EqualTo(i));
182+
}
183+
}
184+
185+
[Test]
186+
public void Should_be_using_the_correct_algorithm_for_sequential_guids()
187+
{
188+
NewId.SetTickProvider(new StopwatchTickProvider());
189+
190+
var first = NewId.NextSequentialGuid();
191+
var next = new Guid[3];
192+
NewId.NextSequentialGuid(next, 0, 3);
193+
194+
for (var i = 0; i < next.Length - 1; i++)
195+
{
196+
Assert.That(next[i].ToString().Substring(0,32), Is.EqualTo(first.ToString().Substring(0,32)));
197+
Assert.That(int.Parse(next[i].ToString().Substring(32,2)), Is.EqualTo(i));
198+
}
199+
}
200+
169201
[Test]
170202
[Explicit]
171203
public void Should_generate_unique_identifiers_with_each_invocation()

0 commit comments

Comments
 (0)