├── .gitignore ├── Flakey.sln ├── LICENSE ├── NuGet.config ├── README.md ├── build.ps1 ├── global.json ├── src └── Flakey │ ├── DefaultTimeSource.cs │ ├── Flakey.xproj │ ├── IIdGenerator.cs │ ├── ITimeSource.cs │ ├── IdGenerator.cs │ ├── InvalidSystemClockException.cs │ ├── MaskConfig.cs │ ├── Properties │ ├── AssemblyInfo.cs │ └── launchSettings.json │ ├── SequenceOverflowException.cs │ └── project.json ├── test ├── Flakey.StressTest │ ├── Flakey.StressTest.xproj │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ └── project.json └── Flakey.Tests │ ├── Flakey.Tests.xproj │ ├── IdGenTests.cs │ ├── MaskConfigTests.cs │ ├── MockTimeSource.cs │ ├── Properties │ ├── AssemblyInfo.cs │ └── launchSettings.json │ └── project.json └── tests.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | bower_components/ 188 | orleans.codegen.cs 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | 239 | -------------------------------------------------------------------------------- /Flakey.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Flakey", "src\Flakey\Flakey.xproj", "{DBF6D78D-277F-4A29-A2ED-73C41913BA29}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Flakey.Tests", "test\Flakey.Tests\Flakey.Tests.xproj", "{F2991AD3-6431-4E82-955D-E60E0971A77B}" 9 | EndProject 10 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Flakey.StressTest", "test\Flakey.StressTest\Flakey.StressTest.xproj", "{58FCEE0E-94A3-4C50-A2F2-AA1829C3EAC8}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {DBF6D78D-277F-4A29-A2ED-73C41913BA29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {DBF6D78D-277F-4A29-A2ED-73C41913BA29}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {DBF6D78D-277F-4A29-A2ED-73C41913BA29}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {DBF6D78D-277F-4A29-A2ED-73C41913BA29}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {F2991AD3-6431-4E82-955D-E60E0971A77B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {F2991AD3-6431-4E82-955D-E60E0971A77B}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {F2991AD3-6431-4E82-955D-E60E0971A77B}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {F2991AD3-6431-4E82-955D-E60E0971A77B}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {58FCEE0E-94A3-4C50-A2F2-AA1829C3EAC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {58FCEE0E-94A3-4C50-A2F2-AA1829C3EAC8}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {58FCEE0E-94A3-4C50-A2F2-AA1829C3EAC8}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {58FCEE0E-94A3-4C50-A2F2-AA1829C3EAC8}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rob Janssen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flakey 2 | Twitter Snowflake-alike ID generator for .Net Core. Available as [Nuget package](https://www.nuget.org/packages/Flakey) 3 | 4 | Note: This is a fork of the most excellent [IdGen](https://github.com/RobThree/IdGen) project. I only forked it because I needed .NET Core support. 5 | 6 | ## Why 7 | 8 | In certain situations you need a low-latency uncoordinated, (roughly) time ordered, compact and highly available Id generation system. This project was inspired by [Twitter's Snowflake](https://github.com/twitter/snowflake) project which has been retired. Note that this project was inspired by Snowflake but is not an *exact* implementation. This library provides a basis for Id generation; it does **not** provide a service for handing out these Id's nor does it provide generator-id ('worker-id') coordination. 9 | 10 | ## How it works 11 | 12 | Flakey generates, like Snowflake, 64 bit Id's. The [Sign Bit](https://en.wikipedia.org/wiki/Sign_bit) is unused since this can cause incorrect ordering on some systems that cannot use unsigned types and/or make it hard to get correct ordering. So, in effect, Flakey generates 63 bit Id's. An Id consists of 3 parts: 13 | 14 | * Timestamp 15 | * Generator-id 16 | * Sequence 17 | 18 | An Id generated with a **Default** `MaskConfig` is structured as follows: 19 | 20 | Sign Timestamp Generator Sequence 21 | ------ ----------------------- ----------- ----------- 22 | 1 bit 41 bits 10 bits 12 bits 23 | 24 | 25 | However, using the `MaskConfig` class you can tune the structure of the created Id's to your own needs; you can use 45 bits for the timestamp (≈1114 years), 2 bits for the generator-id and 16 bits for the sequence to allow, for example, generating 65536 id's per millisecond per generator distributed over 4 hosts/threads giving you a total of 262144 id's per millisecond. As long as all 3 parts (timestamp, generator and sequence) add up to 63 bits you're good to go! 26 | 27 | The **timestamp**-part of the Id should speak for itself; this is incremented every millisecond and represents the number of milliseconds since a certain epoch. By default Flakey uses 2015-01-01 0:00:00Z as epoch, but you can specify a custom epoch. 28 | 29 | The **generator-id**-part of the Id is the part that you 'configure'; it could correspond to a host, thread, datacenter or continent: it's up to you. However, the generator-id should be unique in the system: if you have several hosts generating Id's, each host should have it's own generator-id. This could be based on the hostname, a config-file value or even be retrieved from an coordinating service. Remember: a generator-id should be unique within the entire system to avoid collisions! 30 | 31 | The **sequence**-part is simply a value that is incremented each time a new Id is generated within the same millisecond timespan; it is reset every time the timestamp changes. Speaking of this: 32 | 33 | ## System Clock Dependency 34 | 35 | Flakey protects from non-monotonic clocks, i.e. clocks that run backwards. The [DefaultTimeSource](https://github.com/joshclark/Flakey/blob/master/src/Flakey/DefaultTimeSource.cs) relies on a [64bit system counter](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904.aspx) that is only incremented[1](#note1). However, we still recommend you use NTP to keep your system clock accurate; this will prevent duplicate Id's between system restarts. 36 | 37 | ## Getting started 38 | 39 | Install the [Nuget package](https://www.nuget.org/packages/Flakey) and write the following code: 40 | 41 | ```c# 42 | using Flakey; 43 | using System.Linq; 44 | 45 | class Program 46 | { 47 | static void Main(string[] args) 48 | { 49 | var generator = new IdGenerator(0); 50 | var id = generator.CreateId(); 51 | } 52 | } 53 | ``` 54 | 55 | Voila. You have created your first Id! Want to create 100 Id's? Instead of: 56 | 57 | `var id = generator.CreateId();` 58 | 59 | write: 60 | 61 | `var id = generator.Take(100);` 62 | 63 | This is because the `IdGenerator()` implements `IEnumerable` providing you with a never-ending stream of Id's (so you might want to be careful doing a `.Select(...)` on it!). 64 | 65 | The above example creates a default `IdGenerator` with the GeneratorId (or: 'Worker Id') set to 0. If you're using multiple generators (across machines or in separate threads or...) you'll want to make sure each generator is assigned it's own unique Id. One way of doing this is by simply storing a value in your configuration file for example, another way may involve a service handing out GeneratorId's to machines/threads. Flakey **does not** provide a solution for this since each project or setup may have different requirements or infrastructure to provide these generator-id's. 66 | 67 | The below sample is a bit more complicated; we set a custom epoch, define our own (bit)mask configuration for generated Id's and then display some information about the setup: 68 | 69 | ```c# 70 | using Flakey; 71 | using System; 72 | 73 | class Program 74 | { 75 | static void Main(string[] args) 76 | { 77 | // Let's say we take april 1st 2015 as our epoch 78 | var epoch = new DateTime(2015, 4, 1, 0, 0, 0, DateTimeKind.Utc); 79 | // Create a mask configuration of 45 bits for timestamp, 2 for generator-id 80 | // and 16 for sequence 81 | var mc = new MaskConfig(45, 2, 16); 82 | // Create an IdGenerator with it's generator-id set to 0, our custom epoch 83 | // and mask configuration 84 | var generator = new IdGenerator(0, epoch, mc); 85 | 86 | // Let's ask the mask configuration how many generators we could instantiate 87 | // in this setup (2 bits) 88 | Console.WriteLine("Max. generators : {0}", mc.MaxGenerators); 89 | 90 | // Let's ask the mask configuration how many sequential Id's we could generate 91 | // in a single ms in this setup (16 bits) 92 | Console.WriteLine("Id's/ms per generator : {0}", mc.MaxSequenceIds); 93 | 94 | // Let's calculate the number of Id's we could generate, per ms, should we use 95 | // the maximum number of generators 96 | Console.WriteLine("Id's/ms total : {0}", mc.MaxGenerators * mc.MaxSequenceIds); 97 | 98 | // Let's ask the mask configuration for how long we could generate Id's before 99 | // we experience a 'wraparound' of the timestamp 100 | Console.WriteLine("Wraparound interval : {0}", mc.WraparoundInterval()); 101 | 102 | // And finally: let's ask the mask configuration when this wraparound will happen 103 | // (we'll have to tell it the generator's epoch) 104 | Console.WriteLine("Wraparound date : {0}", mc.WraparoundDate(generator.Epoch).ToString("O")); 105 | } 106 | } 107 | ``` 108 | 109 | Output: 110 | ``` 111 | Max. generators : 4 112 | Id's/ms per generator : 65536 113 | Id's/ms total : 262144 114 | Wraparound interval : 407226.12:41:28.8320000 (about 1114 years) 115 | Wraparound date : 3130-03-13T12:41:28.8320000Z 116 | ``` 117 | 118 | Flakey also provides an `ITimeSouce` interface; this can be handy for [unittesting](test/Flakey.Tests/IdGenTests.cs) purposes or if you want to provide a time-source for the timestamp part of your Id's that is not based on the system time. By default the IdGenerator uses the `DefaultTimeSource` which, internally, uses [QueryPerformanceCounter](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904.aspx). For unittesting we use our own [MockTimeSource](test/Flakey.Tests/MockTimeSource.cs). 119 | 120 | The following constructor overloads are available: 121 | 122 | ```c# 123 | IdGenerator(int generatorId) 124 | IdGenerator(int generatorId, DateTime epoch) 125 | IdGenerator(int generatorId, DateTime epoch, MaskConfig maskConfig) 126 | IdGenerator(int generatorId, DateTime epoch, MaskConfig maskConfig, ITimeSource timeSource) 127 | ``` 128 | 129 | All properties are read-only to prevent changes once an `IdGenerator` has been instantiated. 130 | 131 | The `IdGenerator` class provides two 'factory methods' to quickly create a machine-specific (based on the hostname) or thread-specific `IdGenerator`: 132 | 133 | `var generator = IdGenerator.CreateMachineSpecificGenerator();` 134 | 135 | or: 136 | 137 | `var generator = IdGenerator.CreateThreadSpecificGenerator();` 138 | 139 | These methods (and their overloads that allow you to specify the epoch, `MaskConfig` and `TimeSource`) create an `IdGenerator` based on hostname or (managed) thread-id. However, it is recommended you explicitly set / configure a generator-id since these two methods could cause 'collisions' when machinenames' hashes result in the same id's or when thread-id's collide with thread-id's on other machines. 140 | 141 | 142 |
143 | 144 | [![Build status](https://ci.appveyor.com/api/projects/status/gw40b0nwaedgvilg?svg=true)](https://ci.appveyor.com/project/joshclark/flakey) NuGet version 145 | 146 | 147 | 1 It is possible for this counter to ['leap' sometimes](https://support.microsoft.com/en-us/kb/274323/en-gb); however this shouldn't be a problem for generating Id's. 148 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true)] 3 | [ValidatePattern("^\d+\.\d+\.(?:\d+\.\d+$|\d+$)")] 4 | [string] 5 | $ReleaseVersionNumber, 6 | [Parameter(Mandatory=$true)] 7 | [string] 8 | [AllowEmptyString()] 9 | $PreReleaseName 10 | ) 11 | 12 | $PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName 13 | 14 | " PSScriptFilePath = $PSScriptFilePath" 15 | 16 | $SolutionRoot = Split-Path -Path $PSScriptFilePath -Parent 17 | 18 | $DOTNET = "dotnet" 19 | 20 | # Make sure we don't have a release folder for this version already 21 | $BuildFolder = Join-Path -Path $SolutionRoot -ChildPath "build"; 22 | $ReleaseFolder = Join-Path -Path $BuildFolder -ChildPath "Releases\v$ReleaseVersionNumber$PreReleaseName"; 23 | if ((Get-Item $ReleaseFolder -ErrorAction SilentlyContinue) -ne $null) 24 | { 25 | Write-Warning "$ReleaseFolder already exists on your local machine. It will now be deleted." 26 | Remove-Item $ReleaseFolder -Recurse 27 | } 28 | 29 | # Set the version number in package.json 30 | $ProjectJsonPath = Join-Path -Path $SolutionRoot -ChildPath "src\Flakey\project.json" 31 | (gc -Path $ProjectJsonPath) ` 32 | -replace "(?<=`"version`":\s`")[.\w-]*(?=`",)", "$ReleaseVersionNumber$PreReleaseName" | 33 | sc -Path $ProjectJsonPath -Encoding UTF8 34 | # Set the copyright 35 | $DateYear = (Get-Date).year 36 | (gc -Path $ProjectJsonPath) ` 37 | -replace "(?<=`"copyright`":\s`")[\w\s�]*(?=`",)", "Copyright � Josh Clark $DateYear" | 38 | sc -Path $ProjectJsonPath -Encoding UTF8 39 | 40 | # Build the proj in release mode 41 | 42 | & $DOTNET restore "$ProjectJsonPath" 43 | if (-not $?) 44 | { 45 | throw "The DOTNET restore process returned an error code." 46 | } 47 | 48 | & $DOTNET build "$ProjectJsonPath" 49 | if (-not $?) 50 | { 51 | throw "The DOTNET build process returned an error code." 52 | } 53 | 54 | & $DOTNET pack "$ProjectJsonPath" --configuration Release --output "$ReleaseFolder" 55 | if (-not $?) 56 | { 57 | throw "The DOTNET pack process returned an error code." 58 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test" ] 3 | } -------------------------------------------------------------------------------- /src/Flakey/DefaultTimeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Flakey 5 | { 6 | /// 7 | /// Provides time data to an . Uses the current date and time on this computer. 8 | /// 9 | public class DefaultTimeSource : ITimeSource 10 | { 11 | [DllImport("Kernel32.dll")] 12 | private static extern bool QueryPerformanceCounter(out long lpPerformanceCount); 13 | 14 | [DllImport("Kernel32.dll")] 15 | private static extern bool QueryPerformanceFrequency(out long lpFrequency); 16 | 17 | private long _frequency; 18 | private long _offset; 19 | private DateTime _start; 20 | 21 | /// 22 | /// Initializes a new instance of the . 23 | /// 24 | public DefaultTimeSource() 25 | { 26 | QueryPerformanceFrequency(out _frequency); 27 | QueryPerformanceCounter(out _offset); 28 | _start = DateTime.UtcNow; 29 | } 30 | 31 | 32 | /// 33 | /// Returns a object that is (close to) the current date and time on this computer, expressed 34 | /// as the Coordinated Universal Time (UTC). 35 | /// 36 | /// 37 | /// A object that is (close to) the current date and time on this computer, expressed as the 38 | /// Coordinated Universal Time (UTC). 39 | /// 40 | /// 41 | /// The resolution of this value depends on the system. It does *not* rely on the system- or wall-clock time but 42 | /// on QueryPerformanceCounter and *may* (and *will*) drift ahead of time over time. 43 | /// 44 | public DateTime GetTime() 45 | { 46 | return _start.AddSeconds(GetSecondsSinceStart()); 47 | } 48 | 49 | /// 50 | /// Returns a object that is (close to) the current date and time on this computer, expressed 51 | /// as the Coordinated Universal Time (UTC). 52 | /// 53 | /// 54 | /// A object that is (close to) the current date and time on this computer, expressed as the 55 | /// Coordinated Universal Time (UTC). 56 | /// 57 | /// 58 | /// The resolution of this value depends on the system. It does *not* rely on the system- or wall-clock time but 59 | /// on QueryPerformanceCounter and *may* (and *will*) drift ahead of time over time. 60 | /// 61 | DateTime ITimeSource.GetTime() 62 | { 63 | return this.GetTime(); 64 | } 65 | 66 | private double GetSecondsSinceStart() 67 | { 68 | long t; 69 | QueryPerformanceCounter(out t); 70 | return (double)(t - _offset) / _frequency; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Flakey/Flakey.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | dbf6d78d-277f-4a29-a2ed-73c41913ba29 11 | Flakey 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Flakey/IIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Flakey 4 | { 5 | /// 6 | /// Provides the interface for Id-generators. 7 | /// 8 | /// The type for the generated ID's. 9 | public interface IIdGenerator : IEnumerable 10 | { 11 | /// 12 | /// Creates a new Id. 13 | /// 14 | /// Returns an Id. 15 | T CreateId(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Flakey/ITimeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Flakey 4 | { 5 | /// 6 | /// Provides the interface for timesources that provide time information to s. 7 | /// 8 | public interface ITimeSource 9 | { 10 | /// 11 | /// Returns a to be used by an when creating an Id. 12 | /// 13 | /// A to be used by an when creating an Id. 14 | DateTime GetTime(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Flakey/IdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | using System.Collections.Concurrent; 7 | 8 | namespace Flakey 9 | { 10 | /// 11 | /// Generates Id's inspired by Twitter's (late) Snowflake project. 12 | /// 13 | public class IdGenerator : IIdGenerator 14 | { 15 | private static readonly DateTime defaultepoch = new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc); 16 | private static readonly ITimeSource defaulttimesource = new DefaultTimeSource(); 17 | private static readonly ConcurrentDictionary _namedgenerators = new ConcurrentDictionary(); 18 | 19 | private int _sequence = 0; 20 | private long _lastgen = -1; 21 | 22 | private readonly DateTime _epoch; 23 | private readonly MaskConfig _maskconfig; 24 | private readonly long _generatorId; 25 | 26 | private readonly long MASK_SEQUENCE; 27 | private readonly long MASK_TIME; 28 | private readonly long MASK_GENERATOR; 29 | 30 | private readonly int SHIFT_TIME; 31 | private readonly int SHIFT_GENERATOR; 32 | 33 | private readonly ITimeSource _timesource; 34 | 35 | // Object to lock() on while generating Id's 36 | private object genlock = new object(); 37 | 38 | /// 39 | /// Gets the Id of the generator. 40 | /// 41 | public int Id { get { return (int)_generatorId; } } 42 | 43 | /// 44 | /// Gets the epoch for the . 45 | /// 46 | public DateTime Epoch { get { return _epoch; } } 47 | 48 | /// 49 | /// Gets the for the . 50 | /// 51 | public MaskConfig MaskConfig { get { return _maskconfig; } } 52 | 53 | /// 54 | /// Initializes a new instance of the class, 2015-01-01 0:00:00Z is used as default 55 | /// epoch and the value is used for the . The 56 | /// is used to retrieve timestamp information. 57 | /// 58 | /// The Id of the generator. 59 | /// Thrown when GeneratorId exceeds maximum value. 60 | public IdGenerator(int generatorId) 61 | : this(generatorId, defaultepoch) { } 62 | 63 | /// 64 | /// Initializes a new instance of the class. The 65 | /// value is used for the . The is used to retrieve 66 | /// timestamp information. 67 | /// 68 | /// The Id of the generator. 69 | /// The Epoch of the generator. 70 | /// 71 | /// Thrown when GeneratorId exceeds maximum value or epoch in future. 72 | /// 73 | public IdGenerator(int generatorId, DateTime epoch) 74 | : this(generatorId, epoch, MaskConfig.Default) { } 75 | 76 | /// 77 | /// Initializes a new instance of the class. The is 78 | /// used to retrieve timestamp information. 79 | /// 80 | /// The Id of the generator. 81 | /// The Epoch of the generator. 82 | /// The of the generator. 83 | /// Thrown when maskConfig is null. 84 | /// Thrown when maskConfig defines a non-63 bit bitmask. 85 | /// 86 | /// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future. 87 | /// 88 | public IdGenerator(int generatorId, DateTime epoch, MaskConfig maskConfig) 89 | : this(generatorId, epoch, maskConfig, defaulttimesource) { } 90 | 91 | /// 92 | /// Initializes a new instance of the class. 93 | /// 94 | /// The Id of the generator. 95 | /// The Epoch of the generator. 96 | /// The of the generator. 97 | /// The time-source to use when acquiring time data. 98 | /// Thrown when either maskConfig or timeSource is null. 99 | /// Thrown when maskConfig defines a non-63 bit bitmask. 100 | /// 101 | /// Thrown when GeneratorId or Sequence masks are >31 bit, GeneratorId exceeds maximum value or epoch in future. 102 | /// 103 | public IdGenerator(int generatorId, DateTime epoch, MaskConfig maskConfig, ITimeSource timeSource) 104 | { 105 | if (maskConfig == null) 106 | throw new ArgumentNullException("maskConfig"); 107 | 108 | if (timeSource == null) 109 | throw new ArgumentNullException("timeSource"); 110 | 111 | if (maskConfig.TotalBits != 63) 112 | throw new InvalidOperationException("Number of bits used to generate Id's is not equal to 63"); 113 | 114 | if (maskConfig.GeneratorIdBits > 31) 115 | throw new ArgumentOutOfRangeException("GeneratorId cannot have more than 31 bits"); 116 | 117 | if (maskConfig.SequenceBits > 31) 118 | throw new ArgumentOutOfRangeException("Sequence cannot have more than 31 bits"); 119 | 120 | if (epoch > timeSource.GetTime()) 121 | throw new ArgumentOutOfRangeException("Epoch in future"); 122 | 123 | // Precalculate some values 124 | MASK_TIME = GetMask(maskConfig.TimestampBits); 125 | MASK_GENERATOR = GetMask(maskConfig.GeneratorIdBits); 126 | MASK_SEQUENCE = GetMask(maskConfig.SequenceBits); 127 | 128 | if (generatorId < 0 || generatorId > MASK_GENERATOR) 129 | throw new ArgumentOutOfRangeException(string.Format("GeneratorId must be between 0 and {0} (inclusive).", MASK_GENERATOR)); 130 | 131 | SHIFT_TIME = maskConfig.GeneratorIdBits + maskConfig.SequenceBits; 132 | SHIFT_GENERATOR = maskConfig.SequenceBits; 133 | 134 | // Store instance specific values 135 | _maskconfig = maskConfig; 136 | _timesource = timeSource; 137 | _epoch = epoch; 138 | _generatorId = generatorId; 139 | } 140 | 141 | /// 142 | /// Creates a new Id. 143 | /// 144 | /// Returns an Id based on the 's epoch, generatorid and sequence. 145 | /// Thrown when clock going backwards is detected. 146 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 147 | public long CreateId() 148 | { 149 | lock (genlock) 150 | { 151 | var timestamp = this.GetTimestamp() & MASK_TIME; 152 | if (timestamp < _lastgen) 153 | throw new InvalidSystemClockException($"Clock moved backwards or wrapped around. Refusing to generate id for {_lastgen - timestamp} milliseconds"); 154 | 155 | if (timestamp == _lastgen) 156 | { 157 | if (_sequence < MASK_SEQUENCE) 158 | _sequence++; 159 | else 160 | { 161 | timestamp = TillNextMillis(_lastgen); 162 | _sequence = 0; 163 | _lastgen = timestamp; 164 | } 165 | } 166 | else 167 | { 168 | _sequence = 0; 169 | _lastgen = timestamp; 170 | } 171 | 172 | unchecked 173 | { 174 | return (timestamp << SHIFT_TIME) 175 | + (_generatorId << SHIFT_GENERATOR) // GeneratorId is already masked, we only need to shift 176 | + _sequence; 177 | } 178 | } 179 | } 180 | 181 | /// 182 | /// Returns a new instance of an based on the machine-name. 183 | /// 184 | /// A new instance of an based on the machine-name. 185 | /// 186 | /// Note: be very careful using this method; it is recommended to explicitly set an generatorId instead since 187 | /// a hash of the machinename could result in a collision (especially when the bitmask for the generator is 188 | /// very small) of the generator-id's across machines. Only use this in small setups (few hosts) and if you have 189 | /// no other choice. Prefer to specify generator id's via configuration file or other means instead. 190 | /// 191 | public static IdGenerator CreateMachineSpecificGenerator() 192 | { 193 | return CreateMachineSpecificGenerator(defaultepoch); 194 | } 195 | 196 | /// 197 | /// Returns a new instance of an based on the machine-name. 198 | /// 199 | /// The Epoch of the generator. 200 | /// A new instance of an based on the machine-name. 201 | /// 202 | /// Note: be very careful using this method; it is recommended to explicitly set an generatorId instead since 203 | /// a hash of the machinename could result in a collision (especially when the bitmask for the generator is 204 | /// very small) of the generator-id's across machines. Only use this in small setups (few hosts) and if you have 205 | /// no other choice. Prefer to specify generator id's via configuration file or other means instead. 206 | /// 207 | public static IdGenerator CreateMachineSpecificGenerator(DateTime epoch) 208 | { 209 | return CreateMachineSpecificGenerator(epoch, MaskConfig.Default); 210 | } 211 | 212 | /// 213 | /// Returns a new instance of an based on the machine-name. 214 | /// 215 | /// The Epoch of the generator. 216 | /// The of the generator. 217 | /// A new instance of an based on the machine-name. 218 | /// 219 | /// Note: be very careful using this method; it is recommended to explicitly set an generatorId instead since 220 | /// a hash of the machinename could result in a collision (especially when the bitmask for the generator is 221 | /// very small) of the generator-id's across machines. Only use this in small setups (few hosts) and if you have 222 | /// no other choice. Prefer to specify generator id's via configuration file or other means instead. 223 | /// 224 | public static IdGenerator CreateMachineSpecificGenerator(DateTime epoch, MaskConfig maskConfig) 225 | { 226 | return CreateMachineSpecificGenerator(epoch, maskConfig, defaulttimesource); 227 | } 228 | 229 | /// 230 | /// Returns a new instance of an based on the machine-name. 231 | /// 232 | /// The Epoch of the generator. 233 | /// The of the generator. 234 | /// The time-source to use when acquiring time data. 235 | /// A new instance of an based on the machine-name. 236 | /// 237 | /// Note: be very careful using this method; it is recommended to explicitly set an generatorId instead since 238 | /// a hash of the machinename could result in a collision (especially when the bitmask for the generator is 239 | /// very small) of the generator-id's across machines. Only use this in small setups (few hosts) and if you have 240 | /// no other choice. Prefer to specify generator id's via configuration file or other means instead. 241 | /// 242 | public static IdGenerator CreateMachineSpecificGenerator(DateTime epoch, MaskConfig maskConfig, ITimeSource timeSource) 243 | { 244 | return new IdGenerator(GetMachineHash() & (int)GetMask(maskConfig.GeneratorIdBits), epoch, maskConfig, timeSource); 245 | } 246 | 247 | /// 248 | /// Returns a new instance of an based on the (managed) thread this method is invoked on. 249 | /// 250 | /// A new instance of an based on the (managed) thread this method is invoked on. 251 | /// 252 | /// Note: This method can be used when using several threads on a single machine to get thread-specific generators; 253 | /// if this method is used across machines there's a high probability of collisions in generator-id's. In that 254 | /// case prefer to explicitly set the generator id's via configuration file or other means instead. 255 | /// 256 | public static IdGenerator CreateThreadSpecificGenerator() 257 | { 258 | return CreateThreadSpecificGenerator(defaultepoch); 259 | } 260 | 261 | /// 262 | /// Returns a new instance of an based on the (managed) thread this method is invoked on. 263 | /// 264 | /// The Epoch of the generator. 265 | /// A new instance of an based on the (managed) thread this method is invoked on. 266 | /// 267 | /// Note: This method can be used when using several threads on a single machine to get thread-specific generators; 268 | /// if this method is used across machines there's a high probability of collisions in generator-id's. In that 269 | /// case prefer to explicitly set the generator id's via configuration file or other means instead. 270 | /// 271 | public static IdGenerator CreateThreadSpecificGenerator(DateTime epoch) 272 | { 273 | return CreateThreadSpecificGenerator(epoch, MaskConfig.Default); 274 | } 275 | 276 | /// 277 | /// Returns a new instance of an based on the (managed) thread this method is invoked on. 278 | /// 279 | /// The Epoch of the generator. 280 | /// The of the generator. 281 | /// A new instance of an based on the (managed) thread this method is invoked on. 282 | /// 283 | /// Note: This method can be used when using several threads on a single machine to get thread-specific generators; 284 | /// if this method is used across machines there's a high probability of collisions in generator-id's. In that 285 | /// case prefer to explicitly set the generator id's via configuration file or other means instead. 286 | /// 287 | public static IdGenerator CreateThreadSpecificGenerator(DateTime epoch, MaskConfig maskConfig) 288 | { 289 | return CreateThreadSpecificGenerator(epoch, maskConfig, defaulttimesource); 290 | } 291 | 292 | /// 293 | /// Returns a new instance of an based on the (managed) thread this method is invoked on. 294 | /// 295 | /// The Epoch of the generator. 296 | /// The of the generator. 297 | /// The time-source to use when acquiring time data. 298 | /// A new instance of an based on the (managed) thread this method is invoked on. 299 | /// 300 | /// Note: This method can be used when using several threads on a single machine to get thread-specific generators; 301 | /// if this method is used across machines there's a high probability of collisions in generator-id's. In that 302 | /// case prefer to explicitly set the generator id's via configuration file or other means instead. 303 | /// 304 | public static IdGenerator CreateThreadSpecificGenerator(DateTime epoch, MaskConfig maskConfig, ITimeSource timeSource) 305 | { 306 | return new IdGenerator(GetThreadId() & (int)GetMask(maskConfig.GeneratorIdBits), epoch, maskConfig, timeSource); 307 | } 308 | 309 | /// 310 | /// Gets a unique identifier for the current managed thread. 311 | /// 312 | /// An integer that represents a unique identifier for this managed thread. 313 | private static int GetThreadId() 314 | { 315 | return Thread.CurrentThread.ManagedThreadId; 316 | } 317 | 318 | /// 319 | /// Gets a hashcode based on the . 320 | /// 321 | /// Returns a hashcode based on the . 322 | private static int GetMachineHash() 323 | { 324 | return Environment.MachineName.GetHashCode(); 325 | } 326 | 327 | /// 328 | /// Gets the number of milliseconds since the 's epoch. 329 | /// 330 | /// Returns the number of milliseconds since the 's epoch. 331 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 332 | private long GetTimestamp() 333 | { 334 | return (long)(_timesource.GetTime() - _epoch).TotalMilliseconds; 335 | } 336 | 337 | private long TillNextMillis(long lastTimestamp) 338 | { 339 | var timestamp = this.GetTimestamp() & MASK_TIME; 340 | 341 | while (timestamp <= lastTimestamp) 342 | { 343 | timestamp = this.GetTimestamp() & MASK_TIME; 344 | } 345 | 346 | return timestamp; 347 | } 348 | 349 | /// 350 | /// Returns a bitmask masking out the desired number of bits; a bitmask of 2 returns 000...000011, a bitmask of 351 | /// 5 returns 000...011111. 352 | /// 353 | /// The number of bits to mask. 354 | /// Returns the desired bitmask. 355 | private static long GetMask(byte bits) 356 | { 357 | return (1L << bits) - 1; 358 | } 359 | 360 | /// 361 | /// Returns a 'never ending' stream of Id's. 362 | /// 363 | /// A 'never ending' stream of Id's. 364 | private IEnumerable IdStream() 365 | { 366 | while (true) 367 | yield return this.CreateId(); 368 | } 369 | 370 | /// 371 | /// Returns an enumerator that iterates over Id's. 372 | /// 373 | /// An object that can be used to iterate over Id's. 374 | public IEnumerator GetEnumerator() 375 | { 376 | return this.IdStream().GetEnumerator(); 377 | } 378 | 379 | /// 380 | /// Returns an enumerator that iterates over Id's. 381 | /// 382 | /// An object that can be used to iterate over Id's. 383 | IEnumerator IEnumerable.GetEnumerator() 384 | { 385 | return this.GetEnumerator(); 386 | } 387 | } 388 | } -------------------------------------------------------------------------------- /src/Flakey/InvalidSystemClockException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Flakey 4 | { 5 | /// 6 | /// The exception that is thrown when a clock going backwards is detected. 7 | /// 8 | public class InvalidSystemClockException : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class with a message that describes the error. 12 | /// 13 | /// 14 | /// The message that describes the exception. The caller of this constructor is required to ensure that this 15 | /// string has been localized for the current system culture. 16 | /// 17 | public InvalidSystemClockException(string message) 18 | : base(message) { } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Flakey/MaskConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Flakey 3 | { 4 | /// 5 | /// Specifies the number of bits to use for the different parts of an Id for an . 6 | /// 7 | public class MaskConfig 8 | { 9 | /// 10 | /// Gets number of bits to use for the timestamp part of the Id's to generate. 11 | /// 12 | public byte TimestampBits { get; private set; } 13 | 14 | /// 15 | /// Gets number of bits to use for the generator-id part of the Id's to generate. 16 | /// 17 | public byte GeneratorIdBits { get; private set; } 18 | 19 | /// 20 | /// Gets number of bits to use for the sequence part of the Id's to generate. 21 | /// 22 | public byte SequenceBits { get; private set; } 23 | 24 | /// 25 | /// Gets the total number of bits for the . 26 | /// 27 | public int TotalBits { get { return this.TimestampBits + this.GeneratorIdBits + this.SequenceBits; } } 28 | 29 | /// 30 | /// Returns the maximum number of intervals for this mask configuration. 31 | /// 32 | public long MaxIntervals { get { return (1L << this.TimestampBits); } } 33 | 34 | /// 35 | /// Returns the maximum number of generators available for this mask configuration. 36 | /// 37 | public long MaxGenerators { get { return (1L << this.GeneratorIdBits); } } 38 | 39 | /// 40 | /// Returns the maximum number of sequential Id's for a time-interval (e.g. max. number of Id's generated 41 | /// within a single interval). 42 | /// 43 | public long MaxSequenceIds { get { return (1L << this.SequenceBits); } } 44 | 45 | /// 46 | /// Gets a default with 41 bits for the timestamp part, 10 bits for the generator-id 47 | /// part and 12 bits for the sequence part of the id. 48 | /// 49 | public static MaskConfig Default 50 | { 51 | get 52 | { 53 | return new MaskConfig(41, 10, 12); 54 | } 55 | } 56 | 57 | /// 58 | /// Initializes a bitmask configuration for s. 59 | /// 60 | /// Number of bits to use for the timestamp-part of Id's. 61 | /// Number of bits to use for the generator-id of Id's. 62 | /// Number of bits to use for the sequence-part of Id's. 63 | public MaskConfig(byte timestampBits, byte generatorIdBits, byte sequenceBits) 64 | { 65 | this.TimestampBits = timestampBits; 66 | this.GeneratorIdBits = generatorIdBits; 67 | this.SequenceBits = sequenceBits; 68 | } 69 | 70 | /// 71 | /// Calculates the last date for an Id before a 'wrap around' will occur in the timestamp-part of an Id for the 72 | /// given . 73 | /// 74 | /// The used epoch for the to use as offset. 75 | /// The last date for an Id before a 'wrap around' will occur in the timestamp-part of an Id. 76 | public DateTime WraparoundDate(DateTime epoch) 77 | { 78 | return epoch.AddMilliseconds(this.MaxIntervals); 79 | } 80 | 81 | /// 82 | /// Calculates the interval at wich a 'wrap around' will occur in the timestamp-part of an Id for the given 83 | /// . 84 | /// 85 | /// 86 | /// The interval at wich a 'wrap around' will occur in the timestamp-part of an Id for the given 87 | /// . 88 | /// 89 | public TimeSpan WraparoundInterval() 90 | { 91 | return TimeSpan.FromMilliseconds(this.MaxIntervals); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/Flakey/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Flakey")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Flakey")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("dbf6d78d-277f-4a29-a2ed-73c41913ba29")] 24 | 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyFileVersion("1.0.0.0")] 27 | -------------------------------------------------------------------------------- /src/Flakey/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:53218/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Flakey/SequenceOverflowException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Flakey 4 | { 5 | /// 6 | /// The exception that is thrown when a sequence overflows (e.g. too many Id's generated within the same timespan (ms)). 7 | /// 8 | public class SequenceOverflowException : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class with a message that describes the error. 12 | /// 13 | /// 14 | /// The message that describes the exception. The caller of this constructor is required to ensure that this 15 | /// string has been localized for the current system culture. 16 | /// 17 | public SequenceOverflowException(string message) 18 | : base(message) { } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Flakey/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "Twitter Snowflake-alike ID generator for .Net Core", 4 | "authors": [ "Josh Clark" ], 5 | 6 | "packOptions": { 7 | "owners": [ "Josh Clark" ], 8 | 9 | "copyright": "Copyright © Josh Clark 2016", 10 | "tags": [ "snowflake", "unique", "id", "generator" ], 11 | "projectUrl": "https://github.com/joshclark/Flakey", 12 | "licenseUrl": "http://opensource.org/licenses/MIT" 13 | }, 14 | 15 | "buildOptions": { 16 | "warningsAsErrors": true 17 | }, 18 | 19 | 20 | "dependencies": { 21 | "NETStandard.Library": "1.6.0" 22 | }, 23 | 24 | "frameworks": { 25 | "net451": { 26 | "frameworkAssemblies": { 27 | "System.Threading": "4.0.0.0", 28 | "System.Collections.Concurrent": "4.0.0.0" 29 | } 30 | }, 31 | "netstandard1.5": { 32 | "imports": "dnxcore50", 33 | "dependencies": { 34 | "System.Threading.Thread": "4.0.0", 35 | "System.Collections.Concurrent": "4.0.12" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Flakey.StressTest/Flakey.StressTest.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 58fcee0e-94a3-4c50-a2f2-aa1829c3eac8 11 | Flakey.StressTest 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Flakey.StressTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Flakey.StressTest 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var cpuCount = Environment.ProcessorCount; 13 | Console.WriteLine($"Running stress tests for {cpuCount} processors..."); 14 | 15 | int iterationsPerProc = 20 * 1000000; 16 | var gen = new IdGenerator(1); 17 | 18 | Console.WriteLine($"Generating {iterationsPerProc} ids per processor."); 19 | 20 | var allTasks = new List>>(); 21 | foreach (int cpu in Enumerable.Range(0, cpuCount)) 22 | { 23 | allTasks.Add(GetIds(gen, iterationsPerProc)); 24 | } 25 | 26 | Task.WhenAll(allTasks).Wait(); 27 | 28 | Console.WriteLine($"Verifying generated ids..."); 29 | 30 | foreach (var task in allTasks) 31 | { 32 | VerifyIds(task.Result); 33 | } 34 | 35 | Console.WriteLine("All done."); 36 | } 37 | 38 | private static Task> GetIds(IdGenerator gen, int count) 39 | { 40 | return Task.Run(() => new LinkedList(gen.Take(count))); 41 | } 42 | 43 | private static void VerifyIds(LinkedList result) 44 | { 45 | int index = 0; 46 | var current = result.First.Next; 47 | while (current != null) 48 | { 49 | if (current.Previous != null) 50 | { 51 | if (current.Value <= current.Previous.Value) 52 | { 53 | Console.WriteLine($"Results out of order: Result[{index - 1}]: {current.Previous.Value} Result[{index}]: {current.Value}"); 54 | } 55 | } 56 | current = current.Next; 57 | index++; 58 | } 59 | } 60 | 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/Flakey.StressTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Flakey.StressTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Flakey.StressTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("58fcee0e-94a3-4c50-a2f2-aa1829c3eac8")] 24 | -------------------------------------------------------------------------------- /test/Flakey.StressTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:53219/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /test/Flakey.StressTest/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "buildOptions": { 5 | "emitEntryPoint": true 6 | }, 7 | 8 | "dependencies": { 9 | "Flakey": { "target": "project" } 10 | }, 11 | 12 | "frameworks": { 13 | "netcoreapp1.0": { 14 | "dependencies": { 15 | "Microsoft.NETCore.App": { 16 | "type": "platform", 17 | "version": "1.0.0" 18 | }, 19 | "System.Console": "4.0.0" 20 | }, 21 | "imports": [ "dnxcore50", "portable-net45+win8" ] 22 | }, 23 | "net451": { 24 | "dependencies": { 25 | "Microsoft.NETCore.Platforms": "1.0.1" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/Flakey.Tests/Flakey.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f2991ad3-6431-4e82-955d-e60e0971a77b 10 | Flakey.Tests 11 | .\obj 12 | .\bin\ 13 | v4.5.2 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Flakey.Tests/IdGenTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Flakey 9 | { 10 | public class IdGenTests 11 | { 12 | private readonly DateTime TESTEPOCH = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); 13 | 14 | [Fact] 15 | public void Sequence_ShouldIncrease_EveryInvocation() 16 | { 17 | // We setup our generator so that the time (current - epoch) results in 0, generator id 0 and we're only 18 | // left with the sequence increasing each invocation of CreateId(); 19 | var ts = new MockTimeSource(TESTEPOCH); 20 | var m = MaskConfig.Default; 21 | var g = new IdGenerator(0, TESTEPOCH, MaskConfig.Default, ts); 22 | 23 | 24 | Assert.Equal(0, g.CreateId()); 25 | Assert.Equal(1, g.CreateId()); 26 | Assert.Equal(2, g.CreateId()); 27 | } 28 | 29 | [Fact] 30 | public void Sequence_ShouldIncrease_LargeInvocation() 31 | { 32 | var g = new IdGenerator(0); 33 | var list = g.Take(10000).ToList(); 34 | 35 | for (int i = 1; i < list.Count; ++i) 36 | Assert.True(list[i] > list[i - 1], $"Results out of order: Result[{i - 1}]: {list[i - 1]} Result[{i}]: {list[i]}"); 37 | 38 | } 39 | 40 | 41 | [Fact] 42 | public void Sequence_ShouldReset_EveryNewTick() 43 | { 44 | // We setup our generator so that the time (current - epoch) results in 0, generator id 0 and we're only 45 | // left with the sequence increasing each invocation of CreateId(); 46 | var ts = new MockTimeSource(TESTEPOCH); 47 | var m = MaskConfig.Default; 48 | var g = new IdGenerator(0, TESTEPOCH, m, ts); 49 | 50 | Assert.Equal(0, g.CreateId()); 51 | Assert.Equal(1, g.CreateId()); 52 | ts.NextTick(); 53 | // Since the timestamp has increased, we should now have a much higher value (since the timestamp is shifted 54 | // left a number of bits (specifically GeneratorIdBits + SequenceBits) 55 | Assert.Equal((1 << (m.GeneratorIdBits + m.SequenceBits)) + 0, g.CreateId()); 56 | Assert.Equal((1 << (m.GeneratorIdBits + m.SequenceBits)) + 1, g.CreateId()); 57 | } 58 | 59 | [Fact] 60 | public void GeneratorId_ShouldBePresent_InID1() 61 | { 62 | // We setup our generator so that the time (current - epoch) results in 0, generator id 1023 so that all 10 bits 63 | // are set for the generator. 64 | var ts = new MockTimeSource(TESTEPOCH); 65 | var m = MaskConfig.Default; // We use a default mask-config with 11 bits for the generator this time 66 | var g = new IdGenerator(1023, TESTEPOCH, m, ts); 67 | 68 | // Make sure all expected bits are set 69 | Assert.Equal((1 << m.GeneratorIdBits) - 1 << m.SequenceBits, g.CreateId()); 70 | } 71 | 72 | [Fact] 73 | public void GeneratorId_ShouldBePresent_InID2() 74 | { 75 | // We setup our generator so that the time (current - epoch) results in 0, generator id 4095 so that all 12 bits 76 | // are set for the generator. 77 | var ts = new MockTimeSource(TESTEPOCH); 78 | var m = new MaskConfig(40, 12, 11); // We use a custom mask-config with 12 bits for the generator this time 79 | var g = new IdGenerator(4095, TESTEPOCH, m, ts); 80 | 81 | // Make sure all expected bits are set 82 | Assert.Equal(-1 & ((1 << 12) - 1), g.Id); 83 | Assert.Equal((1 << 12) - 1 << 11, g.CreateId()); 84 | } 85 | 86 | [Fact] 87 | public void GeneratorId_ShouldBeMasked_WhenReadFromProperty() 88 | { 89 | // We setup our generator so that the time (current - epoch) results in 0, generator id 1023 so that all 10 90 | // bits are set for the generator. 91 | var ts = new MockTimeSource(TESTEPOCH); 92 | var m = MaskConfig.Default; 93 | var g = new IdGenerator(1023, TESTEPOCH, m, ts); 94 | 95 | // Make sure all expected bits are set 96 | Assert.Equal((1 << m.GeneratorIdBits) - 1, g.Id); 97 | } 98 | 99 | [Fact] 100 | public void Constructor_Throws_OnNullMaskConfig() 101 | { 102 | Assert.Throws( () => new IdGenerator(0, TESTEPOCH, null) ); 103 | } 104 | 105 | [Fact] 106 | public void Constructor_Throws_OnNullTimeSource() 107 | { 108 | Assert.Throws(() => new IdGenerator(0, TESTEPOCH, MaskConfig.Default, null)); 109 | } 110 | 111 | [Fact] 112 | public void Constructor_Throws_OnMaskConfigNotExactly63Bits() 113 | { 114 | Assert.Throws(() => new IdGenerator(0, TESTEPOCH, new MaskConfig(41, 10, 11))); 115 | } 116 | 117 | [Fact] 118 | public void Constructor_Throws_OnGeneratorIdMoreThan31Bits() 119 | { 120 | Assert.Throws(() => new IdGenerator(0, TESTEPOCH, new MaskConfig(21, 32, 10))); 121 | } 122 | 123 | [Fact] 124 | public void Constructor_Throws_OnSequenceMoreThan31Bits() 125 | { 126 | Assert.Throws(() => new IdGenerator(0, TESTEPOCH, new MaskConfig(21, 10, 32))); 127 | } 128 | 129 | [Fact] 130 | public void CreateId_Pauses_OnSequenceOverflow() 131 | { 132 | var ts = new MockTimeSource(TESTEPOCH); 133 | var g = new IdGenerator(0, TESTEPOCH, new MaskConfig(41, 20, 2), ts); 134 | 135 | // We have a 2-bit sequence; generating 4 id's shouldn't be a problem 136 | for (int i = 0; i < 4; i++) 137 | Assert.Equal(i, g.CreateId()); 138 | 139 | var createIdFinished = new ManualResetEvent(false); 140 | 141 | var thread = new Thread(() => 142 | { 143 | g.CreateId(); 144 | createIdFinished.Set(); 145 | }); 146 | 147 | thread.Start(); 148 | 149 | bool finished = createIdFinished.WaitOne(20); 150 | Assert.True(!finished); 151 | 152 | ts.NextTick(); 153 | 154 | finished = createIdFinished.WaitOne(20); 155 | Assert.True(finished); 156 | } 157 | 158 | 159 | //[Fact] 160 | //public void CheckAllCombinationsForMaskConfigs() 161 | //{ 162 | // var ts = new MockTimeSource(TESTEPOCH); 163 | 164 | // for (byte i = 0; i < 32; i++) 165 | // { 166 | // var genid = (long)(1L << i) - 1; 167 | // for (byte j = 2; j < 32; j++) 168 | // { 169 | // var g = new IdGenerator((int)genid, TESTEPOCH, new MaskConfig((byte)(63 - i - j), i, j), ts); 170 | // var id = g.CreateId(); 171 | // Assert.Equal(genid << j, id); 172 | 173 | // var id2 = g.CreateId(); 174 | // Assert.Equal((genid << j) + 1, id2); 175 | 176 | // ts.NextTick(); 177 | 178 | // var id3 = g.CreateId(); 179 | // var id4 = g.CreateId(); 180 | 181 | //// System.Diagnostics.Trace.WriteLine(Convert.ToString(id, 2).PadLeft(64, '0')); 182 | //// System.Diagnostics.Trace.WriteLine(Convert.ToString(id2, 2).PadLeft(64, '0')); 183 | //// System.Diagnostics.Trace.WriteLine(Convert.ToString(id3, 2).PadLeft(64, '0')); 184 | //// System.Diagnostics.Trace.WriteLine(Convert.ToString(id4, 2).PadLeft(64, '0')); 185 | 186 | // ts.PreviousTick(); 187 | // } 188 | 189 | // } 190 | //} 191 | 192 | [Fact] 193 | public void Constructor_UsesCorrect_Values() 194 | { 195 | Assert.Equal(123, new IdGenerator(123).Id); //Make sure the test-value is not masked so it matches the expected value! 196 | Assert.Equal(TESTEPOCH, new IdGenerator(0, TESTEPOCH).Epoch); 197 | } 198 | 199 | [Fact] 200 | public void Enumerable_ShoudReturn_Ids() 201 | { 202 | var g = new IdGenerator(0); 203 | var ids = g.Take(1000).ToArray(); 204 | 205 | Assert.Equal(1000, ids.Distinct().Count()); 206 | } 207 | 208 | [Fact] 209 | public void CreateId_Throws_OnClockBackwards() 210 | { 211 | var ts = new MockTimeSource(DateTime.UtcNow); 212 | var m = MaskConfig.Default; 213 | var g = new IdGenerator(0, TESTEPOCH, m, ts); 214 | 215 | g.CreateId(); 216 | ts.PreviousTick(); //Set clock back 1 'tick' (ms) 217 | Assert.Throws(() => g.CreateId() ); 218 | } 219 | 220 | [Fact] 221 | public void Constructor_Throws_OnInvalidGeneratorId() 222 | { 223 | Assert.Throws(() => new IdGenerator(1024)); 224 | } 225 | 226 | [Fact] 227 | public void Constructor_Throws_OnEpochInFuture() 228 | { 229 | var ts = new MockTimeSource(TESTEPOCH); 230 | Assert.Throws(() => new IdGenerator(0, TESTEPOCH.AddTicks(1), MaskConfig.Default, ts)); 231 | } 232 | 233 | [Fact] 234 | public void Constructor_Throws_OnTimestampWraparound() 235 | { 236 | var mc = MaskConfig.Default; 237 | var ts = new MockTimeSource(mc.WraparoundDate(TESTEPOCH).AddMilliseconds(-1)); //Set clock to 1 ms before epoch 238 | var g = new IdGenerator(0, TESTEPOCH, MaskConfig.Default, ts); 239 | 240 | Assert.True(g.CreateId() > 0); //Should succeed; 241 | ts.NextTick(); 242 | Assert.Throws(() => g.CreateId()); //Should fail 243 | } 244 | 245 | [Fact] 246 | public void CreateMachineSpecificGenerator_Returns_IdGenerator() 247 | { 248 | var g = IdGenerator.CreateMachineSpecificGenerator(); 249 | Assert.NotNull(g); 250 | } 251 | 252 | [Fact(Skip = "Thread specific code not working on AppVeyour automated build. Needs more research as to the issue.")] 253 | public void CreateThreadSpecificGenerator_Returns_IdGenerator() 254 | { 255 | if (Environment.ProcessorCount > 1) 256 | { 257 | const int gcount = 100; //Create a fair amount of generators 258 | var tasks = new Task[gcount]; 259 | for (int i = 0; i < gcount; i++) 260 | tasks[i] = Task.Run(() => IdGenerator.CreateThreadSpecificGenerator()); 261 | Task.WaitAll(tasks); 262 | 263 | // Get all unique generator ID's in an array 264 | var generatorids = tasks.Select(t => t.Result.Id).Distinct().ToArray(); 265 | // Make sure there's at least more than 1 different Id 266 | Assert.True(generatorids.Length > 1); 267 | } 268 | } 269 | 270 | [Fact] 271 | public void MaskConfigProperty_Returns_CorrectValue() 272 | { 273 | var md = MaskConfig.Default; 274 | var mc = new MaskConfig(21, 21, 21); 275 | 276 | Assert.Same(md, new IdGenerator(0, TESTEPOCH, md).MaskConfig); 277 | Assert.Same(mc, new IdGenerator(0, TESTEPOCH, mc).MaskConfig); 278 | } 279 | 280 | } 281 | } -------------------------------------------------------------------------------- /test/Flakey.Tests/MaskConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Flakey 5 | { 6 | public class MaskConfigTests 7 | { 8 | [Fact] 9 | public void DefaultMaskConfig_Matches_Expectations() 10 | { 11 | var m = MaskConfig.Default; 12 | 13 | Assert.Equal(41, m.TimestampBits); 14 | Assert.Equal(10, m.GeneratorIdBits); 15 | Assert.Equal(12, m.SequenceBits); 16 | Assert.Equal(63, m.TotalBits); 17 | 18 | // We should be able to generate a total of 63 bits worth of Id's 19 | Assert.Equal(long.MaxValue, (m.MaxGenerators * m.MaxIntervals * m.MaxSequenceIds) - 1); 20 | } 21 | 22 | [Fact] 23 | public void MaskConfig_CalculatesWraparoundInterval_Correctly() 24 | { 25 | // 40 bits of Timestamp should give us about 34 years worth of Id's 26 | Assert.Equal(34, (int)(new MaskConfig(40, 11, 12).WraparoundInterval().TotalDays / 365.25)); 27 | // 41 bits of Timestamp should give us about 69 years worth of Id's 28 | Assert.Equal(69, (int)(new MaskConfig(41, 11, 11).WraparoundInterval().TotalDays / 365.25)); 29 | // 42 bits of Timestamp should give us about 139 years worth of Id's 30 | Assert.Equal(139, (int)(new MaskConfig(42, 11, 10).WraparoundInterval().TotalDays / 365.25)); 31 | } 32 | 33 | [Fact] 34 | public void MaskConfig_CalculatesWraparoundDate_Correctly() 35 | { 36 | var m = MaskConfig.Default; 37 | var d = m.WraparoundDate(new DateTime(1970,1,1,0,0,0,DateTimeKind.Utc)); 38 | Assert.Equal(new DateTime(643346200555520000, DateTimeKind.Utc), d); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/Flakey.Tests/MockTimeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Flakey 4 | { 5 | public class MockTimeSource : ITimeSource 6 | { 7 | private DateTime _current; 8 | 9 | public MockTimeSource() 10 | : this(DateTime.UtcNow) { } 11 | 12 | public MockTimeSource(DateTime current) 13 | { 14 | _current = current; 15 | } 16 | 17 | public DateTime GetTime() 18 | { 19 | return _current; 20 | } 21 | 22 | public void NextTick() 23 | { 24 | _current = _current.AddMilliseconds(1); 25 | } 26 | 27 | public void PreviousTick() 28 | { 29 | _current = _current.AddMilliseconds(-1); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Flakey.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Flakey.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Flakey.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f2991ad3-6431-4e82-955d-e60e0971a77b")] 24 | -------------------------------------------------------------------------------- /test/Flakey.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:53217/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /test/Flakey.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "testRunner": "xunit", 4 | 5 | "dependencies": { 6 | "Flakey": { "target": "project" }, 7 | "xunit": "2.2.0-beta2-build3300", 8 | "dotnet-test-xunit": "2.2.0-preview2-build1029" 9 | }, 10 | 11 | "frameworks": { 12 | "netcoreapp1.0": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.0.0" 17 | } 18 | }, 19 | "imports": ["dnxcore50", "portable-net45+win8"] 20 | }, 21 | "net451": { 22 | "dependencies": { 23 | "Microsoft.NETCore.Platforms": "1.0.1" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests.ps1: -------------------------------------------------------------------------------- 1 | $PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName 2 | 3 | " PSScriptFilePath = $PSScriptFilePath" 4 | 5 | $SolutionRoot = Split-Path -Path $PSScriptFilePath -Parent 6 | $TestsFolder = Join-Path -Path $SolutionRoot -ChildPath "test/Flakey.Tests"; 7 | 8 | $DOTNET = "dotnet" 9 | 10 | & $DOTNET restore "$TestsFolder" 11 | if (-not $?) 12 | { 13 | throw "The DOTNET restore process returned an error code." 14 | } 15 | 16 | & $DOTNET build "$TestsFolder" 17 | if (-not $?) 18 | { 19 | throw "The DOTNET build process returned an error code." 20 | } 21 | 22 | # run them 23 | & $DOTNET test "$TestsFolder" 24 | if (-not $?) 25 | { 26 | throw "The DOTNET test process returned an error code." 27 | } --------------------------------------------------------------------------------