Single point of bar complete
Vladimir Bauer
8 years ago
| 31 | 31 | priority int |
| 32 | 32 | index int |
| 33 | 33 | |
| 34 | // the flag is set from Progress monitor goroutine only | |
| 35 | completed bool | |
| 36 | ||
| 34 | 37 | operateState chan func(*bState) |
| 35 | 38 | done chan struct{} |
| 36 | 39 | shutdown chan struct{} |
| 37 | once sync.Once | |
| 38 | 40 | |
| 39 | 41 | // cacheState is used after done is closed |
| 40 | 42 | cacheState *bState |
| 53 | 55 | trimLeftSpace bool |
| 54 | 56 | trimRightSpace bool |
| 55 | 57 | completed bool |
| 56 | aborted bool | |
| 58 | removed bool | |
| 57 | 59 | dynamic bool |
| 58 | 60 | startTime time.Time |
| 59 | 61 | timeElapsed time.Duration |
| 63 | 65 | prependFuncs []decor.DecoratorFunc |
| 64 | 66 | refill *refill |
| 65 | 67 | bufP, bufB, bufA *bytes.Buffer |
| 66 | panic string | |
| 68 | panicMsg string | |
| 67 | 69 | } |
| 68 | 70 | refill struct { |
| 69 | 71 | char rune |
| 70 | 72 | till int64 |
| 71 | 73 | } |
| 72 | bufReader struct { | |
| 74 | toRenderReader struct { | |
| 73 | 75 | io.Reader |
| 74 | completed bool | |
| 76 | toComplete bool | |
| 77 | toRemove bool | |
| 75 | 78 | } |
| 76 | 79 | ) |
| 77 | 80 | |
| 147 | 150 | } |
| 148 | 151 | select { |
| 149 | 152 | case b.operateState <- func(s *bState) { |
| 153 | if s.completed { | |
| 154 | return | |
| 155 | } | |
| 150 | 156 | next := time.Now() |
| 151 | 157 | if s.current == 0 { |
| 152 | 158 | s.startTime = next |
| 252 | 258 | } |
| 253 | 259 | } |
| 254 | 260 | |
| 255 | // InProgress returns true, while progress is running. | |
| 256 | // Can be used as condition in for loop | |
| 257 | func (b *Bar) InProgress() bool { | |
| 258 | select { | |
| 261 | // Completed reports whether the bar is in completed state | |
| 262 | func (b *Bar) Completed() bool { | |
| 263 | result := make(chan bool, 1) | |
| 264 | select { | |
| 265 | case b.operateState <- func(s *bState) { result <- s.completed }: | |
| 266 | return <-result | |
| 267 | case <-b.done: | |
| 268 | return b.cacheState.completed | |
| 269 | } | |
| 270 | } | |
| 271 | ||
| 272 | // Complete stops bar's progress tracking, but doesn't remove the bar from rendering queue. | |
| 273 | // If you need to remove, invoke Progress.RemoveBar(*Bar) instead. | |
| 274 | func (b *Bar) Complete() { | |
| 275 | b.askToComplete(false) | |
| 276 | } | |
| 277 | ||
| 278 | func (b *Bar) askToComplete(toRemove bool) bool { | |
| 279 | result := make(chan bool, 1) | |
| 280 | select { | |
| 281 | case b.operateState <- func(s *bState) { | |
| 282 | s.removed = toRemove | |
| 283 | s.completed = true | |
| 284 | result <- true | |
| 285 | }: | |
| 286 | return <-result | |
| 259 | 287 | case <-b.done: |
| 260 | 288 | return false |
| 261 | default: | |
| 262 | return true | |
| 263 | } | |
| 264 | } | |
| 265 | ||
| 266 | // Complete stops bar's progress tracking, but doesn't remove the bar. | |
| 267 | // If you need to remove, call Progress.RemoveBar(*Bar) instead. | |
| 268 | func (b *Bar) Complete() { | |
| 269 | b.once.Do(func() { | |
| 270 | close(b.shutdown) | |
| 271 | }) | |
| 289 | } | |
| 272 | 290 | } |
| 273 | 291 | |
| 274 | 292 | func (b *Bar) serve(s *bState, wg *sync.WaitGroup, cancel <-chan struct{}) { |
| 275 | defer func() { | |
| 276 | b.cacheState = s | |
| 277 | close(b.done) | |
| 278 | wg.Done() | |
| 279 | }() | |
| 280 | 293 | for { |
| 281 | 294 | select { |
| 282 | 295 | case op := <-b.operateState: |
| 283 | 296 | op(s) |
| 284 | 297 | case <-cancel: |
| 285 | s.aborted = true | |
| 298 | s.completed = true | |
| 299 | cancel = nil | |
| 300 | case <-b.shutdown: | |
| 301 | b.cacheState = s | |
| 302 | close(b.done) | |
| 303 | wg.Done() | |
| 286 | 304 | return |
| 287 | case <-b.shutdown: | |
| 288 | return | |
| 289 | } | |
| 290 | } | |
| 291 | } | |
| 292 | ||
| 293 | func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan *bufReader { | |
| 294 | ch := make(chan *bufReader, 1) | |
| 305 | } | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan *toRenderReader { | |
| 310 | ch := make(chan *toRenderReader, 1) | |
| 295 | 311 | |
| 296 | 312 | go func() { |
| 297 | 313 | select { |
| 298 | 314 | case b.operateState <- func(s *bState) { |
| 315 | var r io.Reader | |
| 299 | 316 | defer func() { |
| 300 | 317 | // recovering if external decorators panic |
| 301 | 318 | if p := recover(); p != nil { |
| 302 | s.panic = fmt.Sprintf("b#%02d panic: %v\n", s.id, p) | |
| 319 | s.panicMsg = fmt.Sprintf("b#%02d panic: %v\n", s.id, p) | |
| 303 | 320 | s.prependFuncs = nil |
| 304 | 321 | s.appendFuncs = nil |
| 305 | ||
| 306 | ch <- &bufReader{strings.NewReader(s.panic), true} | |
| 322 | s.completed = true | |
| 323 | r = strings.NewReader(s.panicMsg) | |
| 307 | 324 | } |
| 308 | close(ch) | |
| 325 | ch <- &toRenderReader{r, s.completed, s.removed} | |
| 309 | 326 | }() |
| 310 | 327 | s.draw(tw, prependWs, appendWs) |
| 311 | ch <- &bufReader{io.MultiReader(s.bufP, s.bufB, s.bufA), s.completed} | |
| 328 | r = io.MultiReader(s.bufP, s.bufB, s.bufA) | |
| 312 | 329 | }: |
| 313 | 330 | case <-b.done: |
| 314 | 331 | s := b.cacheState |
| 315 | 332 | var r io.Reader |
| 316 | if s.panic != "" { | |
| 317 | r = strings.NewReader(s.panic) | |
| 333 | if s.panicMsg != "" { | |
| 334 | r = strings.NewReader(s.panicMsg) | |
| 318 | 335 | } else { |
| 319 | 336 | s.draw(tw, prependWs, appendWs) |
| 320 | 337 | r = io.MultiReader(s.bufP, s.bufB, s.bufA) |
| 321 | 338 | } |
| 322 | ch <- &bufReader{r, false} | |
| 323 | close(ch) | |
| 339 | ch <- &toRenderReader{r, s.completed, s.removed} | |
| 324 | 340 | } |
| 325 | 341 | }() |
| 326 | 342 | |
| 424 | 440 | return &decor.Statistics{ |
| 425 | 441 | ID: s.id, |
| 426 | 442 | Completed: s.completed, |
| 427 | Aborted: s.aborted, | |
| 443 | Removed: s.removed, | |
| 428 | 444 | Total: s.total, |
| 429 | 445 | Current: s.current, |
| 430 | 446 | StartTime: s.startTime, |
| 30 | 30 | type Statistics struct { |
| 31 | 31 | ID int |
| 32 | 32 | Completed bool |
| 33 | Aborted bool | |
| 33 | Removed bool | |
| 34 | 34 | Total int64 |
| 35 | 35 | Current int64 |
| 36 | 36 | StartTime time.Time |
| 54 | 54 | Listen []chan int |
| 55 | 55 | Result []chan int |
| 56 | 56 | } |
| 57 | renderedBar struct { | |
| 57 | toRenderSnapshot struct { | |
| 58 | 58 | bar *Bar |
| 59 | pipe <-chan *bufReader | |
| 59 | pipe <-chan *toRenderReader | |
| 60 | 60 | } |
| 61 | 61 | ) |
| 62 | 62 | |
| 72 | 72 | cw: cwriter.New(os.Stdout), |
| 73 | 73 | rr: prr, |
| 74 | 74 | ticker: time.NewTicker(prr), |
| 75 | cancel: make(chan struct{}), | |
| 76 | 75 | } |
| 77 | 76 | |
| 78 | 77 | for _, opt := range options { |
| 110 | 109 | } |
| 111 | 110 | } |
| 112 | 111 | |
| 113 | // RemoveBar removes bar at any time. | |
| 112 | // RemoveBar removes the bar at next render cycle | |
| 114 | 113 | func (p *Progress) RemoveBar(b *Bar) bool { |
| 115 | result := make(chan bool, 1) | |
| 116 | select { | |
| 117 | case p.operateState <- func(s *pState) { | |
| 118 | if heap.Remove(s.bHeap, b.index) != nil { | |
| 119 | s.heapUpdated = true | |
| 120 | b.Complete() | |
| 121 | result <- true | |
| 122 | } else { | |
| 123 | result <- false | |
| 124 | } | |
| 125 | }: | |
| 126 | return <-result | |
| 127 | case <-p.done: | |
| 128 | return false | |
| 129 | } | |
| 114 | return b.askToComplete(true) | |
| 130 | 115 | } |
| 131 | 116 | |
| 132 | 117 | // UpdateBarPriority provides a way to change bar's order position. |
| 215 | 200 | prependWs := newWidthSync(wSyncTimeout, s.bHeap.Len(), numP) |
| 216 | 201 | appendWs := newWidthSync(wSyncTimeout, s.bHeap.Len(), numA) |
| 217 | 202 | |
| 218 | for _, b := range s.renderByPriority(tw, prependWs, appendWs) { | |
| 219 | r := <-b.pipe | |
| 203 | for _, trs := range s.renderByPriority(tw, prependWs, appendWs) { | |
| 204 | r := <-trs.pipe | |
| 220 | 205 | _, err = s.cw.ReadFrom(r) |
| 221 | if r.completed { | |
| 222 | b.bar.Complete() | |
| 206 | if !trs.bar.completed && r.toComplete { | |
| 207 | trs.bar.completed = true | |
| 208 | close(trs.bar.shutdown) | |
| 209 | } | |
| 210 | if r.toRemove { | |
| 211 | heap.Remove(s.bHeap, trs.bar.index) | |
| 223 | 212 | } |
| 224 | 213 | } |
| 225 | 214 | |
| 233 | 222 | return |
| 234 | 223 | } |
| 235 | 224 | |
| 236 | func (s *pState) renderByPriority(tw int, prependWs, appendWs *widthSync) []*renderedBar { | |
| 237 | slice := make([]*renderedBar, 0, s.bHeap.Len()) | |
| 225 | func (s *pState) renderByPriority(tw int, prependWs, appendWs *widthSync) []*toRenderSnapshot { | |
| 226 | slice := make([]*toRenderSnapshot, 0, s.bHeap.Len()) | |
| 238 | 227 | for s.bHeap.Len() > 0 { |
| 239 | 228 | b := heap.Pop(s.bHeap).(*Bar) |
| 240 | 229 | defer heap.Push(s.bHeap, b) |
| 241 | slice = append(slice, &renderedBar{ | |
| 230 | slice = append(slice, &toRenderSnapshot{ | |
| 242 | 231 | bar: b, |
| 243 | 232 | pipe: b.render(tw, prependWs, appendWs), |
| 244 | 233 | }) |
| 13 | 13 | ) |
| 14 | 14 | |
| 15 | 15 | func (p *Progress) serve(s *pState) { |
| 16 | winch := make(chan os.Signal, 1) | |
| 16 | winch := make(chan os.Signal, 2) | |
| 17 | 17 | signal.Notify(winch, syscall.SIGWINCH) |
| 18 | ||
| 19 | defer func() { | |
| 20 | s.ticker.Stop() | |
| 21 | signal.Stop(winch) | |
| 22 | p.cacheHeap = s.bHeap | |
| 23 | close(p.done) | |
| 24 | if s.shutdownNotifier != nil { | |
| 25 | close(s.shutdownNotifier) | |
| 26 | } | |
| 27 | }() | |
| 28 | 18 | |
| 29 | 19 | var numP, numA int |
| 30 | 20 | var timer *time.Timer |
| 65 | 55 | case <-resumeTicker: |
| 66 | 56 | s.ticker = time.NewTicker(s.rr) |
| 67 | 57 | resumeTicker = nil |
| 68 | case <-s.cancel: | |
| 69 | return | |
| 70 | 58 | case <-p.shutdown: |
| 59 | s.ticker.Stop() | |
| 60 | signal.Stop(winch) | |
| 61 | p.cacheHeap = s.bHeap | |
| 62 | close(p.done) | |
| 63 | if s.shutdownNotifier != nil { | |
| 64 | close(s.shutdownNotifier) | |
| 65 | } | |
| 71 | 66 | return |
| 72 | 67 | } |
| 73 | 68 | } |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | 20 | func TestAddBar(t *testing.T) { |
| 21 | p := mpb.New() | |
| 21 | p := mpb.New(mpb.Output(ioutil.Discard)) | |
| 22 | 22 | |
| 23 | var wg sync.WaitGroup | |
| 24 | wg.Add(1) | |
| 25 | b := p.AddBar(80) | |
| 26 | go func() { | |
| 27 | for i := 0; i < 80; i++ { | |
| 28 | if i == 33 { | |
| 29 | wg.Done() | |
| 30 | } | |
| 31 | b.Increment() | |
| 32 | time.Sleep(randomDuration(80 * time.Millisecond)) | |
| 33 | } | |
| 34 | }() | |
| 35 | ||
| 36 | wg.Wait() | |
| 23 | 37 | count := p.BarCount() |
| 24 | if count != 0 { | |
| 25 | t.Errorf("BarCount want: %q, got: %q\n", 0, count) | |
| 26 | } | |
| 27 | ||
| 28 | bar := p.AddBar(100) | |
| 29 | ||
| 30 | count = p.BarCount() | |
| 31 | 38 | if count != 1 { |
| 32 | 39 | t.Errorf("BarCount want: %q, got: %q\n", 1, count) |
| 33 | 40 | } |
| 34 | 41 | |
| 35 | bar.Complete() | |
| 36 | p.Stop() | |
| 37 | } | |
| 38 | ||
| 39 | func TestRemoveBar(t *testing.T) { | |
| 40 | p := mpb.New() | |
| 41 | ||
| 42 | bar := p.AddBar(10) | |
| 43 | ||
| 44 | if !p.RemoveBar(bar) { | |
| 45 | t.Error("RemoveBar failure") | |
| 46 | } | |
| 47 | ||
| 48 | count := p.BarCount() | |
| 49 | if count != 0 { | |
| 50 | t.Errorf("BarCount want: %q, got: %q\n", 0, count) | |
| 51 | } | |
| 52 | ||
| 53 | bar.Complete() | |
| 42 | b.Complete() | |
| 54 | 43 | p.Stop() |
| 55 | 44 | } |
| 56 | 45 | |
| 87 | 76 | } |
| 88 | 77 | |
| 89 | 78 | func TestWithCancel(t *testing.T) { |
| 90 | var wg sync.WaitGroup | |
| 91 | 79 | cancel := make(chan struct{}) |
| 92 | shutdown := make(chan struct{}) | |
| 93 | 80 | p := mpb.New( |
| 94 | 81 | mpb.Output(ioutil.Discard), |
| 95 | 82 | mpb.WithCancel(cancel), |
| 96 | mpb.WithShutdownNotifier(shutdown), | |
| 97 | mpb.WithWaitGroup(&wg), | |
| 98 | 83 | ) |
| 99 | 84 | |
| 100 | total := 100 | |
| 101 | 85 | numBars := 3 |
| 102 | wg.Add(numBars) | |
| 103 | 86 | |
| 87 | type sample struct { | |
| 88 | id int | |
| 89 | total int64 | |
| 90 | current int64 | |
| 91 | } | |
| 92 | ||
| 93 | resStream := make(chan *sample, numBars) | |
| 104 | 94 | for i := 0; i < numBars; i++ { |
| 105 | name := fmt.Sprintf("Bar#%d:", i) | |
| 106 | bar := p.AddBar(int64(total), mpb.BarID(i), | |
| 107 | mpb.PrependDecorators(decor.StaticName(name, len(name), 0))) | |
| 95 | bar := p.AddBar(int64(200), | |
| 96 | mpb.BarID(i), | |
| 97 | mpb.PrependDecorators( | |
| 98 | func(s *decor.Statistics, _ chan<- int, _ <-chan int) string { | |
| 99 | if s.Completed { | |
| 100 | resStream <- &sample{ | |
| 101 | id: s.ID, | |
| 102 | total: s.Total, | |
| 103 | current: s.Current, | |
| 104 | } | |
| 105 | } | |
| 106 | return "" | |
| 107 | }, | |
| 108 | )) | |
| 108 | 109 | |
| 109 | 110 | go func() { |
| 110 | defer wg.Done() | |
| 111 | for i := 0; i < total; i++ { | |
| 112 | select { | |
| 113 | case <-cancel: | |
| 114 | return | |
| 115 | default: | |
| 116 | } | |
| 111 | for bar.InProgress() { | |
| 112 | bar.Increment() | |
| 117 | 113 | time.Sleep(randomDuration(80 * time.Millisecond)) |
| 118 | bar.Increment() | |
| 119 | 114 | } |
| 120 | 115 | }() |
| 121 | 116 | } |
| 122 | 117 | |
| 123 | time.AfterFunc(300*time.Millisecond, func() { | |
| 118 | time.AfterFunc(100*time.Millisecond, func() { | |
| 124 | 119 | close(cancel) |
| 125 | 120 | }) |
| 126 | 121 | |
| 127 | 122 | p.Stop() |
| 128 | ||
| 129 | select { | |
| 130 | case <-shutdown: | |
| 131 | case <-time.After(300 * time.Millisecond): | |
| 132 | t.Error("ProgressBar didn't stop") | |
| 123 | close(resStream) | |
| 124 | for res := range resStream { | |
| 125 | if res.current >= res.total { | |
| 126 | t.Errorf("bar %d: total = %d, current = %d\n", res.id, res.total, res.current) | |
| 127 | } | |
| 133 | 128 | } |
| 134 | 129 | } |
| 135 | 130 | |
| 10 | 10 | ) |
| 11 | 11 | |
| 12 | 12 | func (p *Progress) serve(s *pState) { |
| 13 | ||
| 14 | defer func() { | |
| 15 | s.ticker.Stop() | |
| 16 | p.cacheHeap = s.bHeap | |
| 17 | close(p.done) | |
| 18 | if s.shutdownNotifier != nil { | |
| 19 | close(s.shutdownNotifier) | |
| 20 | } | |
| 21 | }() | |
| 22 | ||
| 23 | 13 | var numP, numA int |
| 24 | ||
| 25 | 14 | for { |
| 26 | 15 | select { |
| 27 | 16 | case op := <-p.operateState: |
| 41 | 30 | if err != nil { |
| 42 | 31 | fmt.Fprintln(os.Stderr, err) |
| 43 | 32 | } |
| 44 | case <-s.cancel: | |
| 45 | return | |
| 46 | 33 | case <-p.shutdown: |
| 34 | s.ticker.Stop() | |
| 35 | p.cacheHeap = s.bHeap | |
| 36 | close(p.done) | |
| 37 | if s.shutdownNotifier != nil { | |
| 38 | close(s.shutdownNotifier) | |
| 39 | } | |
| 47 | 40 | return |
| 48 | 41 | } |
| 49 | 42 | } |