fix(cli): make tracker iterators snapshot-safe
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c0cb67cada
commit
e1edbc1f9b
2 changed files with 39 additions and 4 deletions
|
|
@ -89,8 +89,11 @@ type TaskTracker struct {
|
||||||
func (tr *TaskTracker) Tasks() iter.Seq[*TrackedTask] {
|
func (tr *TaskTracker) Tasks() iter.Seq[*TrackedTask] {
|
||||||
return func(yield func(*TrackedTask) bool) {
|
return func(yield func(*TrackedTask) bool) {
|
||||||
tr.mu.Lock()
|
tr.mu.Lock()
|
||||||
defer tr.mu.Unlock()
|
tasks := make([]*TrackedTask, len(tr.tasks))
|
||||||
for _, t := range tr.tasks {
|
copy(tasks, tr.tasks)
|
||||||
|
tr.mu.Unlock()
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
if !yield(t) {
|
if !yield(t) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +105,11 @@ func (tr *TaskTracker) Tasks() iter.Seq[*TrackedTask] {
|
||||||
func (tr *TaskTracker) Snapshots() iter.Seq2[string, string] {
|
func (tr *TaskTracker) Snapshots() iter.Seq2[string, string] {
|
||||||
return func(yield func(string, string) bool) {
|
return func(yield func(string, string) bool) {
|
||||||
tr.mu.Lock()
|
tr.mu.Lock()
|
||||||
defer tr.mu.Unlock()
|
tasks := make([]*TrackedTask, len(tr.tasks))
|
||||||
for _, t := range tr.tasks {
|
copy(tasks, tr.tasks)
|
||||||
|
tr.mu.Unlock()
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
name, status, _ := t.snapshot()
|
name, status, _ := t.snapshot()
|
||||||
if !yield(name, status) {
|
if !yield(name, status) {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,35 @@ func TestTaskTracker_Good(t *testing.T) {
|
||||||
assert.NotContains(t, out, "✓")
|
assert.NotContains(t, out, "✓")
|
||||||
assert.NotContains(t, out, "✗")
|
assert.NotContains(t, out, "✗")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("iterators tolerate mutation during iteration", func(t *testing.T) {
|
||||||
|
tr := NewTaskTracker()
|
||||||
|
tr.out = &bytes.Buffer{}
|
||||||
|
|
||||||
|
tr.Add("first")
|
||||||
|
tr.Add("second")
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for task := range tr.Tasks() {
|
||||||
|
task.Update("visited")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, time.Second, 10*time.Millisecond)
|
||||||
|
|
||||||
|
for name, status := range tr.Snapshots() {
|
||||||
|
assert.Equal(t, "visited", status, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskTracker_Bad(t *testing.T) {
|
func TestTaskTracker_Bad(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue