mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Fix data race on the triggerFetch field
startBackgroundFetch assigned the field from its own goroutine, and only after the initial fetch had completed, while the UI thread reads it in triggerImmediateFetch on every repo switch, with no synchronization. Create the channel in startBackgroundRoutines instead, which runs on the UI thread before the fetch goroutine is spawned; everything the UI thread does afterwards is ordered after the write, so the read is race-free without any locking. To make this possible, goEvery now takes the retrigger channel as a parameter instead of creating and returning it; callers that have no use for a retrigger channel pass nil, and a nil channel in a select is simply never ready. As a side effect, a repo switch that happens before the fetch loop has started (during the intro popup or the initial fetch) now latches a trigger and causes an immediate fetch once the loop is running, where previously it was silently dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
58e121b933
commit
3a0ba6bf4d
|
|
@ -43,6 +43,11 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
|||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
if fetchInterval > 0 {
|
||||
// The channel must be created here, on the UI thread and before
|
||||
// the fetch goroutine spawns, so that triggerImmediateFetch (also
|
||||
// running on the UI thread) can read the field without racing the
|
||||
// write. See triggerImmediateFetch for why it is buffered.
|
||||
self.triggerFetch = make(chan struct{}, 1)
|
||||
go utils.Safe(self.startBackgroundFetch)
|
||||
} else {
|
||||
self.gui.c.Log.Errorf(
|
||||
|
|
@ -74,7 +79,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
|||
}
|
||||
|
||||
if self.gui.Config.GetDebug() {
|
||||
self.goEvery(time.Second*time.Duration(10), self.gui.stopChan, func(_ bool) error {
|
||||
self.goEvery(time.Second*time.Duration(10), self.gui.stopChan, nil, func(_ bool) error {
|
||||
formatBytes := func(b uint64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
|
|
@ -125,14 +130,14 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
|||
_ = fetch(true)
|
||||
|
||||
userConfig := self.gui.UserConfig()
|
||||
self.triggerFetch = self.goEvery(userConfig.Refresher.FetchIntervalDuration(), self.gui.stopChan, fetch)
|
||||
self.goEvery(userConfig.Refresher.FetchIntervalDuration(), self.gui.stopChan, self.triggerFetch, fetch)
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
userConfig := self.gui.UserConfig()
|
||||
self.goEvery(userConfig.Refresher.RefreshIntervalDuration(), self.gui.stopChan, func(_ bool) error {
|
||||
self.goEvery(userConfig.Refresher.RefreshIntervalDuration(), self.gui.stopChan, nil, func(_ bool) error {
|
||||
self.gui.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Background: true})
|
||||
return nil
|
||||
})
|
||||
|
|
@ -151,6 +156,7 @@ func (self *BackgroundRoutineMgr) startBackgroundExternalChangeDetection() {
|
|||
self.goEvery(
|
||||
userConfig.Refresher.ExternalChangeCheckIntervalDuration(),
|
||||
self.gui.stopChan,
|
||||
nil,
|
||||
func(_ bool) error {
|
||||
self.checkForExternalChanges()
|
||||
return nil
|
||||
|
|
@ -187,14 +193,10 @@ func (self *BackgroundRoutineMgr) checkForExternalChanges() {
|
|||
self.gui.c.RefreshFromWorker(types.RefreshOptions{Background: true})
|
||||
}
|
||||
|
||||
// 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{} {
|
||||
// Runs function every interval until stop is closed. A send on retrigger (if
|
||||
// non-nil) runs the callback immediately and restarts the interval.
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop, retrigger chan struct{}, function func(bool) error) {
|
||||
done := 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()
|
||||
|
|
@ -227,7 +229,6 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
|||
}
|
||||
}
|
||||
})
|
||||
return retrigger
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue