ident: system identifier generator
This is pretty fast and guarantees uniqueness when initialised correctly. Signed-off-by: Yonah <contrib@gensokyo.uk>
This commit is contained in:
@@ -6,7 +6,10 @@ import (
|
|||||||
"encoding"
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,3 +100,47 @@ func (full *F[V, T]) UnmarshalText(data []byte) (err error) {
|
|||||||
}
|
}
|
||||||
return full.Remote.UnmarshalText(data[sz+1:])
|
return full.Remote.UnmarshalText(data[sz+1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PartG is machine-specific state of [Generator].
|
||||||
|
type PartG struct {
|
||||||
|
// Deployment site, typically denoting a datacenter servicing a region.
|
||||||
|
Site uint32
|
||||||
|
// Servicing host behind load balancer, unique within its Site.
|
||||||
|
Host uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Generator emits unique instances of [S].
|
||||||
|
type Generator struct {
|
||||||
|
PartG
|
||||||
|
mu sync.Mutex
|
||||||
|
r rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
// S populates the value pointed to by p with a new unique value.
|
||||||
|
func (g *Generator) S(p *S) {
|
||||||
|
g.mu.Lock()
|
||||||
|
*p = S{PartG: g.PartG, Time: uint64(time.Now().UnixNano()), ID: g.r.Uint64()}
|
||||||
|
g.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewS creates a unique instance of [S] and returns its address.
|
||||||
|
func (g *Generator) NewS() *S {
|
||||||
|
var sid S
|
||||||
|
g.S(&sid)
|
||||||
|
return &sid
|
||||||
|
}
|
||||||
|
|
||||||
|
// M populates the value pointed to by p with a new unique value.
|
||||||
|
func (g *Generator) M(p *PartM, serial uint64) {
|
||||||
|
g.mu.Lock()
|
||||||
|
*p = PartM{Serial: serial, Time: uint64(time.Now().UnixNano()), ID: g.r.Uint64()}
|
||||||
|
g.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initialises a new instance of [Generator] and returns its address.
|
||||||
|
func New(site, host uint32) *Generator {
|
||||||
|
return &Generator{PartG{site, host}, sync.Mutex{}, rand.NewPCG(
|
||||||
|
uint64(site)<<32|uint64(host),
|
||||||
|
uint64(time.Now().UnixNano()),
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ func TestFS(t *testing.T) {
|
|||||||
|
|
||||||
{"valid", ident.F[ident.S, *ident.S]{
|
{"valid", ident.F[ident.S, *ident.S]{
|
||||||
I: &ident.S{
|
I: &ident.S{
|
||||||
Site: ident.TrivialSite,
|
PartG: ident.PartG{
|
||||||
Host: ident.TrivialHost,
|
Site: ident.TrivialSite,
|
||||||
|
Host: ident.TrivialHost,
|
||||||
|
},
|
||||||
|
|
||||||
Time: uint64(time.Date(
|
Time: uint64(time.Date(
|
||||||
0xfd, 7, 15,
|
0xfd, 7, 15,
|
||||||
@@ -130,8 +132,10 @@ func TestFM(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
System: ident.S{
|
System: ident.S{
|
||||||
Site: ident.TrivialSite,
|
PartG: ident.PartG{
|
||||||
Host: ident.TrivialHost,
|
Site: ident.TrivialSite,
|
||||||
|
Host: ident.TrivialHost,
|
||||||
|
},
|
||||||
|
|
||||||
Time: uint64(time.Date(
|
Time: uint64(time.Date(
|
||||||
0xfd, 7, 15,
|
0xfd, 7, 15,
|
||||||
@@ -207,3 +211,19 @@ func TestErrors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratorS(b *testing.B) {
|
||||||
|
var s ident.S
|
||||||
|
g := ident.New(ident.TrivialSite, ident.TrivialHost)
|
||||||
|
for b.Loop() {
|
||||||
|
g.S(&s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratorM(b *testing.B) {
|
||||||
|
var pm ident.PartM
|
||||||
|
g := ident.New(ident.TrivialSite, ident.TrivialHost)
|
||||||
|
for b.Loop() {
|
||||||
|
g.M(&pm, 0xbadf00d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,8 +98,10 @@ func TestM(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
System: ident.S{
|
System: ident.S{
|
||||||
Site: ident.TrivialSite,
|
PartG: ident.PartG{
|
||||||
Host: ident.TrivialHost,
|
Site: ident.TrivialSite,
|
||||||
|
Host: ident.TrivialHost,
|
||||||
|
},
|
||||||
|
|
||||||
Time: uint64(time.Date(
|
Time: uint64(time.Date(
|
||||||
0xfd, 7, 15,
|
0xfd, 7, 15,
|
||||||
|
|||||||
@@ -17,17 +17,13 @@ const (
|
|||||||
|
|
||||||
// S represents a unique system identifier.
|
// S represents a unique system identifier.
|
||||||
type S struct {
|
type S struct {
|
||||||
// Deployment site, typically denoting a datacenter servicing a region.
|
PartG
|
||||||
Site uint32
|
|
||||||
// Servicing host behind load balancer, unique within its Site.
|
|
||||||
Host uint32
|
|
||||||
|
|
||||||
// An instant in time, some time after the corresponding system metadata
|
// An instant in time, some time after the corresponding system metadata
|
||||||
// first appeared to the backend, represented in nanoseconds since
|
// first appeared to the backend, represented in nanoseconds since
|
||||||
// 1970-01-01.
|
// 1970-01-01.
|
||||||
Time uint64
|
Time uint64
|
||||||
// Randomly generated value. The implementation must guarantee that the same
|
// Randomly generated value. The implementation must guarantee that the same
|
||||||
// value cannot be emitted for a Time value on a servicing Host.
|
// value cannot be emitted for a Time value on a servicing host.
|
||||||
ID uint64
|
ID uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ func TestS(t *testing.T) {
|
|||||||
), ident.ErrNewline},
|
), ident.ErrNewline},
|
||||||
|
|
||||||
{"valid", ident.S{
|
{"valid", ident.S{
|
||||||
Site: ident.TrivialSite,
|
PartG: ident.PartG{
|
||||||
Host: ident.TrivialHost,
|
Site: ident.TrivialSite,
|
||||||
|
Host: ident.TrivialHost,
|
||||||
|
},
|
||||||
|
|
||||||
Time: uint64(time.Date(
|
Time: uint64(time.Date(
|
||||||
0xfd, 7, 15,
|
0xfd, 7, 15,
|
||||||
|
|||||||
Reference in New Issue
Block a user