Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NextIDMono() function. #44

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ func (sf *Sonyflake) NextID() (uint64, error)
NextID can continue to generate IDs for about 174 years from StartTime.
But after the Sonyflake time is over the limit, NextID returns an error.

if you want to use the mono time, you can call the method NextIDMono.

```go
func (sf *Sonyflake) NextIDMono() (uint64, error)
```
NextIDMono can avoid the clock backwards.
> **Note:**
> Sonyflake currently does not use the most significant bit of IDs,
> so you can convert Sonyflake IDs from `uint64` to `int64` safely.
Expand Down
41 changes: 36 additions & 5 deletions sonyflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ type Settings struct {

// Sonyflake is a distributed unique ID generator.
type Sonyflake struct {
mutex *sync.Mutex
startTime int64
elapsedTime int64
sequence uint16
machineID uint16
mutex *sync.Mutex
startTime int64
startTimeMono time.Time
elapsedTime int64
sequence uint16
machineID uint16
}

var (
Expand Down Expand Up @@ -77,8 +78,10 @@ func New(st Settings) (*Sonyflake, error) {

if st.StartTime.IsZero() {
sf.startTime = toSonyflakeTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
sf.startTimeMono = time.Now()
} else {
sf.startTime = toSonyflakeTime(st.StartTime)
sf.startTimeMono = st.StartTime
}

var err error
Expand Down Expand Up @@ -132,6 +135,31 @@ func (sf *Sonyflake) NextID() (uint64, error) {
return sf.toID()
}

// NextIDMono generates a next unique ID.
// After the Sonyflake time overflows, NextID returns an error.
// This function use mono time to avoid clock backwards
func (sf *Sonyflake) NextIDMono() (uint64, error) {
const maskSequence = uint16(1<<BitLenSequence - 1)

sf.mutex.Lock()
defer sf.mutex.Unlock()

current := currentElapsedTimeMono(sf.startTimeMono)
if sf.elapsedTime < current {
sf.elapsedTime = current
sf.sequence = 0
} else {
sf.sequence = (sf.sequence + 1) & maskSequence
if sf.sequence == 0 {
sf.elapsedTime++
overtime := sf.elapsedTime - current
time.Sleep(sleepTime((overtime)))
}
}

return sf.toID()
}

const sonyflakeTimeUnit = 1e7 // nsec, i.e. 10 msec

func toSonyflakeTime(t time.Time) int64 {
Expand All @@ -141,6 +169,9 @@ func toSonyflakeTime(t time.Time) int64 {
func currentElapsedTime(startTime int64) int64 {
return toSonyflakeTime(time.Now()) - startTime
}
func currentElapsedTimeMono(startTimeMono time.Time) int64 {
return time.Now().Sub(startTimeMono).Nanoseconds() / sonyflakeTimeUnit
}

func sleepTime(overtime int64) time.Duration {
return time.Duration(overtime*sonyflakeTimeUnit) -
Expand Down
32 changes: 32 additions & 0 deletions sonyflake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,35 @@ func TestSonyflakeTimeUnit(t *testing.T) {
t.Errorf("unexpected time unit")
}
}

func nextIDMono(t *testing.T) uint64 {
id, err := sf.NextIDMono()
if err != nil {
t.Fatal("id not generated")
}
return id
}
func TestSonyflake_NextIDMono(t *testing.T) {
sleepTime := time.Duration(50 * sonyflakeTimeUnit)
time.Sleep(sleepTime)

id := nextIDMono(t)

actualTime := ElapsedTime(id)
if actualTime < sleepTime || actualTime > sleepTime+sonyflakeTimeUnit {
t.Errorf("unexpected time: %d", actualTime)
}

actualSequence := SequenceNumber(id)
if actualSequence != 0 {
t.Errorf("unexpected sequence: %d", actualSequence)
}

actualMachineID := MachineID(id)
if actualMachineID != machineID {
t.Errorf("unexpected machine id: %d", actualMachineID)
}

fmt.Println("sonyflake id:", id)
fmt.Println("decompose:", Decompose(id))
}