mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
A caller of ReadLines or ReadToEnd is told that the content it asked for has been read by the request's Then being called. A task that reaches the end of its input answers the requests still queued behind the one it was serving, but a task that is stopped drops them, and their callers wait for a callback that never comes. Pressing "/" in the focused main view opens the search prompt from such a callback, so if a re-render replaces the task at that moment the prompt never opens. Answering them as the read loop ends would leave a request handed over after that point unanswered, and there is a window for one. A caller reads the channel to send on, and can reach the send itself only once the loop has gone. So hand requests over through a queue instead. Asking whether a task is there and giving it the request are one step, as are taking the task away and handing back what it never answered; a request made in between goes back to the caller to answer. The queue is unbounded rather than a fixed-size channel, for the reasons gocui's userEventQueue is. Requests are handed over from the UI thread, where a blocking send would deadlock against the task waiting to be let go, and a fixed channel that fills up leaves only blocking, dropping, reordering or panicking to choose between. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
59 lines
1.4 KiB
Go
59 lines
1.4 KiB
Go
package tasks
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestReadRequestQueueHandsBackWhatNoTaskWillServe(t *testing.T) {
|
|
queue := newReadRequestQueue()
|
|
|
|
// Nothing has begun serving, so the request comes straight back to its caller.
|
|
assert.False(t, queue.enqueue(LinesToRead{Total: 1}))
|
|
|
|
queue.beginServing()
|
|
assert.True(t, queue.enqueue(LinesToRead{Total: 1}))
|
|
assert.True(t, queue.enqueue(LinesToRead{Total: 2}))
|
|
|
|
request, ok := queue.dequeue()
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 1, request.Total)
|
|
|
|
// What the task never got to comes back when it stops, and nothing is taken
|
|
// from a caller after that.
|
|
unanswered := queue.stopServing()
|
|
assert.Len(t, unanswered, 1)
|
|
assert.Equal(t, 2, unanswered[0].Total)
|
|
|
|
assert.False(t, queue.enqueue(LinesToRead{Total: 3}))
|
|
_, ok = queue.dequeue()
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
func TestReadRequestQueueRingsTheDoorbell(t *testing.T) {
|
|
queue := newReadRequestQueue()
|
|
doorbell := queue.beginServing()
|
|
|
|
select {
|
|
case <-doorbell:
|
|
assert.Fail(t, "the doorbell rang before anything was queued")
|
|
default:
|
|
}
|
|
|
|
// A burst leaves one token, which stands for everything queued.
|
|
queue.enqueue(LinesToRead{Total: 1})
|
|
queue.enqueue(LinesToRead{Total: 2})
|
|
|
|
select {
|
|
case <-doorbell:
|
|
default:
|
|
assert.Fail(t, "the doorbell didn't ring")
|
|
}
|
|
select {
|
|
case <-doorbell:
|
|
assert.Fail(t, "the doorbell rang twice for one wake")
|
|
default:
|
|
}
|
|
}
|