forked from Mirrors/bubbletea
fix: don't block in Send after shutdown
Send should block before a tea.Program has been started, but result in a no-op when it has already been terminated. Fixed godocs.
This commit is contained in:
parent
a520b7f4e1
commit
3609d87e70
26
tea.go
26
tea.go
|
@ -142,6 +142,9 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||||
msgs: make(chan Msg),
|
msgs: make(chan Msg),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize context and teardown channel.
|
||||||
|
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Apply all options to the program.
|
// Apply all options to the program.
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(p)
|
opt(p)
|
||||||
|
@ -246,12 +249,7 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
|
||||||
// (e.g. tick commands that sleep for half a second). It's not
|
// (e.g. tick commands that sleep for half a second). It's not
|
||||||
// possible to cancel them so we'll have to leak the goroutine
|
// possible to cancel them so we'll have to leak the goroutine
|
||||||
// until Cmd returns.
|
// until Cmd returns.
|
||||||
go func() {
|
go p.Send(cmd())
|
||||||
select {
|
|
||||||
case p.msgs <- cmd():
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -315,10 +313,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||||
go func() {
|
go func() {
|
||||||
// Execute commands one at a time, in order.
|
// Execute commands one at a time, in order.
|
||||||
for _, cmd := range msg {
|
for _, cmd := range msg {
|
||||||
select {
|
p.Send(cmd())
|
||||||
case p.msgs <- cmd():
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -344,7 +339,6 @@ func (p *Program) Run() (Model, error) {
|
||||||
cmds := make(chan Cmd)
|
cmds := make(chan Cmd)
|
||||||
p.errs = make(chan error)
|
p.errs = make(chan error)
|
||||||
|
|
||||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
||||||
defer p.cancel()
|
defer p.cancel()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -504,10 +498,14 @@ func (p *Program) Start() error {
|
||||||
// messages to be injected from outside the program for interoperability
|
// messages to be injected from outside the program for interoperability
|
||||||
// purposes.
|
// purposes.
|
||||||
//
|
//
|
||||||
// If the program is not running this will be a no-op, so it's safe to
|
// If the program hasn't started yet this will be a blocking operation.
|
||||||
// send messages if the program is unstarted, or has exited.
|
// If the program has already been terminated this will be a no-op, so it's safe
|
||||||
|
// to send messages after the program has exited.
|
||||||
func (p *Program) Send(msg Msg) {
|
func (p *Program) Send(msg Msg) {
|
||||||
p.msgs <- msg
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
case p.msgs <- msg:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit is a convenience function for quitting Bubble Tea programs. Use it
|
// Quit is a convenience function for quitting Bubble Tea programs. Use it
|
||||||
|
|
26
tea_test.go
26
tea_test.go
|
@ -149,3 +149,29 @@ func TestTeaSequenceMsg(t *testing.T) {
|
||||||
t.Fatalf("counter should be 2, got %d", m.counter.Load())
|
t.Fatalf("counter should be 2, got %d", m.counter.Load())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTeaSend(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var in bytes.Buffer
|
||||||
|
|
||||||
|
m := &testModel{}
|
||||||
|
p := NewProgram(m, WithInput(&in), WithOutput(&buf))
|
||||||
|
|
||||||
|
// sending before the program is started is a blocking operation
|
||||||
|
go p.Send(Quit())
|
||||||
|
|
||||||
|
if _, err := p.Run(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sending a message after program has quit is a no-op
|
||||||
|
p.Send(Quit())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeaNoRun(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var in bytes.Buffer
|
||||||
|
|
||||||
|
m := &testModel{}
|
||||||
|
NewProgram(m, WithInput(&in), WithOutput(&buf))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue