mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
Log the user-event queue's high-water mark
Now that the queue is unbounded, its depth is a useful signal for understanding how the event loop behaves under load — and we expect it to look very different across builds (e.g. master, which carries the bounce-state-updates-to-ui-thread work, versus the v0.63.0 release this fix ships in). Track the deepest the queue has ever been and log an Info line whenever that record is broken, so the numbers show up in the log for later reasoning. The mark is session-wide and doesn't reset when the queue drains. gocui has no logger of its own, so it exposes the new depth through a handler (matching the existing SetFocusHandler / SetOpenHyperlinkFunc pattern) that the gui registers to log via its own logger. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
49eefbcf37
commit
f0b139f3ab
@@ -603,6 +603,13 @@ func (g *Gui) SetRenderSearchStatusFunc(renderSearchStatusFunc func(*View, int,
|
||||
g.renderSearchStatusFunc = renderSearchStatusFunc
|
||||
}
|
||||
|
||||
// SetUpdateQueueHighWaterMarkHandler registers a diagnostic callback invoked
|
||||
// with the new depth whenever the queue of pending Update callbacks reaches a
|
||||
// new maximum. It may be called from any goroutine.
|
||||
func (g *Gui) SetUpdateQueueHighWaterMarkHandler(f func(depth int)) {
|
||||
g.userEvents.setHighWaterMarkHandler(f)
|
||||
}
|
||||
|
||||
// userEvent represents an event triggered by the user.
|
||||
type userEvent struct {
|
||||
f func(*Gui) error
|
||||
@@ -639,6 +646,13 @@ type userEventQueue struct {
|
||||
mutex sync.Mutex
|
||||
events []userEvent
|
||||
doorbell chan struct{}
|
||||
|
||||
// highWaterMark is the deepest the queue has ever been, and
|
||||
// onHighWaterMark (if set) is called with the new depth each time that
|
||||
// record is broken. Purely diagnostic: it lets us see how deep the queue
|
||||
// gets in practice (see SetUpdateQueueHighWaterMarkHandler).
|
||||
highWaterMark int
|
||||
onHighWaterMark func(int)
|
||||
}
|
||||
|
||||
func newUserEventQueue() *userEventQueue {
|
||||
@@ -649,14 +663,32 @@ func newUserEventQueue() *userEventQueue {
|
||||
func (q *userEventQueue) enqueue(ev userEvent) {
|
||||
q.mutex.Lock()
|
||||
q.events = append(q.events, ev)
|
||||
newHighWaterMark := 0
|
||||
if len(q.events) > q.highWaterMark {
|
||||
q.highWaterMark = len(q.events)
|
||||
newHighWaterMark = q.highWaterMark
|
||||
}
|
||||
onHighWaterMark := q.onHighWaterMark
|
||||
q.mutex.Unlock()
|
||||
|
||||
// Report outside the lock: the handler does I/O (logging) and must not
|
||||
// stall other producers or the draining loop.
|
||||
if newHighWaterMark > 0 && onHighWaterMark != nil {
|
||||
onHighWaterMark(newHighWaterMark)
|
||||
}
|
||||
|
||||
select {
|
||||
case q.doorbell <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (q *userEventQueue) setHighWaterMarkHandler(f func(int)) {
|
||||
q.mutex.Lock()
|
||||
q.onHighWaterMark = f
|
||||
q.mutex.Unlock()
|
||||
}
|
||||
|
||||
// dequeue pops the oldest event, reporting false when the queue is empty.
|
||||
func (q *userEventQueue) dequeue() (userEvent, bool) {
|
||||
q.mutex.Lock()
|
||||
|
||||
@@ -36,6 +36,37 @@ func TestUpdateIsUnboundedAndPreservesOrder(t *testing.T) {
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// The high-water-mark handler fires only when the queue reaches a new maximum
|
||||
// depth, reporting that depth. It does not reset when the queue drains.
|
||||
func TestUpdateQueueHighWaterMark(t *testing.T) {
|
||||
g := newTestGui(t)
|
||||
|
||||
var marks []int
|
||||
g.SetUpdateQueueHighWaterMarkHandler(func(depth int) { marks = append(marks, depth) })
|
||||
|
||||
noop := func(*Gui) error { return nil }
|
||||
|
||||
// Three enqueues with no drain: new highs 1, 2, 3.
|
||||
g.Update(noop)
|
||||
g.Update(noop)
|
||||
g.Update(noop)
|
||||
_, err := g.processRemainingEvents()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Two enqueues stay below the previous high of 3: no new marks.
|
||||
g.Update(noop)
|
||||
g.Update(noop)
|
||||
_, err = g.processRemainingEvents()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Four enqueues with no drain: only depth 4 beats the previous high.
|
||||
for range 4 {
|
||||
g.Update(noop)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{1, 2, 3, 4}, marks)
|
||||
}
|
||||
|
||||
// Concurrent producers must be able to enqueue safely (run under -race). Only
|
||||
// same-goroutine order is guaranteed, so we check that every event is delivered
|
||||
// exactly once and that each producer's own events stay in order.
|
||||
|
||||
@@ -414,6 +414,10 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
|
||||
return nil
|
||||
})
|
||||
|
||||
gui.g.SetUpdateQueueHighWaterMarkHandler(func(depth int) {
|
||||
gui.c.Log.Infof("User-event queue reached a new high-water mark: %d", depth)
|
||||
})
|
||||
|
||||
gui.g.SetOnSelectSearchResultFunc(func(v *gocui.View, selectedLineIdx int) {
|
||||
ctx, ok := gui.helpers.View.ContextForView(v.Name())
|
||||
if ok {
|
||||
|
||||
Reference in New Issue
Block a user