diff --git a/flake.go b/flake.go new file mode 100644 index 0000000..a25e1e5 --- /dev/null +++ b/flake.go @@ -0,0 +1,166 @@ +// generates IDs for Tasktic +// Based on the common flake/snowflake implementations +// 41bit timestamp, 10bit node, 12bit sequence +// can build 4096 unique IDs per millisecond per node. +// node ID must be defined and you should insure the +// system can maintaine accurate time on all nodes. + +// simple generator for.. +// Twitter Snowflake with custom definable epoch + +package flake + +import "encoding/base64" +import "fmt" +import "strconv" +import "sync" +import "time" + +const ( + TimeBits = 41 + NodeBits = 10 + StepBits = 12 + + TimeMask int64 = -1 ^ (-1 << TimeBits) + NodeMask int64 = -1 ^ (-1 << NodeBits) + StepMask int64 = -1 ^ (-1 << StepBits) + + NodeMax = -1 ^ (-1 << NodeBits) + + TimeShift uint8 = NodeBits + StepBits + NodeShift uint8 = StepBits +) + +type Node struct { + sync.Mutex // TODO: find a way to avoid locks? + + // configurable values + epoch int64 + node int64 + + // runtime tracking values + lastTime int64 + step int64 +} + +// Start a new Flake factory / server node using the given node number +// sets with default settings, use helper functions to change +// node, epoch, etc. +func NewFlakeNode(node int64) (*Node, error) { + + if node < 0 || node > NodeMax { + return nil, fmt.Errorf("Invalid node number.") + } + + return &Node{ + epoch: time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond), + node: node, + lastTime: 0, + step: 0, + }, nil +} + +// high performance generator +func (n *Node) Generator(c chan Flake) { + +} + +// Return a freshly generated Flake ID +func (n *Node) LockedGenerate() (Flake, error) { + + n.Lock() + defer n.Unlock() + + now := time.Now().UnixNano() / 1000000 + + if n.lastTime > now { + return 0, fmt.Errorf("Invalid system time.") + } + + if n.lastTime == now { + n.step = (n.step + 1) & StepMask + + if n.step == 0 { + for now <= n.lastTime { + time.Sleep(100 * time.Microsecond) + now = time.Now().UnixNano() / 1000000 + } + } + } else { + n.step = 0 + } + + n.lastTime = now + + return Flake((now-n.epoch)< now { + return 0, fmt.Errorf("Invalid system time.") + } + + if n.lastTime == now { + n.step = (n.step + 1) & StepMask + + if n.step == 0 { + for now <= n.lastTime { + time.Sleep(100 * time.Microsecond) + now = time.Now().UnixNano() / 1000000 + } + } + } else { + n.step = 0 + } + + n.lastTime = now + + return Flake((now-n.epoch)<> 22) + Epoch +} + +func (f Flake) Node() int64 { + return int64(f) & 0x00000000003FF000 >> 12 +} + +func (f Flake) Sequence() int64 { + return int64(f) & 0x0000000000000FFF +} diff --git a/flake_test.go b/flake_test.go new file mode 100644 index 0000000..bd9c042 --- /dev/null +++ b/flake_test.go @@ -0,0 +1,44 @@ +package flake + +import ( + "testing" +) + +////////////////////////////////////////////////////////////////////////////// + +// Benchmarks Presence Update event with fake data. +func BenchmarkGenerate(b *testing.B) { + + node, _ := NewFlakeNode(1) + + b.ReportAllocs() + for n := 0; n < b.N; n++ { + _, _ = node.Generate() + } + +} + +// Benchmarks Presence Update event with fake data. +func BenchmarkGenerateLocks(b *testing.B) { + + node, _ := NewFlakeNode(1) + + b.ReportAllocs() + for n := 0; n < b.N; n++ { + _, _ = node.LockedGenerate() + } + +} + +// Benchmarks Presence Update event with fake data. +func BenchmarkGenerateLocksParallel(b *testing.B) { + + node, _ := NewFlakeNode(1) + + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, _ = node.LockedGenerate() + } + }) +}