Skip to content

Commit

Permalink
Add Async method to generate unique id
Browse files Browse the repository at this point in the history
  • Loading branch information
Shoogn committed Jul 19, 2024
1 parent 144eeff commit f8c0610
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 84 deletions.
2 changes: 1 addition & 1 deletion example/SnowflakeId.AspNetCoreSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
Expand Down
2 changes: 1 addition & 1 deletion example/SnowflakeId.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion src/core/ISnowflakeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -22,6 +24,15 @@ public interface ISnowflakeService
/// <returns>A new unique number that has a long type.</returns>
long GenerateSnowflakeId();


/// <summary>
/// Generate new unique number, it's length is 19 digits asynchrony.
/// </summary>
/// <param name="cancellationToken">cancellationToken</param>
/// <returns>A new unique number that has a long type.</returns>
/// <exception cref="InvalidOperationException"></exception>
Task<long> GenerateSnowflakeIdAsync(CancellationToken cancellationToken =default);

/// <summary>
/// A method caculated the generate date time for a given generated snowflake id.
/// </summary>
Expand Down Expand Up @@ -55,6 +66,6 @@ public interface ISnowflakeService
/// </summary>
/// <param name="snowflakeId">snowflakeId.</param>
/// <returns>Data center id which has int data type.</returns>
int GetDataCenterIdBySnowflakId(long snowflakeId);
int GetDataCenterIdBySnowflakeId(long snowflakeId);
}
}
3 changes: 3 additions & 0 deletions src/core/SnowflakeId.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Everyone is permitted to copy and distribute verbatim copies
using System;
namespace SnowflakeId.Core
{
internal class SnowflakeIdOptionBuilder
internal class SnowflakeIdConfig
{
/// <summary>
/// Total size of the Id in bits, 64
Expand Down
102 changes: 73 additions & 29 deletions src/core/SnowflakeIdService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -21,64 +25,89 @@ 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<SnowflakeIdService> _logger;

/// <summary>
/// When generating the Id <see cref="SnowflakeIdService"/> I use the Epoch that start at 1970 Jan 1s ( Unix Time )
/// </summary>
public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

public SnowflakeIdService(IOptions<SnowflakOptions> options)
private readonly static SemaphoreSlim _sem = new SemaphoreSlim(1);
public SnowflakeIdService(IOptions<SnowflakOptions> options, ILogger<SnowflakeIdService> logger)
{
_snowflakOptions = options.Value;
_logger = logger ?? new NullLogger<SnowflakeIdService>();
}

#region Utilities

private long getTimestamp()
{
return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds;
}

private long waitToGetNextMillis(long currentTimestamp)
/// <summary>
/// Generate new unique number, it's length is 19 digits.
/// </summary>
/// <returns>A new unique number that has a long type.</returns>
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;
}
}

/// <summary>
/// Generate new unique number, it's length is 19 digits.
/// Generate new unique number, it's length is 19 digits asynchrony.
/// </summary>
/// <param name="cancellationToken">cancellationToken</param>
/// <returns>A new unique number that has a long type.</returns>
public virtual long GenerateSnowflakeId()
/// <exception cref="InvalidOperationException"></exception>
public virtual Task<long> 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");
}

if (currentTimestamp == _lastTimestamp)
{
// 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)
{
Expand All @@ -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();
}


}

/// <summary>
/// A method caculated the generate date time for a given generated snowflake id.
Expand Down Expand Up @@ -172,7 +206,7 @@ public virtual long GetSecondsSinceUnixEpochFromId(long snowflakeId)
/// </summary>
/// <param name="snowflakeId">snowflakeId.</param>
/// <returns>Data center id which has int data type.</returns>
public virtual int GetDataCenterIdBySnowflakId(long snowflakeId)
public virtual int GetDataCenterIdBySnowflakeId(long snowflakeId)
{
if (snowflakeId <= 0)
{
Expand All @@ -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;
}
}
}
3 changes: 3 additions & 0 deletions tests/SnowflakeId.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
47 changes: 47 additions & 0 deletions tests/SnowflakeIdConfigTest.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
47 changes: 0 additions & 47 deletions tests/SnowflakeIdOptionBuilderTest.cs

This file was deleted.

Loading

0 comments on commit f8c0610

Please sign in to comment.