Skip to content

Commit dbfd0a1

Browse files
committed
Fixed goroutine leak and tweaked the API a little. A UI can now be provided a parent context to derive its own Run context from. Also, UI handles Read errors a little better.
1 parent 68796cb commit dbfd0a1

6 files changed

Lines changed: 42 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[![GoDoc](https://godoc.org/github.com/Zaba505/sand?status.svg)](https://godoc.org/github.com/Zaba505/sand)
22
[![Go Report Card](https://goreportcard.com/badge/github.com/Zaba505/sand)](https://goreportcard.com/report/github.com/Zaba505/sand)
3+
[![Build Status](https://travis-ci.com/Zaba505/sand.svg?branch=master)](https://travis-ci.com/Zaba505/sand)
34

45
# sand
56
Package for creating interpreters

engine.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ func (ui *UI) exec(ctx context.Context, line string) int {
2727
ui: ui,
2828
respCh: make(chan int),
2929
}
30-
defer close(req.respCh)
31-
32-
ui.reqCh <- req
33-
30+
select {
31+
case <-ctx.Done():
32+
return 0
33+
case ui.reqCh <- req:
34+
}
3435
return <-req.respCh
3536
}
3637

@@ -46,7 +47,13 @@ func runEngine(ctx context.Context, eng Engine, reqChs chan chan execReq) {
4647
go func(rc chan execReq) {
4748
for req := range rc {
4849
resp := eng.Exec(req.ctx, req.line, req.ui)
49-
req.respCh <- resp
50+
select {
51+
case <-ctx.Done():
52+
close(req.respCh)
53+
return
54+
case req.respCh <- resp:
55+
}
56+
close(req.respCh)
5057
}
5158
}(reqCh)
5259
}

example/cobra/cmd_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func TestRootCmd(t *testing.T) {
8181
subT.Error(err)
8282
}
8383

84-
if err = ui.Run(); err != io.EOF {
84+
if err = ui.Run(nil); err != io.EOF {
8585
subT.Error(err)
8686
}
8787

@@ -95,5 +95,6 @@ func TestRootCmd(t *testing.T) {
9595
subT.Fail()
9696
}
9797
})
98+
9899
}
99100
}

example/echo/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func main() {
3232

3333
log.SetOutput(os.Stdout)
3434
err := ui.Run(
35+
nil,
3536
sand.WithPrefix(">"),
3637
sand.WithIO(os.Stdin, os.Stdout),
3738
)

example/tictactoe/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func main() {
175175
ui.SetPrefix(">")
176176
ui.SetIO(os.Stdin, os.Stdout)
177177

178-
if err := ui.Run(); err != nil {
178+
if err := ui.Run(nil); err != nil {
179179
log.Fatal(err)
180180
}
181181
}

ui.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ func (ui *UI) SetIO(in io.Reader, out io.Writer) {
101101
// Run creates a UI and associates the provided Engine to it.
102102
// It then starts the UI. I/O must be provided for this call
103103
// to not panic.
104-
func Run(eng Engine, opts ...Option) error {
104+
func Run(ctx context.Context, eng Engine, opts ...Option) error {
105105
if eng == nil {
106106
return ErrNoEngine
107107
}
108108

109109
ui := NewUI(eng)
110-
return ui.Run(opts...)
110+
return ui.Run(ctx, opts...)
111111
}
112112

113113
// minRead
@@ -116,11 +116,15 @@ const minRead = 512
116116
// Run starts the user interface with the provided sources
117117
// for input and output of the interpreter and engine.
118118
// The prefix will be printed before every line.
119-
func (ui *UI) Run(opts ...Option) (err error) {
119+
func (ui *UI) Run(ctx context.Context, opts ...Option) (err error) {
120120
// Make sure engine is set
121121
if ui.eng == nil {
122122
return ErrNoEngine
123123
}
124+
// Check if context is nil
125+
if ctx == nil {
126+
ctx = context.Background()
127+
}
124128

125129
// Set options
126130
for _, opt := range opts {
@@ -129,7 +133,9 @@ func (ui *UI) Run(opts ...Option) (err error) {
129133

130134
// Set signal handling
131135
var cancel func()
132-
ui.ctx, cancel = context.WithCancel(context.Background())
136+
ui.ctx, cancel = context.WithCancel(ctx)
137+
defer cancel()
138+
defer close(ui.sigs)
133139
go func() {
134140
for sig := range ui.sigs {
135141
handler, exists := ui.sigHandlers[sig]
@@ -159,6 +165,7 @@ func (ui *UI) Run(opts ...Option) (err error) {
159165
}()
160166
defer close(ui.reqCh)
161167

168+
var n int
162169
for {
163170
// Write prefix
164171
_, err = ui.Write(nil)
@@ -168,11 +175,11 @@ func (ui *UI) Run(opts ...Option) (err error) {
168175

169176
// Read line
170177
b := make([]byte, minRead)
171-
_, err = ui.Read(b)
172-
if err != nil {
173-
if err == context.Canceled {
174-
err = nil
175-
}
178+
n, err = ui.Read(b)
179+
if err == context.Canceled {
180+
return nil
181+
}
182+
if err != nil && err != io.EOF || n == 0 {
176183
return
177184
}
178185

@@ -181,6 +188,11 @@ func (ui *UI) Run(opts ...Option) (err error) {
181188
if status != 0 {
182189
return
183190
}
191+
192+
// Check if we hit EOF on previous read
193+
if err == io.EOF {
194+
return
195+
}
184196
}
185197
}
186198

@@ -219,7 +231,10 @@ type readResp struct {
219231
func (ui *UI) readAsync(b []byte, readCh chan readResp) {
220232
var resp readResp
221233
resp.n, resp.err = ui.i.Read(b)
222-
readCh <- resp
234+
select {
235+
case <-ui.ctx.Done():
236+
case readCh <- resp:
237+
}
223238
close(readCh)
224239
}
225240

0 commit comments

Comments
 (0)