diff --git a/README.md b/README.md index 04c07d1..2a49dd6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ A high-performance, fully compliant .NET implementation of ULIDs (Universally Un [](https://www.nuget.org/packages/ByteAether.Ulid/) -ULIDs (Universally Unique Lexicographically Sortable Identifiers) offer a modern, human-readable alternative to traditional GUIDs, optimized specifically for distributed systems and time-ordered data. **ByteAether.Ulid** delivers a high-performance, specification-compliant .NET implementation engineered to resolve critical concurrency and persistence edge cases left unaddressed by alternative libraries. +ULIDs (Universally Unique Lexicographically Sortable Identifiers) offer a modern, human-readable alternative to traditional GUIDs, optimized specifically for distributed systems and time-ordered data. **ByteAether.Ulid** delivers a high-performance, specification-compliant .NET implementation engineered to resolve critical concurrency and persistence edge cases unaddressed by alternative libraries. ### Resilient Concurrency & Monotonic Overflow Handling @@ -150,7 +150,11 @@ You can customize ULID generation by providing `GenerationOptions`. This allows #### Example: Monotonic ULID with Random Increments -To generate ULIDs that are monotonically increasing with a random increment, you can specify the `Monotonicity` option. +The monotonicity state (last generated timestamp and 80-bit random payload) is bound directly to the lifecycle of the `GenerationOptions` instance. + +* **Instance Reuse (Recommended for Sequences):** Reusing a single `GenerationOptions` instance across calls guarantees strict, cross-thread monotonic ordering via lock-free atomic compare-and-exchange (CAS) operations. +* **Instance Isolation:** Passing a new `GenerationOptions` instance on each call isolates state, disabling monotonic sequence tracking between calls and eliminating CAS contention. + ```csharp using System; using ByteAether.Ulid; @@ -241,13 +245,13 @@ The `Ulid` implementation provides the following properties and methods: ### Properties - `Ulid.MinValue`\ - Represents an empty ULID, equivalent to `default(Ulid)` and `Ulid.New(new byte[16])`. + Represents an empty ULID, equivalent to `default(Ulid)` or `Ulid.New(new byte[16])`. - `Ulid.MaxValue`\ Represents the maximum possible value for a ULID (all bytes set to `0xFF`). - `Ulid.Empty`\ Alias for `Ulid.MinValue`. - `Ulid.DefaultGenerationOptions`\ - Default configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. + Gets or sets the global default `GenerationOptions` configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. - `.Time`\ Gets the timestamp component of the ULID as a `DateTimeOffset`. - `.TimeBytes`\ @@ -278,10 +282,15 @@ The `Ulid` implementation provides the following properties and methods: ### GenerationOptions -The `GenerationOptions` class provides detailed configuration for ULID generation, with the following key properties: +The `GenerationOptions` class encapsulates generation strategy, state retention, and lock-free thread synchronization for monotonic ULID generation. + +Configurable properties: - `Monotonicity`\ - Controls the behavior of ULID generation when multiple identifiers are created within the same millisecond. It determines whether ULIDs are strictly increasing or allow for random ordering within that millisecond. Available options include: `NonMonotonic`, `MonotonicIncrement` (default), `MonotonicRandom1Byte`, `MonotonicRandom2Byte`, `MonotonicRandom3Byte`, `MonotonicRandom4Byte`. + Defines the monotonic strategy when generating multiple ULIDs within the same millisecond. Each instance maintains an atomic state machine using lock-free Compare-And-Swap (CAS) primitives to guarantee strict sequential ordering without mutex locking. Options include: + - `NonMonotonic`: Generates fully random 80-bit payloads without state tracking. + - `MonotonicIncrement` (Default): Increments the least significant bit of the random payload upon sub-millisecond collisions. + - `MonotonicRandom1Byte`, `MonotonicRandom2Byte`, `MonotonicRandom3Byte`, `MonotonicRandom4Byte`: Adds a random integer increment within the specified byte range to the payload, strengthening entropy against enumeration attacks while maintaining monotonicity. - `InitialRandomSource`\ An `IRandomProvider` for generating the random bytes of a ULID. The default `CryptographicallySecureRandomProvider` ensures robust, unpredictable ULIDs using a cryptographically secure generator. diff --git a/src/Ulid.Tests/Ulid.New.Tests.cs b/src/Ulid.Tests/Ulid.New.Tests.cs index 3b7c090..b2b870b 100644 --- a/src/Ulid.Tests/Ulid.New.Tests.cs +++ b/src/Ulid.Tests/Ulid.New.Tests.cs @@ -2,18 +2,6 @@ public class UlidNewTests { - // A lock object to synchronize tests that rely on shared static state in the Ulid class, - // preventing race conditions and ensuring test isolation. - - // ReSharper disable once ChangeFieldTypeToSystemThreadingLock for older .net compatibility - private static readonly object _staticStateLock = new(); - - // We need safe DateTimeOffset values for monotonicity - private static readonly DateTimeOffset _lastTimestamp = DateTimeOffset.UtcNow.AddMinutes(1); - private static int _timestampOffsetCounter; - - private static DateTimeOffset GetDateTimeOffset() => _lastTimestamp.AddSeconds(++_timestampOffsetCounter); - /// /// A controllable random provider for testing purposes. It returns pre-configured byte sequences. /// @@ -93,67 +81,61 @@ public void New_WithUnixTimestampMillisecondsAndRandom_ShouldGenerateCorrectTime [Fact] public void New_NonMonotonic_CanProduceSmallerUlids() { - lock (_staticStateLock) + // Arrange + var timestamp = DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeMilliseconds(); + var random1 = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; + var random2 = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + var initialRandomProvider = new ControllableRandomProvider(random1, random2); + var options = new Ulid.GenerationOptions { - // Arrange - var timestamp = GetDateTimeOffset().ToUnixTimeMilliseconds(); - var random1 = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; - var random2 = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - - var initialRandomProvider = new ControllableRandomProvider(random1, random2); - var options = new Ulid.GenerationOptions - { - Monotonicity = Ulid.GenerationOptions.MonotonicityOptions.NonMonotonic, - InitialRandomSource = initialRandomProvider - }; - - // Act - var ulid1 = Ulid.New(timestamp, options); - var ulid2 = Ulid.New(timestamp, options); - - // Assert - Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); - Assert.Equal(random1, ulid1.Random.ToArray()); - - Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); - Assert.Equal(random2, ulid2.Random.ToArray()); - } + Monotonicity = Ulid.GenerationOptions.MonotonicityOptions.NonMonotonic, + InitialRandomSource = initialRandomProvider + }; + + // Act + var ulid1 = Ulid.New(timestamp, options); + var ulid2 = Ulid.New(timestamp, options); + + // Assert + Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); + Assert.Equal(random1, ulid1.Random.ToArray()); + + Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); + Assert.Equal(random2, ulid2.Random.ToArray()); } [Fact] public void New_MonotonicIncrement_ShouldOverflowToTimestamp() { - lock (_staticStateLock) + // Arrange + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var initialRandom = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD }; + + var initialRandomProvider = new ControllableRandomProvider(initialRandom); + + var options = new Ulid.GenerationOptions { - // Arrange - var timestamp = GetDateTimeOffset().ToUnixTimeMilliseconds(); - var initialRandom = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD }; - - var initialRandomProvider = new ControllableRandomProvider(initialRandom); - - var options = new Ulid.GenerationOptions - { - Monotonicity = Ulid.GenerationOptions.MonotonicityOptions.MonotonicIncrement, - InitialRandomSource = initialRandomProvider - }; - - // Act - var ulid1 = Ulid.New(timestamp, options); // Random ...FD - var ulid2 = Ulid.New(timestamp, options); // Random ...FE (incremented) - var ulid3 = Ulid.New(timestamp, options); // Random ...FF (incremented) - var ulid4 = Ulid.New(timestamp, options); // Overflow - - // Assert - Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); - Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); - Assert.Equal(timestamp, ulid3.Time.ToUnixTimeMilliseconds()); - Assert.Equal(timestamp + 1, ulid4.Time.ToUnixTimeMilliseconds()); - - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD }, ulid1.Random.ToArray()); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE }, ulid2.Random.ToArray()); - Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ulid3.Random.ToArray()); - Assert.Equal(new byte[10], ulid4.Random.ToArray()); - } + Monotonicity = Ulid.GenerationOptions.MonotonicityOptions.MonotonicIncrement, + InitialRandomSource = initialRandomProvider + }; + + // Act + var ulid1 = Ulid.New(timestamp, options); // Random ...FD + var ulid2 = Ulid.New(timestamp, options); // Random ...FE (incremented) + var ulid3 = Ulid.New(timestamp, options); // Random ...FF (incremented) + var ulid4 = Ulid.New(timestamp, options); // Overflow + + // Assert + Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); + Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); + Assert.Equal(timestamp, ulid3.Time.ToUnixTimeMilliseconds()); + Assert.Equal(timestamp + 1, ulid4.Time.ToUnixTimeMilliseconds()); + + Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD }, ulid1.Random.ToArray()); + Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE }, ulid2.Random.ToArray()); + Assert.Equal(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ulid3.Random.ToArray()); + Assert.Equal(new byte[10], ulid4.Random.ToArray()); } [Theory] @@ -166,41 +148,38 @@ public void New_MonotonicRandom_ShouldIncrementCorrectly( int incrementSize ) { - lock (_staticStateLock) - { - // Arrange - var timestamp = GetDateTimeOffset().ToUnixTimeMilliseconds(); + // Arrange + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - var initialRandom = new byte[10]; - initialRandom[9] = 0xA; // 10 + var initialRandom = new byte[10]; + initialRandom[9] = 0xA; // 10 - var increment = new byte[incrementSize]; - increment[0] = 4; // 10 + 4 + var increment = new byte[incrementSize]; + increment[0] = 4; // 10 + 4 - var incrementedRandom = new byte[10]; - incrementedRandom[9] = 15; // 10 + 4 + 1 : +1 comes from base implementation of Ulid + var incrementedRandom = new byte[10]; + incrementedRandom[9] = 15; // 10 + 4 + 1 : +1 comes from base implementation of Ulid - var initialRandomProvider = new ControllableRandomProvider(initialRandom); - var incrementRandomProvider = new ControllableRandomProvider(increment); + var initialRandomProvider = new ControllableRandomProvider(initialRandom); + var incrementRandomProvider = new ControllableRandomProvider(increment); - var options = new Ulid.GenerationOptions - { - Monotonicity = monotonicity, - InitialRandomSource = initialRandomProvider, - IncrementRandomSource = incrementRandomProvider - }; + var options = new Ulid.GenerationOptions + { + Monotonicity = monotonicity, + InitialRandomSource = initialRandomProvider, + IncrementRandomSource = incrementRandomProvider + }; - // Act - var ulid1 = Ulid.New(timestamp, options); - var ulid2 = Ulid.New(timestamp, options); + // Act + var ulid1 = Ulid.New(timestamp, options); + var ulid2 = Ulid.New(timestamp, options); - // Assert - Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); - Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); + // Assert + Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); + Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); - Assert.Equal(initialRandom, ulid1.Random.ToArray()); - Assert.Equal(incrementedRandom, ulid2.Random.ToArray()); - } + Assert.Equal(initialRandom, ulid1.Random.ToArray()); + Assert.Equal(incrementedRandom, ulid2.Random.ToArray()); } [Theory] @@ -212,42 +191,39 @@ public void New_MonotonicRandom_ShouldCarryOverIncrement( Ulid.GenerationOptions.MonotonicityOptions monotonicity, int incrementSize ) { - lock (_staticStateLock) - { - // Arrange - var timestamp = GetDateTimeOffset().ToUnixTimeMilliseconds(); + // Arrange + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - var initialRandom = new byte[10]; - initialRandom[9] = 0xFE; // Max - 2 + var initialRandom = new byte[10]; + initialRandom[9] = 0xFE; // Max - 2 - var increment = new byte[incrementSize]; - increment[0] = 0x01; // 1 as the other +1 comes from base implementation + var increment = new byte[incrementSize]; + increment[0] = 0x01; // 1 as the other +1 comes from base implementation - var initialRandomProvider = new ControllableRandomProvider(initialRandom); - var incrementRandomProvider = new ControllableRandomProvider(increment); + var initialRandomProvider = new ControllableRandomProvider(initialRandom); + var incrementRandomProvider = new ControllableRandomProvider(increment); - var options = new Ulid.GenerationOptions - { - Monotonicity = monotonicity, - InitialRandomSource = initialRandomProvider, - IncrementRandomSource = incrementRandomProvider - }; + var options = new Ulid.GenerationOptions + { + Monotonicity = monotonicity, + InitialRandomSource = initialRandomProvider, + IncrementRandomSource = incrementRandomProvider + }; - // Act - var ulid1 = Ulid.New(timestamp, options); - var ulid2 = Ulid.New(timestamp, options); + // Act + var ulid1 = Ulid.New(timestamp, options); + var ulid2 = Ulid.New(timestamp, options); - // Assert - Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); - Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); - Assert.Equal(initialRandom, ulid1.Random.ToArray()); + // Assert + Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); + Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds()); + Assert.Equal(initialRandom, ulid1.Random.ToArray()); - var expectedRandom = new byte[10]; - expectedRandom[8] = 0x01; // ...0100 - expectedRandom[9] = 0x00; + var expectedRandom = new byte[10]; + expectedRandom[8] = 0x01; // ...0100 + expectedRandom[9] = 0x00; - Assert.Equal(expectedRandom, ulid2.Random.ToArray()); - } + Assert.Equal(expectedRandom, ulid2.Random.ToArray()); } @@ -260,40 +236,37 @@ public void New_MonotonicRandom_ShouldOverflowToTimestamp( Ulid.GenerationOptions.MonotonicityOptions monotonicity, int incrementSize ) { - lock (_staticStateLock) + // Arrange + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var initialRandom = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + //Set all the last bytes to 0x00 that should be incremented later + for(var i = initialRandom.Length - incrementSize; i < initialRandom.Length; i++) { - // Arrange - var timestamp = GetDateTimeOffset().ToUnixTimeMilliseconds(); - var initialRandom = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - //Set all the last bytes to 0x00 that should be incremented later - for(var i = initialRandom.Length - incrementSize; i < initialRandom.Length; i++) - { - initialRandom[i] = 0x00; - } - - var increment = Enumerable.Repeat(0xFF, incrementSize).ToArray(); - // overflow +1 comes from the base implementation of Ulid - - var initialRandomProvider = new ControllableRandomProvider(initialRandom); - var incrementRandomProvider = new ControllableRandomProvider(increment); - - var options = new Ulid.GenerationOptions - { - Monotonicity = monotonicity, - InitialRandomSource = initialRandomProvider, - IncrementRandomSource = incrementRandomProvider - }; - - // Act - var ulid1 = Ulid.New(timestamp, options); // Random is all 0xFF - var ulid2 = Ulid.New(timestamp, options); // This should overflow - - // Assert - Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); - Assert.Equal(initialRandom, ulid1.Random.ToArray()); - - Assert.Equal(timestamp + 1, ulid2.Time.ToUnixTimeMilliseconds()); - Assert.Equal(new byte[10], ulid2.Random.ToArray()); + initialRandom[i] = 0x00; } + + var increment = Enumerable.Repeat(0xFF, incrementSize).ToArray(); + // overflow +1 comes from the base implementation of Ulid + + var initialRandomProvider = new ControllableRandomProvider(initialRandom); + var incrementRandomProvider = new ControllableRandomProvider(increment); + + var options = new Ulid.GenerationOptions + { + Monotonicity = monotonicity, + InitialRandomSource = initialRandomProvider, + IncrementRandomSource = incrementRandomProvider + }; + + // Act + var ulid1 = Ulid.New(timestamp, options); // Random is all 0xFF + var ulid2 = Ulid.New(timestamp, options); // This should overflow + + // Assert + Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds()); + Assert.Equal(initialRandom, ulid1.Random.ToArray()); + + Assert.Equal(timestamp + 1, ulid2.Time.ToUnixTimeMilliseconds()); + Assert.Equal(new byte[10], ulid2.Random.ToArray()); } } \ No newline at end of file diff --git a/src/Ulid/Ulid.New.GenerationOptions.cs b/src/Ulid/Ulid.New.GenerationOptions.cs index fe244c8..e3c7c23 100644 --- a/src/Ulid/Ulid.New.GenerationOptions.cs +++ b/src/Ulid/Ulid.New.GenerationOptions.cs @@ -1,3 +1,6 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + namespace ByteAether.Ulid; public readonly partial struct Ulid @@ -138,5 +141,51 @@ public MonotonicityOptions Monotonicity /// Defaults to an instance of . /// public IRandomProvider IncrementRandomSource { get; init; } = new PseudoRandomProvider(); - }; + + internal readonly State CurrentState = new(); + + // Separates Lock and LastUlid into different cache lines to prevent "false sharing" + // x64 has 64-byte cache lines; ARM64 (e.g., Apple Silicon) has 128-byte cache lines + [StructLayout(LayoutKind.Explicit)] + internal class State + { + // Cache Line 1 + [FieldOffset(16)] public LowLatencyLock Lock; + + // Cache Line 2(ARM64)/3(x64) +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + [FieldOffset(16+128)] public ulong LastUlidPart0; + [FieldOffset(16+128+8)] public ulong LastUlidPart1; +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value + +#if NET5_0_OR_GREATER + [SkipLocalsInit] +#endif +#if NETCOREAPP3_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal void Increment(uint addition) + { + var increment = (ulong)addition + 1; // carry = 1 is built-in + var part1 = ReverseOnLittleEndian(LastUlidPart1); + var newPart1 = part1 + increment; + LastUlidPart1 = ReverseOnLittleEndian(newPart1); + + if (newPart1 >= part1) + { + return; + } + + var part0 = ReverseOnLittleEndian(LastUlidPart0); + part0++; + LastUlidPart0 = ReverseOnLittleEndian(part0); + if (part0 == 0) + { + throw new OverflowException("Addition resulted in a ULID value larger than the absolute maximum ULID value."); + } + } + } + } } \ No newline at end of file diff --git a/src/Ulid/Ulid.New.cs b/src/Ulid/Ulid.New.cs index d924832..1a498ba 100644 --- a/src/Ulid/Ulid.New.cs +++ b/src/Ulid/Ulid.New.cs @@ -161,23 +161,6 @@ ref random.GetPinnableReference(), return ulid; } - // Separates Lock and LastUlid into different cache lines to prevent "false sharing" - // x64 has 64-byte cache lines; ARM64 (e.g., Apple Silicon) has 128-byte cache lines - [StructLayout(LayoutKind.Explicit)] - private class State - { - // Cache Line 1 - [FieldOffset(16)] public LowLatencyLock Lock; - - // Cache Line 2(ARM64)/3(x64) -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value - [FieldOffset(16+128)] public ulong LastUlidPart0; - [FieldOffset(16+128+8)] public ulong LastUlidPart1; -#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value - } - - private static readonly State _state = new(); - #if NET5_0_OR_GREATER [SkipLocalsInit] #endif @@ -202,13 +185,15 @@ private static void FillRandom(ref byte ulidBytesRef, long timestamp, Generation return; } - ref var lastUlidRef = ref Unsafe.As(ref _state.LastUlidPart0); + var state = options.CurrentState; - using(_state.Lock.Enter()) + ref var lastUlidRef = ref Unsafe.As(ref state.LastUlidPart0); + + using(state.Lock.Enter()) { // Read the last timestamp (from bytes 0-7 of "last ULID") // Shift it to get 48 bits. - var lastTime = ReverseOnLittleEndian(_state.LastUlidPart0); + var lastTime = ReverseOnLittleEndian(state.LastUlidPart0); lastTime >>= 16; // If the timestamp is bigger than the last one, generate a new ULID @@ -234,7 +219,7 @@ private static void FillRandom(ref byte ulidBytesRef, long timestamp, Generation if (monotonicity == GenerationOptions.MonotonicityOptions.MonotonicIncrement) { - LastUlidIncrement(0); + state.Increment(0); } else { @@ -253,7 +238,7 @@ private static void FillRandom(ref byte ulidBytesRef, long timestamp, Generation var mask = (uint)((1UL << totalBitsToKeep) - 1); increment &= mask; - LastUlidIncrement(increment); + state.Increment(increment); } // Copy full last ULID back to generated ULID @@ -261,33 +246,4 @@ private static void FillRandom(ref byte ulidBytesRef, long timestamp, Generation } } } - -#if NET5_0_OR_GREATER - [SkipLocalsInit] -#endif -#if NETCOREAPP3_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] -#else - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif - private static void LastUlidIncrement(uint addition) - { - var increment = (ulong)addition + 1; // carry = 1 is built-in - var part1 = ReverseOnLittleEndian(_state.LastUlidPart1); - var newPart1 = part1 + increment; - _state.LastUlidPart1 = ReverseOnLittleEndian(newPart1); - - if (newPart1 >= part1) - { - return; - } - - var part0 = ReverseOnLittleEndian(_state.LastUlidPart0); - part0++; - _state.LastUlidPart0 = ReverseOnLittleEndian(part0); - if (part0 == 0) - { - throw new OverflowException("Addition resulted in a ULID value larger than the absolute maximum ULID value."); - } - } } \ No newline at end of file