Skip to content

Commit

Permalink
chore: store handlers and simplify teardown
Browse files Browse the repository at this point in the history
  • Loading branch information
muesli committed Oct 7, 2022
1 parent 76ce669 commit 6477a53
Showing 1 changed file with 39 additions and 18 deletions.
57 changes: 39 additions & 18 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"os"
"os/signal"
"runtime/debug"
"sync"
"syscall"
"time"

Expand Down Expand Up @@ -54,6 +55,8 @@ type Model interface {
// update function.
type Cmd func() Msg

type handlers []chan struct{}

// Options to customize the program during its initialization. These are
// generally set with ProgramOptions.
//
Expand Down Expand Up @@ -178,6 +181,7 @@ func (p *Program) handleSignals() chan struct{} {
select {
case <-p.ctx.Done():
return

case <-sig:
if !p.ignoreSignals {
p.msgs <- quitMsg{}
Expand Down Expand Up @@ -332,6 +336,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {

// StartReturningModel initializes the program. Returns the final model.
func (p *Program) StartReturningModel() (Model, error) {
handlers := handlers{}
cmds := make(chan Cmd)
p.errs = make(chan error)

Expand Down Expand Up @@ -360,7 +365,6 @@ func (p *Program) StartReturningModel() (Model, error) {
if !isFile {
break
}

if isatty.IsTerminal(f.Fd()) {
break
}
Expand All @@ -376,11 +380,8 @@ func (p *Program) StartReturningModel() (Model, error) {
}

// Handle signals.
sigintLoopDone := make(chan struct{})
if !p.startupOptions.has(withoutSignalHandler) {
sigintLoopDone = p.handleSignals()
} else {
close(sigintLoopDone)
handlers.add(p.handleSignals())
}

// Recover from panics.
Expand Down Expand Up @@ -417,18 +418,19 @@ func (p *Program) StartReturningModel() (Model, error) {
}

// Initialize the program.
initSignalDone := make(chan struct{})
model := p.initialModel
if initCmd := model.Init(); initCmd != nil {
ch := make(chan struct{})
handlers.add(ch)

go func() {
defer close(initSignalDone)
defer close(ch)

select {
case cmds <- initCmd:
case <-p.ctx.Done():
}
}()
} else {
close(initSignalDone)
}

// Start the renderer.
Expand All @@ -442,16 +444,15 @@ func (p *Program) StartReturningModel() (Model, error) {
if err := p.initCancelReader(); err != nil {
return model, err
}
} else {
defer close(p.readLoopDone)
defer p.cancelReader.Close() //nolint:errcheck
handlers.add(p.readLoopDone)
}
defer p.cancelReader.Close() //nolint:errcheck

// Handle resize events.
resizeLoopDone := p.handleResize()
handlers.add(p.handleResize())

// Process commands.
cmdLoopDone := p.handleCommands(cmds)
handlers.add(p.handleCommands(cmds))

// Run event loop, handle updates and draw.
model, err := p.eventLoop(model, cmds)
Expand All @@ -463,10 +464,11 @@ func (p *Program) StartReturningModel() (Model, error) {
if p.cancelReader.Cancel() {
p.waitForReadLoop()
}
<-cmdLoopDone
<-resizeLoopDone
<-sigintLoopDone
<-initSignalDone

// Wait for all handlers to finish.
handlers.shutdown()

// Restore terminal state.
p.shutdown(false)

return model, err
Expand Down Expand Up @@ -587,3 +589,22 @@ func (p *Program) Printf(template string, args ...interface{}) {
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()
}

0 comments on commit 6477a53

Please sign in to comment.