Skip to content

Commit 0d9010e

Browse files
authored
chore: fix 30% startup time hit from userpassword (#12769)
pbkdf2 is too expensive to run in init, so this change makes it load lazily. I introduced a lazy package that I hope to use more in my `GODEBUG=inittrace=1` adventure. Benchmark results: ``` $ hyperfine "coder --help" "coder-new --help" Benchmark 1: coder --help Time (mean ± σ): 82.1 ms ± 3.8 ms [User: 93.3 ms, System: 30.4 ms] Range (min … max): 72.2 ms … 90.7 ms 35 runs Benchmark 2: coder-new --help Time (mean ± σ): 52.0 ms ± 4.3 ms [User: 62.4 ms, System: 30.8 ms] Range (min … max): 41.9 ms … 62.2 ms 52 runs Summary coder-new --help ran 1.58 ± 0.15 times faster than coder --help ```
1 parent 73fbdbb commit 0d9010e

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

coderd/userpassword/userpassword.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"golang.org/x/crypto/pbkdf2"
1515
"golang.org/x/exp/slices"
1616
"golang.org/x/xerrors"
17+
18+
"github.com/coder/coder/v2/coderd/util/lazy"
1719
)
1820

1921
var (
@@ -38,8 +40,15 @@ var (
3840
defaultSaltSize = 16
3941

4042
// The simulated hash is used when trying to simulate password checks for
41-
// users that don't exist.
42-
simulatedHash, _ = Hash("hunter2")
43+
// users that don't exist. It's meant to preserve the timing of the hash
44+
// comparison.
45+
simulatedHash = lazy.New(func() string {
46+
h, err := Hash("hunter2")
47+
if err != nil {
48+
panic(err)
49+
}
50+
return h
51+
})
4352
)
4453

4554
// Make password hashing much faster in tests.
@@ -65,7 +74,9 @@ func init() {
6574
func Compare(hashed string, password string) (bool, error) {
6675
// If the hased password provided is empty, simulate comparing a real hash.
6776
if hashed == "" {
68-
hashed = simulatedHash
77+
// TODO: this seems ripe for creating a vulnerability where
78+
// hunter2 can log into any account.
79+
hashed = simulatedHash.Load()
6980
}
7081

7182
if len(hashed) < hashLength {

coderd/util/lazy/value.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Package lazy provides a lazy value implementation.
2+
// It's useful especially in global variable initialization to avoid
3+
// slowing down the program startup time.
4+
package lazy
5+
6+
import (
7+
"sync"
8+
"sync/atomic"
9+
)
10+
11+
type Value[T any] struct {
12+
once sync.Once
13+
fn func() T
14+
cached atomic.Pointer[T]
15+
}
16+
17+
func (v *Value[T]) Load() T {
18+
v.once.Do(func() {
19+
vv := v.fn()
20+
v.cached.Store(&vv)
21+
})
22+
return *v.cached.Load()
23+
}
24+
25+
// New creates a new lazy value with the given load function.
26+
func New[T any](fn func() T) *Value[T] {
27+
return &Value[T]{fn: fn}
28+
}

0 commit comments

Comments
 (0)