mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Add IsUIThread and OnUIThreadAndWait to gocui
The next commits move refresh workers to read UI-thread-owned state (the model, contexts, selection) on the UI thread rather than off it. Two primitives support that: - OnUIThreadAndWait runs a function on the main event loop and blocks the caller until it has run, so a worker can read that state without racing. OnUIThreadAndWaitBackground is the same for background routines, whose work must not count towards the program being busy. - IsUIThread reports whether the caller is on the main event loop, for a debug-only assertion that a refresh was issued from the thread it claims. It records the main loop's goroutine id in MainLoop and compares via goid, so it's promoted from an indirect to a direct dependency. goid is used only by that debug assertion, never to drive production control flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0ea537956
commit
1def541acb
2
go.mod
2
go.mod
|
|
@ -25,6 +25,7 @@ require (
|
|||
github.com/lucasb-eyer/go-colorful v1.4.0
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
github.com/sahilm/fuzzy v0.1.3
|
||||
github.com/samber/lo v1.53.0
|
||||
|
|
@ -62,7 +63,6 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.34.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/petermattis/goid"
|
||||
"github.com/rivo/uniseg"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
|
@ -200,6 +202,11 @@ type Gui struct {
|
|||
currentTask Task
|
||||
|
||||
lastHoverView *View
|
||||
|
||||
// uiThreadID is the goroutine id of the main event loop, recorded when
|
||||
// MainLoop starts. IsUIThread compares against it. Written once, read from
|
||||
// worker goroutines, so it's atomic.
|
||||
uiThreadID atomic.Int64
|
||||
}
|
||||
|
||||
type NewGuiOpts struct {
|
||||
|
|
@ -684,6 +691,46 @@ func (g *Gui) updateContentOnly(f func(*Gui) error, background bool) {
|
|||
g.userEvents <- userEvent{f: f, task: task, contentOnly: true}
|
||||
}
|
||||
|
||||
// IsUIThread reports whether the caller is running on the main event-loop
|
||||
// goroutine (the one running MainLoop). It calls goid.Get, so use it only for
|
||||
// debug assertions, not to drive production control flow.
|
||||
func (g *Gui) IsUIThread() bool {
|
||||
return goid.Get() == g.uiThreadID.Load()
|
||||
}
|
||||
|
||||
// OnUIThreadAndWait runs f on the main event-loop goroutine and blocks the
|
||||
// caller until f has run, returning f's error. Use it to read UI-thread-owned
|
||||
// state (the model, contexts) from a worker without racing the UI thread.
|
||||
//
|
||||
// It must be called from a worker goroutine, never from the UI thread itself:
|
||||
// the UI thread would block waiting for a callback only it can run, which
|
||||
// deadlocks. Callers arrange this by construction (see the refresh helper's
|
||||
// RefreshFromWorker); a debug-only assertion there guards against getting it
|
||||
// wrong.
|
||||
func (g *Gui) OnUIThreadAndWait(f func() error) error {
|
||||
return g.onUIThreadAndWait(f, false)
|
||||
}
|
||||
|
||||
// Like OnUIThreadAndWait, but the enqueued work belongs to a background routine,
|
||||
// so it doesn't count towards the program being busy (see UpdateBackground).
|
||||
func (g *Gui) OnUIThreadAndWaitBackground(f func() error) error {
|
||||
return g.onUIThreadAndWait(f, true)
|
||||
}
|
||||
|
||||
func (g *Gui) onUIThreadAndWait(f func() error, background bool) error {
|
||||
enqueue := g.Update
|
||||
if background {
|
||||
enqueue = g.UpdateBackground
|
||||
}
|
||||
|
||||
result := make(chan error, 1)
|
||||
enqueue(func(*Gui) error {
|
||||
result <- f()
|
||||
return nil
|
||||
})
|
||||
return <-result
|
||||
}
|
||||
|
||||
// Calls a function in a goroutine. Handles panics gracefully and tracks
|
||||
// number of background tasks.
|
||||
// Always use this when you want to spawn a goroutine and you want lazygit to
|
||||
|
|
@ -766,6 +813,8 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
|||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
g.uiThreadID.Store(goid.Get())
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
|
|
|
|||
Loading…
Reference in a new issue