Band-aid fix for rare crashes when refreshing files (#5074)

Users have filed issues with crash reports that seem to indicate that
the FileTreeViewModel gets swapped out (by a refresh) while a call to
itemsSelected is in progress, iterating over the previous items. Guard
against this by locking the mutex that we already have for this for the
duration of the call.

I don't have a good way of testing whether the fix helps, because the
crashes only occurred very infrequently. Let's just see if the crash
reports stop coming in after we ship this.

Note also that this is only the minimal fix for the crashes that were
reported. Theoretically, the same problem could happen for a key handler
itself, but we never saw reports about that, so we don't bother doing
anything about that yet.

Note also that long-term I envision a different solution to this class
of problems (discussed in
https://github.com/jesseduffield/lazygit/issues/2974), that's why I want
to avoid locking mutexes more than necessary now.

Fixes #3646 
Fixes #4154
Fixes #4301
Fixes #5070
This commit is contained in:
Stefan Haller 2025-12-03 08:10:13 +01:00 committed by GitHub
commit 8396747cf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -44,7 +44,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withItems(self.press),
GetDisabledReason: self.require(self.itemsSelected()),
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected())),
Description: self.c.Tr.Stage,
Tooltip: self.c.Tr.StageTooltip,
DisplayOnScreen: true,
@ -91,7 +91,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.withItems(self.edit),
GetDisabledReason: self.require(self.itemsSelected(self.canEditFiles)),
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canEditFiles))),
Description: self.c.Tr.Edit,
Tooltip: self.c.Tr.EditFileTooltip,
DisplayOnScreen: true,
@ -145,7 +145,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.withItems(self.remove),
GetDisabledReason: self.require(self.itemsSelected(self.canRemove)),
GetDisabledReason: self.withFileTreeViewModelMutex(self.require(self.itemsSelected(self.canRemove))),
Description: self.c.Tr.Discard,
Tooltip: self.c.Tr.DiscardFileChangesTooltip,
OpensMenu: true,
@ -182,7 +182,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.withItems(self.openMergeConflictMenu),
Description: self.c.Tr.ViewMergeConflictOptions,
Tooltip: self.c.Tr.ViewMergeConflictOptionsTooltip,
GetDisabledReason: self.require(self.itemsSelected(self.canOpenMergeConflictMenu)),
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canOpenMergeConflictMenu))),
OpensMenu: true,
DisplayOnScreen: true,
},
@ -209,6 +209,15 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
}
}
func (self *FilesController) withFileTreeViewModelMutex(callback func() *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
self.c.Contexts().Files.FileTreeViewModel.RWMutex.RLock()
defer self.c.Contexts().Files.FileTreeViewModel.RWMutex.RUnlock()
return callback()
}
}
func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{