Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cdsctl): Sync render and load with chan to prevent concurrent dat… #3364

Merged
merged 1 commit into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 61 additions & 27 deletions cli/cdsctl/monitoring.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"fmt"
"math"
"net/url"
Expand Down Expand Up @@ -38,7 +39,38 @@ func monitoringRun(v cli.Values) (interface{}, error) {
ui.init()
ui.staticRender()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

ui.loadChan = make(chan func() error)
go func() {
for {
select {
case <-ctx.Done():
return
case f := <-ui.loadChan:
if err := f(); err != nil {
ui.msg = fmt.Sprintf("[%s](bg-red)", err.Error())
}
ui.render()
}
}
}()

ui.renderChan = make(chan func())
go func() {
for {
select {
case <-ctx.Done():
return
case f := <-ui.renderChan:
f()
}
}
}()

termui.Loop()

return nil, nil
}

Expand Down Expand Up @@ -71,9 +103,14 @@ type Termui struct {
queue *cli.ScrollableList
statusHatcheriesWorkers *cli.ScrollableList
statusServices *cli.ScrollableList

loadChan chan func() error
renderChan chan func()
}

func (ui *Termui) loadData() error {
func (ui *Termui) loadData() { ui.loadChan <- ui.execLoadData }

func (ui *Termui) execLoadData() error {
urlUI, err := client.ConfigUser()
if err != nil {
return err
Expand Down Expand Up @@ -119,10 +156,6 @@ func (ui *Termui) loadData() error {
return err
}

if err := ui.loadQueue(); err != nil {
return err
}

start = time.Now()
if _, err := client.QueueCountWorkflowNodeJobRun(nil, nil, "", nil); err != nil {
return err
Expand All @@ -132,16 +165,9 @@ func (ui *Termui) loadData() error {
return nil
}

func (ui *Termui) loadQueue() error {
switch ui.queueTabSelected {
case 0:
ui.statusSelected = []sdk.Status{sdk.StatusWaiting}
case 1:
ui.statusSelected = []sdk.Status{sdk.StatusBuilding}
case 2:
ui.statusSelected = []sdk.Status{sdk.StatusBuilding, sdk.StatusWaiting}
}
func (ui *Termui) loadQueue() { ui.loadChan <- ui.execLoadQueue }

func (ui *Termui) execLoadQueue() error {
var err error

start := time.Now()
Expand All @@ -165,10 +191,8 @@ func (ui *Termui) init() {
// init termui handlers
termui.Merge("/timer/2s", termui.NewTimerCh(time.Second*2))
termui.Handle("/timer/2s", func(e termui.Event) {
if err := ui.loadData(); err != nil {
ui.msg = fmt.Sprintf("[%s](bg-red)", err.Error())
}
ui.render()
ui.loadData()
ui.loadQueue()
})
termui.Handle("/sys/kbd/h", func(termui.Event) {
ui.msg = fmt.Sprintf("shortcuts: ⇥ to select panel, esc to deselect panel, ↑ and ↓ to select line, ← and → to change filters, ↩ to open in ui")
Expand Down Expand Up @@ -201,6 +225,7 @@ func (ui *Termui) init() {
ui.times = newPar()

ui.selected = nothingSelected
ui.updateSelectStatus()

// prepare queue list
ui.queue = cli.NewScrollableList()
Expand Down Expand Up @@ -274,7 +299,9 @@ func (ui *Termui) staticRender() {
ui.commonRender()
}

func (ui *Termui) render() {
func (ui *Termui) render() { ui.renderChan <- ui.execRender }

func (ui *Termui) execRender() {
if ui.msg == "" {
ui.times.Text = fmt.Sprintf(
"[count queue wf %s](fg-cyan) | [queue wf %s](fg-cyan) | [workers %s](fg-cyan) | [wModels %s](fg-cyan) | [status %s](fg-cyan)",
Expand Down Expand Up @@ -368,9 +395,8 @@ func (ui *Termui) incrementQueueFilter() {
} else {
ui.queueTabSelected = 0
}
if err := ui.loadQueue(); err != nil {
ui.msg = fmt.Sprintf("[%s](bg-red)", err.Error())
}
ui.updateSelectStatus()
ui.loadQueue()
}

func (ui *Termui) decrementQueueFilter() {
Expand All @@ -379,8 +405,18 @@ func (ui *Termui) decrementQueueFilter() {
} else {
ui.queueTabSelected = 2
}
if err := ui.loadQueue(); err != nil {
ui.msg = fmt.Sprintf("[%s](bg-red)", err.Error())
ui.updateSelectStatus()
ui.loadQueue()
}

func (ui *Termui) updateSelectStatus() {
switch ui.queueTabSelected {
case 0:
ui.statusSelected = []sdk.Status{sdk.StatusWaiting}
case 1:
ui.statusSelected = []sdk.Status{sdk.StatusBuilding}
case 2:
ui.statusSelected = []sdk.Status{sdk.StatusBuilding, sdk.StatusWaiting}
}
}

Expand Down Expand Up @@ -551,10 +587,8 @@ func (ui *Termui) updateQueue(baseURL string) {
}
ui.queue.SetItems(items...)

jobCount := len(ui.pipelineBuildJob) + len(ui.workflowNodeJobRun)

ui.queue.BorderLabel = fmt.Sprintf(" Queue(%s):%d ",
strings.Join(sdk.StatusToStrings(ui.statusSelected), ","), jobCount)
strings.Join(sdk.StatusToStrings(ui.statusSelected), ","), len(items))

for _, s := range ui.statusSelected {
switch s {
Expand Down
33 changes: 22 additions & 11 deletions cli/scrollablelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ licence: https://github.com/mikepea/go-jira-ui/blob/master/LICENSE
*/

import (
"sync"

ui "github.com/gizak/termui"
)

// Default color values.
const (
DefaultItemFgColor = ui.ColorWhite
DefaultItemBgColor = ui.ColorBlack
DefaultItemBgColor = ui.ColorDefault
DefaultCursorFgColor = ui.ColorBlack
DefaultCursorBgColor = ui.ColorWhite
)
Expand All @@ -41,6 +43,10 @@ type ScrollableList struct {
// The items in the list
items []string

// lock should be used to prevent inconsistency between offset, cursor and items
// because termui rendering is async (Buffer func)
dataLock sync.Mutex

// The window's offset relative to the start of `items`
offset int

Expand Down Expand Up @@ -73,24 +79,24 @@ func (sl *ScrollableList) GetItems() []string { return sl.items }

// SetItems update list's items and fix cursor.
func (sl *ScrollableList) SetItems(is ...string) {
sl.items = is
sl.dataLock.Lock()
defer sl.dataLock.Unlock()

h := sl.getInnerheight()

// fix cursor and offset
if len(sl.items) < h+sl.offset {
if len(is) < h+sl.offset {
sl.offset = 0
}
if len(sl.items) <= sl.cursor {
if len(is) <= sl.cursor {
sl.cursor = 0
}

sl.items = is
}

// SetHeader to list.
func (sl *ScrollableList) SetHeader(h string) { sl.header = h }

func (sl *ScrollableList) render() { ui.Render(sl) }

func min(a, b int) int {
if a < b {
return a
Expand All @@ -107,6 +113,9 @@ func max(a, b int) int {

// Buffer implements the termui.Bufferer interface
func (sl *ScrollableList) Buffer() ui.Buffer {
sl.dataLock.Lock()
defer sl.dataLock.Unlock()

b := sl.Block.Buffer()

h := sl.getInnerheight()
Expand Down Expand Up @@ -160,6 +169,9 @@ func (sl *ScrollableList) getInnerheight() int {
// CursorDown move the cursor down one row; moving the cursor out of the window will cause
// scrolling.
func (sl *ScrollableList) CursorDown() {
sl.dataLock.Lock()
defer sl.dataLock.Unlock()

if sl.cursor < len(sl.items)-1 {
sl.cursor++
}
Expand All @@ -168,19 +180,18 @@ func (sl *ScrollableList) CursorDown() {
if sl.cursor >= h+sl.offset {
sl.offset = (sl.cursor - h) + 1
}

sl.render()
}

// CursorUp move the cursor up one row; moving the cursor out of the window will cause
// scrolling.
func (sl *ScrollableList) CursorUp() {
sl.dataLock.Lock()
defer sl.dataLock.Unlock()

if sl.cursor > 0 {
sl.cursor--
}
if sl.cursor < sl.offset {
sl.offset = sl.cursor
}

sl.render()
}