From 723b00ab8976dba885d493a1309fca18a8d1ca0d Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Mon, 13 Mar 2023 08:35:12 -0600 Subject: [PATCH] done that was fun --- main.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ main_test.go | 45 +++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 main.go create mode 100644 main_test.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..cc6ea47 --- /dev/null +++ b/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "hash/fnv" + "log" + "os" + "strconv" + "sync" + "sync/atomic" + "time" +) + +func main() { + log.Println(host_10b) + log.Println(epoch) + log.Println(epoch.UnixNano() / int64(time.Millisecond) / 10) + log.Println(ts_41b()) + for i := 0; i < 7; i++ { + time.Sleep(time.Duration(i) * time.Millisecond) + log.Printf("%v = ts_41b/%v/seq_13b/%v/host_10b/%v", id(), ts_41b(), seq_13b(), host_10b) + } +} + +func id() uint64 { + ts := ts_41b() << (64 - 41) + seq := seq_13b() << (64 - 41 - 13) + host := host_10b + return ts | seq | host +} + +func ts_41b() uint64 { + durationSinceEpoch := time.Now().Sub(epoch) + return uint64(durationSinceEpoch/time.Millisecond) / 10 +} + +var seqLock = &sync.RWMutex{} +var seqV = &atomic.Uint64{} +var seqTS = &atomic.Int64{} + +func seq_13b() uint64 { + _seq_13b_rollover() + got := func() uint64 { + seqLock.RLock() + defer seqLock.RUnlock() + return seqV.Add(1) + }() + if got > (1 << 13) { + time.Sleep(time.Millisecond * 10) + return seq_13b() + } + return got +} + +func _seq_13b_now() int64 { + return time.Now().UnixNano() / int64(time.Millisecond) / 10 +} + +func _seq_13b_rollover() { + now := _seq_13b_now() + if seqTS.Load() == now { + return + } + func() { + seqLock.Lock() + defer seqLock.Unlock() + if seqTS.Load() == now { + return + } + seqTS.Store(now) + seqV.Store(0) + }() +} + +var epoch = func() time.Time { + t := time.Unix(0, 0) + if v := os.Getenv("SNOWFLAKE_UID_SERVICE_EPOCH"); v != "" { + // RFC3339 = "2006-01-02T15:04:05Z07:00" + t2, err := time.ParseInLocation(time.RFC3339, v, time.UTC) + if err != nil { + panic(err) + } + t = t2 + } + return t // uint64(t.UnixNano() / int64(time.Millisecond) / 10) +}() + +var host_10b = func() uint64 { + if v := os.Getenv("SNOWFLAKE_UID_SERVICE_HOST"); v != "" { + v, err := strconv.ParseUint(v, 10, 10) + if err != nil { + panic(err) + } + ui64 := uint64(v) + return uint64(ui64 << 54 >> 54) + } + hash := fnv.New32() + hash.Write([]byte(os.Getenv("HOSTNAME"))) + ui32 := hash.Sum32() + return uint64((ui32 << 22) >> 22) +}() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..7a5a3ec --- /dev/null +++ b/main_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "testing" + "time" +) + +func TestID(t *testing.T) { + last := id() + for i := 0; i < 1000; i++ { + next := id() + if next <= last { + t.Error(i, last, next) + } + last = next + } +} + +func TestTS(t *testing.T) { + got := int64(ts_41b()) + if got != ((got << (64 - 41)) >> (64 - 41)) { + t.Error("got > 41b") + } + gotTime := time.Unix(got/100, 10*(got%100)) + if time.Since(gotTime) > time.Second { + t.Error(gotTime) + } +} + +func TestSeq(t *testing.T) { + last := seq_13b() + for i := 0; i < 1000; i++ { + next := seq_13b() + if next <= last { + t.Error(i, last, next) + } + last = next + } + + seqV.Store(1 << 14) + got := seq_13b() + if got > 2 { + t.Error(got) + } +}