mirror of
https://github.com/sony/sonyflake.git
synced 2026-01-06 03:14:51 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f167a9d531 | ||
|
|
5c401f9c06 | ||
|
|
0cdef9e4fe | ||
|
|
114716564a | ||
|
|
2343cac676 | ||
|
|
5347433c8c | ||
|
|
774342570a |
20
sonyflake.go
20
sonyflake.go
@@ -57,6 +57,7 @@ var (
|
||||
ErrNoPrivateAddress = errors.New("no private ip address")
|
||||
ErrOverTimeLimit = errors.New("over the time limit")
|
||||
ErrInvalidMachineID = errors.New("invalid machine id")
|
||||
ErrInvalidSequence = errors.New("invalid sequence number")
|
||||
)
|
||||
|
||||
var defaultInterfaceAddrs = net.InterfaceAddrs
|
||||
@@ -213,6 +214,25 @@ func MachineID(id uint64) uint64 {
|
||||
return id & maskMachineID
|
||||
}
|
||||
|
||||
// Compose creates a Sonyflake ID from its parts.
|
||||
func Compose(sf *Sonyflake, t time.Time, sequence uint16, machineID uint16) (uint64, error) {
|
||||
elapsedTime := toSonyflakeTime(t.UTC()) - sf.startTime
|
||||
if elapsedTime < 0 {
|
||||
return 0, ErrStartTimeAhead
|
||||
}
|
||||
if elapsedTime >= 1<<BitLenTime {
|
||||
return 0, ErrOverTimeLimit
|
||||
}
|
||||
|
||||
if sequence >= 1<<BitLenSequence {
|
||||
return 0, ErrInvalidSequence
|
||||
}
|
||||
|
||||
return uint64(elapsedTime)<<(BitLenSequence+BitLenMachineID) |
|
||||
uint64(sequence)<<BitLenMachineID |
|
||||
uint64(machineID), nil
|
||||
}
|
||||
|
||||
// Decompose returns a set of Sonyflake ID parts.
|
||||
func Decompose(id uint64) map[string]uint64 {
|
||||
msb := id >> 63
|
||||
|
||||
@@ -312,3 +312,36 @@ func TestSonyflakeTimeUnit(t *testing.T) {
|
||||
t.Errorf("unexpected time unit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
var st Settings
|
||||
st.StartTime = time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
sf, err := New(st)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
sequence := uint16(123)
|
||||
machineID := uint16(456)
|
||||
|
||||
id, err := Compose(sf, now, sequence, machineID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parts := Decompose(id)
|
||||
|
||||
actualTime := toSonyflakeTime(now) - toSonyflakeTime(st.StartTime)
|
||||
if parts["time"] != uint64(actualTime) {
|
||||
t.Errorf("unexpected time: %d", parts["time"])
|
||||
}
|
||||
|
||||
if parts["sequence"] != uint64(sequence) {
|
||||
t.Errorf("unexpected sequence: %d", parts["sequence"])
|
||||
}
|
||||
|
||||
if parts["machine-id"] != uint64(machineID) {
|
||||
t.Errorf("unexpected machine id: %d", parts["machine-id"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ type Sonyflake struct {
|
||||
|
||||
sequence int
|
||||
machine int
|
||||
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -74,8 +76,9 @@ var (
|
||||
ErrInvalidBitsSequence = errors.New("invalid bit length for sequence number")
|
||||
ErrInvalidBitsMachineID = errors.New("invalid bit length for machine id")
|
||||
ErrInvalidTimeUnit = errors.New("invalid time unit")
|
||||
ErrInvalidSequence = errors.New("invalid sequence number")
|
||||
ErrInvalidMachineID = errors.New("invalid machine id")
|
||||
ErrStartTimeAhead = errors.New("start time is ahead of now")
|
||||
ErrStartTimeAhead = errors.New("start time is ahead")
|
||||
ErrOverTimeLimit = errors.New("over the time limit")
|
||||
ErrNoPrivateAddress = errors.New("no private ip address")
|
||||
)
|
||||
@@ -115,6 +118,7 @@ func New(st Settings) (*Sonyflake, error) {
|
||||
|
||||
sf := new(Sonyflake)
|
||||
sf.mutex = new(sync.Mutex)
|
||||
sf.now = time.Now
|
||||
|
||||
if st.BitsSequence == 0 {
|
||||
sf.bitsSequence = defaultBitsSequence
|
||||
@@ -157,6 +161,10 @@ func New(st Settings) (*Sonyflake, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sf.machine < 0 || sf.machine >= 1<<sf.bitsMachine {
|
||||
return nil, ErrInvalidMachineID
|
||||
}
|
||||
|
||||
if st.CheckMachineID != nil && !st.CheckMachineID(sf.machine) {
|
||||
return nil, ErrInvalidMachineID
|
||||
}
|
||||
@@ -193,12 +201,12 @@ func (sf *Sonyflake) toInternalTime(t time.Time) int64 {
|
||||
}
|
||||
|
||||
func (sf *Sonyflake) currentElapsedTime() int64 {
|
||||
return sf.toInternalTime(time.Now()) - sf.startTime
|
||||
return sf.toInternalTime(sf.now()) - sf.startTime
|
||||
}
|
||||
|
||||
func (sf *Sonyflake) sleep(overtime int64) {
|
||||
sleepTime := time.Duration(overtime*sf.timeUnit) -
|
||||
time.Duration(time.Now().UTC().UnixNano()%sf.timeUnit)
|
||||
time.Duration(sf.now().UTC().UnixNano()%sf.timeUnit)
|
||||
time.Sleep(sleepTime)
|
||||
}
|
||||
|
||||
@@ -252,6 +260,32 @@ func (sf *Sonyflake) ToTime(id int64) time.Time {
|
||||
return time.Unix(0, (sf.startTime+sf.timePart(id))*sf.timeUnit)
|
||||
}
|
||||
|
||||
// Compose creates a Sonyflake ID from its components.
|
||||
// The time parameter should be the time when the ID was generated.
|
||||
// The sequence parameter should be between 0 and 2^BitsSequence-1 (inclusive).
|
||||
// The machineID parameter should be between 0 and 2^BitsMachineID-1 (inclusive).
|
||||
func (sf *Sonyflake) Compose(t time.Time, sequence, machineID int) (int64, error) {
|
||||
elapsedTime := sf.toInternalTime(t.UTC()) - sf.startTime
|
||||
if elapsedTime < 0 {
|
||||
return 0, ErrStartTimeAhead
|
||||
}
|
||||
if elapsedTime >= 1<<sf.bitsTime {
|
||||
return 0, ErrOverTimeLimit
|
||||
}
|
||||
|
||||
if sequence < 0 || sequence >= 1<<sf.bitsSequence {
|
||||
return 0, ErrInvalidSequence
|
||||
}
|
||||
|
||||
if machineID < 0 || machineID >= 1<<sf.bitsMachine {
|
||||
return 0, ErrInvalidMachineID
|
||||
}
|
||||
|
||||
return elapsedTime<<(sf.bitsSequence+sf.bitsMachine) |
|
||||
int64(sequence)<<sf.bitsMachine |
|
||||
int64(machineID), nil
|
||||
}
|
||||
|
||||
// Decompose returns a set of Sonyflake ID parts.
|
||||
func (sf *Sonyflake) Decompose(id int64) map[string]int64 {
|
||||
time := sf.timePart(id)
|
||||
|
||||
@@ -2,7 +2,6 @@ package sonyflake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
errGetMachineID := fmt.Errorf("failed to get machine id")
|
||||
errGetMachineID := errors.New("failed to get machine id")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -65,6 +64,24 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
err: errGetMachineID,
|
||||
},
|
||||
{
|
||||
name: "too large machine id",
|
||||
settings: Settings{
|
||||
MachineID: func() (int, error) {
|
||||
return 1 << defaultBitsMachine, nil
|
||||
},
|
||||
},
|
||||
err: ErrInvalidMachineID,
|
||||
},
|
||||
{
|
||||
name: "negative machine id",
|
||||
settings: Settings{
|
||||
MachineID: func() (int, error) {
|
||||
return -1, nil
|
||||
},
|
||||
},
|
||||
err: ErrInvalidMachineID,
|
||||
},
|
||||
{
|
||||
name: "invalid machine id",
|
||||
settings: Settings{
|
||||
@@ -120,15 +137,16 @@ func defaultMachineID(t *testing.T) int {
|
||||
}
|
||||
|
||||
func TestNextID(t *testing.T) {
|
||||
sf := newSonyflake(t, Settings{StartTime: time.Now()})
|
||||
start := time.Now()
|
||||
sf := newSonyflake(t, Settings{StartTime: start})
|
||||
|
||||
sleepTime := int64(50)
|
||||
time.Sleep(time.Duration(sleepTime * sf.timeUnit))
|
||||
sf.now = func() time.Time { return start.Add(time.Duration(sleepTime * sf.timeUnit)) }
|
||||
|
||||
id := nextID(t, sf)
|
||||
|
||||
actualTime := sf.timePart(id)
|
||||
if actualTime < sleepTime || actualTime > sleepTime+1 {
|
||||
if actualTime != sleepTime {
|
||||
t.Errorf("unexpected time: %d", actualTime)
|
||||
}
|
||||
|
||||
@@ -142,17 +160,17 @@ func TestNextID(t *testing.T) {
|
||||
t.Errorf("unexpected machine: %d", actualMachine)
|
||||
}
|
||||
|
||||
fmt.Println("sonyflake id:", id)
|
||||
fmt.Println("decompose:", sf.Decompose(id))
|
||||
t.Log("sonyflake id:", id)
|
||||
t.Log("decompose:", sf.Decompose(id))
|
||||
}
|
||||
|
||||
func TestNextID_InSequence(t *testing.T) {
|
||||
now := time.Now()
|
||||
start := time.Now()
|
||||
sf := newSonyflake(t, Settings{
|
||||
TimeUnit: time.Millisecond,
|
||||
StartTime: now,
|
||||
StartTime: start,
|
||||
})
|
||||
startTime := sf.toInternalTime(now)
|
||||
startTime := sf.toInternalTime(start)
|
||||
machineID := int64(defaultMachineID(t))
|
||||
|
||||
var numID int
|
||||
@@ -192,11 +210,11 @@ func TestNextID_InSequence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if maxSeq != 1<<sf.bitsSequence-1 {
|
||||
if maxSeq > 1<<sf.bitsSequence-1 {
|
||||
t.Errorf("unexpected max sequence: %d", maxSeq)
|
||||
}
|
||||
fmt.Println("max sequence:", maxSeq)
|
||||
fmt.Println("number of id:", numID)
|
||||
t.Log("max sequence:", maxSeq)
|
||||
t.Log("number of id:", numID)
|
||||
}
|
||||
|
||||
func TestNextID_InParallel(t *testing.T) {
|
||||
@@ -205,7 +223,7 @@ func TestNextID_InParallel(t *testing.T) {
|
||||
|
||||
numCPU := runtime.NumCPU()
|
||||
runtime.GOMAXPROCS(numCPU)
|
||||
fmt.Println("number of cpu:", numCPU)
|
||||
t.Log("number of cpu:", numCPU)
|
||||
|
||||
consumer := make(chan int64)
|
||||
|
||||
@@ -232,17 +250,18 @@ func TestNextID_InParallel(t *testing.T) {
|
||||
}
|
||||
set[id] = struct{}{}
|
||||
}
|
||||
fmt.Println("number of id:", len(set))
|
||||
t.Log("number of id:", len(set))
|
||||
}
|
||||
|
||||
func pseudoSleep(sf *Sonyflake, period time.Duration) {
|
||||
sf.startTime -= int64(period) / sf.timeUnit
|
||||
}
|
||||
|
||||
const year = time.Duration(365*24) * time.Hour
|
||||
|
||||
func TestNextID_ReturnsError(t *testing.T) {
|
||||
sf := newSonyflake(t, Settings{StartTime: time.Now()})
|
||||
|
||||
year := time.Duration(365*24) * time.Hour
|
||||
pseudoSleep(sf, time.Duration(174)*year)
|
||||
nextID(t, sf)
|
||||
|
||||
@@ -253,22 +272,6 @@ func TestNextID_ReturnsError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToTime(t *testing.T) {
|
||||
start := time.Now()
|
||||
sf := newSonyflake(t, Settings{
|
||||
TimeUnit: time.Millisecond,
|
||||
StartTime: start,
|
||||
})
|
||||
|
||||
id := nextID(t, sf)
|
||||
|
||||
tm := sf.ToTime(id)
|
||||
diff := tm.Sub(start)
|
||||
if diff < 0 || diff >= time.Duration(sf.timeUnit) {
|
||||
t.Errorf("unexpected time: %v", tm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateIPv4(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
@@ -351,3 +354,143 @@ func TestLower16BitPrivateIP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToTime(t *testing.T) {
|
||||
start := time.Now()
|
||||
sf := newSonyflake(t, Settings{
|
||||
TimeUnit: time.Millisecond,
|
||||
StartTime: start,
|
||||
})
|
||||
|
||||
sf.now = func() time.Time { return start }
|
||||
id := nextID(t, sf)
|
||||
|
||||
tm := sf.ToTime(id)
|
||||
diff := tm.Sub(start)
|
||||
if diff < 0 || diff >= time.Duration(sf.timeUnit) {
|
||||
t.Errorf("unexpected time: %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeAndDecompose(t *testing.T) {
|
||||
now := time.Now()
|
||||
sf := newSonyflake(t, Settings{
|
||||
TimeUnit: time.Millisecond,
|
||||
StartTime: now,
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
time time.Time
|
||||
sequence int
|
||||
machineID int
|
||||
}{
|
||||
{
|
||||
name: "zero values",
|
||||
time: now,
|
||||
sequence: 0,
|
||||
machineID: 0,
|
||||
},
|
||||
{
|
||||
name: "max sequence",
|
||||
time: now,
|
||||
sequence: 1<<sf.bitsSequence - 1,
|
||||
machineID: 0,
|
||||
},
|
||||
{
|
||||
name: "max machine id",
|
||||
time: now,
|
||||
sequence: 0,
|
||||
machineID: 1<<sf.bitsMachine - 1,
|
||||
},
|
||||
{
|
||||
name: "future time",
|
||||
time: now.Add(time.Hour),
|
||||
sequence: 0,
|
||||
machineID: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
id, err := sf.Compose(tc.time, tc.sequence, tc.machineID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
parts := sf.Decompose(id)
|
||||
|
||||
// Verify time part
|
||||
expectedTime := sf.toInternalTime(tc.time.UTC()) - sf.startTime
|
||||
if parts["time"] != expectedTime {
|
||||
t.Errorf("time mismatch: got %d, want %d", parts["time"], expectedTime)
|
||||
}
|
||||
|
||||
// Verify sequence part
|
||||
if parts["sequence"] != int64(tc.sequence) {
|
||||
t.Errorf("sequence mismatch: got %d, want %d", parts["sequence"], tc.sequence)
|
||||
}
|
||||
|
||||
// Verify machine id part
|
||||
if parts["machine"] != int64(tc.machineID) {
|
||||
t.Errorf("machine id mismatch: got %d, want %d", parts["machine"], tc.machineID)
|
||||
}
|
||||
|
||||
// Verify id part
|
||||
if parts["id"] != id {
|
||||
t.Errorf("id mismatch: got %d, want %d", parts["id"], id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompose_ReturnsError(t *testing.T) {
|
||||
start := time.Now()
|
||||
sf := newSonyflake(t, Settings{StartTime: start})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
time time.Time
|
||||
sequence int
|
||||
machineID int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "start time ahead",
|
||||
time: start.Add(-time.Second),
|
||||
sequence: 0,
|
||||
machineID: 0,
|
||||
err: ErrStartTimeAhead,
|
||||
},
|
||||
{
|
||||
name: "over time limit",
|
||||
time: start.Add(time.Duration(175) * year),
|
||||
sequence: 0,
|
||||
machineID: 0,
|
||||
err: ErrOverTimeLimit,
|
||||
},
|
||||
{
|
||||
name: "invalid sequence",
|
||||
time: start,
|
||||
sequence: 1 << sf.bitsSequence,
|
||||
machineID: 0,
|
||||
err: ErrInvalidSequence,
|
||||
},
|
||||
{
|
||||
name: "invalid machine id",
|
||||
time: start,
|
||||
sequence: 0,
|
||||
machineID: 1 << sf.bitsMachine,
|
||||
err: ErrInvalidMachineID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := sf.Compose(tc.time, tc.sequence, tc.machineID)
|
||||
if !errors.Is(err, tc.err) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user