blob: 0b5e7d0e49168a30bbf37f095c12551eab1dd43e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
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
}
|