mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Don't block the UI thread when triggering an immediate fetch on repo switch
Switching repos triggers an immediate background fetch by sending on
the goEvery retrigger channel. The send was blocking, but the goEvery
loop only receives between callbacks: while a fetch is in flight, it
waits for that fetch to finish before returning to its select. So a
repo switch that landed while a fetch was in flight would stall the UI
thread for the remainder of the fetch.
Worse, since worker refreshes capture state on the UI thread with a
blocking OnUIThreadAndWaitBackground call, the in-flight fetch's
post-fetch refresh can itself be waiting for the UI thread, turning
that stall into a deadlock cycle:
UI thread: switchTo -> triggerImmediateFetch, blocking send
goEvery loop: waiting for the in-flight fetch to finish
fetch worker: PostFetchRefresh -> RefreshFromWorker, waiting for
the UI thread
Make the send non-blocking, and give the channel a buffer of one so
that a trigger arriving while a fetch is in flight is latched rather
than dropped; that fetch is fetching the previous repo, so we still
need another one after it. The goEvery loop picks the trigger up as
soon as it returns to its select, and concurrent triggers coalesce.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a61727cd5e
commit
58e121b933
|
|
@ -190,7 +190,11 @@ func (self *BackgroundRoutineMgr) checkForExternalChanges() {
|
|||
// returns a channel that can be used to trigger the callback immediately
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func(bool) error) chan struct{} {
|
||||
done := make(chan struct{})
|
||||
retrigger := make(chan struct{})
|
||||
// Buffered so that a retrigger arriving while the callback is running is
|
||||
// latched rather than lost: the loop below doesn't receive again until the
|
||||
// callback has finished, and the callback (a fetch) may be for the wrong
|
||||
// repo if the retrigger came from a repo switch.
|
||||
retrigger := make(chan struct{}, 1)
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
|
@ -234,6 +238,16 @@ func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
|||
|
||||
func (self *BackgroundRoutineMgr) triggerImmediateFetch() {
|
||||
if self.triggerFetch != nil {
|
||||
self.triggerFetch <- struct{}{}
|
||||
// This runs on the UI thread, which must never block waiting for a
|
||||
// background routine; in particular, the goEvery loop only receives
|
||||
// between callbacks, and an in-flight fetch can itself be waiting for
|
||||
// the UI thread to perform its post-fetch refresh, so a blocking send
|
||||
// here would deadlock. The channel has a buffer of one, so the trigger
|
||||
// is latched even when the loop isn't currently receiving; if one is
|
||||
// already pending, the two coalesce.
|
||||
select {
|
||||
case self.triggerFetch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue