From f8c06105f936efc049a748bd05f1edef4fd58e0f Mon Sep 17 00:00:00 2001 From: Mohammed Ahmed Hussien Date: Fri, 19 Jul 2024 21:06:26 +0300 Subject: [PATCH] Add Async method to generate unique id --- .../SnowflakeId.AspNetCoreSample/Program.cs | 2 +- example/SnowflakeId.Example/Program.cs | 2 +- .../SnowflakeIdServiceExtensions.cs | 1 + src/core/ISnowflakeService.cs | 13 ++- src/core/SnowflakeId.Core.csproj | 3 + ...dOptionBuilder.cs => SnowflakeIdConfig.cs} | 2 +- src/core/SnowflakeIdService.cs | 102 +++++++++++++----- tests/SnowflakeId.Tests.csproj | 3 + tests/SnowflakeIdConfigTest.cs | 47 ++++++++ tests/SnowflakeIdOptionBuilderTest.cs | 47 -------- tests/SnowflakeIdServiceExtensionsTest.cs | 6 +- tests/SnowflakeIdServiceTest.cs | 31 +++++- 12 files changed, 175 insertions(+), 84 deletions(-) rename src/core/{SnowflakeIdOptionBuilder.cs => SnowflakeIdConfig.cs} (96%) create mode 100644 tests/SnowflakeIdConfigTest.cs delete mode 100644 tests/SnowflakeIdOptionBuilderTest.cs diff --git a/example/SnowflakeId.AspNetCoreSample/Program.cs b/example/SnowflakeId.AspNetCoreSample/Program.cs index a93f481..609fee0 100644 --- a/example/SnowflakeId.AspNetCoreSample/Program.cs +++ b/example/SnowflakeId.AspNetCoreSample/Program.cs @@ -15,7 +15,7 @@ { long generatingId = snowflakeService.GenerateSnowflakeId(); DateTime generatedAt = snowflakeService.GetGeneratedDateTimeBySnowflakeId(generatingId); - int dataCenterId = snowflakeService.GetDataCenterIdBySnowflakId(generatingId); + int dataCenterId = snowflakeService.GetDataCenterIdBySnowflakeId(generatingId); return $"The genrated Id is: { generatingId } - and is genrated at: { generatedAt } - at Data Center Id: {dataCenterId}"; diff --git a/example/SnowflakeId.Example/Program.cs b/example/SnowflakeId.Example/Program.cs index 2849588..3e237e7 100644 --- a/example/SnowflakeId.Example/Program.cs +++ b/example/SnowflakeId.Example/Program.cs @@ -31,7 +31,7 @@ Build and implemented with love by Mohammed Ahmed Hussien Babiker Console.WriteLine("The Id is: {0} and is generated At: {1}", uniqueId, generatedAt); -var dataCenterId = idServive.GetDataCenterIdBySnowflakId(uniqueId); +var dataCenterId = idServive.GetDataCenterIdBySnowflakeId(uniqueId); Console.WriteLine("The id is generated at data center has id: {0}", dataCenterId); Console.ReadLine(); diff --git a/src/core/DependencyInjection/SnowflakeIdServiceExtensions.cs b/src/core/DependencyInjection/SnowflakeIdServiceExtensions.cs index 65016a3..087fef0 100644 --- a/src/core/DependencyInjection/SnowflakeIdServiceExtensions.cs +++ b/src/core/DependencyInjection/SnowflakeIdServiceExtensions.cs @@ -10,6 +10,7 @@ Everyone is permitted to copy and distribute verbatim copies using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection; using SnowflakeId.Core; +using Microsoft.Extensions.Logging.Abstractions; namespace SnowflakeId.DependencyInjection diff --git a/src/core/ISnowflakeService.cs b/src/core/ISnowflakeService.cs index 2915d9a..0844188 100644 --- a/src/core/ISnowflakeService.cs +++ b/src/core/ISnowflakeService.cs @@ -8,6 +8,8 @@ Everyone is permitted to copy and distribute verbatim copies using System; +using System.Threading.Tasks; +using System.Threading; namespace SnowflakeId.Core { @@ -22,6 +24,15 @@ public interface ISnowflakeService /// A new unique number that has a long type. long GenerateSnowflakeId(); + + /// + /// Generate new unique number, it's length is 19 digits asynchrony. + /// + /// cancellationToken + /// A new unique number that has a long type. + /// + Task GenerateSnowflakeIdAsync(CancellationToken cancellationToken =default); + /// /// A method caculated the generate date time for a given generated snowflake id. /// @@ -55,6 +66,6 @@ public interface ISnowflakeService /// /// snowflakeId. /// Data center id which has int data type. - int GetDataCenterIdBySnowflakId(long snowflakeId); + int GetDataCenterIdBySnowflakeId(long snowflakeId); } } diff --git a/src/core/SnowflakeId.Core.csproj b/src/core/SnowflakeId.Core.csproj index c703f9a..1748232 100644 --- a/src/core/SnowflakeId.Core.csproj +++ b/src/core/SnowflakeId.Core.csproj @@ -25,18 +25,21 @@ + + + diff --git a/src/core/SnowflakeIdOptionBuilder.cs b/src/core/SnowflakeIdConfig.cs similarity index 96% rename from src/core/SnowflakeIdOptionBuilder.cs rename to src/core/SnowflakeIdConfig.cs index b1fc6bd..b6fa36a 100644 --- a/src/core/SnowflakeIdOptionBuilder.cs +++ b/src/core/SnowflakeIdConfig.cs @@ -9,7 +9,7 @@ Everyone is permitted to copy and distribute verbatim copies using System; namespace SnowflakeId.Core { - internal class SnowflakeIdOptionBuilder + internal class SnowflakeIdConfig { /// /// Total size of the Id in bits, 64 diff --git a/src/core/SnowflakeIdService.cs b/src/core/SnowflakeIdService.cs index 2ac31ca..50e0aba 100644 --- a/src/core/SnowflakeIdService.cs +++ b/src/core/SnowflakeIdService.cs @@ -7,8 +7,12 @@ Everyone is permitted to copy and distribute verbatim copies */ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; +using System.Threading; +using System.Threading.Tasks; namespace SnowflakeId.Core { @@ -21,56 +25,81 @@ public class SnowflakeIdService : ISnowflakeService private long _sequence = 0L; // result is 22 - const int _timeStampShift = SnowflakeIdOptionBuilder.TotalBits - SnowflakeIdOptionBuilder.EpochBits; + const int _timeStampShift = SnowflakeIdConfig.TotalBits - SnowflakeIdConfig.EpochBits; // result is 12 - const int _machaineIdShift = SnowflakeIdOptionBuilder.TotalBits - SnowflakeIdOptionBuilder.EpochBits - SnowflakeIdOptionBuilder.MachineIdBits; + const int _machaineIdShift = SnowflakeIdConfig.TotalBits - SnowflakeIdConfig.EpochBits - SnowflakeIdConfig.MachineIdBits; private readonly SnowflakOptions _snowflakOptions; + private readonly ILogger _logger; /// /// When generating the Id I use the Epoch that start at 1970 Jan 1s ( Unix Time ) /// public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - - public SnowflakeIdService(IOptions options) + private readonly static SemaphoreSlim _sem = new SemaphoreSlim(1); + public SnowflakeIdService(IOptions options, ILogger logger) { _snowflakOptions = options.Value; + _logger = logger ?? new NullLogger(); } - #region Utilities - - private long getTimestamp() - { - return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds; - } - - private long waitToGetNextMillis(long currentTimestamp) + /// + /// Generate new unique number, it's length is 19 digits. + /// + /// A new unique number that has a long type. + public virtual long GenerateSnowflakeId() { - while (currentTimestamp == _lastTimestamp) + lock (threadLock) { - currentTimestamp = getTimestamp(); - } - return currentTimestamp; - } + long currentTimestamp = getTimestamp(); - #endregion + if (currentTimestamp < _lastTimestamp) + { + _logger.LogError("error in the server clock, thecurrent timestamp should be bigger than generated one, current timestamp is: {0}, and the last generated timestamp is: {1}", currentTimestamp, _lastTimestamp); + throw new InvalidOperationException("Error_In_The_Server_Clock"); + } + if (currentTimestamp == _lastTimestamp) + { + // generate a new timestamp when the _sequence is reached the ( 4096 - 1 ) - #region Methods + _sequence = (_sequence + 1) & SnowflakeIdConfig.MaxSequenceId; + + if (_sequence == 0) + { + currentTimestamp = waitToGetNextMillis(currentTimestamp); + } + } + else + { + _sequence = 0; + } + + _lastTimestamp = currentTimestamp; + + long result = (currentTimestamp << _timeStampShift) | ((long)_snowflakOptions.DataCenterId << _machaineIdShift) | (_sequence); + _logger.LogInformation("the gnerated unique id is {0}", result); + return result; + } + } /// - /// Generate new unique number, it's length is 19 digits. + /// Generate new unique number, it's length is 19 digits asynchrony. /// + /// cancellationToken /// A new unique number that has a long type. - public virtual long GenerateSnowflakeId() + /// + public virtual Task GenerateSnowflakeIdAsync(CancellationToken cancellationToken = default) { - lock (threadLock) + try { + _sem.Wait(cancellationToken); long currentTimestamp = getTimestamp(); if (currentTimestamp < _lastTimestamp) { + _logger.LogError("error in the server clock, thecurrent timestamp should be bigger than generated one, current timestamp is: {0}, and the last generated timestamp is: {1}", currentTimestamp, _lastTimestamp); throw new InvalidOperationException("Error_In_The_Server_Clock"); } @@ -78,7 +107,7 @@ public virtual long GenerateSnowflakeId() { // generate a new timestamp when the _sequence is reached the ( 4096 - 1 ) - _sequence = (_sequence + 1) & SnowflakeIdOptionBuilder.MaxSequenceId; + _sequence = (_sequence + 1) & SnowflakeIdConfig.MaxSequenceId; if (_sequence == 0) { @@ -93,11 +122,16 @@ public virtual long GenerateSnowflakeId() _lastTimestamp = currentTimestamp; long result = (currentTimestamp << _timeStampShift) | ((long)_snowflakOptions.DataCenterId << _machaineIdShift) | (_sequence); - - return result; + _logger.LogInformation("the gnerated unique id is {0}", result); + return Task.FromResult(result); } - } + finally + { + _sem.Release(); + } + + } /// /// A method caculated the generate date time for a given generated snowflake id. @@ -172,7 +206,7 @@ public virtual long GetSecondsSinceUnixEpochFromId(long snowflakeId) /// /// snowflakeId. /// Data center id which has int data type. - public virtual int GetDataCenterIdBySnowflakId(long snowflakeId) + public virtual int GetDataCenterIdBySnowflakeId(long snowflakeId) { if (snowflakeId <= 0) { @@ -185,10 +219,20 @@ public virtual int GetDataCenterIdBySnowflakId(long snowflakeId) return dataCenterId; } - #endregion - + private long getTimestamp() + { + return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds; + } + private long waitToGetNextMillis(long currentTimestamp) + { + while (currentTimestamp == _lastTimestamp) + { + currentTimestamp = getTimestamp(); + } + return currentTimestamp; + } } } diff --git a/tests/SnowflakeId.Tests.csproj b/tests/SnowflakeId.Tests.csproj index 1790bc5..bdafb9e 100644 --- a/tests/SnowflakeId.Tests.csproj +++ b/tests/SnowflakeId.Tests.csproj @@ -21,6 +21,7 @@ + @@ -28,6 +29,7 @@ + @@ -35,6 +37,7 @@ + diff --git a/tests/SnowflakeIdConfigTest.cs b/tests/SnowflakeIdConfigTest.cs new file mode 100644 index 0000000..fa3ba45 --- /dev/null +++ b/tests/SnowflakeIdConfigTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SnowflakeId.Core; + +namespace SnowflakeId.Tests +{ + public class SnowflakeIdConfigTest + { + [Fact] + public void SnowflakeIdConfig_TotalBits_Lenght_Is_64() + { + Assert.Equal(64, SnowflakeIdConfig.TotalBits); + } + + [Fact] + public void SnowflakeIdConfig_EpochBits_Length_Is_42() + { + Assert.Equal(42, SnowflakeIdConfig.EpochBits); + } + + [Fact] + public void SnowflakeIdConfig_MachineIdBits_Length_Is_10() + { + Assert.Equal(10, SnowflakeIdConfig.MachineIdBits); + } + + [Fact] + public void SnowflakeIdConfig_SequenceBits_Length_Is_12() + { + Assert.Equal(12, SnowflakeIdConfig.SequenceBits); + } + + [Fact] + public void SnowflakeIdConfig_MaxMachineId_Is_Equal_1023() + { + Assert.Equal(1023, SnowflakeIdConfig.MaxMachineId); + } + + [Fact] + public void SnowflakeIdConfig_MaxSequenceId_Is_Equal_4095() + { + Assert.Equal(4095, SnowflakeIdConfig.MaxSequenceId); + } + } +} diff --git a/tests/SnowflakeIdOptionBuilderTest.cs b/tests/SnowflakeIdOptionBuilderTest.cs deleted file mode 100644 index f5d2e4e..0000000 --- a/tests/SnowflakeIdOptionBuilderTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SnowflakeId.Core; - -namespace SnowflakeId.Tests -{ - public class SnowflakeIdOptionBuilderTest - { - [Fact] - public void SnowflakeIdOptionBuilder_TotalBits_Lenght_Is_64() - { - Assert.Equal(64, SnowflakeIdOptionBuilder.TotalBits); - } - - [Fact] - public void SnowflakeIdOptionBuilder_EpochBits_Length_Is_42() - { - Assert.Equal(42, SnowflakeIdOptionBuilder.EpochBits); - } - - [Fact] - public void SnowflakeIdOptionBuilder_MachineIdBits_Length_Is_10() - { - Assert.Equal(10, SnowflakeIdOptionBuilder.MachineIdBits); - } - - [Fact] - public void SnowflakeIdOptionBuilder_SequenceBits_Length_Is_12() - { - Assert.Equal(12, SnowflakeIdOptionBuilder.SequenceBits); - } - - [Fact] - public void SnowflakeIdOptionBuilder_MaxMachineId_Is_Equal_1023() - { - Assert.Equal(1023, SnowflakeIdOptionBuilder.MaxMachineId); - } - - [Fact] - public void SnowflakeIdOptionBuilder_MaxSequenceId_Is_Equal_4095() - { - Assert.Equal(4095, SnowflakeIdOptionBuilder.MaxSequenceId); - } - } -} diff --git a/tests/SnowflakeIdServiceExtensionsTest.cs b/tests/SnowflakeIdServiceExtensionsTest.cs index a422189..e74a037 100644 --- a/tests/SnowflakeIdServiceExtensionsTest.cs +++ b/tests/SnowflakeIdServiceExtensionsTest.cs @@ -21,7 +21,7 @@ public class SnowflakeIdServiceExtensionsTest public void AddSnowflakeUniqueId_ThrowArgumentNullException_With_Null_SnowflakOptions() { var services = new ServiceCollection(); - + services.AddLogging(); Assert.Throws(() => services.AddSnowflakeUniqueId(null)); } @@ -30,6 +30,7 @@ public void AddSnowflakeUniqueId_ThrowArgumentNullException_With_Null_SnowflakOp public void Can_Add_SnowflakeId_To_Service_Collections_With_SnowflakOptions() { var services = new ServiceCollection(); + services.AddLogging(); services.AddSnowflakeUniqueId(s => s.DataCenterId = 1); var serviceProvider = services.BuildServiceProvider(); @@ -45,6 +46,7 @@ public void Can_Add_SnowflakeId_To_Service_Collections_With_SnowflakOptions() public void Can_Replace_SnowflakeId_Default_Registration_By_Creating_Object_That_Implement_ISnowflakeService() { var services = new ServiceCollection(); + services.AddLogging(); services.AddScoped(typeof(ISnowflakeService), sp => Mock.Of()); services.AddSnowflakeUniqueId(s => s.DataCenterId = 1); @@ -62,7 +64,7 @@ public void Can_Replace_SnowflakeId_Default_Registration_By_Creating_Object_That public void AddSnowflakeUniqueId_Allow_Chaining() { var services = new ServiceCollection(); - + services.AddLogging(); Assert.Same(services, services.AddSnowflakeUniqueId(_ => { })); } } diff --git a/tests/SnowflakeIdServiceTest.cs b/tests/SnowflakeIdServiceTest.cs index 666f8f2..bb51a70 100644 --- a/tests/SnowflakeIdServiceTest.cs +++ b/tests/SnowflakeIdServiceTest.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using SnowflakeId.Core; using SnowflakeId.DependencyInjection; +using System.Collections.Generic; namespace SnowflakeId.Tests { @@ -10,8 +11,9 @@ public class SnowflakeIdServiceTest public void Can_Genertate_UniqueId() { var services = new ServiceCollection(); + services.AddLogging(); services.AddSnowflakeUniqueId(s => s.DataCenterId = 1); - + var serviceProvider = services.BuildServiceProvider(); var snowflakeService = serviceProvider.GetRequiredService(); @@ -22,17 +24,42 @@ public void Can_Genertate_UniqueId() Assert.True(idLength); } + [Fact] + public async void Can_Genertate_UniqueId_Asynchrony() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSnowflakeUniqueId(s => s.DataCenterId = 1); + + var serviceProvider = services.BuildServiceProvider(); + var snowflakeService = serviceProvider.GetRequiredService(); + + List uniqueIds = new List(); + var cts = new CancellationTokenSource(); + for (int i = 0; i < 100; i++) + { + var id = await snowflakeService.GenerateSnowflakeIdAsync(cts.Token); + var idLength = id.ToString().Length == 19; + uniqueIds.Add(id); + Assert.IsType(id); + Assert.True(idLength); + } + var listUniqeness = uniqueIds.Count != uniqueIds.Distinct().Count(); + Assert.False(listUniqeness); + } + [Fact] public void Can_Find_Data_CenterId_From_Generated_UniqueId() { var services = new ServiceCollection(); + services.AddLogging(); services.AddSnowflakeUniqueId(s => s.DataCenterId = 17); var serviceProvider = services.BuildServiceProvider(); var snowflakeService = serviceProvider.GetRequiredService(); var id = snowflakeService.GenerateSnowflakeId(); - var dataCenterId = snowflakeService.GetDataCenterIdBySnowflakId(id); + var dataCenterId = snowflakeService.GetDataCenterIdBySnowflakeId(id); Assert.IsType(dataCenterId); Assert.Equal(17, dataCenterId);