Merge branch 'master' into monotonic_clock
This commit is contained in:
commit
a3f3d0ff71
12
.travis.yml
12
.travis.yml
@ -1,8 +1,12 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.4
|
- 1.11.x
|
||||||
- 1.5
|
- 1.12.x
|
||||||
- 1.6
|
|
||||||
install:
|
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 ./...
|
||||||
|
@ -135,7 +135,7 @@ To benchmark the generator on your system run the following command inside the
|
|||||||
snowflake package directory.
|
snowflake package directory.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test -bench=.
|
go test -run=^$ -bench=.
|
||||||
```
|
```
|
||||||
|
|
||||||
If your curious, check out this commit that shows benchmarks that compare a few
|
If your curious, check out this commit that shows benchmarks that compare a few
|
||||||
|
44
snowflake.go
44
snowflake.go
@ -18,19 +18,21 @@ var (
|
|||||||
// Note: add time.Duration to time.Now() to make sure we use the monotonic clock if available.
|
// 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))
|
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
|
// Remember, you have a total 22 bits to share between Node/Step
|
||||||
NodeBits uint8 = 10
|
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
|
// Remember, you have a total 22 bits to share between Node/Step
|
||||||
StepBits uint8 = 12
|
StepBits uint8 = 12
|
||||||
|
|
||||||
|
// DEPRECATED: the below four variables will be removed in a future release.
|
||||||
|
mu sync.Mutex
|
||||||
nodeMax int64 = -1 ^ (-1 << NodeBits)
|
nodeMax int64 = -1 ^ (-1 << NodeBits)
|
||||||
nodeMask int64 = nodeMax << StepBits
|
nodeMask = nodeMax << StepBits
|
||||||
stepMask int64 = -1 ^ (-1 << StepBits)
|
stepMask int64 = -1 ^ (-1 << StepBits)
|
||||||
timeShift uint8 = NodeBits + StepBits
|
timeShift = NodeBits + StepBits
|
||||||
nodeShift uint8 = StepBits
|
nodeShift = StepBits
|
||||||
)
|
)
|
||||||
|
|
||||||
const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"
|
const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"
|
||||||
@ -81,6 +83,12 @@ type Node struct {
|
|||||||
time time.Duration
|
time time.Duration
|
||||||
node int64
|
node int64
|
||||||
step 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
|
// 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) {
|
func NewNode(node int64) (*Node, error) {
|
||||||
|
|
||||||
// re-calc in case custom NodeBits or StepBits were set
|
// 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)
|
nodeMax = -1 ^ (-1 << NodeBits)
|
||||||
nodeMask = nodeMax << StepBits
|
nodeMask = nodeMax << StepBits
|
||||||
stepMask = -1 ^ (-1 << StepBits)
|
stepMask = -1 ^ (-1 << StepBits)
|
||||||
timeShift = NodeBits + StepBits
|
timeShift = NodeBits + StepBits
|
||||||
nodeShift = StepBits
|
nodeShift = StepBits
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
if node < 0 || node > nodeMax {
|
n := Node{}
|
||||||
return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(nodeMax, 10))
|
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
|
// Generate creates and returns a unique snowflake ID
|
||||||
@ -113,7 +132,7 @@ func (n *Node) Generate() ID {
|
|||||||
now := time.Since(Epoch)
|
now := time.Since(Epoch)
|
||||||
|
|
||||||
if now-n.time < time.Millisecond {
|
if now-n.time < time.Millisecond {
|
||||||
n.step = (n.step + 1) & stepMask
|
n.step = (n.step + 1) & n.stepMask
|
||||||
|
|
||||||
if n.step == 0 {
|
if n.step == 0 {
|
||||||
for now-n.time < time.Millisecond {
|
for now-n.time < time.Millisecond {
|
||||||
@ -126,8 +145,8 @@ func (n *Node) Generate() ID {
|
|||||||
|
|
||||||
n.time = now
|
n.time = now
|
||||||
|
|
||||||
r := ID((now.Nanoseconds()/1000000)<<timeShift |
|
r := ID((now.Nanoseconds()/1000000)<<n.timeShift |
|
||||||
(n.node << nodeShift) |
|
(n.node << n.nodeShift) |
|
||||||
(n.step),
|
(n.step),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -251,16 +270,19 @@ func (f ID) IntBytes() [8]byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Time returns an int64 unix timestamp in miliseconds of the snowflake ID time
|
// Time returns an int64 unix timestamp in miliseconds of the snowflake ID time
|
||||||
|
// DEPRECATED: the below function will be removed in a future release.
|
||||||
func (f ID) Time() int64 {
|
func (f ID) Time() int64 {
|
||||||
return (int64(f) >> timeShift) + (Epoch.UnixNano() / 1000000)
|
return (int64(f) >> timeShift) + (Epoch.UnixNano() / 1000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node returns an int64 of the snowflake ID node number
|
// 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 {
|
func (f ID) Node() int64 {
|
||||||
return int64(f) & nodeMask >> nodeShift
|
return int64(f) & nodeMask >> nodeShift
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step returns an int64 of the snowflake step (or sequence) number
|
// 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 {
|
func (f ID) Step() int64 {
|
||||||
return int64(f) & stepMask
|
return int64(f) & stepMask
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,25 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestMarshalJSON(t *testing.T) {
|
||||||
id := ID(13587)
|
id := ID(13587)
|
||||||
expected := "\"13587\""
|
expected := "\"13587\""
|
||||||
@ -31,7 +50,7 @@ func TestMarshalsIntBytes(t *testing.T) {
|
|||||||
func TestUnmarshalJSON(t *testing.T) {
|
func TestUnmarshalJSON(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
json string
|
json string
|
||||||
expectedId ID
|
expectedID ID
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{`"13587"`, 13587, nil},
|
{`"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)
|
t.Errorf("Expected to get error '%s' decoding JSON, but got '%s'", tc.expectedErr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id != tc.expectedId {
|
if id != tc.expectedID {
|
||||||
t.Errorf("Expected to get ID '%s' decoding JSON, but got '%s'", tc.expectedId, id)
|
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) {
|
func BenchmarkParseBase58(b *testing.B) {
|
||||||
|
|
||||||
node, _ := NewNode(1)
|
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) {
|
func BenchmarkUnmarshal(b *testing.B) {
|
||||||
// Generate the ID to unmarshal
|
// Generate the ID to unmarshal
|
||||||
node, _ := NewNode(1)
|
node, _ := NewNode(1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user