Make terminal UI shutdown idempotent
This commit is contained in:
committed by
Brandon McGinty
parent
41759b95f6
commit
5bf3ed8b84
@@ -231,7 +231,7 @@ Priority 3: configuration, UI, and binding hardening
|
|||||||
never closes the FIFO descriptor. Exit the reader on terminal errors,
|
never closes the FIFO descriptor. Exit the reader on terminal errors,
|
||||||
close the descriptor, and make shutdown cancellable.
|
close the descriptor, and make shutdown cancellable.
|
||||||
|
|
||||||
30. Empty UI tree can panic; UI startup errors are swallowed
|
[x] 30. Empty UI tree can panic; UI startup errors are swallowed
|
||||||
Files: uiterm/tree.go, uiterm/ui.go
|
Files: uiterm/tree.go, uiterm/ui.go
|
||||||
Tree.uiKeyEvent indexes lines[activeLine] for a non-arrow key even when
|
Tree.uiKeyEvent indexes lines[activeLine] for a non-arrow key even when
|
||||||
no lines exist. Guard empty trees. Ui.Run returns nil when termbox.Init
|
no lines exist. Guard empty trees. Ui.Run returns nil when termbox.Init
|
||||||
|
|||||||
+13
-8
@@ -3,6 +3,7 @@ package uiterm
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
@@ -20,8 +21,9 @@ type UiManager interface {
|
|||||||
type Ui struct {
|
type Ui struct {
|
||||||
Fg, Bg Attribute
|
Fg, Bg Attribute
|
||||||
|
|
||||||
close chan bool
|
close chan struct{}
|
||||||
manager UiManager
|
closeOnce sync.Once
|
||||||
|
manager UiManager
|
||||||
|
|
||||||
drawCount int32
|
drawCount int32
|
||||||
elements map[string]*uiElement
|
elements map[string]*uiElement
|
||||||
@@ -39,7 +41,7 @@ type uiElement struct {
|
|||||||
|
|
||||||
func New(manager UiManager) *Ui {
|
func New(manager UiManager) *Ui {
|
||||||
ui := &Ui{
|
ui := &Ui{
|
||||||
close: make(chan bool, 10),
|
close: make(chan struct{}),
|
||||||
elements: make(map[string]*uiElement),
|
elements: make(map[string]*uiElement),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
keyListeners: make(map[Key][]KeyListener),
|
keyListeners: make(map[Key][]KeyListener),
|
||||||
@@ -48,10 +50,9 @@ func New(manager UiManager) *Ui {
|
|||||||
return ui
|
return ui
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close is safe to call repeatedly and never blocks a caller.
|
||||||
func (ui *Ui) Close() {
|
func (ui *Ui) Close() {
|
||||||
if termbox.IsInit {
|
ui.closeOnce.Do(func() { close(ui.close) })
|
||||||
ui.close <- true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *Ui) Refresh() {
|
func (ui *Ui) Refresh() {
|
||||||
@@ -97,7 +98,7 @@ func (ui *Ui) Run(cmds chan string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := termbox.Init(); err != nil {
|
if err := termbox.Init(); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
defer termbox.Close()
|
defer termbox.Close()
|
||||||
termbox.SetInputMode(termbox.InputAlt)
|
termbox.SetInputMode(termbox.InputAlt)
|
||||||
@@ -119,7 +120,11 @@ func (ui *Ui) Run(cmds chan string) error {
|
|||||||
select {
|
select {
|
||||||
case <-ui.close:
|
case <-ui.close:
|
||||||
return nil
|
return nil
|
||||||
case cmd := <-cmds:
|
case cmd, ok := <-cmds:
|
||||||
|
if !ok {
|
||||||
|
cmds = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
ui.onCommandEvent(cmd)
|
ui.onCommandEvent(cmd)
|
||||||
case event := <-events:
|
case event := <-events:
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package uiterm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// Regression: Close sent to a bounded channel and could block or enqueue
|
||||||
|
// duplicate shutdowns when called more than once.
|
||||||
|
func TestCloseIsNonblockingAndIdempotent(t *testing.T) {
|
||||||
|
ui := New(nil)
|
||||||
|
ui.Close()
|
||||||
|
ui.Close()
|
||||||
|
select {
|
||||||
|
case <-ui.close:
|
||||||
|
default:
|
||||||
|
t.Fatal("Close did not signal shutdown")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user