From 564a60a44c11088d76062e0488116af70c89123b Mon Sep 17 00:00:00 2001 From: Nishaad Ajani Date: Fri, 15 Mar 2019 12:46:51 +0800 Subject: [PATCH 1/6] Use monotonic clock if available from the package runtime --- snowflake.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/snowflake.go b/snowflake.go index f112a05..fd34ef7 100644 --- a/snowflake.go +++ b/snowflake.go @@ -14,7 +14,7 @@ import ( var ( // Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC // You may customize this to set a different epoch for your application. - Epoch int64 = 1288834974657 + Epoch = time.Date(2010, time.November, 4, 1, 42, 54, 0, time.UTC) // Number of bits to use for Node // Remember, you have a total 22 bits to share between Node/Step @@ -76,7 +76,7 @@ var ErrInvalidBase32 = errors.New("invalid base32") // node type Node struct { mu sync.Mutex - time int64 + time time.Time node int64 step int64 } @@ -95,13 +95,13 @@ 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)) } return &Node{ - time: 0, + // time: 0, node: node, step: 0, }, nil @@ -112,14 +112,14 @@ func (n *Node) Generate() ID { n.mu.Lock() - now := time.Now().UnixNano() / 1000000 + now := time.Now() - if n.time == now { + if now.Sub(n.time) < time.Millisecond { n.step = (n.step + 1) & stepMask if n.step == 0 { - for now <= n.time { - now = time.Now().UnixNano() / 1000000 + for now.Sub(n.time) < time.Millisecond { + now = time.Now() } } } else { @@ -128,7 +128,7 @@ func (n *Node) Generate() ID { n.time = now - r := ID((now-Epoch)<> timeShift) + Epoch + return (int64(f) >> timeShift) + (Epoch.UnixNano() / 1000000) } // Node returns an int64 of the snowflake ID node number From af41c88b244d490f64eb1e5c41b820e14f0725dd Mon Sep 17 00:00:00 2001 From: Nishaad Ajani Date: Fri, 15 Mar 2019 13:10:57 +0800 Subject: [PATCH 2/6] code cleanup --- snowflake.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/snowflake.go b/snowflake.go index fd34ef7..8d17e10 100644 --- a/snowflake.go +++ b/snowflake.go @@ -100,11 +100,7 @@ func NewNode(node int64) (*Node, error) { return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(nodeMax, 10)) } - return &Node{ - // time: 0, - node: node, - step: 0, - }, nil + return &Node{node: node}, nil } // Generate creates and returns a unique snowflake ID From 0685b6ac316158085ce9cc22c3d8f076b80a7d15 Mon Sep 17 00:00:00 2001 From: Nishaad Ajani Date: Wed, 3 Apr 2019 14:23:35 +0800 Subject: [PATCH 3/6] Rounding time to nearest millisecond --- snowflake.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snowflake.go b/snowflake.go index 8d17e10..c4efceb 100644 --- a/snowflake.go +++ b/snowflake.go @@ -108,14 +108,14 @@ func (n *Node) Generate() ID { n.mu.Lock() - now := time.Now() + now := time.Now().Round(time.Millisecond) if now.Sub(n.time) < time.Millisecond { n.step = (n.step + 1) & stepMask if n.step == 0 { for now.Sub(n.time) < time.Millisecond { - now = time.Now() + now = time.Now().Round(time.Millisecond) } } } else { From 2983bd9d27931e360653ca36cf6911c796bd33e5 Mon Sep 17 00:00:00 2001 From: Nishaad Ajani Date: Wed, 3 Apr 2019 14:57:50 +0800 Subject: [PATCH 4/6] Saving time as a duration in Node --- snowflake.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/snowflake.go b/snowflake.go index c4efceb..2f60570 100644 --- a/snowflake.go +++ b/snowflake.go @@ -76,7 +76,7 @@ var ErrInvalidBase32 = errors.New("invalid base32") // node type Node struct { mu sync.Mutex - time time.Time + time time.Duration node int64 step int64 } @@ -108,14 +108,14 @@ func (n *Node) Generate() ID { n.mu.Lock() - now := time.Now().Round(time.Millisecond) + now := time.Now().Sub(Epoch) - if now.Sub(n.time) < time.Millisecond { + if now-n.time < time.Millisecond { n.step = (n.step + 1) & stepMask if n.step == 0 { - for now.Sub(n.time) < time.Millisecond { - now = time.Now().Round(time.Millisecond) + for now-n.time < time.Millisecond { + now = time.Now().Sub(Epoch) } } } else { @@ -124,7 +124,7 @@ func (n *Node) Generate() ID { n.time = now - r := ID((now.Sub(Epoch).Nanoseconds()/1000000)< Date: Wed, 10 Apr 2019 17:02:30 +0800 Subject: [PATCH 5/6] Bug fix and optimization * Fix for default Epoch to use monotonic clock. * Using common case optimization in the time pkg. --- snowflake.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/snowflake.go b/snowflake.go index 2f60570..02b0606 100644 --- a/snowflake.go +++ b/snowflake.go @@ -12,9 +12,11 @@ import ( ) var ( + curTime = time.Now() // Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC // You may customize this to set a different epoch for your application. - Epoch = time.Date(2010, time.November, 4, 1, 42, 54, 0, time.UTC) + // 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 // Remember, you have a total 22 bits to share between Node/Step @@ -108,14 +110,14 @@ func (n *Node) Generate() ID { n.mu.Lock() - now := time.Now().Sub(Epoch) + now := time.Since(Epoch) if now-n.time < time.Millisecond { n.step = (n.step + 1) & stepMask if n.step == 0 { for now-n.time < time.Millisecond { - now = time.Now().Sub(Epoch) + now = time.Since(Epoch) } } } else { From 1c6d654675cc3398a360deeb9ff77be95555fe6c Mon Sep 17 00:00:00 2001 From: Nishaad Ajani Date: Thu, 11 Apr 2019 11:17:23 +0800 Subject: [PATCH 6/6] Update to avoid breaking the existing API --- snowflake.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/snowflake.go b/snowflake.go index fcc4108..614c3ab 100644 --- a/snowflake.go +++ b/snowflake.go @@ -12,11 +12,9 @@ import ( ) var ( - curTime = time.Now() - // Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC + // Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC in milliseconds // You may customize this to set a different epoch for your application. - // 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 int64 = 1288834974657 // NodeBits holds the number of bits to use for Node // Remember, you have a total 22 bits to share between Node/Step @@ -79,10 +77,11 @@ var ErrInvalidBase32 = errors.New("invalid base32") // A Node struct holds the basic information needed for a snowflake generator // node type Node struct { - mu sync.Mutex - time time.Duration - node int64 - step int64 + mu sync.Mutex + epoch time.Time + time time.Duration + node int64 + step int64 nodeMax int64 nodeMask int64 @@ -121,6 +120,10 @@ func NewNode(node int64) (*Node, error) { return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10)) } + var curTime = time.Now() + // add time.Duration to curTime to make sure we use the monotonic clock if available + n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime)) + return &n, nil } @@ -129,14 +132,14 @@ func (n *Node) Generate() ID { n.mu.Lock() - now := time.Since(Epoch) + now := time.Since(n.epoch) if now-n.time < time.Millisecond { n.step = (n.step + 1) & n.stepMask if n.step == 0 { for now-n.time < time.Millisecond { - now = time.Since(Epoch) + now = time.Since(n.epoch) } } } else { @@ -269,10 +272,10 @@ func (f ID) IntBytes() [8]byte { return b } -// Time returns an int64 unix timestamp in miliseconds of the snowflake ID time +// Time returns an int64 unix timestamp in milliseconds of the snowflake ID time // DEPRECATED: the below function will be removed in a future release. func (f ID) Time() int64 { - return (int64(f) >> timeShift) + (Epoch.UnixNano() / 1000000) + return (int64(f) >> timeShift) + Epoch } // Node returns an int64 of the snowflake ID node number