Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions example/SnowflakeId.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,33 @@ Build and implemented with love by Mohammed Ahmed Hussien Babiker
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SnowflakeId.Core;
using SnowflakeId.Core.Events;
using SnowflakeId.DependencyInjection;
using System;
using System.Threading.Tasks;


IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddSnowflakeUniqueId(options =>
services.AddSnowflakeUniqueId( options =>
{
options.DataCenterId = 7;
});
options.Events = new SnowflakeIdEvents
{
OnCreatedSnowflakeId = context =>
{
Console.WriteLine("OnCreatedSnowflakeId --> The Id is: {0} and is generated At: {1}", context.Id, context.GeneratedDateTime);
return Task.CompletedTask;
},
OnCreatingSnowflakeId = context =>
{
Console.WriteLine("OnCreatingSnowflakeId --> Generating Id at data center has id: {0}", context.DataCenterId);
return Task.CompletedTask;
}
};
}
);
}).Build();

var idServive = host.Services.GetRequiredService<ISnowflakeService>();
Expand Down
13 changes: 7 additions & 6 deletions src/core/DependencyInjection/SnowflakeIdServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ namespace SnowflakeId.DependencyInjection
public static class SnowflakeIdServiceExtensions
{
public static IServiceCollection AddSnowflakeUniqueId(this IServiceCollection services,
Action<SnowflakOptions> setupAction)
Action<SnowflakOptions> setupAction = null)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
services.AddOptions();
var o = new SnowflakOptions();
services.AddSingleton(o);
setupAction?.Invoke(o);

services.TryAddScoped<ISnowflakeService, SnowflakeIdService>();
services.Configure(setupAction);
if (setupAction != null)
services.Configure(setupAction);
return services;
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/core/Events/SnowflakeIdCreatedContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Read more about the licenses under the root of the project in the LICENSE.txt file.

using System;
namespace SnowflakeId.Core.Events;

public class SnowflakeIdCreatedContext
{
/// <summary>
/// Get ot set snowflakeId.
/// </summary>
public long Id { get; set; }

/// <summary>
/// Get or set the generation datetime of the snowflakeId.
/// </summary>
public DateTime GeneratedDateTime { get; set; }

/// <summary>
/// Gets or sets the identifier of the data center.
/// </summary>
public int DataCenterId { get; set; }

/// <summary>
/// Gets or sets the default epoch (1970 Jan 1st - Unix Time).
/// Default value is 1970 Jan 1st if the <see cref="SnowflakOptions.CustomEpoch"/> is null."/>
/// </summary>
public DateTime DefaultEpoch { get; set; }
}
18 changes: 18 additions & 0 deletions src/core/Events/SnowflakeIdCreatingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Read more about the licenses under the root of the project in the LICENSE.txt file.

using System;
namespace SnowflakeId.Core.Events;

public class SnowflakeIdCreatingContext
{
/// <summary>
/// Gets or sets the default epoch (1970 Jan 1st - Unix Time).
/// Default value is 1970 Jan 1st if the <see cref="SnowflakOptions.CustomEpoch"/> is null."/>
/// </summary>
public DateTime DefaultEpoch { get; set; }

/// <summary>
/// Gets or sets the identifier of the data center.
/// </summary>
public int DataCenterId { get; set; }
}
42 changes: 42 additions & 0 deletions src/core/Events/SnowflakeIdEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Read more about the licenses under the root of the project in the LICENSE.txt file.

using System;
using System.Threading.Tasks;
namespace SnowflakeId.Core.Events;

public class SnowflakeIdEvents
{
/// <summary>
/// Invoke before creating a snowflakeId.
/// </summary>
public Func<SnowflakeIdCreatingContext, Task> OnCreatingSnowflakeId { get; set; } = context => Task.CompletedTask;

/// <summary>
/// Invoke after a snowflakeId has been created.
/// </summary>
public Func<SnowflakeIdCreatedContext, Task> OnCreatedSnowflakeId { get; set; } = context => Task.CompletedTask;

/// <summary>
/// Invoked before creating a snowflakeId.
/// </summary>
public virtual Task CreatingSnowflakeId(SnowflakeIdCreatingContext context)
{
if (OnCreatingSnowflakeId != null)
{
return OnCreatingSnowflakeId.Invoke(context);
}
return Task.CompletedTask;
}

/// <summary>
/// Invoked after a snowflakeId has been created.
/// </summary>
public virtual Task CreatedSnowflakeId(SnowflakeIdCreatedContext context)
{
if (OnCreatedSnowflakeId != null)
{
return OnCreatedSnowflakeId.Invoke(context);
}
return Task.CompletedTask;
}
}
15 changes: 14 additions & 1 deletion src/core/SnowflakOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Read more about the licenses under the root of the project in the LICENSE.txt file.

using SnowflakeId.Core.Events;
using System;

namespace SnowflakeId.Core
{
public class SnowflakOptions
Expand All @@ -9,11 +12,21 @@ public class SnowflakOptions
/// or any other unique things in your webfarm
/// Be sure to set this property with number greater than zero.
/// </summary>
public int DataCenterId { get; set; }
public int? DataCenterId { get; set; }

/// <summary>
/// Get or set the value that determined whether using console log or not, the default value is <c>false</c>.
/// </summary>
public bool UseConsoleLog { get; set; }

/// <summary>
/// Get or set the custom epoch, the default value of this property with set to epoch is 1970 Jan 1s ( Unix Time ) if value is null.
/// </summary>
public DateTime? CustomEpoch { get; set; }

/// <summary>
/// events
public SnowflakeIdEvents Events { get; set; } = new SnowflakeIdEvents();

}
}
13 changes: 5 additions & 8 deletions src/core/SnowflakeId.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
<Authors>Mohammed Ahmed Hussien</Authors>
<PackageProjectUrl>https://github.com/Shoogn/SnowflakeId</PackageProjectUrl>
<RepositoryUrl>https://github.com/Shoogn/SnowflakeId</RepositoryUrl>
<Version>3.0.1</Version>
<Version>3.2.0</Version>
<RepositoryType>git</RepositoryType>
<PackageTags>SnowflakeId, UniqueSnowflakeId, UniqueId</PackageTags>
<PackageTags>SnowflakeId, UniqueSnowflakeId, UniqueId, Primary Keys</PackageTags>
<NeutralLanguage>en-SD</NeutralLanguage>
<Description>
This package implements the twitter's snowflakeId algorithm,
the source code is written in C# programming language,
and the main benefits of this library is to help anyone
wroks with Distrbuted Systems to generate a unique Ids for these Systems or
for Primary Keys is the RDMS such as SQL Server, Oracle, MySQL; or for any other situation that reuired unique Ids.
This is an implementation of snowflakeId algorithm in C# programming language, this algorithm is developed by X formally (Twitter), the source code is written in C# programming language,
and the main benefits of this library is to help anyone wroks with Distrbuted Systems to generate a unique Ids for these Systems or for Primary Keys is the [RDMS] such as SQL Server, Oracle, MySQL; or for any other situation that required unique Ids.
</Description>
</PropertyGroup>

Expand All @@ -42,7 +39,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(ExtensionsVersion)" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="SnowflakeId.Tests"/>
<InternalsVisibleTo Include="SnowflakeId.Tests" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/core/SnowflakeIdConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal class SnowflakeIdConfig

/// <summary>
/// 1023
/// ((1 << MachineIdBits) - 1);
/// </summary>
public static readonly int MaxMachineId = (int)(Math.Pow(2, MachineIdBits) - 1);

Expand Down
71 changes: 60 additions & 11 deletions src/core/SnowflakeIdService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using SnowflakeId.Core.Events;
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -13,6 +14,7 @@ public class SnowflakeIdService : ISnowflakeService, IDisposable
{
// Lock Token
private readonly object threadLock = new object();
private readonly SemaphoreSlim sem = new SemaphoreSlim(1, 1);

private long _lastTimestamp = -1L;
private long _sequence = 0L;
Expand All @@ -25,17 +27,27 @@ public class SnowflakeIdService : ISnowflakeService, IDisposable

private readonly SnowflakOptions _snowflakOptions;
private readonly ILogger<SnowflakeIdService> _logger;
protected readonly int DataCenterId = 1;
private static readonly DateTime DefaultEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);


/// <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 readonly DateTime UnixEpoch;

private bool _disposed;
public SnowflakeIdService(IOptions<SnowflakOptions> options, ILogger<SnowflakeIdService> logger)
{
_snowflakOptions = options.Value;
_logger = logger ?? new NullLogger<SnowflakeIdService>();

UnixEpoch = options.Value.CustomEpoch ?? DefaultEpoch;
_snowflakOptions.DataCenterId ??= DataCenterId;
if (_snowflakOptions.DataCenterId < 0 || _snowflakOptions.DataCenterId > SnowflakeIdConfig.MaxMachineId)
{
throw new ArgumentException(string.Format("DataCenterId must be between 0 and {0}", SnowflakeIdConfig.MaxMachineId));
}
}

/// <summary>
Expand All @@ -46,12 +58,20 @@ public virtual long GenerateSnowflakeId()
{
lock (threadLock)
{
var idCreatingContext = new SnowflakeIdCreatingContext
{
DataCenterId = _snowflakOptions.DataCenterId.Value,
DefaultEpoch = UnixEpoch
};
_snowflakOptions.Events.CreatingSnowflakeId(idCreatingContext).GetAwaiter().GetResult();

long currentTimestamp = getTimestamp();

if (currentTimestamp < _lastTimestamp)
{
if (_snowflakOptions.UseConsoleLog)
_logger.LogError("error in the server clock, the current timestamp should be bigger than generated one, current timestamp is: {0}, and the last generated timestamp is: {1}", currentTimestamp, _lastTimestamp);
_logger.LogError("error in the server clock, the current timestamp should be bigger than generated one, " +
"current timestamp is: {CurrentTimestamp}, and the last generated timestamp is: {LastTimestamp}", currentTimestamp, _lastTimestamp);
throw new InvalidOperationException("Error_In_The_Server_Clock");
}

Expand All @@ -74,7 +94,18 @@ public virtual long GenerateSnowflakeId()

long result = (currentTimestamp << _timeStampShift) | ((long)_snowflakOptions.DataCenterId << _machaineIdShift) | (_sequence);
if (_snowflakOptions.UseConsoleLog)
_logger.LogInformation("the gnerated unique id is {0}", result);
_logger.LogInformation("the gnerated unique id is {UniqueId}", result);

var idCreatedContext = new SnowflakeIdCreatedContext
{
Id = result,
GeneratedDateTime = GetDateTimeBySecondsSinceUnixEpoch(GetSecondsSinceUnixEpochFromId(result)),
DataCenterId = _snowflakOptions.DataCenterId.Value,
DefaultEpoch = UnixEpoch
};
_snowflakOptions.Events.CreatedSnowflakeId(idCreatedContext).GetAwaiter().GetResult();


return result;
}
}
Expand All @@ -85,18 +116,25 @@ public virtual long GenerateSnowflakeId()
/// <param name="cancellationToken">cancellationToken</param>
/// <returns>A new unique number that has a long type.</returns>
/// <exception cref="InvalidOperationException"></exception>
public virtual Task<long> GenerateSnowflakeIdAsync(CancellationToken cancellationToken = default)
public virtual async Task<long> GenerateSnowflakeIdAsync(CancellationToken cancellationToken = default)
{
SemaphoreSlim sem = new SemaphoreSlim(1, 1);
try
{
sem.Wait(cancellationToken);
await sem.WaitAsync(cancellationToken).ConfigureAwait(false);
var idCreatingContext = new SnowflakeIdCreatingContext
{
DataCenterId = _snowflakOptions.DataCenterId.Value,
DefaultEpoch = UnixEpoch
};
await _snowflakOptions.Events.CreatingSnowflakeId(idCreatingContext).ConfigureAwait(false);

long currentTimestamp = getTimestamp();

if (currentTimestamp < _lastTimestamp)
{
if (_snowflakOptions.UseConsoleLog)
_logger.LogError("error in the server clock, the current timestamp should be bigger than generated one, current timestamp is: {0}, and the last generated timestamp is: {1}", currentTimestamp, _lastTimestamp);
_logger.LogError("error in the server clock, the current timestamp should be bigger than generated one, current timestamp is: {CurrentTimestamp}, " +
"and the last generated timestamp is: {LastTimestamp}", currentTimestamp, _lastTimestamp);
throw new InvalidOperationException("Error_In_The_Server_Clock");
}

Expand All @@ -119,8 +157,19 @@ public virtual Task<long> GenerateSnowflakeIdAsync(CancellationToken cancellatio

long result = (currentTimestamp << _timeStampShift) | ((long)_snowflakOptions.DataCenterId << _machaineIdShift) | (_sequence);
if (_snowflakOptions.UseConsoleLog)
_logger.LogInformation("the gnerated unique id is {0}", result);
return Task.FromResult(result);
_logger.LogInformation("the gnerated unique id is {UniqueId}", result);

var idCreatedContext = new SnowflakeIdCreatedContext
{
Id = result,
GeneratedDateTime = GetDateTimeBySecondsSinceUnixEpoch(GetSecondsSinceUnixEpochFromId(result)),
DataCenterId = _snowflakOptions.DataCenterId.Value,
DefaultEpoch = UnixEpoch

};
await _snowflakOptions.Events.CreatedSnowflakeId(idCreatedContext).ConfigureAwait(false);

return result;
}
finally
{
Expand Down Expand Up @@ -194,7 +243,7 @@ public virtual long GetSecondsSinceUnixEpochFromId(long snowflakeId)
}

// 41 bits of 1s, will shifted left by 22 bits.
long timestampMask = 0x1FFFFFFFFFF;
long timestampMask = 0x1FFFFFFFFFF;
long timeStamp = (snowflakeId >> _timeStampShift) & timestampMask;
return timeStamp;
}
Expand All @@ -212,7 +261,7 @@ public virtual int GetDataCenterIdBySnowflakeId(long snowflakeId)
}

// 10 bits mask (0b1111111111) will shifted left by 12 bits.
long dataCenterIdMask = 0x3FF;
long dataCenterIdMask = 0x3FF;

long dataCenterId = (snowflakeId >> _machaineIdShift) & dataCenterIdMask;
return (int)dataCenterId;
Expand Down
6 changes: 1 addition & 5 deletions tests/SnowflakeIdConfigTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SnowflakeId.Core;
using SnowflakeId.Core;

namespace SnowflakeId.Tests
{
Expand Down
Loading
Loading