use context instead of struct channel

pull/392/head
Jesse Duffield 2 years ago
parent 7f38cf2ab2
commit b0241c79db

@ -18,25 +18,23 @@ import (
func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc { func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{ return gui.NewTickerTask(TickerTaskOpts{
Func: func(stop, notifyStopped chan struct{}) { Func: func(ctx context.Context, notifyStopped chan struct{}) {
gui.renderContainerLogsToMainAux(container, stop, notifyStopped) gui.renderContainerLogsToMainAux(container, ctx, notifyStopped)
}, },
Duration: time.Millisecond * 200, Duration: time.Millisecond * 200,
// TODO: see why this isn't working (when switching from Top tab to Logs tab in the services panel, the tops tab's content isn't removed) // TODO: see why this isn't working (when switching from Top tab to Logs tab in the services panel, the tops tab's content isn't removed)
Before: func(stop chan struct{}) { gui.clearMainView() }, Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: true, Autoscroll: true,
}) })
} }
func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, stop, notifyStopped chan struct{}) { func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, ctx context.Context, notifyStopped chan struct{}) {
gui.clearMainView() gui.clearMainView()
defer func() { defer func() {
notifyStopped <- struct{}{} notifyStopped <- struct{}{}
}() }()
ctx := stopIntoCtx(stop)
mainView := gui.Views.Main mainView := gui.Views.Main
if err := gui.writeContainerLogs(container, ctx, mainView); err != nil { if err := gui.writeContainerLogs(container, ctx, mainView); err != nil {
@ -49,7 +47,7 @@ func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, stop
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
result, err := container.Inspect() result, err := container.Inspect()

@ -212,7 +212,7 @@ func (gui *Gui) containerConfigStr(container *commands.Container) string {
func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFunc { func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{ return gui.NewTickerTask(TickerTaskOpts{
Func: func(stop, notifyStopped chan struct{}) { Func: func(ctx context.Context, notifyStopped chan struct{}) {
contents, err := presentation.RenderStats(gui.Config.UserConfig, container, gui.Views.Main.Width()) contents, err := presentation.RenderStats(gui.Config.UserConfig, container, gui.Views.Main.Width())
if err != nil { if err != nil {
_ = gui.createErrorPanel(err.Error()) _ = gui.createErrorPanel(err.Error())
@ -221,26 +221,15 @@ func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFu
gui.reRenderStringMain(contents) gui.reRenderStringMain(contents)
}, },
Duration: time.Second, Duration: time.Second,
Before: func(stop chan struct{}) { gui.clearMainView() }, Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: false, // wrapping looks bad here so we're overriding the config value Wrap: false, // wrapping looks bad here so we're overriding the config value
Autoscroll: false, Autoscroll: false,
}) })
} }
// TODO: remove this and just use a context
func stopIntoCtx(stop chan struct{}) context.Context {
ctx, ctxCancel := context.WithCancel(context.Background())
go func() {
<-stop
ctxCancel()
}()
return ctx
}
func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc { func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{ return gui.NewTickerTask(TickerTaskOpts{
Func: func(stop, notifyStopped chan struct{}) { Func: func(ctx context.Context, notifyStopped chan struct{}) {
ctx := stopIntoCtx(stop)
contents, err := container.RenderTop(ctx) contents, err := container.RenderTop(ctx)
if err != nil { if err != nil {
gui.RenderStringMain(err.Error()) gui.RenderStringMain(err.Error())
@ -249,7 +238,7 @@ func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc
gui.reRenderStringMain(contents) gui.reRenderStringMain(contents)
}, },
Duration: time.Second, Duration: time.Second,
Before: func(stop chan struct{}) { gui.clearMainView() }, Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: false, Autoscroll: false,
}) })

@ -76,13 +76,6 @@ func (gui *Gui) jumpToTopMain(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) onMainTabClick(tabIndex int) error { func (gui *Gui) onMainTabClick(tabIndex int) error {
gui.Log.Warn(tabIndex) gui.Log.Warn(tabIndex)
viewName := gui.currentViewName()
mainView := gui.Views.Main
if viewName == "main" && mainView.ParentView != nil {
viewName = mainView.ParentView.Name()
}
currentSidePanel, ok := gui.currentSidePanel() currentSidePanel, ok := gui.currentSidePanel()
if !ok { if !ok {

@ -41,10 +41,6 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
} }
} }
// } else if v.ParentView != nil && binding.ViewName == v.ParentView.Name() {
// // only add this if we don't have our own matching binding
// bindingsPanel = append(bindingsPanel, binding)
// append dummy element to have a separator between // append dummy element to have a separator between
// panel and global keybindings // panel and global keybindings
bindingsPanel = append(bindingsPanel, &Binding{}) bindingsPanel = append(bindingsPanel, &Binding{})

@ -1,6 +1,7 @@
package panels package panels
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@ -74,7 +75,7 @@ type IGui interface {
IgnoreStrings() []string IgnoreStrings() []string
Update(func() error) Update(func() error)
QueueTask(f func(stop chan struct{})) error QueueTask(f func(ctx context.Context)) error
} }
func (self *SideListPanel[T]) HandleClick() error { func (self *SideListPanel[T]) HandleClick() error {

@ -2,6 +2,7 @@ package gui
import ( import (
"bytes" "bytes"
"context"
"path" "path"
"strings" "strings"
@ -115,7 +116,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
return gui.NewTask(TaskOpts{ return gui.NewTask(TaskOpts{
Autoscroll: true, Autoscroll: true,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Func: func(stop chan struct{}) { Func: func(ctx context.Context) {
gui.clearMainView() gui.clearMainView()
cmd := gui.OSCommand.RunCustomCommand( cmd := gui.OSCommand.RunCustomCommand(
@ -132,7 +133,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
_ = cmd.Start() _ = cmd.Start()
go func() { go func() {
<-stop <-ctx.Done()
if err := gui.OSCommand.Kill(cmd); err != nil { if err := gui.OSCommand.Kill(cmd); err != nil {
gui.Log.Error(err) gui.Log.Error(err)
} }

@ -1,6 +1,7 @@
package gui package gui
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@ -106,8 +107,7 @@ func (gui *Gui) renderServiceStats(service *commands.Service) tasks.TaskFunc {
func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc { func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{ return gui.NewTickerTask(TickerTaskOpts{
Func: func(stop, notifyStopped chan struct{}) { Func: func(ctx context.Context, notifyStopped chan struct{}) {
ctx := stopIntoCtx(stop)
contents, err := service.RenderTop(ctx) contents, err := service.RenderTop(ctx)
if err != nil { if err != nil {
gui.RenderStringMain(err.Error()) gui.RenderStringMain(err.Error())
@ -116,7 +116,7 @@ func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc {
gui.reRenderStringMain(contents) gui.reRenderStringMain(contents)
}, },
Duration: time.Second, Duration: time.Second,
Before: func(stop chan struct{}) { gui.clearMainView() }, Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: false, Autoscroll: false,
}) })

@ -1,12 +1,13 @@
package gui package gui
import ( import (
"context"
"time" "time"
"github.com/jesseduffield/lazydocker/pkg/tasks" "github.com/jesseduffield/lazydocker/pkg/tasks"
) )
func (gui *Gui) QueueTask(f func(stop chan struct{})) error { func (gui *Gui) QueueTask(f func(ctx context.Context)) error {
return gui.taskManager.NewTask(f) return gui.taskManager.NewTask(f)
} }
@ -19,13 +20,13 @@ type RenderStringTaskOpts struct {
type TaskOpts struct { type TaskOpts struct {
Autoscroll bool Autoscroll bool
Wrap bool Wrap bool
Func func(stop chan struct{}) Func func(ctx context.Context)
} }
type TickerTaskOpts struct { type TickerTaskOpts struct {
Duration time.Duration Duration time.Duration
Before func(stop chan struct{}) Before func(ctx context.Context)
Func func(stop, notifyStopped chan struct{}) Func func(ctx context.Context, notifyStopped chan struct{})
Autoscroll bool Autoscroll bool
Wrap bool Wrap bool
} }
@ -34,10 +35,7 @@ func (gui *Gui) NewRenderStringTask(opts RenderStringTaskOpts) tasks.TaskFunc {
taskOpts := TaskOpts{ taskOpts := TaskOpts{
Autoscroll: opts.Autoscroll, Autoscroll: opts.Autoscroll,
Wrap: opts.Wrap, Wrap: opts.Wrap,
Func: func(stop chan struct{}) { Func: func(ctx context.Context) {
// GetStrContent may be a slow function, so we clear the main view first
// so that we're not seeing the previous tab's content appear.
gui.clearMainView()
gui.RenderStringMain(opts.GetStrContent()) gui.RenderStringMain(opts.GetStrContent())
}, },
} }
@ -55,12 +53,12 @@ func (gui *Gui) NewSimpleRenderStringTask(getContent func() string) tasks.TaskFu
} }
func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc { func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc {
return func(stop chan struct{}) { return func(ctx context.Context) {
mainView := gui.Views.Main mainView := gui.Views.Main
mainView.Autoscroll = opts.Autoscroll mainView.Autoscroll = opts.Autoscroll
mainView.Wrap = opts.Wrap mainView.Wrap = opts.Wrap
opts.Func(stop) opts.Func(ctx)
} }
} }
@ -70,25 +68,25 @@ func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc {
func (gui *Gui) NewTickerTask(opts TickerTaskOpts) tasks.TaskFunc { func (gui *Gui) NewTickerTask(opts TickerTaskOpts) tasks.TaskFunc {
notifyStopped := make(chan struct{}, 10) notifyStopped := make(chan struct{}, 10)
task := func(stop chan struct{}) { task := func(ctx context.Context) {
if opts.Before != nil { if opts.Before != nil {
opts.Before(stop) opts.Before(ctx)
} }
tickChan := time.NewTicker(opts.Duration) tickChan := time.NewTicker(opts.Duration)
defer tickChan.Stop() defer tickChan.Stop()
// calling f first so that we're not waiting for the first tick // calling f first so that we're not waiting for the first tick
opts.Func(stop, notifyStopped) opts.Func(ctx, notifyStopped)
for { for {
select { select {
case <-notifyStopped: case <-notifyStopped:
gui.Log.Info("exiting ticker task due to notifyStopped channel") gui.Log.Info("exiting ticker task due to notifyStopped channel")
return return
case <-stop: case <-ctx.Done():
gui.Log.Info("exiting ticker task due to stopped channel") gui.Log.Info("exiting ticker task due to stopped channel")
return return
case <-tickChan.C: case <-tickChan.C:
gui.Log.Info("running ticker task again") gui.Log.Info("running ticker task again")
opts.Func(stop, notifyStopped) opts.Func(ctx, notifyStopped)
} }
} }
} }

@ -1,6 +1,7 @@
package tasks package tasks
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@ -19,15 +20,16 @@ type TaskManager struct {
} }
type Task struct { type Task struct {
stop chan struct{} ctx context.Context
cancel context.CancelFunc
stopped bool stopped bool
stopMutex deadlock.Mutex stopMutex deadlock.Mutex
notifyStopped chan struct{} notifyStopped chan struct{}
Log *logrus.Entry Log *logrus.Entry
f func(chan struct{}) f func(ctx context.Context)
} }
type TaskFunc func(stop chan struct{}) type TaskFunc func(ctx context.Context)
func NewTaskManager(log *logrus.Entry, translationSet *i18n.TranslationSet) *TaskManager { func NewTaskManager(log *logrus.Entry, translationSet *i18n.TranslationSet) *TaskManager {
return &TaskManager{Log: log, Tr: translationSet} return &TaskManager{Log: log, Tr: translationSet}
@ -54,7 +56,7 @@ func (t *TaskManager) Close() {
} }
} }
func (t *TaskManager) NewTask(f func(stop chan struct{})) error { func (t *TaskManager) NewTask(f func(ctx context.Context)) error {
go func() { go func() {
t.taskIDMutex.Lock() t.taskIDMutex.Lock()
t.newTaskId++ t.newTaskId++
@ -67,7 +69,7 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
return return
} }
stop := make(chan struct{}, 1) // we don't want to block on this in case the task already returned ctx, cancel := context.WithCancel(context.Background())
notifyStopped := make(chan struct{}) notifyStopped := make(chan struct{})
if t.currentTask != nil { if t.currentTask != nil {
@ -77,14 +79,15 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
} }
t.currentTask = &Task{ t.currentTask = &Task{
stop: stop, ctx: ctx,
cancel: cancel,
notifyStopped: notifyStopped, notifyStopped: notifyStopped,
Log: t.Log, Log: t.Log,
f: f, f: f,
} }
go func() { go func() {
f(stop) f(ctx)
t.Log.Info("returned from function, closing notifyStopped") t.Log.Info("returned from function, closing notifyStopped")
close(notifyStopped) close(notifyStopped)
}() }()
@ -99,7 +102,8 @@ func (t *Task) Stop() {
if t.stopped { if t.stopped {
return return
} }
close(t.stop)
t.cancel()
t.Log.Info("closed stop channel, waiting for notifyStopped message") t.Log.Info("closed stop channel, waiting for notifyStopped message")
<-t.notifyStopped <-t.notifyStopped
t.Log.Info("received notifystopped message") t.Log.Info("received notifystopped message")
@ -109,28 +113,28 @@ func (t *Task) Stop() {
// NewTickerTask is a convenience function for making a new task that repeats some action once per e.g. second // NewTickerTask is a convenience function for making a new task that repeats some action once per e.g. second
// the before function gets called after the lock is obtained, but before the ticker starts. // the before function gets called after the lock is obtained, but before the ticker starts.
// if you handle a message on the stop channel in f() you need to send a message on the notifyStopped channel because returning is not sufficient. Here, unlike in a regular task, simply returning means we're now going to wait till the next tick to run again. // if you handle a message on the stop channel in f() you need to send a message on the notifyStopped channel because returning is not sufficient. Here, unlike in a regular task, simply returning means we're now going to wait till the next tick to run again.
func (t *TaskManager) NewTickerTask(duration time.Duration, before func(stop chan struct{}), f func(stop, notifyStopped chan struct{})) error { func (t *TaskManager) NewTickerTask(duration time.Duration, before func(ctx context.Context), f func(ctx context.Context, notifyStopped chan struct{})) error {
notifyStopped := make(chan struct{}, 10) notifyStopped := make(chan struct{}, 10)
return t.NewTask(func(stop chan struct{}) { return t.NewTask(func(ctx context.Context) {
if before != nil { if before != nil {
before(stop) before(ctx)
} }
tickChan := time.NewTicker(duration) tickChan := time.NewTicker(duration)
defer tickChan.Stop() defer tickChan.Stop()
// calling f first so that we're not waiting for the first tick // calling f first so that we're not waiting for the first tick
f(stop, notifyStopped) f(ctx, notifyStopped)
for { for {
select { select {
case <-notifyStopped: case <-notifyStopped:
t.Log.Info("exiting ticker task due to notifyStopped channel") t.Log.Info("exiting ticker task due to notifyStopped channel")
return return
case <-stop: case <-ctx.Done():
t.Log.Info("exiting ticker task due to stopped cahnnel") t.Log.Info("exiting ticker task due to stopped cahnnel")
return return
case <-tickChan.C: case <-tickChan.C:
t.Log.Info("running ticker task again") t.Log.Info("running ticker task again")
f(stop, notifyStopped) f(ctx, notifyStopped)
} }
} }
}) })

Loading…
Cancel
Save