Created
April 26, 2022 22:25
-
-
Save spraints/93a9f82d5a958e3790138716d68e86ef to your computer and use it in GitHub Desktop.
Compare locking strategies for generating certs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package sharedcertprovider | |
import ( | |
"context" | |
"sync" | |
"time" | |
) | |
type Provider interface { | |
GetCert() Cert | |
} | |
type Cert struct { | |
validUntil time.Time | |
} | |
func newCert(refreshInterval time.Duration) Cert { | |
return Cert{validUntil: time.Now().Add(10 * refreshInterval)} | |
} | |
func (c Cert) Valid() bool { | |
return time.Now().Before(c.validUntil) | |
} | |
func ChannelProvider(ctx context.Context, refreshInterval time.Duration) Provider { | |
provider := &channelProvider{ | |
c: make(chan Cert), | |
} | |
go provider.provide(ctx, refreshInterval) | |
return provider | |
} | |
type channelProvider struct { | |
c chan Cert | |
} | |
func (c *channelProvider) provide(ctx context.Context, refreshInterval time.Duration) { | |
r := time.After(refreshInterval) | |
cert := newCert(refreshInterval) | |
for { | |
select { | |
case c.c <- cert: | |
// OK! | |
case <-r: | |
r = time.After(refreshInterval) | |
cert = newCert(refreshInterval) | |
case <-ctx.Done(): | |
return | |
} | |
} | |
} | |
func (c *channelProvider) GetCert() Cert { | |
return <-c.c | |
} | |
func MutexProvider(ctx context.Context, refreshInterval time.Duration) Provider { | |
provider := &mutexProvider{} | |
provider.cert = newCert(refreshInterval) | |
go provider.refresh(ctx, refreshInterval) | |
return provider | |
} | |
type mutexProvider struct { | |
lock sync.Mutex | |
cert Cert | |
} | |
func (m *mutexProvider) refresh(ctx context.Context, refreshInterval time.Duration) { | |
t := time.NewTicker(refreshInterval) | |
defer t.Stop() | |
for range t.C { | |
if ctx.Err() != nil { | |
return | |
} | |
newCert := newCert(refreshInterval) | |
m.lock.Lock() | |
m.cert = newCert | |
m.lock.Unlock() | |
} | |
} | |
func (m *mutexProvider) GetCert() Cert { | |
m.lock.Lock() | |
defer m.lock.Unlock() | |
return m.cert | |
} | |
func RWMutexProvider(ctx context.Context, refreshInterval time.Duration) Provider { | |
provider := &rwMutexProvider{} | |
provider.cert = newCert(refreshInterval) | |
go provider.refresh(ctx, refreshInterval) | |
return provider | |
} | |
type rwMutexProvider struct { | |
lock sync.RWMutex | |
cert Cert | |
} | |
func (r *rwMutexProvider) refresh(ctx context.Context, refreshInterval time.Duration) { | |
t := time.NewTicker(refreshInterval) | |
defer t.Stop() | |
for range t.C { | |
if ctx.Err() != nil { | |
return | |
} | |
newCert := newCert(refreshInterval) | |
r.lock.Lock() | |
r.cert = newCert | |
r.lock.Unlock() | |
} | |
} | |
func (r *rwMutexProvider) GetCert() Cert { | |
r.lock.RLock() | |
defer r.lock.RUnlock() | |
return r.cert | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package sharedcertprovider | |
/* | |
$ go test -bench=. . | |
goos: darwin | |
goarch: amd64 | |
pkg: sharedcertprovider | |
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz | |
BenchmarkChannelProvider-16 49003 25901 ns/op | |
BenchmarkMutexProvider-16 106971 10535 ns/op | |
BenchmarkRWMutexProvider-16 5733031 194.7 ns/op | |
*/ | |
import ( | |
"context" | |
"testing" | |
"time" | |
) | |
func BenchmarkChannelProvider(b *testing.B) { | |
bench(b, ChannelProvider) | |
} | |
func BenchmarkMutexProvider(b *testing.B) { | |
bench(b, MutexProvider) | |
} | |
func BenchmarkRWMutexProvider(b *testing.B) { | |
bench(b, RWMutexProvider) | |
} | |
func bench(b *testing.B, newProvider func(context.Context, time.Duration) Provider) { | |
ctx, cancel := context.WithCancel(context.Background()) | |
defer cancel() | |
provider := newProvider(ctx, time.Second/1000) | |
for i := 0; i < 50; i++ { | |
go contend(ctx, provider) | |
} | |
for i := 0; i < b.N; i++ { | |
cert := provider.GetCert() | |
if !cert.Valid() { | |
b.Logf("bad cert! %v xxx %v", time.Now(), provider.GetCert().validUntil) | |
b.FailNow() | |
} | |
} | |
} | |
func contend(ctx context.Context, provider Provider) { | |
for ctx.Err() == nil { | |
provider.GetCert() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment