diff --git a/bar.go b/bar.go index cee3f82..cc1c5d8 100644 --- a/bar.go +++ b/bar.go @@ -18,7 +18,6 @@ // Bar represents a progress bar. type Bar struct { index int // used by heap - toShutdown bool hasEwmaDecorators bool frameCh chan *frame operateState chan func(*bState) @@ -63,8 +62,10 @@ } type frame struct { - reader io.Reader - lines int + reader io.Reader + lines int + shutdown bool + abort bool } func newBar(container *Progress, bs *bState) (*Bar, func()) { @@ -275,35 +276,16 @@ // if bar is already in complete state. If drop is true bar will be // removed as well. func (b *Bar) Abort(drop bool) { - done := make(chan struct{}) - select { - case b.operateState <- func(s *bState) { - if s.completed { - close(done) + select { + case b.operateState <- func(s *bState) { + if s.completed || s.aborted { return } s.aborted = true - b.cancel() - // container must be run during lifetime of this inner goroutine - // we control this by done channel declared above - go func() { - if drop { - b.container.dropBar(b) - } else { - var anyOtherUncompleted bool - b.container.traverseBars(func(bar *Bar) bool { - anyOtherUncompleted = b != bar && !bar.Completed() - return !anyOtherUncompleted - }) - if !anyOtherUncompleted { - b.container.refreshCh <- time.Now() - } - } - close(done) // release hold of Abort - }() - }: - // guarantee: container is alive during lifetime of this hold - <-done + s.dropOnComplete = drop + go b.forceRefreshIfLastUncompleted() + }: + <-b.done case <-b.done: } } @@ -322,37 +304,46 @@ func (b *Bar) render(tw int) { select { case b.operateState <- func(s *bState) { + var reader io.Reader + var lines int stat := newStatistics(tw, s) defer func() { // recovering if user defined decorator panics for example if p := recover(); p != nil { - if b.recoveredPanic == nil { - if s.debugOut != nil { - fmt.Fprintln(s.debugOut, p) - _, _ = s.debugOut.Write(debug.Stack()) - } - s.extender = makePanicExtender(p) - b.toShutdown = !b.toShutdown - b.recoveredPanic = p + if s.debugOut != nil { + fmt.Fprintln(s.debugOut, p) + _, _ = s.debugOut.Write(debug.Stack()) } - reader, lines := s.extender(nil, s.reqWidth, stat) - b.frameCh <- &frame{reader, lines + 1} - } - s.completeFlushed = s.completed + s.aborted = true + s.extender = makePanicExtender(p) + reader, lines = s.extender(nil, s.reqWidth, stat) + b.recoveredPanic = p + } + b.frameCh <- &frame{ + reader: reader, + lines: lines + 1, + shutdown: s.completed && !s.completeFlushed, + abort: s.aborted && !s.completeFlushed, + } + s.completeFlushed = s.completed || s.aborted }() - reader, lines := s.extender(s.draw(stat), s.reqWidth, stat) - b.toShutdown = s.completed && !s.completeFlushed - b.frameCh <- &frame{reader, lines + 1} - }: - case <-b.done: - s := b.bs - stat := newStatistics(tw, s) - var r io.Reader if b.recoveredPanic == nil { - r = s.draw(stat) - } - reader, lines := s.extender(r, s.reqWidth, stat) - b.frameCh <- &frame{reader, lines + 1} + reader = s.draw(stat) + } + reader, lines = s.extender(reader, s.reqWidth, stat) + }: + case <-b.done: + var reader io.Reader + var lines int + stat, s := newStatistics(tw, b.bs), b.bs + if b.recoveredPanic == nil { + reader = s.draw(stat) + } + reader, lines = s.extender(reader, s.reqWidth, stat) + b.frameCh <- &frame{ + reader: reader, + lines: lines + 1, + } } } @@ -522,7 +513,7 @@ Total: s.total, Current: s.current, Refill: s.refill, - Completed: s.completeFlushed, + Completed: s.completeFlushed && !s.aborted, Aborted: s.aborted, } } diff --git a/progress.go b/progress.go index de8cfbf..04a50e0 100644 --- a/progress.go +++ b/progress.go @@ -16,8 +16,7 @@ ) const ( - // default RefreshRate - prr = 150 * time.Millisecond + prr = 150 * time.Millisecond // default RefreshRate ) // Progress represents a container that renders one or more progress bars. @@ -140,19 +139,6 @@ case <-p.done: p.bwg.Done() panic(fmt.Sprintf("%T instance can't be reused after it's done!", p)) - } -} - -func (p *Progress) dropBar(b *Bar) { - select { - case p.operateState <- func(s *pState) { - if b.index < 0 { - return - } - heap.Remove(&s.bHeap, b.index) - s.heapUpdated = true - }: - case <-p.done: } } @@ -318,7 +304,7 @@ func (s *pState) flush(cw *cwriter.Writer) error { var totalLines int - bm := make(map[*Bar]int, s.bHeap.Len()) + bm := make(map[*Bar]*frame, s.bHeap.Len()) for s.bHeap.Len() > 0 { b := heap.Pop(&s.bHeap).(*Bar) frame := <-b.frameCh @@ -326,38 +312,38 @@ if err != nil { return err } - if b.toShutdown { - if b.recoveredPanic != nil { + if frame.abort { + s.barShutdownQueue = append(s.barShutdownQueue, b) + } else if frame.shutdown { + // shutdown at next flush + // this ensures no bar ends up with less than 100% rendered + defer func() { s.barShutdownQueue = append(s.barShutdownQueue, b) - b.toShutdown = false - } else { - // shutdown at next flush - // this ensures no bar ends up with less than 100% rendered - defer func() { - s.barShutdownQueue = append(s.barShutdownQueue, b) - }() - } - } - bm[b] = frame.lines + }() + } totalLines += frame.lines + bm[b] = frame } for _, b := range s.barShutdownQueue { + b.cancel() + <-b.done // waiting for b.done, so it's safe to read b.bs + var toDrop bool if qb, ok := s.queueBars[b]; ok { + delete(s.queueBars, b) qb.bar.bs.priority = b.bs.priority heap.Push(&s.bHeap, qb.bar) - delete(s.queueBars, b) - b.bs.dropOnComplete = true go qb.serve() + toDrop = true } else if s.popCompleted && !b.bs.noPop { - totalLines -= bm[b] - b.bs.dropOnComplete = true - } - if b.bs.dropOnComplete { + frame := bm[b] + totalLines -= frame.lines + toDrop = true + } + if toDrop || b.bs.dropOnComplete { delete(bm, b) s.heapUpdated = true } - b.cancel() } s.barShutdownQueue = s.barShutdownQueue[0:0]