Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: store handlers and simplify teardown #505

Merged
merged 1 commit into from
Oct 7, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}