From 1d51515945e950f8bbe8b40e8da0b4ff437a7f62 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:02:19 +0000 Subject: [PATCH 1/7] An attempt at catching race issues --- snowflake_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/snowflake_test.go b/snowflake_test.go index 2f25f22..a454e5a 100644 --- a/snowflake_test.go +++ b/snowflake_test.go @@ -6,6 +6,25 @@ import ( "testing" ) +// I feel like there's probably a better way +func TestRace(t *testing.T) { + + node, _ := NewNode(1) + + go func() { + for i := 0; i < 1000000000; i++ { + + NewNode(1) + } + }() + + for i := 0; i < 4000; i++ { + + node.Generate() + } + +} + func TestMarshalJSON(t *testing.T) { id := ID(13587) expected := "\"13587\"" @@ -112,6 +131,7 @@ func TestBase58(t *testing.T) { } } } + func BenchmarkParseBase58(b *testing.B) { node, _ := NewNode(1) From c3f55288095a0daf0d267bcf68f8f80799dd4e0e Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:06:02 +0000 Subject: [PATCH 2/7] Check more, Check against Go 11.x/12.x --- .travis.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ce1bba..d817401 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: go go: - - 1.4 - - 1.5 - - 1.6 + - 1.11.x + - 1.12.x install: - - go get github.com/bwmarrin/flake - - go get -v . + - go get -v . +script: + - diff <(gofmt -d .) <(echo -n) + - go vet -x ./... + - golint -set_exit_status ./... + - go test -v -race ./... From 272c8fb2159cd0d0a451433bb1613944508bd679 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:12:21 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=91=8CLinting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- snowflake.go | 12 ++++++------ snowflake_test.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/snowflake.go b/snowflake.go index f112a05..0462cd7 100644 --- a/snowflake.go +++ b/snowflake.go @@ -16,19 +16,19 @@ var ( // You may customize this to set a different epoch for your application. Epoch int64 = 1288834974657 - // Number of bits to use for Node + // NodeBits holds the number of bits to use for Node // Remember, you have a total 22 bits to share between Node/Step NodeBits uint8 = 10 - // Number of bits to use for Step + // StepBits holds the number of bits to use for Step // Remember, you have a total 22 bits to share between Node/Step StepBits uint8 = 12 nodeMax int64 = -1 ^ (-1 << NodeBits) - nodeMask int64 = nodeMax << StepBits + nodeMask = nodeMax << StepBits stepMask int64 = -1 ^ (-1 << StepBits) - timeShift uint8 = NodeBits + StepBits - nodeShift uint8 = StepBits + timeShift = NodeBits + StepBits + nodeShift = StepBits ) const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769" @@ -95,7 +95,7 @@ func NewNode(node int64) (*Node, error) { stepMask = -1 ^ (-1 << StepBits) timeShift = NodeBits + StepBits nodeShift = StepBits - + if node < 0 || node > nodeMax { return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(nodeMax, 10)) } diff --git a/snowflake_test.go b/snowflake_test.go index a454e5a..18f5d9f 100644 --- a/snowflake_test.go +++ b/snowflake_test.go @@ -50,7 +50,7 @@ func TestMarshalsIntBytes(t *testing.T) { func TestUnmarshalJSON(t *testing.T) { tt := []struct { json string - expectedId ID + expectedID ID expectedErr error }{ {`"13587"`, 13587, nil}, @@ -65,8 +65,8 @@ func TestUnmarshalJSON(t *testing.T) { t.Errorf("Expected to get error '%s' decoding JSON, but got '%s'", tc.expectedErr, err) } - if id != tc.expectedId { - t.Errorf("Expected to get ID '%s' decoding JSON, but got '%s'", tc.expectedId, id) + if id != tc.expectedID { + t.Errorf("Expected to get ID '%s' decoding JSON, but got '%s'", tc.expectedID, id) } } } From 0de6e0c0745b50e7bc88c91219d482ad92c4d255 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:21:34 +0000 Subject: [PATCH 4/7] Don't let tests skew benchmark --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc029d6..0e6623c 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ To benchmark the generator on your system run the following command inside the snowflake package directory. ```sh -go test -bench=. +go test -run=^$ -bench=. ``` If your curious, check out this commit that shows benchmarks that compare a few From 267d02f8b9813a5375a1b8b58ff051a8c245ca8b Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:32:30 +0000 Subject: [PATCH 5/7] Add BenchmarkGenerateMaxSequence func This allows a benchmark showing how fast ids can be generated with 21 bits assigned to the sequence number --- snowflake_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/snowflake_test.go b/snowflake_test.go index 18f5d9f..3d84763 100644 --- a/snowflake_test.go +++ b/snowflake_test.go @@ -169,6 +169,20 @@ func BenchmarkGenerate(b *testing.B) { } } +func BenchmarkGenerateMaxSequence(b *testing.B) { + + NodeBits = 1 + StepBits = 21 + node, _ := NewNode(1) + + b.ReportAllocs() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = node.Generate() + } +} + func BenchmarkUnmarshal(b *testing.B) { // Generate the ID to unmarshal node, _ := NewNode(1) From 20ab5dc6a45e1c57b60936ea1d26f9c72d22ade9 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:33:37 +0000 Subject: [PATCH 6/7] Deprecate several global variables This allows better data race protection and makes it more possible to have multi nodes running on the same program with different options. A temp global mutex was added to protect data races caused by NewNode until the deprecated variables are removed. fixed #15 --- snowflake.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/snowflake.go b/snowflake.go index 0462cd7..a5952a9 100644 --- a/snowflake.go +++ b/snowflake.go @@ -24,6 +24,8 @@ var ( // Remember, you have a total 22 bits to share between Node/Step StepBits uint8 = 12 + // DEPRECATED: the below four variables will be removed in a future release. + mu sync.Mutex nodeMax int64 = -1 ^ (-1 << NodeBits) nodeMask = nodeMax << StepBits stepMask int64 = -1 ^ (-1 << StepBits) @@ -79,6 +81,12 @@ type Node struct { time int64 node int64 step int64 + + nodeMax int64 + nodeMask int64 + stepMask int64 + timeShift uint8 + nodeShift uint8 } // An ID is a custom type used for a snowflake ID. This is used so we can @@ -90,21 +98,28 @@ type ID int64 func NewNode(node int64) (*Node, error) { // re-calc in case custom NodeBits or StepBits were set + // DEPRECATED: the below block will be removed in a future release. + mu.Lock() nodeMax = -1 ^ (-1 << NodeBits) nodeMask = nodeMax << StepBits stepMask = -1 ^ (-1 << StepBits) timeShift = NodeBits + StepBits nodeShift = StepBits + mu.Unlock() - if node < 0 || node > nodeMax { - return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(nodeMax, 10)) + n := Node{} + n.node = node + n.nodeMax = -1 ^ (-1 << NodeBits) + n.nodeMask = n.nodeMax << StepBits + n.stepMask = -1 ^ (-1 << StepBits) + n.timeShift = NodeBits + StepBits + n.nodeShift = StepBits + + if n.node < 0 || n.node > n.nodeMax { + return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10)) } - return &Node{ - time: 0, - node: node, - step: 0, - }, nil + return &n, nil } // Generate creates and returns a unique snowflake ID @@ -115,7 +130,7 @@ func (n *Node) Generate() ID { now := time.Now().UnixNano() / 1000000 if n.time == now { - n.step = (n.step + 1) & stepMask + n.step = (n.step + 1) & n.stepMask if n.step == 0 { for now <= n.time { @@ -128,8 +143,8 @@ func (n *Node) Generate() ID { n.time = now - r := ID((now-Epoch)<> timeShift) + Epoch } // Node returns an int64 of the snowflake ID node number +// DEPRECATED: the below function will be removed in a future release. func (f ID) Node() int64 { return int64(f) & nodeMask >> nodeShift } // Step returns an int64 of the snowflake step (or sequence) number +// DEPRECATED: the below function will be removed in a future release. func (f ID) Step() int64 { return int64(f) & stepMask } From 74a8a18aca3be8b9df1532dc1ee0ddaa38d5dc49 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Wed, 10 Apr 2019 01:41:37 +0000 Subject: [PATCH 7/7] Install golint:) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d817401..75d9f5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - 1.12.x install: - go get -v . + - go get -v golang.org/x/lint/golint script: - diff <(gofmt -d .) <(echo -n) - go vet -x ./...