mirror of
https://github.com/charmbracelet/bubbles.git
synced 2026-07-22 11:39:28 -06:00
fix(cursor): fix data race on blinkTag (#784)
This commit is contained in:
parent
daab808a4d
commit
c376ce3ef1
@ -173,11 +173,13 @@ func (m *Model) BlinkCmd() tea.Cmd {
|
||||
|
||||
m.blinkTag++
|
||||
|
||||
blinkMsg := BlinkMsg{id: m.id, tag: m.blinkTag}
|
||||
|
||||
return func() tea.Msg {
|
||||
defer cancel()
|
||||
<-ctx.Done()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return BlinkMsg{id: m.id, tag: m.blinkTag}
|
||||
return blinkMsg
|
||||
}
|
||||
return blinkCanceled{}
|
||||
}
|
||||
|
||||
50
cursor/cursor_test.go
Normal file
50
cursor/cursor_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestBlinkCmdDataRace tests for a race on [Cursor.blinkTag].
|
||||
//
|
||||
// The original [Model.BlinkCmd] implementation returned a closure over the pointer receiver:
|
||||
//
|
||||
// return func() tea.Msg {
|
||||
// defer cancel()
|
||||
// <-ctx.Done()
|
||||
// if ctx.Err() == context.DeadlineExceeded {
|
||||
// return BlinkMsg{id: m.id, tag: m.blinkTag}
|
||||
// }
|
||||
// return blinkCanceled{}
|
||||
// }
|
||||
//
|
||||
// A race on “m.blinkTag” will occur if:
|
||||
// 1. [Model.BlinkCmd] is called e.g. by calling [Model.Focus] from
|
||||
// ["github.com/charmbracelet/bubbletea".Model.Update];
|
||||
// 2. ["github.com/charmbracelet/bubbletea".handleCommands] is kept sufficiently busy that it does not recieve and
|
||||
// execute the [Model.BlinkCmd] e.g. by other long running command or commands;
|
||||
// 3. at least [Mode.BlinkSpeed] time elapses;
|
||||
// 4. [Model.BlinkCmd] is called again;
|
||||
// 5. ["github.com/charmbracelet/bubbletea".handleCommands] gets around to receiving and executing the original
|
||||
// closure.
|
||||
//
|
||||
// Even if this did not formally race, the value of the tag fetched would be semantically incorrect (likely being the
|
||||
// current value rather than the value at the time the closure was created).
|
||||
func TestBlinkCmdDataRace(t *testing.T) {
|
||||
m := New()
|
||||
cmd := m.BlinkCmd()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(m.BlinkSpeed * 3)
|
||||
cmd()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(m.BlinkSpeed * 2)
|
||||
m.BlinkCmd()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user