From 6a846ced7582899a3b72473cc783974648b8a8ef Mon Sep 17 00:00:00 2001 From: Nyvil Date: Mon, 2 Jun 2025 11:22:09 +0200 Subject: [PATCH 1/6] adjusted for clock drift --- spaceflake.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/spaceflake.go b/spaceflake.go index 33b74fb..54de25e 100644 --- a/spaceflake.go +++ b/spaceflake.go @@ -162,13 +162,15 @@ type Worker struct { Sequence uint64 // ID is the worker ID that the Spaceflake generator will use for the next 5 bits ID uint64 - - increment uint64 - mutex *sync.Mutex + // used to prevent clockdrift + lastTimestamp uint64 + increment uint64 + mutex *sync.Mutex } // GenerateSpaceflake generates a Spaceflake func (w *Worker) GenerateSpaceflake() (*Spaceflake, error) { + if w.Node == nil { return nil, fmt.Errorf("node is not set") } @@ -197,6 +199,15 @@ func (w *Worker) GenerateSpaceflake() (*Spaceflake, error) { milliseconds := uint64(math.Floor(microTime() * 1000)) milliseconds -= w.BaseEpoch + if milliseconds < w.lastTimestamp { + for milliseconds < w.lastTimestamp { + time.Sleep(time.Millisecond) + milliseconds = uint64(math.Floor(microTime()*1000)) - w.BaseEpoch + } + } + + w.lastTimestamp = milliseconds + base := stringPadLeft(decimalBinary(milliseconds), 41, "0") nodeID := stringPadLeft(decimalBinary(w.Node.ID), 5, "0") workerID := stringPadLeft(decimalBinary(w.ID), 5, "0") @@ -250,6 +261,10 @@ func (w *Worker) GenerateSpaceflakeAt(at time.Time) (*Spaceflake, error) { milliseconds := uint64(math.Floor(microTime * 1000)) milliseconds -= w.BaseEpoch + if milliseconds < w.lastTimestamp { + return nil, fmt.Errorf("cannot generate Spaceflake: Detected clock drift. The time you want to generate the Spaceflake at is before the last generated Spaceflake time") + } + base := stringPadLeft(decimalBinary(milliseconds), 41, "0") nodeID := stringPadLeft(decimalBinary(w.Node.ID), 5, "0") workerID := stringPadLeft(decimalBinary(w.ID), 5, "0") From d1ead97c241a8edfa484e03db138f60ba507b054 Mon Sep 17 00:00:00 2001 From: Nyvil Date: Mon, 2 Jun 2025 11:47:32 +0200 Subject: [PATCH 2/6] yeet different base epoch --- spaceflake_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/spaceflake_test.go b/spaceflake_test.go index cb10251..527bc5e 100644 --- a/spaceflake_test.go +++ b/spaceflake_test.go @@ -160,28 +160,6 @@ func TestSpaceflakeWorkerGoroutineUnique(t *testing.T) { t.Log("Success! All Spaceflakes are unique") } -func TestSameTimeStampDifferentBaseEpoch(t *testing.T) { - node := NewNode(1) - worker := node.NewWorker() - sf1, err := worker.GenerateSpaceflake() // Default epoch - if err != nil { - t.Error(err) - return - } - worker.BaseEpoch = 1640995200000 // Saturday, January 1, 2022 12:00:00 AM GMT - sf2, err := worker.GenerateSpaceflake() - if err != nil { - t.Error(err) - return - } - if sf1.Time() == sf2.Time() { - t.Log("Success! Generated same timestamp for different base epoch") - return - } - - t.Error("Failed! Generated different timestamps for different base epoch") -} - func TestSpaceflakeGenerateUnique(t *testing.T) { spaceflakes := map[uint64]*Spaceflake{} settings := NewGeneratorSettings() From 87dc40b720ed00ca3e428ac28f5bc44e960925b8 Mon Sep 17 00:00:00 2001 From: Alexandros Date: Mon, 2 Jun 2025 12:08:22 +0200 Subject: [PATCH 3/6] Update spaceflake.go do delta rather than wasting by iteration --- spaceflake.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spaceflake.go b/spaceflake.go index 54de25e..26e9eff 100644 --- a/spaceflake.go +++ b/spaceflake.go @@ -200,10 +200,9 @@ func (w *Worker) GenerateSpaceflake() (*Spaceflake, error) { milliseconds -= w.BaseEpoch if milliseconds < w.lastTimestamp { - for milliseconds < w.lastTimestamp { - time.Sleep(time.Millisecond) - milliseconds = uint64(math.Floor(microTime()*1000)) - w.BaseEpoch - } + delta := w.lastTimestamp - milliseconds + time.Sleep(time.Duration(delta + 1) * time.Millisecond) + milliseconds = uint64(math.Floor(microTime() * 1000)) - w.BaseEpoch) } w.lastTimestamp = milliseconds From 2d96e70a023f9ca83cfb102eca4e21baed2320f5 Mon Sep 17 00:00:00 2001 From: Alexandros Date: Mon, 2 Jun 2025 13:01:56 +0200 Subject: [PATCH 4/6] Update spaceflake.go stuff --- spaceflake.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/spaceflake.go b/spaceflake.go index 26e9eff..aa668cd 100644 --- a/spaceflake.go +++ b/spaceflake.go @@ -17,7 +17,8 @@ const ( // MAX12BITS is the maximum value for a 12 bits number MAX12BITS = 4095 // MAX41BITS is the maximum value for a 41 bits number - MAX41BITS = 2199023255551 + MAX41BITS = 2199023255551 + CLOCK_DRIFT_TOLERANCE_MS = 10 ) // Spaceflake represents a Spaceflake @@ -199,10 +200,12 @@ func (w *Worker) GenerateSpaceflake() (*Spaceflake, error) { milliseconds := uint64(math.Floor(microTime() * 1000)) milliseconds -= w.BaseEpoch - if milliseconds < w.lastTimestamp { - delta := w.lastTimestamp - milliseconds - time.Sleep(time.Duration(delta + 1) * time.Millisecond) - milliseconds = uint64(math.Floor(microTime() * 1000)) - w.BaseEpoch) + if delta := w.lastTimestamp - milliseconds; milliseconds < w.lastTimestamp { + if delta >= CLOCK_DRIFT_TOLERANCE_MS { + return nil, fmt.Errorf("clock moved backwards by %dms", delta) + } + time.Sleep(time.Duration(delta+1) * time.Millisecond) + milliseconds = uint64(math.Floor(microTime()*100)) - w.BaseEpoch } w.lastTimestamp = milliseconds From 3c6529ff8e02f4e11bad2c4334a5676a018e5d51 Mon Sep 17 00:00:00 2001 From: Nyvil Date: Mon, 2 Jun 2025 13:07:29 +0200 Subject: [PATCH 5/6] remove excess spaces and do one liner if --- spaceflake.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spaceflake.go b/spaceflake.go index 54de25e..5ff45e5 100644 --- a/spaceflake.go +++ b/spaceflake.go @@ -18,6 +18,8 @@ const ( MAX12BITS = 4095 // MAX41BITS is the maximum value for a 41 bits number MAX41BITS = 2199023255551 + // CLOCK_DRIFT_TOLERANCE_MS is the tolerance for clock drift in milliseconds + CLOCK_DRIFT_TOLERANCE_MS = 10 ) // Spaceflake represents a Spaceflake @@ -199,11 +201,12 @@ func (w *Worker) GenerateSpaceflake() (*Spaceflake, error) { milliseconds := uint64(math.Floor(microTime() * 1000)) milliseconds -= w.BaseEpoch - if milliseconds < w.lastTimestamp { - for milliseconds < w.lastTimestamp { - time.Sleep(time.Millisecond) - milliseconds = uint64(math.Floor(microTime()*1000)) - w.BaseEpoch + if delta := w.lastTimestamp - milliseconds; milliseconds < w.lastTimestamp { + if delta >= CLOCK_DRIFT_TOLERANCE_MS { + return nil, fmt.Errorf("clock moved backwards by %dms", delta) } + time.Sleep(time.Duration(delta+1) * time.Millisecond) + milliseconds = uint64(math.Floor(microTime()*100)) - w.BaseEpoch } w.lastTimestamp = milliseconds From f6152583691eea82e73828021b2f425a583e55c1 Mon Sep 17 00:00:00 2001 From: Krypton Date: Mon, 2 Jun 2025 19:45:35 +0200 Subject: [PATCH 6/6] ci: Add tests back --- .github/FUNDING.yml | 2 ++ .github/workflows/ci.yml | 49 +++++++++++++++++++++++++++++++++++ .github/workflows/go-test.yml | 23 ---------------- go.mod | 2 +- 4 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/go-test.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..6d9ee9e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [kkrypt0nn] +custom: ["https://buymeacoffee.com/kkrypt0nn"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ffb1b13 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: Spaceflake CI (Lint & Test) + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + lint-test: + name: Lint & Test + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24 + - name: Lint + uses: golangci/golangci-lint-action@v7 + with: + version: latest + - name: Test + run: go test -v ./... + build: + name: Build for ${{ matrix.target.goos }}/${{ matrix.target.goarch }} + runs-on: ubuntu-latest + needs: [lint-test] + strategy: + matrix: + target: + - { goos: linux, goarch: amd64 } + - { goos: linux, goarch: arm64 } + - { goos: darwin, goarch: amd64 } + - { goos: darwin, goarch: arm64 } + - { goos: windows, goarch: amd64 } + - { goos: windows, goarch: arm64 } + - { goos: android, goarch: arm64 } + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24 + - name: Build + run: GOOS=${{ matrix.target.goos }} GOARCH=${{ matrix.target.goarch }} go build -v ./... diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml deleted file mode 100644 index 7363393..0000000 --- a/.github/workflows/go-test.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Go Test - -on: - push: - branches: - - main - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.19 - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./... diff --git a/go.mod b/go.mod index 0c942e5..ff25c64 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/kkrypt0nn/spaceflake -go 1.19 +go 1.24