Run all refresh scopes on plain goroutines

The two branches of the `refresh` closure ran the scope function
identically; they differed only in that the UI-thread path registered
each scope as its own gocui task while the worker/demo path used a bare
goroutine (and only the latter logged per-scope timing).

Those per-scope tasks were redundant. performRefresh always runs under a
task that stays busy until the wg.Wait in waitAndFinalize joins every
scope goroutine: the calling worker's own task when called from a worker,
or the waitAndFinalize worker task when called from the UI thread — and
that task is created (busy) before the triggering event's task goes Done,
so there is no window in which nothing is busy. Repo-switch safety and the
integration-test idle signal are therefore already covered without giving
each scope its own task.

Collapsing to the single goroutine path also means the timing log now
fires for UI-thread refreshes too, not just worker ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-09 18:11:24 +02:00
parent a247dfd76d
commit 9bb9fc8933

View file

@ -234,27 +234,19 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
wg := sync.WaitGroup{}
refresh := func(name string, f func()) {
wg.Add(1)
// A refresh issued from the UI thread must not block it, so its scopes
// run as their own worker tasks and the caller returns immediately (the
// finishing step below is dispatched to a worker too). A refresh issued
// from a worker blocks that worker instead, running its scopes as plain
// goroutines that it joins. In a demo we always take the blocking path
// so everything updates in a single, deterministic frame.
if !self.c.InDemo() && !calledFromWorker {
self.onWorker(env.background, func(t gocui.Task) error {
defer wg.Done()
f()
return nil
})
} else {
go utils.Safe(func() {
t := time.Now()
defer wg.Done()
f()
self.c.Log.Infof("refreshed %s in %s", name, time.Since(t))
})
}
// Each scope runs on its own goroutine, joined by the wg.Wait in
// waitAndFinalize. They don't need to be registered as gocui tasks for
// repo-switch safety: performRefresh always runs under a task that stays
// busy until that wg.Wait returns — the calling worker's task when
// called from a worker, or the waitAndFinalize worker task when called
// from the UI thread (created before the triggering event's task ends,
// so there's no gap) — and that task already covers the whole refresh.
go utils.Safe(func() {
t := time.Now()
defer wg.Done()
f()
self.c.Log.Infof("refreshed %s in %s", name, time.Since(t))
})
}
branchesAndRemotesWg := sync.WaitGroup{}