-
Notifications
You must be signed in to change notification settings - Fork 130
Expand file tree
/
Copy pathloader.go
More file actions
144 lines (128 loc) · 2.98 KB
/
Copy pathloader.go
File metadata and controls
144 lines (128 loc) · 2.98 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main
import (
"io"
"os"
"strings"
"time"
"loov.dev/lensm/internal/disasm"
"loov.dev/lensm/internal/goobj"
"loov.dev/lensm/internal/wasmobj"
)
type fileLoadRequest struct {
generation uint64
path string
}
type fileLoadResult struct {
generation uint64
file disasm.File
err error
}
func loadDisasmFile(path string) (disasm.File, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
var magic [4]byte
_, _ = io.ReadFull(f, magic[:])
_ = f.Close()
if string(magic[:]) == "\x00asm" {
return wasmobj.Load(path)
}
return goobj.Load(path)
}
// loader loads disassembly files on its own goroutine and, when watch
// is set, polls the current path for modifications with a debounce so
// a binary is not reloaded mid-write.
type loader struct {
loadFile func(string) (disasm.File, error)
watch bool
requests chan fileLoadRequest
results chan fileLoadResult
stop chan struct{}
}
func newLoader(loadFile func(string) (disasm.File, error), watch bool) *loader {
l := &loader{
loadFile: loadFile,
watch: watch,
requests: make(chan fileLoadRequest, 1),
results: make(chan fileLoadResult, 1),
stop: make(chan struct{}),
}
go l.run()
return l
}
func (l *loader) Results() <-chan fileLoadResult { return l.results }
func (l *loader) Close() { close(l.stop) }
// Request replaces any queued request; only the latest path matters.
// Main event loop only.
func (l *loader) Request(generation uint64, path string) {
select {
case <-l.requests:
default:
}
l.requests <- fileLoadRequest{generation: generation, path: path}
}
// finish publishes a result, replacing an unconsumed previous one and
// closing its file.
func (l *loader) finish(result fileLoadResult) {
select {
case old := <-l.results:
if old.file != nil {
_ = old.file.Close()
}
default:
}
l.results <- result
}
func (l *loader) run() {
var lastModTime time.Time
var pendingModTime time.Time
var pendingSince time.Time
var path string
var generation uint64
tick := time.NewTicker(150 * time.Millisecond)
defer tick.Stop()
load := func(force bool, now time.Time) {
if path == "" {
return
}
stat, err := os.Stat(path)
if err != nil {
l.finish(fileLoadResult{generation: generation, err: err})
return
}
if !force && stat.ModTime().Equal(lastModTime) {
return
}
if !force {
if !stat.ModTime().Equal(pendingModTime) {
pendingModTime = stat.ModTime()
pendingSince = now
return
}
if now.Sub(pendingSince) < 300*time.Millisecond {
return
}
}
lastModTime = stat.ModTime()
pendingModTime = time.Time{}
file, err := l.loadFile(path)
l.finish(fileLoadResult{generation: generation, file: file, err: err})
}
for {
select {
case req := <-l.requests:
path = strings.TrimSpace(req.path)
generation = req.generation
lastModTime = time.Time{}
pendingModTime = time.Time{}
load(true, time.Now())
case now := <-tick.C:
if l.watch {
load(false, now)
}
case <-l.stop:
return
}
}
}