mediocre-blog/srv/pow/store.go
2021-08-01 17:54:53 -06:00

93 lines
1.7 KiB
Go

package pow
import (
"errors"
"sync"
"time"
"github.com/tilinna/clock"
)
// ErrSeedSolved is used to indicate a seed has already been solved.
var ErrSeedSolved = errors.New("seed already solved")
// Store is used to track information related to proof-of-work challenges and
// solutions.
type Store interface {
// MarkSolved will return ErrSeedSolved if the seed was already marked. The
// seed will be cleared from the Store once expiresAt is reached.
MarkSolved(seed []byte, expiresAt time.Time) error
Close() error
}
type inMemStore struct {
clock clock.Clock
m map[string]time.Time
l sync.Mutex
closeCh chan struct{}
spinLoopCh chan struct{} // only used by tests
}
const inMemStoreGCPeriod = 5 * time.Second
// NewMemoryStore initializes and returns an in-memory Store implementation.
func NewMemoryStore(clock clock.Clock) Store {
s := &inMemStore{
clock: clock,
m: map[string]time.Time{},
closeCh: make(chan struct{}),
spinLoopCh: make(chan struct{}, 1),
}
go s.spin(s.clock.NewTicker(inMemStoreGCPeriod))
return s
}
func (s *inMemStore) spin(ticker *clock.Ticker) {
defer ticker.Stop()
for {
select {
case <-ticker.C:
now := s.clock.Now()
s.l.Lock()
for seed, expiresAt := range s.m {
if !now.Before(expiresAt) {
delete(s.m, seed)
}
}
s.l.Unlock()
case <-s.closeCh:
return
}
select {
case s.spinLoopCh <- struct{}{}:
default:
}
}
}
func (s *inMemStore) MarkSolved(seed []byte, expiresAt time.Time) error {
seedStr := string(seed)
s.l.Lock()
defer s.l.Unlock()
if _, ok := s.m[seedStr]; ok {
return ErrSeedSolved
}
s.m[seedStr] = expiresAt
return nil
}
func (s *inMemStore) Close() error {
close(s.closeCh)
return nil
}