-
Notifications
You must be signed in to change notification settings - Fork 0
/
reloader.go
130 lines (108 loc) · 2.85 KB
/
reloader.go
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package reloader
import (
"os"
"sync"
"sync/atomic"
"time"
"github.com/effective-security/xlog"
"github.com/pkg/errors"
)
var logger = xlog.NewPackageLogger("github.com/effective-security/x/fileutil", "reloader")
// Wrap time.Tick so we can override it in tests.
var makeTicker = func(interval time.Duration) (func(), <-chan time.Time) {
t := time.NewTicker(interval)
return t.Stop, t.C
}
// OnChangedFunc is a called when the file has been modified
type OnChangedFunc func(filePath string, modifiedAt time.Time)
// Reloader keeps necessary info to provide reloaded certificate
type Reloader struct {
lock sync.RWMutex
loadedAt time.Time
count uint32
filePath string
fileModifiedAt time.Time
onChangedFunc OnChangedFunc
inProgress bool
stopChan chan<- struct{}
closed bool
}
// NewReloader return an instance of the file re-loader
func NewReloader(filePath string, checkInterval time.Duration, onChangedFunc OnChangedFunc) (*Reloader, error) {
result := &Reloader{
filePath: filePath,
onChangedFunc: onChangedFunc,
stopChan: make(chan struct{}),
}
logger.KV(xlog.INFO, "status", "started", "file", filePath)
stopChan := make(chan struct{})
result.stopChan = stopChan
tickerStop, tickChan := makeTicker(checkInterval)
go func() {
for {
select {
case <-stopChan:
tickerStop()
logger.KV(xlog.INFO, "status", "closed", "count", result.LoadedCount(), "file", filePath)
return
case <-tickChan:
modified := false
fi, err := os.Stat(filePath)
if err == nil {
modified = fi.ModTime().After(result.fileModifiedAt)
if modified {
result.fileModifiedAt = fi.ModTime()
err := result.Reload()
if err != nil {
logger.KV(xlog.ERROR, "err", err)
}
}
} else {
logger.KV(xlog.WARNING, "reason", "stat", "file", filePath, "err", err)
}
}
}
}()
return result, nil
}
// Reload will explicitly call the callback function
func (k *Reloader) Reload() error {
k.lock.Lock()
if k.inProgress {
k.lock.Unlock()
return nil
}
k.inProgress = true
defer func() {
k.inProgress = false
k.lock.Unlock()
}()
atomic.AddUint32(&k.count, 1)
k.loadedAt = time.Now().UTC()
go k.onChangedFunc(k.filePath, k.fileModifiedAt)
return nil
}
// LoadedAt return the last time when the pair was loaded
func (k *Reloader) LoadedAt() time.Time {
k.lock.RLock()
defer k.lock.RUnlock()
return k.loadedAt
}
// LoadedCount returns the number of times the pair was loaded from disk
func (k *Reloader) LoadedCount() uint32 {
return atomic.LoadUint32(&k.count)
}
// Close will close the reloader and release its resources
func (k *Reloader) Close() error {
if k == nil {
return nil
}
k.lock.RLock()
defer k.lock.RUnlock()
if k.closed {
return errors.New("already closed")
}
k.closed = true
k.stopChan <- struct{}{}
return nil
}