chore: store handlers and simplify teardown

This commit is contained in:
Christian Muehlhaeuser 2022-10-07 22:40:16 +02:00
parent 76ce669474
commit 6477a53545
1 changed files with 39 additions and 18 deletions

57
tea.go
View File

@ -16,6 +16,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"runtime/debug" "runtime/debug"
"sync"
"syscall" "syscall"
"time" "time"
@ -54,6 +55,8 @@ type Model interface {
// update function. // update function.
type Cmd func() Msg type Cmd func() Msg
type handlers []chan struct{}
// Options to customize the program during its initialization. These are // Options to customize the program during its initialization. These are
// generally set with ProgramOptions. // generally set with ProgramOptions.
// //
@ -178,6 +181,7 @@ func (p *Program) handleSignals() chan struct{} {
select { select {
case <-p.ctx.Done(): case <-p.ctx.Done():
return return
case <-sig: case <-sig:
if !p.ignoreSignals { if !p.ignoreSignals {
p.msgs <- quitMsg{} p.msgs <- quitMsg{}
@ -332,6 +336,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
// StartReturningModel initializes the program. Returns the final model. // StartReturningModel initializes the program. Returns the final model.
func (p *Program) StartReturningModel() (Model, error) { func (p *Program) StartReturningModel() (Model, error) {
handlers := handlers{}
cmds := make(chan Cmd) cmds := make(chan Cmd)
p.errs = make(chan error) p.errs = make(chan error)
@ -360,7 +365,6 @@ func (p *Program) StartReturningModel() (Model, error) {
if !isFile { if !isFile {
break break
} }
if isatty.IsTerminal(f.Fd()) { if isatty.IsTerminal(f.Fd()) {
break break
} }
@ -376,11 +380,8 @@ func (p *Program) StartReturningModel() (Model, error) {
} }
// Handle signals. // Handle signals.
sigintLoopDone := make(chan struct{})
if !p.startupOptions.has(withoutSignalHandler) { if !p.startupOptions.has(withoutSignalHandler) {
sigintLoopDone = p.handleSignals() handlers.add(p.handleSignals())
} else {
close(sigintLoopDone)
} }
// Recover from panics. // Recover from panics.
@ -417,18 +418,19 @@ func (p *Program) StartReturningModel() (Model, error) {
} }
// Initialize the program. // Initialize the program.
initSignalDone := make(chan struct{})
model := p.initialModel model := p.initialModel
if initCmd := model.Init(); initCmd != nil { if initCmd := model.Init(); initCmd != nil {
ch := make(chan struct{})
handlers.add(ch)
go func() { go func() {
defer close(initSignalDone) defer close(ch)
select { select {
case cmds <- initCmd: case cmds <- initCmd:
case <-p.ctx.Done(): case <-p.ctx.Done():
} }
}() }()
} else {
close(initSignalDone)
} }
// Start the renderer. // Start the renderer.
@ -442,16 +444,15 @@ func (p *Program) StartReturningModel() (Model, error) {
if err := p.initCancelReader(); err != nil { if err := p.initCancelReader(); err != nil {
return model, err return model, err
} }
} else { defer p.cancelReader.Close() //nolint:errcheck
defer close(p.readLoopDone) handlers.add(p.readLoopDone)
} }
defer p.cancelReader.Close() //nolint:errcheck
// Handle resize events. // Handle resize events.
resizeLoopDone := p.handleResize() handlers.add(p.handleResize())
// Process commands. // Process commands.
cmdLoopDone := p.handleCommands(cmds) handlers.add(p.handleCommands(cmds))
// Run event loop, handle updates and draw. // Run event loop, handle updates and draw.
model, err := p.eventLoop(model, cmds) model, err := p.eventLoop(model, cmds)
@ -463,10 +464,11 @@ func (p *Program) StartReturningModel() (Model, error) {
if p.cancelReader.Cancel() { if p.cancelReader.Cancel() {
p.waitForReadLoop() p.waitForReadLoop()
} }
<-cmdLoopDone
<-resizeLoopDone // Wait for all handlers to finish.
<-sigintLoopDone handlers.shutdown()
<-initSignalDone
// Restore terminal state.
p.shutdown(false) p.shutdown(false)
return model, err return model, err
@ -587,3 +589,22 @@ func (p *Program) Printf(template string, args ...interface{}) {
messageBody: fmt.Sprintf(template, args...), messageBody: fmt.Sprintf(template, args...),
} }
} }
// Adds a handler to the list of handlers. We wait for all handlers to terminate
// gracefully on shutdown.
func (h *handlers) add(ch chan struct{}) {
*h = append(*h, ch)
}
// Shutdown waits for all handlers to terminate.
func (h handlers) shutdown() {
var wg sync.WaitGroup
for _, ch := range h {
wg.Add(1)
go func(ch chan struct{}) {
<-ch
wg.Done()
}(ch)
}
wg.Wait()
}