mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Repo-switch safety needs to answer, synchronously on the UI thread, "is any foreground work in flight right now?" so it can refuse a switch that would run against a repo about to be swapped out. gocui already tracks a task per OnWorker/Update for the test idle-listener; extend that. Tasks gain a background flag: background tasks (the ongoing routines like auto-fetch, and the refreshes they trigger) don't count towards busy, because their model writes are already guarded against a concurrent switch by the repo generation. Add OnWorkerBackground, UpdateBackground and UpdateContentOnlyBackground (plus the gui-layer OnUIThreadBackground / OnUIThreadContentOnlyBackground / OnWorkerBackground on IGuiCommon) so the few background call sites can opt in without touching the hundreds of foreground callers. TaskManager.hasBusyForegroundTaskExcept answers the query; Gui.Busy() wraps it, excluding the event currently being processed (recorded as currentTask) so a handler asking the question doesn't count itself. Nothing gates on Busy() yet; this is the mechanism only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
2.3 KiB
Go
90 lines
2.3 KiB
Go
package gocui
|
|
|
|
import "sync"
|
|
|
|
// Tracks whether the program is busy (i.e. either something is happening on
|
|
// the main goroutine or a worker goroutine). Used by integration tests
|
|
// to wait until the program is idle before progressing.
|
|
type TaskManager struct {
|
|
// each of these listeners will be notified when the program goes from busy to idle
|
|
idleListeners []chan struct{}
|
|
tasks map[int]Task
|
|
// auto-incrementing id for new tasks
|
|
nextId int
|
|
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
func newTaskManager() *TaskManager {
|
|
return &TaskManager{
|
|
tasks: make(map[int]Task),
|
|
idleListeners: []chan struct{}{},
|
|
}
|
|
}
|
|
|
|
func (self *TaskManager) NewTask(background bool) *TaskImpl {
|
|
self.mutex.Lock()
|
|
defer self.mutex.Unlock()
|
|
|
|
self.nextId++
|
|
taskId := self.nextId
|
|
|
|
onDone := func() { self.delete(taskId) }
|
|
task := &TaskImpl{id: taskId, busy: true, background: background, onDone: onDone, withMutex: self.withMutex}
|
|
self.tasks[taskId] = task
|
|
|
|
return task
|
|
}
|
|
|
|
// hasBusyForegroundTaskExcept reports whether any task other than `ignore` is
|
|
// currently busy and not a background task. It's used to decide whether a repo
|
|
// switch is safe: a foreground operation (or the refresh it triggers, or that
|
|
// refresh's follow-up callbacks) still in flight means the switch must wait, so
|
|
// it doesn't run against a repo that's about to be swapped out.
|
|
//
|
|
// `ignore` is the event currently being processed on the UI thread — the switch
|
|
// attempt itself — which is always busy and so must not count as a reason to
|
|
// refuse itself.
|
|
func (self *TaskManager) hasBusyForegroundTaskExcept(ignore Task) bool {
|
|
self.mutex.Lock()
|
|
defer self.mutex.Unlock()
|
|
|
|
for _, task := range self.tasks {
|
|
if task != ignore && task.isBusy() && !task.isBackground() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (self *TaskManager) addIdleListener(c chan struct{}) {
|
|
self.idleListeners = append(self.idleListeners, c)
|
|
}
|
|
|
|
func (self *TaskManager) withMutex(f func()) {
|
|
self.mutex.Lock()
|
|
defer self.mutex.Unlock()
|
|
|
|
f()
|
|
|
|
// Check if all tasks are done
|
|
for _, task := range self.tasks {
|
|
if task.isBusy() {
|
|
return
|
|
}
|
|
}
|
|
|
|
// If we get here, all tasks are done, so
|
|
// notify listeners that the program is idle
|
|
for _, listener := range self.idleListeners {
|
|
listener <- struct{}{}
|
|
}
|
|
}
|
|
|
|
func (self *TaskManager) delete(taskId int) {
|
|
self.withMutex(func() {
|
|
delete(self.tasks, taskId)
|
|
})
|
|
}
|