diff --git a/.travis.yml b/.travis.yml index 0ce1bba..75d9f5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ 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 . + - go get -v golang.org/x/lint/golint +script: + - diff <(gofmt -d .) <(echo -n) + - go vet -x ./... + - golint -set_exit_status ./... + - go test -v -race ./... 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 diff --git a/snowflake.go b/snowflake.go index 02b0606..fcc4108 100644 --- a/snowflake.go +++ b/snowflake.go @@ -18,19 +18,21 @@ var ( // Note: add time.Duration to time.Now() to make sure we use the monotonic clock if available. Epoch = curTime.Add(time.Date(2010, time.November, 4, 1, 42, 54, 0, time.UTC).Sub(curTime)) - // 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 + // DEPRECATED: the below four variables will be removed in a future release. + mu sync.Mutex 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" @@ -81,6 +83,12 @@ type Node struct { time time.Duration 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 @@ -92,17 +100,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{node: node}, nil + return &n, nil } // Generate creates and returns a unique snowflake ID @@ -113,7 +132,7 @@ func (n *Node) Generate() ID { now := time.Since(Epoch) if now-n.time < time.Millisecond { - n.step = (n.step + 1) & stepMask + n.step = (n.step + 1) & n.stepMask if n.step == 0 { for now-n.time < time.Millisecond { @@ -126,8 +145,8 @@ func (n *Node) Generate() ID { n.time = now - r := ID((now.Nanoseconds()/1000000)<> timeShift) + (Epoch.UnixNano() / 1000000) } // 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 } diff --git a/snowflake_test.go b/snowflake_test.go index 2f25f22..3d84763 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\"" @@ -31,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}, @@ -46,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) } } } @@ -112,6 +131,7 @@ func TestBase58(t *testing.T) { } } } + func BenchmarkParseBase58(b *testing.B) { node, _ := NewNode(1) @@ -149,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)