From 37e9cd1bbfa03e95113d2a05348ecc00584b8219 Mon Sep 17 00:00:00 2001
From: Peter Bourgon <peterbourgon@users.noreply.github.com>
Date: Mon, 27 Mar 2023 21:59:21 +0200
Subject: [PATCH] Refactor default entropy and fix test (#98)
* Refactor default entropy and fix test
* Update comments
---
ulid.go | 49 +++++++++++++++++++++++--------------------------
ulid_test.go | 4 +++-
2 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/ulid.go b/ulid.go
index 0cb258d..f3ad993 100644
--- a/ulid.go
+++ b/ulid.go
@@ -122,21 +122,15 @@ func MustNew(ms uint64, entropy io.Reader) ULID {
return id
}
-var (
- entropy io.Reader
- entropyOnce sync.Once
-)
+var defaultEntropy = func() io.Reader {
+ rng := rand.New(rand.NewSource(time.Now().UnixNano()))
+ return &LockedMonotonicReader{MonotonicReader: Monotonic(rng, 0)}
+}()
// DefaultEntropy returns a thread-safe per process monotonically increasing
// entropy source.
func DefaultEntropy() io.Reader {
- entropyOnce.Do(func() {
- rng := rand.New(rand.NewSource(time.Now().UnixNano()))
- entropy = &LockedMonotonicReader{
- MonotonicReader: Monotonic(rng, 0),
- }
- })
- return entropy
+ return defaultEntropy
}
// Make returns an ULID with the current time in Unix milliseconds and
@@ -145,7 +139,7 @@ func DefaultEntropy() io.Reader {
// contention.
func Make() (id ULID) {
// NOTE: MustNew can't panic since DefaultEntropy never returns an error.
- return MustNew(Now(), DefaultEntropy())
+ return MustNew(Now(), defaultEntropy)
}
// Parse parses an encoded ULID, returning an error in case of failure.
@@ -532,20 +526,23 @@ func (id ULID) Value() (driver.Value, error) {
return id.MarshalBinary()
}
-// Monotonic returns an entropy source that is guaranteed to yield
-// strictly increasing entropy bytes for the same ULID timestamp.
-// On conflicts, the previous ULID entropy is incremented with a
-// random number between 1 and `inc` (inclusive).
+// Monotonic returns a source of entropy that yields strictly increasing entropy
+// bytes, to a limit governeed by the `inc` parameter.
+//
+// Specifically, calls to MonotonicRead within the same ULID timestamp return
+// entropy incremented by a random number between 1 and `inc` inclusive. If an
+// increment results in entropy that would overflow available space,
+// MonotonicRead returns ErrMonotonicOverflow.
//
-// The provided entropy source must actually yield random bytes or else
-// monotonic reads are not guaranteed to terminate, since there isn't
-// enough randomness to compute an increment number.
+// Passing `inc == 0` results in the reasonable default `math.MaxUint32`. Lower
+// values of `inc` provide more monotonic entropy in a single millisecond, at
+// the cost of easier "guessability" of generated ULIDs. If your code depends on
+// ULIDs having secure entropy bytes, then it's recommended to use the secure
+// default value of `inc == 0`, unless you know what you're doing.
//
-// When `inc == 0`, it'll be set to a secure default of `math.MaxUint32`.
-// The lower the value of `inc`, the easier the next ULID within the
-// same millisecond is to guess. If your code depends on ULIDs having
-// secure entropy bytes, then don't go under this default unless you know
-// what you're doing.
+// The provided entropy source must actually yield random bytes. Otherwise,
+// monotonic reads are not guaranteed to terminate, since there isn't enough
+// randomness to compute an increment number.
//
// The returned type isn't safe for concurrent use.
func Monotonic(entropy io.Reader, inc uint64) *MonotonicEntropy {
@@ -567,8 +564,8 @@ func Monotonic(entropy io.Reader, inc uint64) *MonotonicEntropy {
type rng interface{ Int63n(n int64) int64 }
-// LockedMonotonicReader wraps a MonotonicReader with a sync.Mutex for
-// safe concurrent use.
+// LockedMonotonicReader wraps a MonotonicReader with a sync.Mutex for safe
+// concurrent use.
type LockedMonotonicReader struct {
mu sync.Mutex
MonotonicReader
diff --git a/ulid_test.go b/ulid_test.go
index 2a9476d..2a6946f 100644
--- a/ulid_test.go
+++ b/ulid_test.go
@@ -597,7 +597,8 @@ func TestMonotonicSafe(t *testing.T) {
t.Parallel()
var (
- safe = ulid.DefaultEntropy()
+ rng = rand.New(rand.NewSource(time.Now().UnixNano()))
+ safe = &ulid.LockedMonotonicReader{MonotonicReader: ulid.Monotonic(rng, 0)}
t0 = ulid.Timestamp(time.Now())
)
@@ -620,6 +621,7 @@ func TestMonotonicSafe(t *testing.T) {
errs <- nil
}()
}
+
for i := 0; i < cap(errs); i++ {
if err := <-errs; err != nil {
t.Fatal(err)
--
2.43.0