Repository: edwingeng/wuid
Branch: master
Commit: 29d94c466647
Files: 39
Total size: 77.2 KB
Directory structure:
gitextract_35st_wr2/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── callback/
│ └── wuid/
│ ├── coverage.sh
│ ├── vet.sh
│ ├── wuid.go
│ └── wuid_test.go
├── check.sh
├── go.mod
├── go.sum
├── internal/
│ ├── coverage.sh
│ ├── vet.sh
│ ├── wuid.go
│ └── wuid_test.go
├── mongo/
│ ├── docker-mongo-client.sh
│ ├── docker-mongo-server.sh
│ └── wuid/
│ ├── coverage.sh
│ ├── vet.sh
│ ├── wuid.go
│ └── wuid_test.go
├── mysql/
│ ├── db.sql
│ ├── docker-mysql-client.sh
│ ├── docker-mysql-server.sh
│ └── wuid/
│ ├── coverage.sh
│ ├── vet.sh
│ ├── wuid.go
│ └── wuid_test.go
├── redis/
│ ├── docker-redis-client.sh
│ ├── docker-redis-server.sh
│ ├── v8/
│ │ └── wuid/
│ │ ├── coverage.sh
│ │ ├── vet.sh
│ │ ├── wuid.go
│ │ └── wuid_test.go
│ └── wuid/
│ ├── coverage.sh
│ ├── vet.sh
│ ├── wuid.go
│ └── wuid_test.go
└── wuid.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Misc
/.idea
/vendor
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2018-2022, Edwin Geng
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Overview
- `WUID` is a universal unique identifier generator.
- `WUID` is much faster than traditional UUID. Each `WUID` instance can even generate 100M unique identifiers in a single second.
- In the nutshell, `WUID` generates 64-bit integers in sequence. The high 28 bits are loaded from a data source. By now, Redis, MySQL, MongoDB and Callback are supported.
- The uniqueness is guaranteed as long as all `WUID` instances share a same data source or each group of them has a different section ID.
- `WUID` automatically renews the high 28 bits when the low 36 bits are about to run out.
- `WUID` is thread-safe, and lock free.
- Obfuscation is supported.
# Benchmarks
```
BenchmarkWUID 159393580 7.661 ns/op 0 B/op 0 allocs/op
BenchmarkRand 100000000 14.95 ns/op 0 B/op 0 allocs/op
BenchmarkTimestamp 164224915 7.359 ns/op 0 B/op 0 allocs/op
BenchmarkUUID_V1 23629536 43.42 ns/op 0 B/op 0 allocs/op
BenchmarkUUID_V2 29351550 43.96 ns/op 0 B/op 0 allocs/op
BenchmarkUUID_V3 4703044 254.2 ns/op 144 B/op 4 allocs/op
BenchmarkUUID_V4 5796310 210.0 ns/op 16 B/op 1 allocs/op
BenchmarkUUID_V5 4051291 310.7 ns/op 168 B/op 4 allocs/op
BenchmarkRedis 2996 38725 ns/op 160 B/op 5 allocs/op
BenchmarkSnowflake 1000000 2092 ns/op 0 B/op 0 allocs/op
BenchmarkULID 5660170 207.7 ns/op 16 B/op 1 allocs/op
BenchmarkXID 49639082 26.21 ns/op 0 B/op 0 allocs/op
BenchmarkShortID 1312386 922.2 ns/op 320 B/op 11 allocs/op
BenchmarkKsuid 19717675 59.79 ns/op 0 B/op 0 allocs/op
```
# Getting Started
``` bash
go get -u github.com/edwingeng/wuid
```
# Usages
### Redis
``` go
import "github.com/edwingeng/wuid/redis/v8/wuid"
newClient := func() (redis.UniversalClient, bool, error) {
var client redis.UniversalClient
// ...
return client, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromRedis(newClient, "wuid")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
```
### MySQL
``` go
import "github.com/edwingeng/wuid/mysql/wuid"
openDB := func() (*sql.DB, bool, error) {
var db *sql.DB
// ...
return db, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromMysql(openDB, "wuid")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
```
### MongoDB
``` go
import "github.com/edwingeng/wuid/mongo/wuid"
newClient := func() (*mongo.Client, bool, error) {
var client *mongo.Client
// ...
return client, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromMongo(newClient, "test", "wuid", "default")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
```
### Callback
``` go
import "github.com/edwingeng/wuid/callback/wuid"
callback := func() (int64, func(), error) {
var h28 int64
// ...
return h28, nil, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28WithCallback(callback)
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
```
# Mysql Table Creation
``` sql
CREATE TABLE IF NOT EXISTS `wuid` (
`h` int(10) NOT NULL AUTO_INCREMENT,
`x` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`x`),
UNIQUE KEY `h` (`h`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
```
# Options
- `WithSection` brands a section ID on each generated number. A section ID must be in between [0, 7].
- `WithStep` sets the step and the floor for each generated number.
- `WithObfuscation` enables number obfuscation.
# Attentions
It is highly recommended to pass a logger to `wuid.NewWUID` and keep an eye on the warnings that include "renew failed". It indicates that the low 36 bits are about to run out in hours to hundreds of hours, and the renewal program failed for some reason. `WUID` will make many renewal attempts until succeeded.
# Special thanks
- [dustinfog](https://github.com/dustinfog)
# Ports
- swift - https://github.com/ekscrypto/SwiftWUID
================================================
FILE: callback/wuid/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: callback/wuid/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: callback/wuid/wuid.go
================================================
package wuid
import (
"errors"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
)
// WUID is an extremely fast universal unique identifier generator.
type WUID struct {
w internal.WUID
}
// NewWUID creates a new WUID instance.
func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
return &WUID{w: *internal.NewWUID(name, logger, opts...)}
}
// Next returns a unique identifier.
func (w *WUID) Next() int64 {
return w.w.Next()
}
type H28Callback func() (h28 int64, cleanUp func(), err error)
// LoadH28WithCallback invokes cb to acquire a number. The number is used as the high 28 bits
// of all generated numbers. In addition, cb is saved for future renewal.
func (w *WUID) LoadH28WithCallback(cb H28Callback) error {
if cb == nil {
return errors.New("cb cannot be nil")
}
h28, cleanUp, err := cb()
if err != nil {
return err
}
if cleanUp != nil {
defer cleanUp()
}
if err = w.w.VerifyH28(h28); err != nil {
return err
}
w.w.Reset(h28 << 36)
w.w.Infof("<wuid> new h28: %d. name: %s", h28, w.w.Name)
w.w.Lock()
defer w.w.Unlock()
if w.w.Renew != nil {
return nil
}
w.w.Renew = func() error {
return w.LoadH28WithCallback(cb)
}
return nil
}
// RenewNow reacquires the high 28 bits immediately.
func (w *WUID) RenewNow() error {
return w.w.RenewNow()
}
type Option = internal.Option
// WithH28Verifier adds an extra verifier for the high 28 bits.
func WithH28Verifier(cb func(h28 int64) error) Option {
return internal.WithH28Verifier(cb)
}
// WithSection brands a section ID on each generated number. A section ID must be in between [0, 7].
func WithSection(section int8) Option {
return internal.WithSection(section)
}
// WithStep sets the step and the floor for each generated number.
func WithStep(step int64, floor int64) Option {
return internal.WithStep(step, floor)
}
// WithObfuscation enables number obfuscation.
func WithObfuscation(seed int) Option {
return internal.WithObfuscation(seed)
}
================================================
FILE: callback/wuid/wuid_test.go
================================================
package wuid
import (
"errors"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"math/rand"
"strings"
"sync/atomic"
"testing"
"time"
)
var (
dumb = slog.NewDumbLogger()
)
func TestWUID_LoadH28WithCallback_Error(t *testing.T) {
w := NewWUID("alpha", dumb)
err1 := w.LoadH28WithCallback(nil)
if err1 == nil {
t.Fatal("LoadH28WithCallback should fail when cb is nil")
}
err2 := w.LoadH28WithCallback(func() (int64, func(), error) {
return 0, nil, errors.New("foo")
})
if err2 == nil {
t.Fatal("LoadH28WithCallback should fail when cb returns an error")
}
err3 := w.LoadH28WithCallback(func() (int64, func(), error) {
return 0, nil, nil
})
if err3 == nil {
t.Fatal("LoadH28WithCallback should fail when cb returns an invalid h28")
}
}
func TestWUID_LoadH28WithCallback(t *testing.T) {
var h28, counter int64
done := func() {
counter++
}
cb := func() (int64, func(), error) {
return atomic.AddInt64(&h28, 1), done, nil
}
w := NewWUID("alpha", dumb)
err := w.LoadH28WithCallback(cb)
if err != nil {
t.Fatal(err)
}
for i := 1; i < 1000; i++ {
if err := w.RenewNow(); err != nil {
t.Fatal(err)
}
v := (int64(i) + 1) << 36
if atomic.LoadInt64(&w.w.N) != v {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), v, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
if counter != 1000 {
t.Fatalf("the callback done do not work as expected. counter: %d", counter)
}
}
func TestWUID_LoadH28WithCallback_Section(t *testing.T) {
var h28 int64
cb := func() (int64, func(), error) {
return atomic.AddInt64(&h28, 1), nil, nil
}
w := NewWUID("alpha", dumb, WithSection(1))
for i := 0; i < 1000; i++ {
err := w.LoadH28WithCallback(cb)
if err != nil {
t.Fatal(err)
}
v := (int64(i) + 1 + 0x1000000) << 36
if atomic.LoadInt64(&w.w.N) != v {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), v, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
}
func TestWUID_LoadH28WithCallback_Same(t *testing.T) {
cb := func() (int64, func(), error) {
return 100, nil, nil
}
w1 := NewWUID("alpha", dumb)
_ = w1.LoadH28WithCallback(cb)
if err := w1.LoadH28WithCallback(cb); err == nil {
t.Fatal("LoadH28WithCallback should return an error")
}
w2 := NewWUID("alpha", dumb, WithSection(1))
_ = w2.LoadH28WithCallback(cb)
if err := w2.LoadH28WithCallback(cb); err == nil {
t.Fatal("LoadH28WithCallback should return an error")
}
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second {
if atomic.LoadInt64(&w.w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Renew(t *testing.T) {
w := NewWUID("alpha", slog.NewScavenger())
err := w.LoadH28WithCallback(func() (h28 int64, clean func(), err error) {
return (atomic.LoadInt64(&w.w.N) >> 36) + 1, nil, nil
})
if err != nil {
t.Fatal(err)
}
h28 := atomic.LoadInt64(&w.w.N) >> 36
atomic.StoreInt64(&w.w.N, (h28<<36)|internal.Bye)
n1a := w.Next()
if n1a>>36 != h28 {
t.Fatal(`n1a>>36 != h28`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != (h28+1)<<36+1 {
t.Fatal(`n1b != (h28+1)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+1)<<36)|internal.Bye)
n2a := w.Next()
if n2a>>36 != h28+1 {
t.Fatal(`n2a>>36 != h28+1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != (h28+2)<<36+1 {
t.Fatal(`n2b != (h28+2)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)|internal.Bye)
n3a := w.Next()
if n3a>>36 != h28+2 {
t.Fatal(`n3a>>36 != h28+2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != (h28+3)<<36+1 {
t.Fatal(`n3b != (h28+3)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)+internal.Bye+1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3`)
}
var num int
sc := w.w.Logger.(*slog.Scavenger)
sc.Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func Example() {
callback := func() (int64, func(), error) {
var h28 int64
// ...
return h28, nil, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28WithCallback(callback)
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
}
================================================
FILE: check.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
find . -name coverage.sh | xargs -n 1 bash -c \
'cmp --silent internal/coverage.sh "$0" || echo "files are different. file: $0"'
find . -name vet.sh | xargs -n 1 bash -c \
'cmp --silent internal/vet.sh "$0" || echo "files are different. file: $0"'
================================================
FILE: go.mod
================================================
module github.com/edwingeng/wuid
go 1.18
require (
github.com/edwingeng/slog v0.0.0-20221027170832-482f0dfb6247
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0
go.mongodb.org/mongo-driver v1.10.2
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/edwingeng/slog v0.0.0-20221027170832-482f0dfb6247 h1:1Vb/cbeFfMh9q+CxEMLSJlHciX9x3JHdaNyHlvhGxhk=
github.com/edwingeng/slog v0.0.0-20221027170832-482f0dfb6247/go.mod h1:mfngKiTrPWlUpIkQjkVzlumITH8chNhxsAiMklqH4Bo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.2 h1:4Wk3cnqOrQCn0P92L3/mmurMxzdvWWs5J9jinAVKD+k=
go.mongodb.org/mongo-driver v1.10.2/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: internal/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: internal/wuid.go
================================================
package internal
import (
"errors"
"fmt"
"github.com/edwingeng/slog"
"sync"
"sync/atomic"
)
const (
// PanicValue indicates when Next starts to panic.
PanicValue int64 = ((1 << 36) * 96 / 100) & ^1023
// CriticalValue indicates when to renew the high 28 bits.
CriticalValue int64 = ((1 << 36) * 80 / 100) & ^1023
// RenewIntervalMask indicates the 'time' between two renewal attempts.
RenewIntervalMask int64 = 0x20000000 - 1
)
const (
Bye = ((CriticalValue + RenewIntervalMask) & ^RenewIntervalMask) - 1
)
const (
H28Mask = 0x07FFFFFF << 36
L36Mask = 0x0FFFFFFFFF
)
type WUID struct {
N int64
Step int64
Floor int64
Flags int8
Obfuscation bool
Monolithic bool
ObfuscationMask int64
Section int64
slog.Logger
Name string
H28Verifier func(h28 int64) error
sync.Mutex
Renew func() error
Stats struct {
NumRenewAttempts int64
NumRenewed int64
}
}
func NewWUID(name string, logger slog.Logger, opts ...Option) (w *WUID) {
w = &WUID{Step: 1, Name: name, Monolithic: true}
if logger != nil {
w.Logger = logger
} else {
w.Logger = slog.NewDevelopmentConfig().MustBuild()
}
for _, opt := range opts {
opt(w)
}
if !w.Obfuscation || w.Floor == 0 {
return
}
ones := w.Step - 1
w.ObfuscationMask |= ones
return
}
func (w *WUID) Next() int64 {
v1 := atomic.AddInt64(&w.N, w.Step)
v2 := v1 & L36Mask
if v2 >= PanicValue {
panicValue := v1&H28Mask | PanicValue
atomic.CompareAndSwapInt64(&w.N, v1, panicValue)
panic(fmt.Errorf("the low 36 bits are about to run out"))
}
if v2 >= CriticalValue && v2&RenewIntervalMask == 0 {
go renewImpl(w)
}
switch w.Flags {
case 0:
return v1
case 1:
x := v1 ^ w.ObfuscationMask
r := v1&H28Mask | x&L36Mask
return r
case 2:
r := v1 / w.Floor * w.Floor
return r
case 3:
x := v1 ^ w.ObfuscationMask
q := v1&H28Mask | x&L36Mask
r := q / w.Floor * w.Floor
return r
default:
panic("impossible")
}
}
func renewImpl(w *WUID) {
defer func() {
atomic.AddInt64(&w.Stats.NumRenewAttempts, 1)
}()
defer func() {
if r := recover(); r != nil {
w.Warnf("<wuid> panic, renew failed. name: %s, reason: %+v", w.Name, r)
}
}()
err := w.RenewNow()
if err != nil {
w.Warnf("<wuid> renew failed. name: %s, reason: %+v", w.Name, err)
} else {
w.Infof("<wuid> renew succeeded. name: %s", w.Name)
atomic.AddInt64(&w.Stats.NumRenewed, 1)
}
}
func (w *WUID) RenewNow() error {
w.Lock()
f := w.Renew
w.Unlock()
return f()
}
func (w *WUID) Reset(n int64) {
if n < 0 {
panic("n cannot be negative")
}
if n&L36Mask >= PanicValue {
panic("n is too old")
}
if w.Monolithic {
// Empty
} else {
const L60Mask = 0x0FFFFFFFFFFFFFFF
n = n&L60Mask | w.Section
}
if w.Floor > 1 {
if n&(w.Step-1) == 0 {
atomic.StoreInt64(&w.N, n)
} else {
atomic.StoreInt64(&w.N, n&^(w.Step-1)+w.Step)
}
} else {
atomic.StoreInt64(&w.N, n)
}
}
func (w *WUID) VerifyH28(h28 int64) error {
if h28 <= 0 {
return errors.New("h28 must be positive")
}
if w.Monolithic {
if h28 > 0x07FFFFFF {
return errors.New("h28 should not exceed 0x07FFFFFF")
}
} else {
if h28 > 0x00FFFFFF {
return errors.New("h28 should not exceed 0x00FFFFFF")
}
}
current := atomic.LoadInt64(&w.N) >> 36
if w.Monolithic {
if h28 == current {
return fmt.Errorf("h28 should be a different value other than %d", h28)
}
} else {
if h28 == current&0x00FFFFFF {
return fmt.Errorf("h28 should be a different value other than %d", h28)
}
}
if w.H28Verifier != nil {
if err := w.H28Verifier(h28); err != nil {
return err
}
}
return nil
}
type Option func(w *WUID)
func WithH28Verifier(cb func(h28 int64) error) Option {
return func(w *WUID) {
w.H28Verifier = cb
}
}
func WithSection(section int8) Option {
if section < 0 || section > 7 {
panic("section must be in between [0, 7]")
}
return func(w *WUID) {
w.Monolithic = false
w.Section = int64(section) << 60
}
}
func WithStep(step int64, floor int64) Option {
switch step {
case 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024:
default:
panic("the step must be one of these values: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024")
}
if floor != 0 && (floor < 0 || floor >= step) {
panic(fmt.Errorf("floor must be in between [0, %d)", step))
}
return func(w *WUID) {
if w.Step != 1 {
panic("a second WithStep detected")
}
w.Step = step
if floor >= 2 {
w.Floor = floor
w.Flags |= 2
}
}
}
func WithObfuscation(seed int) Option {
if seed == 0 {
panic("seed cannot be zero")
}
return func(w *WUID) {
w.Obfuscation = true
x := uint64(seed)
x = (x ^ (x >> 30)) * uint64(0xbf58476d1ce4e5b9)
x = (x ^ (x >> 27)) * uint64(0x94d049bb133111eb)
x = (x ^ (x >> 31)) & 0x7FFFFFFFFFFFFFFF
w.ObfuscationMask = int64(x)
w.Flags |= 1
}
}
================================================
FILE: internal/wuid_test.go
================================================
package internal
import (
"errors"
"github.com/edwingeng/slog"
"math/rand"
"sort"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
func (w *WUID) Scavenger() *slog.Scavenger {
return w.Logger.(*slog.Scavenger)
}
func TestWUID_Next(t *testing.T) {
for i := 0; i < 100; i++ {
w := NewWUID("alpha", nil)
w.Reset(int64(i+1) << 36)
v := atomic.LoadInt64(&w.N)
for j := 0; j < 100; j++ {
v++
if id := w.Next(); id != v {
t.Fatalf("the id is %d, while it should be %d", id, v)
}
}
}
}
func TestWUID_Next_Concurrent(t *testing.T) {
w := NewWUID("alpha", nil)
var mu sync.Mutex
const N1 = 100
const N2 = 100
a := make([]int64, 0, N1*N2)
var wg sync.WaitGroup
for i := 0; i < N1; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < N2; j++ {
id := w.Next()
mu.Lock()
a = append(a, id)
mu.Unlock()
}
}()
}
wg.Wait()
sort.Slice(a, func(i, j int) bool {
return a[i] < a[j]
})
for i := 0; i < N1*N2-1; i++ {
if a[i] == a[i+1] {
t.Fatalf("duplication detected")
}
}
}
func TestWUID_Next_Panic(t *testing.T) {
const total = 100
w := NewWUID("alpha", nil)
atomic.StoreInt64(&w.N, PanicValue)
ch := make(chan int64, total)
for i := 0; i < total; i++ {
go func() {
defer func() {
if r := recover(); r != nil {
ch <- 0
}
}()
ch <- w.Next()
}()
}
for i := 0; i < total; i++ {
v := <-ch
if v != 0 {
t.Fatal("something is wrong with Next()")
}
}
}
func waitUntilNumRenewAttemptsReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second {
if atomic.LoadInt64(&w.Stats.NumRenewAttempts) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second {
if atomic.LoadInt64(&w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Renew(t *testing.T) {
w := NewWUID("alpha", slog.NewScavenger())
w.Renew = func() error {
w.Reset(((atomic.LoadInt64(&w.N) >> 36) + 1) << 36)
return nil
}
w.Reset(Bye)
n1a := w.Next()
if n1a>>36 != 0 {
t.Fatal(`n1a>>36 != 0`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != 1<<36+1 {
t.Fatal(`n1b != 1<<36+1`)
}
w.Reset(1<<36 | Bye)
n2a := w.Next()
if n2a>>36 != 1 {
t.Fatal(`n2a>>36 != 1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != 2<<36+1 {
t.Fatal(`n2b != 2<<36+1`)
}
w.Reset(2<<36 | Bye + RenewIntervalMask + 1)
n3a := w.Next()
if n3a>>36 != 2 {
t.Fatal(`n3a>>36 != 2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != 3<<36+1 {
t.Fatal(`n3b != 3<<36+1`)
}
w.Reset(Bye + 1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 3`)
}
var num int
w.Scavenger().Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func TestWUID_Renew_Error(t *testing.T) {
w := NewWUID("alpha", slog.NewScavenger())
w.Renew = func() error {
return errors.New("foo")
}
w.Reset((1 >> 36 << 36) | Bye)
w.Next()
waitUntilNumRenewAttemptsReaches(t, w, 1)
w.Next()
w.Reset((2 >> 36 << 36) | Bye)
w.Next()
waitUntilNumRenewAttemptsReaches(t, w, 2)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 2 {
t.Fatal(`atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 2`)
}
if atomic.LoadInt64(&w.Stats.NumRenewed) != 0 {
t.Fatal(`atomic.LoadInt64(&w.Stats.NumRenewed) != 0`)
}
var num int
w.Scavenger().Filter(func(level, msg string) bool {
if level == slog.LevelWarn && strings.Contains(msg, "renew failed") && strings.Contains(msg, "foo") {
num++
}
return true
})
if num != 2 {
t.Fatal(`num != 2`)
}
}
func TestWUID_Renew_Panic(t *testing.T) {
w := NewWUID("alpha", slog.NewScavenger())
w.Renew = func() error {
panic("foo")
}
w.Reset((1 >> 36 << 36) | Bye)
w.Next()
waitUntilNumRenewAttemptsReaches(t, w, 1)
w.Next()
w.Reset((2 >> 36 << 36) | Bye)
w.Next()
waitUntilNumRenewAttemptsReaches(t, w, 2)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 2 {
t.Fatal(`atomic.LoadInt64(&w.Stats.NumRenewAttempts) != 2`)
}
if atomic.LoadInt64(&w.Stats.NumRenewed) != 0 {
t.Fatal(`atomic.LoadInt64(&w.Stats.NumRenewed) != 0`)
}
var num int
w.Scavenger().Filter(func(level, msg string) bool {
if level == slog.LevelWarn && strings.Contains(msg, "renew failed") && strings.Contains(msg, "foo") {
num++
}
return true
})
if num != 2 {
t.Fatal(`num != 2`)
}
}
func TestWUID_Step(t *testing.T) {
const step = 16
w := NewWUID("alpha", slog.NewScavenger(), WithStep(step, 0))
w.Reset(17 << 36)
w.Renew = func() error {
w.Reset(((atomic.LoadInt64(&w.N) >> 36) + 1) << 36)
return nil
}
for i := int64(1); i < 100; i++ {
if w.Next()&L36Mask != step*i {
t.Fatal("w.Next()&L36Mask != step*i")
}
}
n1 := w.Next()
w.Reset(((n1 >> 36 << 36) | Bye) & ^(step - 1))
w.Next()
waitUntilNumRenewedReaches(t, w, 1)
n2 := w.Next()
w.Reset(((n2 >> 36 << 36) | Bye) & ^(step - 1))
w.Next()
waitUntilNumRenewedReaches(t, w, 2)
n3 := w.Next()
if n2>>36-n1>>36 != 1 || n3>>36-n2>>36 != 1 {
t.Fatalf("the renew mechanism does not work as expected: %x, %x, %x", n1>>36, n2>>36, n3>>36)
}
var num int
w.Scavenger().Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 2 {
t.Fatal(`num != 2`)
}
func() {
defer func() {
_ = recover()
}()
NewWUID("alpha", nil, WithStep(5, 0))
t.Fatal("WithStep should have panicked")
}()
}
func TestWUID_Floor(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().Unix()))
allSteps := []int64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}
for loop := 0; loop < 10000; loop++ {
step := allSteps[r.Intn(len(allSteps))]
var floor = r.Int63n(step)
w := NewWUID("alpha", slog.NewScavenger(), WithStep(step, floor))
if floor < 2 {
if w.Flags != 0 {
t.Fatal(`w.Flags != 0`)
}
} else {
if w.Flags != 2 {
t.Fatal(`w.Flags != 2`)
}
}
w.Reset(r.Int63n(100) << 36)
baseValue := atomic.LoadInt64(&w.N)
for i := int64(1); i < 100; i++ {
x := w.Next()
if floor != 0 {
if reminder := x % floor; reminder != 0 {
t.Fatal("reminder != 0")
}
}
if x <= baseValue+i*step-step || x > baseValue+i*step {
t.Fatal("x <= baseValue+i*step-step || x > baseValue+i*step")
}
}
}
func() {
defer func() {
_ = recover()
}()
NewWUID("alpha", nil, WithStep(1024, 2000))
t.Fatal("WithStep should have panicked")
}()
func() {
defer func() {
_ = recover()
}()
NewWUID("alpha", nil, WithStep(1024, 0), WithStep(128, 0))
t.Fatal("WithStep should have panicked")
}()
}
func TestWUID_VerifyH28(t *testing.T) {
w1 := NewWUID("alpha", nil)
w1.Reset(H28Mask)
if err := w1.VerifyH28(100); err != nil {
t.Fatalf("VerifyH28 does not work as expected. n: 100, error: %s", err)
}
if err := w1.VerifyH28(0); err == nil {
t.Fatalf("VerifyH28 does not work as expected. n: 0")
}
if err := w1.VerifyH28(0x08000000); err == nil {
t.Fatalf("VerifyH28 does not work as expected. n: 0x08000000")
}
if err := w1.VerifyH28(0x07FFFFFF); err == nil {
t.Fatalf("VerifyH28 does not work as expected. n: 0x07FFFFFF")
}
w2 := NewWUID("alpha", nil, WithSection(1))
w2.Reset(H28Mask)
if err := w2.VerifyH28(100); err != nil {
t.Fatalf("VerifyH28 does not work as expected. section: 1, n: 100, error: %s", err)
}
if err := w2.VerifyH28(0); err == nil {
t.Fatalf("VerifyH28 does not work as expected. section: 1, n: 0")
}
if err := w2.VerifyH28(0x01000000); err == nil {
t.Fatalf("VerifyH28 does not work as expected. section: 1, n: 0x01000000")
}
if err := w2.VerifyH28(0x00FFFFFF); err == nil {
t.Fatalf("VerifyH28 does not work as expected. section: 1, n: 0x00FFFFFF")
}
}
func TestWithSection_Panic(t *testing.T) {
for i := -100; i <= 100; i++ {
func(j int8) {
defer func() {
_ = recover()
}()
WithSection(j)
if j >= 8 {
t.Fatalf("WithSection should only accept the values in [0, 7]. j: %d", j)
}
}(int8(i))
}
}
func TestWithSection_Reset(t *testing.T) {
for i := 0; i < 28; i++ {
n := int64(1) << (uint(i) + 36)
func() {
defer func() {
if r := recover(); r != nil {
if i != 27 {
t.Fatal(r)
}
}
}()
for j := int8(1); j < 8; j++ {
w := NewWUID("alpha", nil, WithSection(j))
w.Reset(n)
v := atomic.LoadInt64(&w.N)
if v>>60 != int64(j) {
t.Fatalf("w.Section does not work as expected. w.N: %x, n: %x, i: %d, j: %d", v, n, i, j)
}
}
}()
}
func() {
defer func() {
_ = recover()
}()
w := NewWUID("alpha", nil)
w.Reset((1 << 36) | PanicValue)
t.Fatal("Reset should have panicked")
}()
}
func TestWithH28Verifier(t *testing.T) {
w := NewWUID("alpha", nil, WithH28Verifier(func(h28 int64) error {
if h28 >= 20 {
return errors.New("bomb")
}
return nil
}))
if err := w.VerifyH28(10); err != nil {
t.Fatal("the H28Verifier should not return error")
}
if err := w.VerifyH28(20); err == nil || err.Error() != "bomb" {
t.Fatal("the H28Verifier was not called")
}
}
//gocyclo:ignore
func TestWithObfuscation(t *testing.T) {
w1 := NewWUID("alpha", nil, WithObfuscation(1))
if w1.Flags != 1 {
t.Fatal(`w1.Flags != 1`)
}
if w1.ObfuscationMask == 0 {
t.Fatal(`w1.ObfuscationMask == 0`)
}
w1.Reset(1 << 36)
for i := 1; i < 100; i++ {
v := w1.Next()
if v&H28Mask != 1<<36 {
t.Fatal(`v&H28Mask != 1<<36`)
}
tmp := v ^ w1.ObfuscationMask
if tmp&L36Mask != int64(i) {
t.Fatal(`tmp&L36Mask != int64(i)`)
}
}
w2 := NewWUID("alpha", nil, WithObfuscation(1), WithStep(128, 100))
if w2.Flags != 3 {
t.Fatal(`w2.Flags != 3`)
}
if w2.ObfuscationMask == 0 {
t.Fatal(`w2.ObfuscationMask == 0`)
}
w2.Reset(1 << 36)
for i := 1; i < 100; i++ {
v := w2.Next()
if v%w2.Floor != 0 {
t.Fatal(`v%w2.Floor != 0`)
}
if v&H28Mask != 1<<36 {
t.Fatal(`v&H28Mask != 1<<36`)
}
tmp := v ^ w2.ObfuscationMask
if tmp&L36Mask&^(w2.Step-1) != w2.Step*int64(i) {
t.Fatal(`tmp&L36Mask&^(w2.Step-1) != w2.Step*int64(i)`)
}
}
w3 := NewWUID("alpha", nil, WithObfuscation(1), WithStep(1024, 659))
if w3.Flags != 3 {
t.Fatal(`w3.Flags != 3`)
}
if w3.ObfuscationMask == 0 {
t.Fatal(`w3.ObfuscationMask == 0`)
}
w3.Reset(1<<36 + 1)
for i := 1; i < 100; i++ {
v := w3.Next()
if v%w3.Floor != 0 {
t.Fatal(`v%w3.Floor != 0`)
}
if v&H28Mask != 1<<36 {
t.Fatal(`v&H28Mask != 1<<36`)
}
tmp := v ^ w3.ObfuscationMask
if tmp&L36Mask&^(w3.Step-1) != w3.Step*int64(i+1) {
t.Fatal(`tmp&L36Mask&^(w3.Step-1) != w3.Step*int64(i+1)`)
}
}
func() {
defer func() {
_ = recover()
}()
NewWUID("alpha", nil, WithObfuscation(0))
t.Fatal("WithObfuscation should have panicked")
}()
}
================================================
FILE: mongo/docker-mongo-client.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run -it --rm mongo mongosh host.docker.internal
================================================
FILE: mongo/docker-mongo-server.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run --name mongo-server -p 27017:27017 -d mongo
================================================
FILE: mongo/wuid/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: mongo/wuid/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: mongo/wuid/wuid.go
================================================
package wuid
import (
"context"
"errors"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"time"
)
// WUID is an extremely fast universal unique identifier generator.
type WUID struct {
w *internal.WUID
}
// NewWUID creates a new WUID instance.
func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
return &WUID{w: internal.NewWUID(name, logger, opts...)}
}
// Next returns a unique identifier.
func (w *WUID) Next() int64 {
return w.w.Next()
}
type NewClient func() (client *mongo.Client, autoDisconnect bool, err error)
// LoadH28FromMongo adds 1 to a specific number in MongoDB and fetches its new value.
// The new value is used as the high 28 bits of all generated numbers. In addition, all the
// arguments passed in are saved for future renewal.
func (w *WUID) LoadH28FromMongo(newClient NewClient, dbName, coll, docID string) error {
if len(dbName) == 0 {
return errors.New("dbName cannot be empty")
}
if len(coll) == 0 {
return errors.New("coll cannot be empty")
}
if len(docID) == 0 {
return errors.New("docID cannot be empty")
}
client, autoDisconnect, err := newClient()
if err != nil {
return err
}
defer func() {
if autoDisconnect {
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second*5)
defer cancel2()
_ = client.Disconnect(ctx2)
}
}()
ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second*5)
defer cancel1()
if err := client.Ping(ctx1, readpref.Primary()); err != nil {
return err
}
collOpts := &options.CollectionOptions{
ReadConcern: readconcern.Majority(),
WriteConcern: writeconcern.New(writeconcern.WMajority()),
ReadPreference: readpref.Primary(),
}
var doc struct {
N int32
}
filter := bson.D{
{Key: "_id", Value: docID},
}
update := bson.D{
{
Key: "$inc",
Value: bson.D{
{Key: "n", Value: int32(1)},
},
},
}
var findOneAndUpdateOptions options.FindOneAndUpdateOptions
findOneAndUpdateOptions.SetUpsert(true).SetReturnDocument(options.After)
c := client.Database(dbName).Collection(coll, collOpts)
err = c.FindOneAndUpdate(ctx1, filter, update, &findOneAndUpdateOptions).Decode(&doc)
if err != nil {
return err
}
h28 := int64(doc.N)
if err = w.w.VerifyH28(h28); err != nil {
return err
}
w.w.Reset(h28 << 36)
w.w.Logger.Infof("<wuid> new h28: %d. name: %s", h28, w.w.Name)
w.w.Lock()
defer w.w.Unlock()
if w.w.Renew != nil {
return nil
}
w.w.Renew = func() error {
return w.LoadH28FromMongo(newClient, dbName, coll, docID)
}
return nil
}
// RenewNow reacquires the high 28 bits immediately.
func (w *WUID) RenewNow() error {
return w.w.RenewNow()
}
type Option = internal.Option
// WithH28Verifier adds an extra verifier for the high 28 bits.
func WithH28Verifier(cb func(h28 int64) error) Option {
return internal.WithH28Verifier(cb)
}
// WithSection brands a section ID on each generated number. A section ID must be in between [0, 7].
func WithSection(section int8) Option {
return internal.WithSection(section)
}
// WithStep sets the step and the floor for each generated number.
func WithStep(step int64, floor int64) Option {
return internal.WithStep(step, floor)
}
// WithObfuscation enables number obfuscation.
func WithObfuscation(seed int) Option {
return internal.WithObfuscation(seed)
}
================================================
FILE: mongo/wuid/wuid_test.go
================================================
package wuid
import (
"context"
"errors"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"math/rand"
"strings"
"sync/atomic"
"testing"
"time"
)
var (
dumb = slog.NewDumbLogger()
)
var (
cfg struct {
addr string
dbName string
coll string
docID string
}
)
func init() {
cfg.addr = "127.0.0.1:27017"
cfg.dbName = "test"
cfg.coll = "wuid"
cfg.docID = "default"
}
func connectMongodb() (*mongo.Client, error) {
ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second*3)
defer cancel1()
uri := fmt.Sprintf("mongodb://%s", cfg.addr)
return mongo.Connect(ctx1, options.Client().ApplyURI(uri))
}
func TestWUID_LoadH28FromMongo(t *testing.T) {
newClient := func() (*mongo.Client, bool, error) {
client, err := connectMongodb()
return client, true, err
}
w := NewWUID(cfg.docID, dumb)
err := w.LoadH28FromMongo(newClient, cfg.dbName, cfg.coll, cfg.docID)
if err != nil {
t.Fatal(err)
}
initial := atomic.LoadInt64(&w.w.N)
for i := 1; i < 100; i++ {
if err := w.RenewNow(); err != nil {
t.Fatal(err)
}
expected := ((initial >> 36) + int64(i)) << 36
if atomic.LoadInt64(&w.w.N) != expected {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), expected, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
}
func TestWUID_LoadH28FromMongo_Error(t *testing.T) {
w := NewWUID(cfg.docID, dumb)
if w.LoadH28FromMongo(nil, "", cfg.coll, cfg.docID) == nil {
t.Fatal("dbName is not properly checked")
}
if w.LoadH28FromMongo(nil, cfg.dbName, "", cfg.docID) == nil {
t.Fatal("coll is not properly checked")
}
if w.LoadH28FromMongo(nil, cfg.dbName, cfg.coll, "") == nil {
t.Fatal("docID is not properly checked")
}
newErrorClient := func() (*mongo.Client, bool, error) {
return nil, true, errors.New("beta")
}
if w.LoadH28FromMongo(newErrorClient, cfg.dbName, cfg.coll, cfg.docID) == nil {
t.Fatal(`w.LoadH28FromMongo(newErrorClient, cfg.dbName, cfg.coll, cfg.docID) == nil`)
}
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second*3 {
if atomic.LoadInt64(&w.w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Renew(t *testing.T) {
client, err := connectMongodb()
if err != nil {
t.Fatal(err)
}
newClient := func() (*mongo.Client, bool, error) {
return client, false, err
}
w := NewWUID(cfg.docID, slog.NewScavenger())
err = w.LoadH28FromMongo(newClient, cfg.dbName, cfg.coll, cfg.docID)
if err != nil {
t.Fatal(err)
}
h28 := atomic.LoadInt64(&w.w.N) >> 36
atomic.StoreInt64(&w.w.N, (h28<<36)|internal.Bye)
n1a := w.Next()
if n1a>>36 != h28 {
t.Fatal(`n1a>>36 != h28`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != (h28+1)<<36+1 {
t.Fatal(`n1b != (h28+1)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+1)<<36)|internal.Bye)
n2a := w.Next()
if n2a>>36 != h28+1 {
t.Fatal(`n2a>>36 != h28+1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != (h28+2)<<36+1 {
t.Fatal(`n2b != (h28+2)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)|internal.Bye)
n3a := w.Next()
if n3a>>36 != h28+2 {
t.Fatal(`n3a>>36 != h28+2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != (h28+3)<<36+1 {
t.Fatal(`n3b != (h28+3)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)+internal.Bye+1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3`)
}
var num int
sc := w.w.Logger.(*slog.Scavenger)
sc.Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func Example() {
newClient := func() (*mongo.Client, bool, error) {
var client *mongo.Client
// ...
return client, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromMongo(newClient, "test", "wuid", "default")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
}
================================================
FILE: mysql/db.sql
================================================
CREATE DATABASE IF NOT EXISTS test;
use test;
CREATE TABLE IF NOT EXISTS `wuid` (
`h` int(10) NOT NULL AUTO_INCREMENT,
`x` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`x`),
UNIQUE KEY `h` (`h`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
================================================
FILE: mysql/docker-mysql-client.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run -it --rm mysql mysql -h host.docker.internal -u root -phello test
================================================
FILE: mysql/docker-mysql-server.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=hello -d mysql
[[ $? -ne 0 ]] && exit 1
printImportantMessage "It may take quite a few seconds to get ready."
for ((i=0;i<1000;i++)); do
docker run -it --rm mysql mysqladmin ping -h host.docker.internal --silent
[[ $? -eq 0 ]] && echo "Ready." && break
echo "Waiting $((i+1))..."
sleep 1
done
sleep 1
docker run -v `pwd`/db.sql:/tmp/db.sql -it --rm mysql /bin/bash -c 'cat /tmp/db.sql | mysql -h host.docker.internal -u root -phello'
[[ $? -ne 0 ]] && exit 1
echo "Job done."
================================================
FILE: mysql/wuid/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: mysql/wuid/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: mysql/wuid/wuid.go
================================================
package wuid
import (
"database/sql"
"errors"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
_ "github.com/go-sql-driver/mysql"
)
// WUID is an extremely fast universal unique identifier generator.
type WUID struct {
w *internal.WUID
}
// NewWUID creates a new WUID instance.
func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
return &WUID{w: internal.NewWUID(name, logger, opts...)}
}
// Next returns a unique identifier.
func (w *WUID) Next() int64 {
return w.w.Next()
}
type OpenDB func() (client *sql.DB, autoClose bool, err error)
// LoadH28FromMysql adds 1 to a specific number in MySQL and fetches its new value.
// The new value is used as the high 28 bits of all generated numbers. In addition, all the
// arguments passed in are saved for future renewal.
func (w *WUID) LoadH28FromMysql(openDB OpenDB, table string) error {
if len(table) == 0 {
return errors.New("table cannot be empty")
}
db, autoClose, err := openDB()
if err != nil {
return err
}
defer func() {
if autoClose {
_ = db.Close()
}
}()
result, err := db.Exec(fmt.Sprintf("REPLACE INTO %s (x) VALUES (0)", table))
if err != nil {
return err
}
h28, err := result.LastInsertId()
if err != nil {
return err
}
if err = w.w.VerifyH28(h28); err != nil {
return err
}
w.w.Reset(h28 << 36)
w.w.Logger.Infof("<wuid> new h28: %d. name: %s", h28, w.w.Name)
w.w.Lock()
defer w.w.Unlock()
if w.w.Renew != nil {
return nil
}
w.w.Renew = func() error {
return w.LoadH28FromMysql(openDB, table)
}
return nil
}
// RenewNow reacquires the high 28 bits immediately.
func (w *WUID) RenewNow() error {
return w.w.RenewNow()
}
type Option = internal.Option
// WithH28Verifier adds an extra verifier for the high 28 bits.
func WithH28Verifier(cb func(h28 int64) error) Option {
return internal.WithH28Verifier(cb)
}
// WithSection brands a section ID on each generated number. A section ID must be in between [0, 7].
func WithSection(section int8) Option {
return internal.WithSection(section)
}
// WithStep sets the step and the floor for each generated number.
func WithStep(step int64, floor int64) Option {
return internal.WithStep(step, floor)
}
// WithObfuscation enables number obfuscation.
func WithObfuscation(seed int) Option {
return internal.WithObfuscation(seed)
}
================================================
FILE: mysql/wuid/wuid_test.go
================================================
package wuid
import (
"database/sql"
"errors"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
_ "github.com/go-sql-driver/mysql"
"math/rand"
"strings"
"sync/atomic"
"testing"
"time"
)
var (
dumb = slog.NewDumbLogger()
)
var (
cfg struct {
addr string
user string
pass string
dbName string
table string
}
)
func init() {
cfg.addr = "127.0.0.1:3306"
cfg.user = "root"
cfg.pass = "hello"
cfg.dbName = "test"
cfg.table = "wuid"
}
func connect() (*sql.DB, error) {
dsn := cfg.user
if len(cfg.pass) > 0 {
dsn += ":" + cfg.pass
}
dsn += "@tcp(" + cfg.addr + ")/" + cfg.dbName
return sql.Open("mysql", dsn)
}
func TestWUID_LoadH28FromMysql(t *testing.T) {
openDB := func() (*sql.DB, bool, error) {
db, err := connect()
return db, true, err
}
w := NewWUID("alpha", dumb)
err := w.LoadH28FromMysql(openDB, cfg.table)
if err != nil {
t.Fatal(err)
}
initial := atomic.LoadInt64(&w.w.N)
for i := 1; i < 100; i++ {
if err := w.RenewNow(); err != nil {
t.Fatal(err)
}
expected := ((initial >> 36) + int64(i)) << 36
if atomic.LoadInt64(&w.w.N) != expected {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), expected, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
}
func TestWUID_LoadH28FromMysql_Error(t *testing.T) {
w := NewWUID("alpha", dumb)
if w.LoadH28FromMysql(nil, "") == nil {
t.Fatal("table is not properly checked")
}
newErrorDB := func() (client *sql.DB, autoClose bool, err error) {
return nil, true, errors.New("beta")
}
if w.LoadH28FromMysql(newErrorDB, "beta") == nil {
t.Fatal(`w.LoadH28FromMysql(newErrorDB, "beta") == nil`)
}
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second*3 {
if atomic.LoadInt64(&w.w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Next_Renew(t *testing.T) {
db, err := connect()
if err != nil {
t.Fatal(err)
}
openDB := func() (*sql.DB, bool, error) {
return db, false, err
}
w := NewWUID("alpha", slog.NewScavenger())
err = w.LoadH28FromMysql(openDB, cfg.table)
if err != nil {
t.Fatal(err)
}
h28 := atomic.LoadInt64(&w.w.N) >> 36
atomic.StoreInt64(&w.w.N, (h28<<36)|internal.Bye)
n1a := w.Next()
if n1a>>36 != h28 {
t.Fatal(`n1a>>36 != h28`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != (h28+1)<<36+1 {
t.Fatal(`n1b != (h28+1)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+1)<<36)|internal.Bye)
n2a := w.Next()
if n2a>>36 != h28+1 {
t.Fatal(`n2a>>36 != h28+1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != (h28+2)<<36+1 {
t.Fatal(`n2b != (h28+2)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)|internal.Bye)
n3a := w.Next()
if n3a>>36 != h28+2 {
t.Fatal(`n3a>>36 != h28+2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != (h28+3)<<36+1 {
t.Fatal(`n3b != (h28+3)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)+internal.Bye+1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3`)
}
var num int
sc := w.w.Logger.(*slog.Scavenger)
sc.Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func Example() {
openDB := func() (*sql.DB, bool, error) {
var db *sql.DB
// ...
return db, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromMysql(openDB, "wuid")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
}
================================================
FILE: redis/docker-redis-client.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run -it --rm redis redis-cli -h host.docker.internal
================================================
FILE: redis/docker-redis-server.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
docker run --name redis-server -d -p 6379-6383:6379 redis
================================================
FILE: redis/v8/wuid/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: redis/v8/wuid/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: redis/v8/wuid/wuid.go
================================================
package wuid
import (
"context"
"errors"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"github.com/go-redis/redis/v8"
"time"
)
// WUID is an extremely fast universal unique identifier generator.
type WUID struct {
w *internal.WUID
}
// NewWUID creates a new WUID instance.
func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
return &WUID{w: internal.NewWUID(name, logger, opts...)}
}
// Next returns a unique identifier.
func (w *WUID) Next() int64 {
return w.w.Next()
}
type NewClient func() (client redis.UniversalClient, autoClose bool, err error)
// LoadH28FromRedis adds 1 to a specific number in Redis and fetches its new value.
// The new value is used as the high 28 bits of all generated numbers. In addition, all the
// arguments passed in are saved for future renewal.
func (w *WUID) LoadH28FromRedis(newClient NewClient, key string) error {
if len(key) == 0 {
return errors.New("key cannot be empty")
}
client, autoClose, err := newClient()
if err != nil {
return err
}
defer func() {
if autoClose {
_ = client.Close()
}
}()
ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second*5)
defer cancel1()
h28, err := client.Incr(ctx1, key).Result()
if err != nil {
return err
}
if err = w.w.VerifyH28(h28); err != nil {
return err
}
w.w.Reset(h28 << 36)
w.w.Logger.Infof("<wuid> new h28: %d. name: %s", h28, w.w.Name)
w.w.Lock()
defer w.w.Unlock()
if w.w.Renew != nil {
return nil
}
w.w.Renew = func() error {
return w.LoadH28FromRedis(newClient, key)
}
return nil
}
// RenewNow reacquires the high 28 bits immediately.
func (w *WUID) RenewNow() error {
return w.w.RenewNow()
}
type Option = internal.Option
// WithH28Verifier adds an extra verifier for the high 28 bits.
func WithH28Verifier(cb func(h28 int64) error) Option {
return internal.WithH28Verifier(cb)
}
// WithSection brands a section ID on each generated number. A section ID must be in between [0, 7].
func WithSection(section int8) Option {
return internal.WithSection(section)
}
// WithStep sets the step and the floor for each generated number.
func WithStep(step int64, floor int64) Option {
return internal.WithStep(step, floor)
}
// WithObfuscation enables number obfuscation.
func WithObfuscation(seed int) Option {
return internal.WithObfuscation(seed)
}
================================================
FILE: redis/v8/wuid/wuid_test.go
================================================
package wuid
import (
"errors"
"flag"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"github.com/go-redis/redis/v8"
"math/rand"
"strings"
"sync/atomic"
"testing"
"time"
)
var redisCluster = flag.Bool("cluster", false, "")
var (
dumb = slog.NewDumbLogger()
)
var (
cfg struct {
addrs []string
password string
key string
}
)
func init() {
cfg.addrs = []string{"127.0.0.1:6379", "127.0.0.1:6380", "127.0.0.1:6381"}
cfg.key = "v8:wuid"
}
func connect() redis.UniversalClient {
if *redisCluster {
return redis.NewClusterClient(&redis.ClusterOptions{
Addrs: cfg.addrs,
Password: cfg.password,
})
} else {
return redis.NewClient(&redis.Options{
Addr: cfg.addrs[0],
Password: cfg.password,
})
}
}
func TestWUID_LoadH28FromRedis(t *testing.T) {
newClient := func() (redis.UniversalClient, bool, error) {
return connect(), true, nil
}
w := NewWUID("alpha", dumb)
err := w.LoadH28FromRedis(newClient, cfg.key)
if err != nil {
t.Fatal(err)
}
initial := atomic.LoadInt64(&w.w.N)
for i := 1; i < 100; i++ {
if err := w.RenewNow(); err != nil {
t.Fatal(err)
}
expected := ((initial >> 36) + int64(i)) << 36
if atomic.LoadInt64(&w.w.N) != expected {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), expected, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
}
func TestWUID_LoadH28FromRedis_Error(t *testing.T) {
w := NewWUID("alpha", dumb)
if w.LoadH28FromRedis(nil, "") == nil {
t.Fatal("key is not properly checked")
}
newErrorClient := func() (redis.UniversalClient, bool, error) {
return nil, true, errors.New("beta")
}
if w.LoadH28FromRedis(newErrorClient, "beta") == nil {
t.Fatal(`w.LoadH28FromRedis(newErrorClient, "beta") == nil`)
}
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second*3 {
if atomic.LoadInt64(&w.w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Next_Renew(t *testing.T) {
client := connect()
newClient := func() (redis.UniversalClient, bool, error) {
return client, false, nil
}
w := NewWUID("alpha", slog.NewScavenger())
err := w.LoadH28FromRedis(newClient, cfg.key)
if err != nil {
t.Fatal(err)
}
h28 := atomic.LoadInt64(&w.w.N) >> 36
atomic.StoreInt64(&w.w.N, (h28<<36)|internal.Bye)
n1a := w.Next()
if n1a>>36 != h28 {
t.Fatal(`n1a>>36 != h28`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != (h28+1)<<36+1 {
t.Fatal(`n1b != (h28+1)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+1)<<36)|internal.Bye)
n2a := w.Next()
if n2a>>36 != h28+1 {
t.Fatal(`n2a>>36 != h28+1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != (h28+2)<<36+1 {
t.Fatal(`n2b != (h28+2)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)|internal.Bye)
n3a := w.Next()
if n3a>>36 != h28+2 {
t.Fatal(`n3a>>36 != h28+2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != (h28+3)<<36+1 {
t.Fatal(`n3b != (h28+3)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)+internal.Bye+1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3`)
}
var num int
sc := w.w.Logger.(*slog.Scavenger)
sc.Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func Example() {
newClient := func() (redis.UniversalClient, bool, error) {
var client redis.UniversalClient
// ...
return client, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromRedis(newClient, "wuid")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
}
================================================
FILE: redis/wuid/coverage.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
go test -cover -coverprofile=c.out -v "$@" && go tool cover -html=c.out
================================================
FILE: redis/wuid/vet.sh
================================================
#!/usr/bin/env bash
[[ "$TRACE" ]] && set -x
pushd `dirname "$0"` > /dev/null
trap __EXIT EXIT
colorful=false
tput setaf 7 > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
colorful=true
fi
function __EXIT() {
popd > /dev/null
}
function printError() {
$colorful && tput setaf 1
>&2 echo "Error: $@"
$colorful && tput setaf 7
}
function printImportantMessage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
function printUsage() {
$colorful && tput setaf 3
>&2 echo "$@"
$colorful && tput setaf 7
}
printImportantMessage "====== gofmt"
gofmt -w .
printImportantMessage "====== go vet"
go vet ./...
printImportantMessage "====== gocyclo"
gocyclo -over 15 .
printImportantMessage "====== ineffassign"
ineffassign ./...
printImportantMessage "====== misspell"
misspell *
================================================
FILE: redis/wuid/wuid.go
================================================
package wuid
import (
"errors"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"github.com/go-redis/redis"
)
// WUID is an extremely fast universal unique identifier generator.
type WUID struct {
w *internal.WUID
}
// NewWUID creates a new WUID instance.
func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
return &WUID{w: internal.NewWUID(name, logger, opts...)}
}
// Next returns a unique identifier.
func (w *WUID) Next() int64 {
return w.w.Next()
}
type NewClient func() (client redis.UniversalClient, autoClose bool, err error)
// LoadH28FromRedis adds 1 to a specific number in Redis and fetches its new value.
// The new value is used as the high 28 bits of all generated numbers. In addition, all the
// arguments passed in are saved for future renewal.
func (w *WUID) LoadH28FromRedis(newClient NewClient, key string) error {
if len(key) == 0 {
return errors.New("key cannot be empty")
}
client, autoClose, err := newClient()
if err != nil {
return err
}
defer func() {
if autoClose {
_ = client.Close()
}
}()
h28, err := client.Incr(key).Result()
if err != nil {
return err
}
if err = w.w.VerifyH28(h28); err != nil {
return err
}
w.w.Reset(h28 << 36)
w.w.Logger.Infof("<wuid> new h28: %d. name: %s", h28, w.w.Name)
w.w.Lock()
defer w.w.Unlock()
if w.w.Renew != nil {
return nil
}
w.w.Renew = func() error {
return w.LoadH28FromRedis(newClient, key)
}
return nil
}
// RenewNow reacquires the high 28 bits immediately.
func (w *WUID) RenewNow() error {
return w.w.RenewNow()
}
type Option = internal.Option
// WithH28Verifier adds an extra verifier for the high 28 bits.
func WithH28Verifier(cb func(h28 int64) error) Option {
return internal.WithH28Verifier(cb)
}
// WithSection brands a section ID on each generated number. A section ID must be in between [0, 7].
func WithSection(section int8) Option {
return internal.WithSection(section)
}
// WithStep sets the step and the floor for each generated number.
func WithStep(step int64, floor int64) Option {
return internal.WithStep(step, floor)
}
// WithObfuscation enables number obfuscation.
func WithObfuscation(seed int) Option {
return internal.WithObfuscation(seed)
}
================================================
FILE: redis/wuid/wuid_test.go
================================================
package wuid
import (
"errors"
"flag"
"fmt"
"github.com/edwingeng/slog"
"github.com/edwingeng/wuid/internal"
"github.com/go-redis/redis"
"math/rand"
"strings"
"sync/atomic"
"testing"
"time"
)
var redisCluster = flag.Bool("cluster", false, "")
var (
dumb = slog.NewDumbLogger()
)
var (
cfg struct {
addrs []string
password string
key string
}
)
func init() {
cfg.addrs = []string{"127.0.0.1:6379", "127.0.0.1:6380", "127.0.0.1:6381"}
cfg.key = "wuid"
}
func connect() redis.UniversalClient {
if *redisCluster {
return redis.NewClusterClient(&redis.ClusterOptions{
Addrs: cfg.addrs,
Password: cfg.password,
})
} else {
return redis.NewClient(&redis.Options{
Addr: cfg.addrs[0],
Password: cfg.password,
})
}
}
func TestWUID_LoadH28FromRedis(t *testing.T) {
newClient := func() (redis.UniversalClient, bool, error) {
return connect(), true, nil
}
w := NewWUID("alpha", dumb)
err := w.LoadH28FromRedis(newClient, cfg.key)
if err != nil {
t.Fatal(err)
}
initial := atomic.LoadInt64(&w.w.N)
for i := 1; i < 100; i++ {
if err := w.RenewNow(); err != nil {
t.Fatal(err)
}
expected := ((initial >> 36) + int64(i)) << 36
if atomic.LoadInt64(&w.w.N) != expected {
t.Fatalf("w.w.N is %d, while it should be %d. i: %d", atomic.LoadInt64(&w.w.N), expected, i)
}
n := rand.Intn(10)
for j := 0; j < n; j++ {
w.Next()
}
}
}
func TestWUID_LoadH28FromRedis_Error(t *testing.T) {
w := NewWUID("alpha", dumb)
if w.LoadH28FromRedis(nil, "") == nil {
t.Fatal("key is not properly checked")
}
newErrorClient := func() (redis.UniversalClient, bool, error) {
return nil, true, errors.New("beta")
}
if w.LoadH28FromRedis(newErrorClient, "beta") == nil {
t.Fatal(`w.LoadH28FromRedis(newErrorClient, "beta") == nil`)
}
}
func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
t.Helper()
startTime := time.Now()
for time.Since(startTime) < time.Second*3 {
if atomic.LoadInt64(&w.w.Stats.NumRenewed) == expected {
return
}
time.Sleep(time.Millisecond * 10)
}
t.Fatal("timeout")
}
func TestWUID_Next_Renew(t *testing.T) {
client := connect()
newClient := func() (redis.UniversalClient, bool, error) {
return client, false, nil
}
w := NewWUID("alpha", slog.NewScavenger())
err := w.LoadH28FromRedis(newClient, cfg.key)
if err != nil {
t.Fatal(err)
}
h28 := atomic.LoadInt64(&w.w.N) >> 36
atomic.StoreInt64(&w.w.N, (h28<<36)|internal.Bye)
n1a := w.Next()
if n1a>>36 != h28 {
t.Fatal(`n1a>>36 != h28`)
}
waitUntilNumRenewedReaches(t, w, 1)
n1b := w.Next()
if n1b != (h28+1)<<36+1 {
t.Fatal(`n1b != (h28+1)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+1)<<36)|internal.Bye)
n2a := w.Next()
if n2a>>36 != h28+1 {
t.Fatal(`n2a>>36 != h28+1`)
}
waitUntilNumRenewedReaches(t, w, 2)
n2b := w.Next()
if n2b != (h28+2)<<36+1 {
t.Fatal(`n2b != (h28+2)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)|internal.Bye)
n3a := w.Next()
if n3a>>36 != h28+2 {
t.Fatal(`n3a>>36 != h28+2`)
}
waitUntilNumRenewedReaches(t, w, 3)
n3b := w.Next()
if n3b != (h28+3)<<36+1 {
t.Fatal(`n3b != (h28+3)<<36+1`)
}
atomic.StoreInt64(&w.w.N, ((h28+2)<<36)+internal.Bye+1)
for i := 0; i < 100; i++ {
w.Next()
}
if atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3 {
t.Fatal(`atomic.LoadInt64(&w.w.Stats.NumRenewAttempts) != 3`)
}
var num int
sc := w.w.Logger.(*slog.Scavenger)
sc.Filter(func(level, msg string) bool {
if level == slog.LevelInfo && strings.Contains(msg, "renew succeeded") {
num++
}
return true
})
if num != 3 {
t.Fatal(`num != 3`)
}
}
func Example() {
newClient := func() (redis.UniversalClient, bool, error) {
var client redis.UniversalClient
// ...
return client, true, nil
}
// Setup
w := NewWUID("alpha", nil)
err := w.LoadH28FromRedis(newClient, "wuid")
if err != nil {
panic(err)
}
// Generate
for i := 0; i < 10; i++ {
fmt.Printf("%#016x\n", w.Next())
}
}
================================================
FILE: wuid.go
================================================
package wuid
type WUID interface {
Next() int64
}
gitextract_35st_wr2/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── callback/ │ └── wuid/ │ ├── coverage.sh │ ├── vet.sh │ ├── wuid.go │ └── wuid_test.go ├── check.sh ├── go.mod ├── go.sum ├── internal/ │ ├── coverage.sh │ ├── vet.sh │ ├── wuid.go │ └── wuid_test.go ├── mongo/ │ ├── docker-mongo-client.sh │ ├── docker-mongo-server.sh │ └── wuid/ │ ├── coverage.sh │ ├── vet.sh │ ├── wuid.go │ └── wuid_test.go ├── mysql/ │ ├── db.sql │ ├── docker-mysql-client.sh │ ├── docker-mysql-server.sh │ └── wuid/ │ ├── coverage.sh │ ├── vet.sh │ ├── wuid.go │ └── wuid_test.go ├── redis/ │ ├── docker-redis-client.sh │ ├── docker-redis-server.sh │ ├── v8/ │ │ └── wuid/ │ │ ├── coverage.sh │ │ ├── vet.sh │ │ ├── wuid.go │ │ └── wuid_test.go │ └── wuid/ │ ├── coverage.sh │ ├── vet.sh │ ├── wuid.go │ └── wuid_test.go └── wuid.go
SYMBOL INDEX (121 symbols across 14 files)
FILE: callback/wuid/wuid.go
type WUID (line 10) | type WUID struct
method Next (line 20) | func (w *WUID) Next() int64 {
method LoadH28WithCallback (line 28) | func (w *WUID) LoadH28WithCallback(cb H28Callback) error {
method RenewNow (line 62) | func (w *WUID) RenewNow() error {
function NewWUID (line 15) | func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
type H28Callback (line 24) | type H28Callback
function WithH28Verifier (line 69) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 74) | func WithSection(section int8) Option {
function WithStep (line 79) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 84) | func WithObfuscation(seed int) Option {
FILE: callback/wuid/wuid_test.go
function TestWUID_LoadH28WithCallback_Error (line 19) | func TestWUID_LoadH28WithCallback_Error(t *testing.T) {
function TestWUID_LoadH28WithCallback (line 41) | func TestWUID_LoadH28WithCallback(t *testing.T) {
function TestWUID_LoadH28WithCallback_Section (line 74) | func TestWUID_LoadH28WithCallback_Section(t *testing.T) {
function TestWUID_LoadH28WithCallback_Same (line 97) | func TestWUID_LoadH28WithCallback_Same(t *testing.T) {
function waitUntilNumRenewedReaches (line 115) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Renew (line 127) | func TestWUID_Renew(t *testing.T) {
function Example (line 194) | func Example() {
FILE: internal/wuid.go
constant PanicValue (line 13) | PanicValue int64 = ((1 << 36) * 96 / 100) & ^1023
constant CriticalValue (line 15) | CriticalValue int64 = ((1 << 36) * 80 / 100) & ^1023
constant RenewIntervalMask (line 17) | RenewIntervalMask int64 = 0x20000000 - 1
constant Bye (line 21) | Bye = ((CriticalValue + RenewIntervalMask) & ^RenewIntervalMask) - 1
constant H28Mask (line 25) | H28Mask = 0x07FFFFFF << 36
constant L36Mask (line 26) | L36Mask = 0x0FFFFFFFFF
type WUID (line 29) | type WUID struct
method Next (line 72) | func (w *WUID) Next() int64 {
method RenewNow (line 123) | func (w *WUID) RenewNow() error {
method Reset (line 130) | func (w *WUID) Reset(n int64) {
method VerifyH28 (line 155) | func (w *WUID) VerifyH28(h28 int64) error {
function NewWUID (line 53) | func NewWUID(name string, logger slog.Logger, opts ...Option) (w *WUID) {
function renewImpl (line 104) | func renewImpl(w *WUID) {
type Option (line 190) | type Option
function WithH28Verifier (line 192) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 198) | func WithSection(section int8) Option {
function WithStep (line 208) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 229) | func WithObfuscation(seed int) Option {
FILE: internal/wuid_test.go
method Scavenger (line 15) | func (w *WUID) Scavenger() *slog.Scavenger {
function TestWUID_Next (line 19) | func TestWUID_Next(t *testing.T) {
function TestWUID_Next_Concurrent (line 33) | func TestWUID_Next_Concurrent(t *testing.T) {
function TestWUID_Next_Panic (line 66) | func TestWUID_Next_Panic(t *testing.T) {
function waitUntilNumRenewAttemptsReaches (line 92) | func waitUntilNumRenewAttemptsReaches(t *testing.T, w *WUID, expected in...
function waitUntilNumRenewedReaches (line 104) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Renew (line 116) | func TestWUID_Renew(t *testing.T) {
function TestWUID_Renew_Error (line 179) | func TestWUID_Renew_Error(t *testing.T) {
function TestWUID_Renew_Panic (line 216) | func TestWUID_Renew_Panic(t *testing.T) {
function TestWUID_Step (line 253) | func TestWUID_Step(t *testing.T) {
function TestWUID_Floor (line 304) | func TestWUID_Floor(t *testing.T) {
function TestWUID_VerifyH28 (line 354) | func TestWUID_VerifyH28(t *testing.T) {
function TestWithSection_Panic (line 386) | func TestWithSection_Panic(t *testing.T) {
function TestWithSection_Reset (line 400) | func TestWithSection_Reset(t *testing.T) {
function TestWithH28Verifier (line 432) | func TestWithH28Verifier(t *testing.T) {
function TestWithObfuscation (line 448) | func TestWithObfuscation(t *testing.T) {
FILE: mongo/wuid/wuid.go
type WUID (line 18) | type WUID struct
method Next (line 28) | func (w *WUID) Next() int64 {
method LoadH28FromMongo (line 37) | func (w *WUID) LoadH28FromMongo(newClient NewClient, dbName, coll, doc...
method RenewNow (line 117) | func (w *WUID) RenewNow() error {
function NewWUID (line 23) | func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
type NewClient (line 32) | type NewClient
function WithH28Verifier (line 124) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 129) | func WithSection(section int8) Option {
function WithStep (line 134) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 139) | func WithObfuscation(seed int) Option {
FILE: mongo/wuid/wuid_test.go
function init (line 31) | func init() {
function connectMongodb (line 38) | func connectMongodb() (*mongo.Client, error) {
function TestWUID_LoadH28FromMongo (line 45) | func TestWUID_LoadH28FromMongo(t *testing.T) {
function TestWUID_LoadH28FromMongo_Error (line 73) | func TestWUID_LoadH28FromMongo_Error(t *testing.T) {
function waitUntilNumRenewedReaches (line 93) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Renew (line 105) | func TestWUID_Renew(t *testing.T) {
function Example (line 178) | func Example() {
FILE: mysql/db.sql
type `wuid` (line 5) | CREATE TABLE IF NOT EXISTS `wuid` (
FILE: mysql/wuid/wuid.go
type WUID (line 13) | type WUID struct
method Next (line 23) | func (w *WUID) Next() int64 {
method LoadH28FromMysql (line 32) | func (w *WUID) LoadH28FromMysql(openDB OpenDB, table string) error {
method RenewNow (line 76) | func (w *WUID) RenewNow() error {
function NewWUID (line 18) | func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
type OpenDB (line 27) | type OpenDB
function WithH28Verifier (line 83) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 88) | func WithSection(section int8) Option {
function WithStep (line 93) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 98) | func WithObfuscation(seed int) Option {
FILE: mysql/wuid/wuid_test.go
function init (line 31) | func init() {
function connect (line 39) | func connect() (*sql.DB, error) {
function TestWUID_LoadH28FromMysql (line 48) | func TestWUID_LoadH28FromMysql(t *testing.T) {
function TestWUID_LoadH28FromMysql_Error (line 75) | func TestWUID_LoadH28FromMysql_Error(t *testing.T) {
function waitUntilNumRenewedReaches (line 89) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Next_Renew (line 101) | func TestWUID_Next_Renew(t *testing.T) {
function Example (line 174) | func Example() {
FILE: redis/v8/wuid/wuid.go
type WUID (line 13) | type WUID struct
method Next (line 23) | func (w *WUID) Next() int64 {
method LoadH28FromRedis (line 32) | func (w *WUID) LoadH28FromRedis(newClient NewClient, key string) error {
method RenewNow (line 74) | func (w *WUID) RenewNow() error {
function NewWUID (line 18) | func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
type NewClient (line 27) | type NewClient
function WithH28Verifier (line 81) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 86) | func WithSection(section int8) Option {
function WithStep (line 91) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 96) | func WithObfuscation(seed int) Option {
FILE: redis/v8/wuid/wuid_test.go
function init (line 31) | func init() {
function connect (line 36) | func connect() redis.UniversalClient {
function TestWUID_LoadH28FromRedis (line 50) | func TestWUID_LoadH28FromRedis(t *testing.T) {
function TestWUID_LoadH28FromRedis_Error (line 76) | func TestWUID_LoadH28FromRedis_Error(t *testing.T) {
function waitUntilNumRenewedReaches (line 90) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Next_Renew (line 102) | func TestWUID_Next_Renew(t *testing.T) {
function Example (line 172) | func Example() {
FILE: redis/wuid/wuid.go
type WUID (line 11) | type WUID struct
method Next (line 21) | func (w *WUID) Next() int64 {
method LoadH28FromRedis (line 30) | func (w *WUID) LoadH28FromRedis(newClient NewClient, key string) error {
method RenewNow (line 70) | func (w *WUID) RenewNow() error {
function NewWUID (line 16) | func NewWUID(name string, logger slog.Logger, opts ...Option) *WUID {
type NewClient (line 25) | type NewClient
function WithH28Verifier (line 77) | func WithH28Verifier(cb func(h28 int64) error) Option {
function WithSection (line 82) | func WithSection(section int8) Option {
function WithStep (line 87) | func WithStep(step int64, floor int64) Option {
function WithObfuscation (line 92) | func WithObfuscation(seed int) Option {
FILE: redis/wuid/wuid_test.go
function init (line 31) | func init() {
function connect (line 36) | func connect() redis.UniversalClient {
function TestWUID_LoadH28FromRedis (line 50) | func TestWUID_LoadH28FromRedis(t *testing.T) {
function TestWUID_LoadH28FromRedis_Error (line 76) | func TestWUID_LoadH28FromRedis_Error(t *testing.T) {
function waitUntilNumRenewedReaches (line 90) | func waitUntilNumRenewedReaches(t *testing.T, w *WUID, expected int64) {
function TestWUID_Next_Renew (line 102) | func TestWUID_Next_Renew(t *testing.T) {
function Example (line 172) | func Example() {
FILE: wuid.go
type WUID (line 3) | type WUID interface
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (88K chars).
[
{
"path": ".gitattributes",
"chars": 20,
"preview": "* text=auto eol=lf\n\n"
},
{
"path": ".gitignore",
"chars": 298,
"preview": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of"
},
{
"path": "LICENSE",
"chars": 1515,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2018-2022, Edwin Geng\nAll rights reserved.\n\nRedistribution and use in source and bin"
},
{
"path": "README.md",
"chars": 4424,
"preview": "# Overview\n- `WUID` is a universal unique identifier generator.\n- `WUID` is much faster than traditional UUID. Each `WUI"
},
{
"path": "callback/wuid/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "callback/wuid/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "callback/wuid/wuid.go",
"chars": 1991,
"preview": "package wuid\n\nimport (\n\t\"errors\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n)\n\n// WUID is an ext"
},
{
"path": "callback/wuid/wuid_test.go",
"chars": 4623,
"preview": "package wuid\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"math/rand\"\n"
},
{
"path": "check.sh",
"chars": 821,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "go.mod",
"chars": 1213,
"preview": "module github.com/edwingeng/wuid\n\ngo 1.18\n\nrequire (\n\tgithub.com/edwingeng/slog v0.0.0-20221027170832-482f0dfb6247\n\tgith"
},
{
"path": "go.sum",
"chars": 7970,
"preview": "github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/cespare/xxhash/v2 v2.1.2 "
},
{
"path": "internal/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "internal/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "internal/wuid.go",
"chars": 4827,
"preview": "package internal\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nconst (\n\t// PanicValu"
},
{
"path": "internal/wuid_test.go",
"chars": 11243,
"preview": "package internal\n\nimport (\n\t\"errors\"\n\t\"github.com/edwingeng/slog\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\""
},
{
"path": "mongo/docker-mongo-client.sh",
"chars": 620,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mongo/docker-mongo-server.sh",
"chars": 620,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mongo/wuid/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mongo/wuid/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mongo/wuid/wuid.go",
"chars": 3593,
"preview": "package wuid\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"go.mong"
},
{
"path": "mongo/wuid/wuid_test.go",
"chars": 4344,
"preview": "package wuid\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\""
},
{
"path": "mysql/db.sql",
"chars": 254,
"preview": "CREATE DATABASE IF NOT EXISTS test;\n\nuse test;\n\nCREATE TABLE IF NOT EXISTS `wuid` (\n `h` int(10) NOT NULL AUTO_INCREM"
},
{
"path": "mysql/docker-mysql-client.sh",
"chars": 642,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mysql/docker-mysql-server.sh",
"chars": 1127,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mysql/wuid/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mysql/wuid/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "mysql/wuid/wuid.go",
"chars": 2349,
"preview": "package wuid\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/interna"
},
{
"path": "mysql/wuid/wuid_test.go",
"chars": 3838,
"preview": "package wuid\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/interna"
},
{
"path": "redis/docker-redis-client.sh",
"chars": 625,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/docker-redis-server.sh",
"chars": 623,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/v8/wuid/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/v8/wuid/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/v8/wuid/wuid.go",
"chars": 2361,
"preview": "package wuid\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"github."
},
{
"path": "redis/v8/wuid/wuid_test.go",
"chars": 3979,
"preview": "package wuid\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"git"
},
{
"path": "redis/wuid/coverage.sh",
"chars": 637,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/wuid/vet.sh",
"chars": 838,
"preview": "#!/usr/bin/env bash\n\n[[ \"$TRACE\" ]] && set -x\npushd `dirname \"$0\"` > /dev/null\ntrap __EXIT EXIT\n\ncolorful=false\ntput set"
},
{
"path": "redis/wuid/wuid.go",
"chars": 2241,
"preview": "package wuid\n\nimport (\n\t\"errors\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"github.com/go-redi"
},
{
"path": "redis/wuid/wuid_test.go",
"chars": 3973,
"preview": "package wuid\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"github.com/edwingeng/slog\"\n\t\"github.com/edwingeng/wuid/internal\"\n\t\"git"
},
{
"path": "wuid.go",
"chars": 52,
"preview": "package wuid\n\ntype WUID interface {\n\tNext() int64\n}\n"
}
]
About this extraction
This page contains the full source code of the edwingeng/wuid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (77.2 KB), approximately 29.0k tokens, and a symbol index with 121 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.