Skip to content

Commit

Permalink
fix(cdsctl): Sync render and load with chan to prevent concurrent dat…
Browse files Browse the repository at this point in the history
…a modification (#3364)
  • Loading branch information
richardlt authored and yesnault committed Sep 26, 2018
1 parent 303bd97 commit 1395d0c
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 38 deletions.
88 changes: 61 additions & 27 deletions cli/cdsctl/monitoring.go
@@ -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
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()
}

0 comments on commit 1395d0c

Please sign in to comment.