Skip to content

Instantly share code, notes, and snippets.

@spraints
Created April 26, 2022 22:25
Show Gist options
  • Save spraints/93a9f82d5a958e3790138716d68e86ef to your computer and use it in GitHub Desktop.
Save spraints/93a9f82d5a958e3790138716d68e86ef to your computer and use it in GitHub Desktop.
Compare locking strategies for generating certs
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
}
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