1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-17 00:18:05 +02:00

add some safe goroutines

WIP
This commit is contained in:
Jesse Duffield
2020-10-07 21:19:38 +11:00
parent ba4c3e5bc4
commit 79e59d5460
15 changed files with 84 additions and 64 deletions

View File

@ -9,6 +9,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/creack/pty" "github.com/creack/pty"
) )
@ -31,14 +32,14 @@ func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(s
return err return err
} }
go func() { go utils.Safe(func() {
scanner := bufio.NewScanner(ptmx) scanner := bufio.NewScanner(ptmx)
scanner.Split(scanWordsWithNewLines) scanner.Split(scanWordsWithNewLines)
for scanner.Scan() { for scanner.Scan() {
toOutput := strings.Trim(scanner.Text(), " ") toOutput := strings.Trim(scanner.Text(), " ")
_, _ = ptmx.WriteString(output(toOutput)) _, _ = ptmx.WriteString(output(toOutput))
} }
}() })
err = cmd.Wait() err = cmd.Wait()
ptmx.Close() ptmx.Close()

View File

@ -411,7 +411,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
for _, cmd := range cmds { for _, cmd := range cmds {
currentCmd := cmd currentCmd := cmd
go func() { go utils.Safe(func() {
stderr, err := currentCmd.StderrPipe() stderr, err := currentCmd.StderrPipe()
if err != nil { if err != nil {
c.Log.Error(err) c.Log.Error(err)
@ -432,7 +432,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
} }
wg.Done() wg.Done()
}() })
} }
wg.Wait() wg.Wait()

View File

@ -50,14 +50,14 @@ func (m *statusManager) getStatusString() string {
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing // WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (gui *Gui) WithWaitingStatus(name string, f func() error) error { func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
go func() { go utils.Safe(func() {
gui.statusManager.addWaitingStatus(name) gui.statusManager.addWaitingStatus(name)
defer func() { defer func() {
gui.statusManager.removeStatus(name) gui.statusManager.removeStatus(name)
}() }()
go func() { go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * 50) ticker := time.NewTicker(time.Millisecond * 50)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
@ -67,14 +67,14 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
} }
gui.renderString("appStatus", appStatus) gui.renderString("appStatus", appStatus)
} }
}() })
if err := f(); err != nil { if err := f(); err != nil {
gui.g.Update(func(g *gocui.Gui) error { gui.g.Update(func(g *gocui.Gui) error {
return gui.surfaceError(err) return gui.surfaceError(err)
}) })
} }
}() })
return nil return nil
} }

View File

@ -103,11 +103,11 @@ func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
if err := gui.createLoaderPanel(v, gui.Tr.FetchWait); err != nil { if err := gui.createLoaderPanel(v, gui.Tr.FetchWait); err != nil {
return err return err
} }
go func() { go utils.Safe(func() {
err := gui.fetch(true) err := gui.fetch(true)
gui.handleCredentialsPopup(err) gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC}) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
}() })
return nil return nil
} }
@ -385,7 +385,7 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
"to": branch.Name, "to": branch.Name,
}, },
) )
go func() { go utils.Safe(func() {
_ = gui.createLoaderPanel(v, message) _ = gui.createLoaderPanel(v, message)
if gui.State.Panels.Branches.SelectedLineIdx == 0 { if gui.State.Panels.Branches.SelectedLineIdx == 0 {
@ -395,7 +395,7 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
gui.handleCredentialsPopup(err) gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{BRANCHES}}) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{BRANCHES}})
} }
}() })
return nil return nil
} }

View File

@ -24,11 +24,11 @@ func (gui *Gui) handleCommitSelect() error {
state := gui.State.Panels.Commits state := gui.State.Panels.Commits
if state.SelectedLineIdx > 290 && state.LimitCommits { if state.SelectedLineIdx > 290 && state.LimitCommits {
state.LimitCommits = false state.LimitCommits = false
go func() { go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil { if err := gui.refreshCommitsWithLimit(); err != nil {
_ = gui.surfaceError(err) _ = gui.surfaceError(err)
} }
}() })
} }
gui.escapeLineByLinePanel() gui.escapeLineByLinePanel()
@ -60,11 +60,11 @@ func (gui *Gui) handleCommitSelect() error {
func (gui *Gui) refreshReflogCommitsConsideringStartup() { func (gui *Gui) refreshReflogCommitsConsideringStartup() {
switch gui.State.StartupStage { switch gui.State.StartupStage {
case INITIAL: case INITIAL:
go func() { go utils.Safe(func() {
_ = gui.refreshReflogCommits() _ = gui.refreshReflogCommits()
gui.refreshBranches() gui.refreshBranches()
gui.State.StartupStage = COMPLETE gui.State.StartupStage = COMPLETE
}() })
case COMPLETE: case COMPLETE:
_ = gui.refreshReflogCommits() _ = gui.refreshReflogCommits()
@ -78,14 +78,14 @@ func (gui *Gui) refreshCommits() error {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(2)
go func() { go utils.Safe(func() {
gui.refreshReflogCommitsConsideringStartup() gui.refreshReflogCommitsConsideringStartup()
gui.refreshBranches() gui.refreshBranches()
wg.Done() wg.Done()
}() })
go func() { go utils.Safe(func() {
_ = gui.refreshCommitsWithLimit() _ = gui.refreshCommitsWithLimit()
context, ok := gui.Contexts.CommitFiles.Context.GetParentContext() context, ok := gui.Contexts.CommitFiles.Context.GetParentContext()
if ok && context.GetKey() == BRANCH_COMMITS_CONTEXT_KEY { if ok && context.GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
@ -102,7 +102,7 @@ func (gui *Gui) refreshCommits() error {
} }
} }
wg.Done() wg.Done()
}() })
wg.Wait() wg.Wait()

View File

@ -189,14 +189,14 @@ func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error {
} }
confirmationView.Editable = opts.editable confirmationView.Editable = opts.editable
if opts.editable { if opts.editable {
go func() { go utils.Safe(func() {
// TODO: remove this wait (right now if you remove it the EditGotoToEndOfLine method doesn't seem to work) // TODO: remove this wait (right now if you remove it the EditGotoToEndOfLine method doesn't seem to work)
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
gui.g.Update(func(g *gocui.Gui) error { gui.g.Update(func(g *gocui.Gui) error {
confirmationView.EditGotoToEndOfLine() confirmationView.EditGotoToEndOfLine()
return nil return nil
}) })
}() })
} }
gui.renderString("confirmation", opts.prompt) gui.renderString("confirmation", opts.prompt)

View File

@ -727,7 +727,7 @@ func (gui *Gui) mustContextForContextKey(contextKey string) Context {
context, ok := gui.contextForContextKey(contextKey) context, ok := gui.contextForContextKey(contextKey)
if !ok { if !ok {
panic(fmt.Sprintf("context now found for key %s", contextKey)) panic(fmt.Sprintf("context not found for key %s", contextKey))
} }
return context return context

View File

@ -6,6 +6,7 @@ import (
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -120,7 +121,7 @@ func (gui *Gui) watchFilesForChanges() {
if gui.fileWatcher.Disabled { if gui.fileWatcher.Disabled {
return return
} }
go func() { go utils.Safe(func() {
for { for {
select { select {
// watch for events // watch for events
@ -141,5 +142,5 @@ func (gui *Gui) watchFilesForChanges() {
} }
} }
} }
}() })
} }

View File

@ -511,7 +511,7 @@ func (gui *Gui) pullFiles(opts PullFilesOptions) error {
mode := gui.Config.GetUserConfig().Git.Pull.Mode mode := gui.Config.GetUserConfig().Git.Pull.Mode
go gui.pullWithMode(mode, opts) go utils.Safe(func() { gui.pullWithMode(mode, opts) })
return nil return nil
} }
@ -551,7 +551,7 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
if err := gui.createLoaderPanel(v, gui.Tr.PushWait); err != nil { if err := gui.createLoaderPanel(v, gui.Tr.PushWait); err != nil {
return err return err
} }
go func() { go utils.Safe(func() {
branchName := gui.getCheckedOutBranch().Name branchName := gui.getCheckedOutBranch().Name
err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential) err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential)
if err != nil && !force && strings.Contains(err.Error(), "Updates were rejected") { if err != nil && !force && strings.Contains(err.Error(), "Updates were rejected") {
@ -571,7 +571,7 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
} }
gui.handleCredentialsPopup(err) gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC}) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
}() })
return nil return nil
} }

View File

@ -443,7 +443,7 @@ func (gui *Gui) Run() error {
defer g.Close() defer g.Close()
if recordEvents { if recordEvents {
go gui.recordEvents() go utils.Safe(gui.recordEvents)
} }
if gui.State.Modes.Filtering.Active() { if gui.State.Modes.Filtering.Active() {
@ -481,10 +481,10 @@ func (gui *Gui) Run() error {
gui.waitForIntro.Add(1) gui.waitForIntro.Add(1)
if gui.Config.GetUserConfig().Git.AutoFetch { if gui.Config.GetUserConfig().Git.AutoFetch {
go gui.startBackgroundFetch() go utils.Safe(gui.startBackgroundFetch)
} }
gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFilesAndSubmodules) gui.goEvery(time.Millisecond*50, gui.stopChan, gui.refreshFilesAndSubmodules)
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout())) g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
@ -499,7 +499,7 @@ func (gui *Gui) Run() error {
// otherwise it handles the error, possibly by quitting the application // otherwise it handles the error, possibly by quitting the application
func (gui *Gui) RunWithSubprocesses() error { func (gui *Gui) RunWithSubprocesses() error {
gui.StartTime = time.Now() gui.StartTime = time.Now()
go gui.replayRecordedEvents() go utils.Safe(gui.replayRecordedEvents)
for { for {
gui.stopChan = make(chan struct{}) gui.stopChan = make(chan struct{})
@ -584,18 +584,18 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
gui.waitForIntro.Add(len(tasks)) gui.waitForIntro.Add(len(tasks))
done := make(chan struct{}) done := make(chan struct{})
go func() { go utils.Safe(func() {
for _, task := range tasks { for _, task := range tasks {
go func() { go utils.Safe(func() {
if err := task(done); err != nil { if err := task(done); err != nil {
_ = gui.surfaceError(err) _ = gui.surfaceError(err)
} }
}() })
<-done <-done
gui.waitForIntro.Done() gui.waitForIntro.Done()
} }
}() })
} }
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error { func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
@ -614,7 +614,7 @@ func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
} }
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) { func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
go func() { go utils.Safe(func() {
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
defer ticker.Stop() defer ticker.Stop()
for { for {
@ -625,7 +625,7 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
return return
} }
} }
}() })
} }
func (gui *Gui) startBackgroundFetch() { func (gui *Gui) startBackgroundFetch() {
@ -641,7 +641,7 @@ func (gui *Gui) startBackgroundFetch() {
prompt: gui.Tr.NoAutomaticGitFetchBody, prompt: gui.Tr.NoAutomaticGitFetchBody,
}) })
} else { } else {
gui.goEvery(time.Second*60, gui.stopChan, func() error { gui.goEvery(time.Millisecond*50, gui.stopChan, func() error {
err := gui.fetch(false) err := gui.fetch(false)
return err return err
}) })

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
func recordingEvents() bool { func recordingEvents() bool {
@ -28,10 +29,10 @@ func (gui *Gui) replayRecordedEvents() {
return return
} }
go func() { go utils.Safe(func() {
time.Sleep(time.Second * 20) time.Sleep(time.Second * 20)
log.Fatal("20 seconds is up, lazygit recording took too long to complete") log.Fatal("20 seconds is up, lazygit recording took too long to complete")
}() })
events, err := gui.loadRecordedEvents() events, err := gui.loadRecordedEvents()
if err != nil { if err != nil {

View File

@ -110,7 +110,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshCommits() go utils.Safe(func() { gui.refreshCommits() })
} else { } else {
gui.refreshCommits() gui.refreshCommits()
} }
@ -122,7 +122,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshFilesAndSubmodules() go utils.Safe(func() { gui.refreshFilesAndSubmodules() })
} else { } else {
gui.refreshFilesAndSubmodules() gui.refreshFilesAndSubmodules()
} }
@ -134,7 +134,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshStashEntries() go utils.Safe(func() { gui.refreshStashEntries() })
} else { } else {
gui.refreshStashEntries() gui.refreshStashEntries()
} }
@ -146,7 +146,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshTags() go utils.Safe(func() { gui.refreshTags() })
} else { } else {
gui.refreshTags() gui.refreshTags()
} }
@ -158,7 +158,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshRemotes() go utils.Safe(func() { gui.refreshRemotes() })
} else { } else {
gui.refreshRemotes() gui.refreshRemotes()
} }

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -41,14 +42,14 @@ func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(
} }
func (m *ViewBufferManager) ReadLines(n int) { func (m *ViewBufferManager) ReadLines(n int) {
go func() { go utils.Safe(func() {
m.readLines <- n m.readLines <- n
}() })
} }
func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string, linesToRead int, onDone func()) func(chan struct{}) error { func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string, linesToRead int, onDone func()) func(chan struct{}) error {
return func(stop chan struct{}) error { return func(stop chan struct{}) error {
go func() { go utils.Safe(func() {
<-stop <-stop
if err := oscommands.Kill(cmd); err != nil { if err := oscommands.Kill(cmd); err != nil {
if !strings.Contains(err.Error(), "process already finished") { if !strings.Contains(err.Error(), "process already finished") {
@ -58,7 +59,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
if onDone != nil { if onDone != nil {
onDone() onDone()
} }
}() })
loadingMutex := sync.Mutex{} loadingMutex := sync.Mutex{}
@ -67,13 +68,13 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
done := make(chan struct{}) done := make(chan struct{})
go func() { go utils.Safe(func() {
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines) scanner.Split(bufio.ScanLines)
loaded := false loaded := false
go func() { go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * 100) ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop() defer ticker.Stop()
select { select {
@ -88,7 +89,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
case <-stop: case <-stop:
return return
} }
}() })
outer: outer:
for { for {
@ -139,7 +140,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string
} }
close(done) close(done)
}() })
m.readLines <- linesToRead m.readLines <- linesToRead
@ -157,10 +158,10 @@ func (t *ViewBufferManager) Close() {
c := make(chan struct{}) c := make(chan struct{})
go func() { go utils.Safe(func() {
t.currentTask.Stop() t.currentTask.Stop()
c <- struct{}{} c <- struct{}{}
}() })
select { select {
case <-c: case <-c:
@ -175,7 +176,7 @@ func (t *ViewBufferManager) Close() {
// 2) string based, where the manager can also be asked to read more lines // 2) string based, where the manager can also be asked to read more lines
func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error { func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
go func() { go utils.Safe(func() {
m.taskIDMutex.Lock() m.taskIDMutex.Lock()
m.newTaskId++ m.newTaskId++
taskID := m.newTaskId taskID := m.newTaskId
@ -202,14 +203,14 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
f: f, f: f,
} }
go func() { go utils.Safe(func() {
if err := f(stop); err != nil { if err := f(stop); err != nil {
m.Log.Error(err) // might need an onError callback m.Log.Error(err) // might need an onError callback
} }
close(notifyStopped) close(notifyStopped)
}() })
}() })
return nil return nil
} }

View File

@ -145,12 +145,12 @@ func (u *Updater) CheckForNewUpdate(onFinish func(string, error) error, userRequ
return return
} }
go func() { go utils.Safe(func() {
newVersion, err := u.checkForNewUpdate() newVersion, err := u.checkForNewUpdate()
if err = onFinish(newVersion, err); err != nil { if err = onFinish(newVersion, err); err != nil {
u.Log.Error(err) u.Log.Error(err)
} }
}() })
} }
func (u *Updater) skipUpdateCheck() bool { func (u *Updater) skipUpdateCheck() bool {
@ -235,12 +235,12 @@ func (u *Updater) getBinaryUrl(newVersion string) (string, error) {
// Update downloads the latest binary and replaces the current binary with it // Update downloads the latest binary and replaces the current binary with it
func (u *Updater) Update(newVersion string, onFinish func(error) error) { func (u *Updater) Update(newVersion string, onFinish func(error) error) {
go func() { go utils.Safe(func() {
err := u.update(newVersion) err := u.update(newVersion)
if err = onFinish(err); err != nil { if err = onFinish(err); err != nil {
u.Log.Error(err) u.Log.Error(err)
} }
}() })
} }
func (u *Updater) update(newVersion string) error { func (u *Updater) update(newVersion string) error {

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/termbox-go"
) )
// SplitLines takes a multiline string and splits it on newlines // SplitLines takes a multiline string and splits it on newlines
@ -360,3 +361,18 @@ func ResolveTemplate(templateStr string, object interface{}) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
// Safe will close termbox if a panic occurs so that we don't end up in a malformed
// terminal state
func Safe(f func()) {
panicking := true
defer func() {
if panicking {
termbox.Close()
}
}()
f()
panicking = false
}