Improve complete after flush algorithm
Vladimir Bauer
8 years ago
| 35 | 35 | |
| 36 | 36 | // following are used after b.done is receiveable |
| 37 | 37 | cacheState state |
| 38 | ||
| 39 | once sync.Once | |
| 38 | 40 | } |
| 39 | 41 | |
| 40 | 42 | type ( |
| 63 | 65 | prependFuncs []decor.DecoratorFunc |
| 64 | 66 | refill *refill |
| 65 | 67 | bufP, bufB, bufA *bytes.Buffer |
| 68 | panic string | |
| 69 | } | |
| 70 | writeBuf struct { | |
| 71 | buf []byte | |
| 72 | completeAfterFlush bool | |
| 66 | 73 | } |
| 67 | 74 | ) |
| 68 | 75 | |
| 250 | 257 | // of process completion. If you don't call this method, it will be called |
| 251 | 258 | // implicitly, upon p.Stop() call. |
| 252 | 259 | func (b *Bar) Complete() { |
| 253 | select { | |
| 254 | case <-b.quit: | |
| 255 | default: | |
| 256 | close(b.quit) | |
| 257 | } | |
| 258 | } | |
| 259 | ||
| 260 | func (b *Bar) complete() { | |
| 261 | select { | |
| 262 | case b.ops <- func(s *state) { | |
| 263 | if !s.completed { | |
| 264 | b.Complete() | |
| 265 | } | |
| 266 | }: | |
| 267 | case <-time.After(prr): | |
| 268 | } | |
| 260 | b.once.Do(b.shutdown) | |
| 261 | } | |
| 262 | ||
| 263 | func (b *Bar) shutdown() { | |
| 264 | close(b.quit) | |
| 269 | 265 | } |
| 270 | 266 | |
| 271 | 267 | func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) { |
| 284 | 280 | cancel = nil |
| 285 | 281 | b.Complete() |
| 286 | 282 | case <-b.quit: |
| 287 | s.completed = true | |
| 288 | 283 | return |
| 289 | 284 | } |
| 290 | 285 | } |
| 291 | 286 | } |
| 292 | 287 | |
| 293 | func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan []byte { | |
| 294 | ch := make(chan []byte, 1) | |
| 288 | func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan *writeBuf { | |
| 289 | ch := make(chan *writeBuf, 1) | |
| 295 | 290 | |
| 296 | 291 | go func() { |
| 297 | var st state | |
| 298 | defer func() { | |
| 299 | // recovering if external decorators panic | |
| 300 | if p := recover(); p != nil { | |
| 301 | ch <- []byte(fmt.Sprintf("bar%02d panic: %q\n", st.id, p)) | |
| 292 | select { | |
| 293 | case b.ops <- func(s *state) { | |
| 294 | defer func() { | |
| 295 | // recovering if external decorators panic | |
| 296 | if p := recover(); p != nil { | |
| 297 | s.panic = fmt.Sprintf("b#%02d panic: %v\n", s.id, p) | |
| 298 | s.prependFuncs = nil | |
| 299 | s.appendFuncs = nil | |
| 300 | ||
| 301 | ch <- &writeBuf{[]byte(s.panic), true} | |
| 302 | } | |
| 303 | close(ch) | |
| 304 | }() | |
| 305 | s.draw(tw, prependWs, appendWs) | |
| 306 | ch <- &writeBuf{s.toBytes(), s.isFull()} | |
| 307 | }: | |
| 308 | case <-b.done: | |
| 309 | s := b.cacheState | |
| 310 | var buf []byte | |
| 311 | if s.panic != "" { | |
| 312 | buf = []byte(s.panic) | |
| 313 | } else { | |
| 314 | s.draw(tw, prependWs, appendWs) | |
| 315 | buf = s.toBytes() | |
| 302 | 316 | } |
| 317 | ch <- &writeBuf{buf, true} | |
| 303 | 318 | close(ch) |
| 304 | }() | |
| 305 | result := make(chan state, 1) | |
| 306 | select { | |
| 307 | case b.ops <- func(s *state) { result <- *s }: | |
| 308 | st = <-result | |
| 309 | if st.completed { | |
| 310 | b.Complete() | |
| 311 | } | |
| 312 | case <-b.done: | |
| 313 | st = b.cacheState | |
| 314 | } | |
| 315 | st.draw(tw, prependWs, appendWs) | |
| 316 | buf := make([]byte, 0, st.bufP.Len()+st.bufB.Len()+st.bufA.Len()) | |
| 317 | buf = concatenateBlocks(buf, st.bufP.Bytes(), st.bufB.Bytes(), st.bufA.Bytes()) | |
| 318 | buf = append(buf, '\n') | |
| 319 | ch <- buf | |
| 319 | } | |
| 320 | 320 | }() |
| 321 | 321 | |
| 322 | 322 | return ch |
| 323 | } | |
| 324 | ||
| 325 | func (s *state) toBytes() []byte { | |
| 326 | buf := make([]byte, 0, s.bufP.Len()+s.bufB.Len()+s.bufA.Len()) | |
| 327 | buf = concatenateBlocks(buf, s.bufP.Bytes(), s.bufB.Bytes(), s.bufA.Bytes()) | |
| 328 | return buf | |
| 323 | 329 | } |
| 324 | 330 | |
| 325 | 331 | func (s *state) updateTimePerItemEstimate(amount int) { |
| 326 | 332 | lastBlockTime := time.Since(s.blockStartTime) // shorthand for time.Now().Sub(t) |
| 327 | 333 | lastItemEstimate := float64(lastBlockTime) / float64(amount) |
| 328 | 334 | s.timePerItem = time.Duration((s.etaAlpha * lastItemEstimate) + (1-s.etaAlpha)*float64(s.timePerItem)) |
| 335 | } | |
| 336 | ||
| 337 | func (s *state) isFull() bool { | |
| 338 | if !s.completed { | |
| 339 | return false | |
| 340 | } | |
| 341 | bar := s.bufB.Bytes() | |
| 342 | var r rune | |
| 343 | var n int | |
| 344 | for i := 0; len(bar) > 0; i++ { | |
| 345 | r, n = utf8.DecodeLastRune(bar) | |
| 346 | bar = bar[:len(bar)-n] | |
| 347 | if i == 1 { | |
| 348 | break | |
| 349 | } | |
| 350 | } | |
| 351 | return r == s.format[rFill] | |
| 329 | 352 | } |
| 330 | 353 | |
| 331 | 354 | func (s *state) draw(termWidth int, prependWs, appendWs *widthSync) { |
| 365 | 388 | shrinkWidth := termWidth - prependCount - appendCount |
| 366 | 389 | s.fillBar(shrinkWidth) |
| 367 | 390 | } |
| 391 | s.bufA.WriteByte('\n') | |
| 368 | 392 | } |
| 369 | 393 | |
| 370 | 394 | func (s *state) fillBar(width int) { |
| 241 | 241 | |
| 242 | 242 | out := bytes.Split(removeLastRune(buf.Bytes()), []byte("\n")) |
| 243 | 243 | gotPanic := out[len(out)-1] |
| 244 | if string(gotPanic) != fmt.Sprintf("bar02 panic: %q", wantPanic) { | |
| 244 | wantPanic = fmt.Sprintf("b#%02d panic: %v", 2, wantPanic) | |
| 245 | ||
| 246 | if string(gotPanic) != wantPanic { | |
| 245 | 247 | t.Errorf("Want: %q, got: %q\n", wantPanic, gotPanic) |
| 246 | 248 | } |
| 247 | 249 | } |
| 0 | 0 | package mpb |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | "fmt" | |
| 3 | 4 | "io" |
| 4 | 5 | "os" |
| 5 | 6 | "runtime" |
| 71 | 72 | cw: cwriter.New(os.Stdout), |
| 72 | 73 | rr: prr, |
| 73 | 74 | ticker: time.NewTicker(prr), |
| 75 | cancel: make(chan struct{}), | |
| 74 | 76 | } |
| 75 | 77 | |
| 76 | 78 | for _, opt := range options { |
| 155 | 157 | case <-p.quit: |
| 156 | 158 | return |
| 157 | 159 | default: |
| 158 | // complete Total unknown bars | |
| 159 | p.ops <- func(c *pConf) { | |
| 160 | for _, b := range c.bars { | |
| 161 | b.complete() | |
| 162 | } | |
| 163 | } | |
| 164 | 160 | // wait for all bars to quit |
| 165 | 161 | p.wg.Wait() |
| 166 | 162 | // request p.server to quit |
| 180 | 176 | |
| 181 | 177 | // server monitors underlying channels and renders any progress bars |
| 182 | 178 | func (p *Progress) server(conf pConf) { |
| 183 | ||
| 184 | 179 | defer func() { |
| 185 | 180 | if conf.shutdownNotifier != nil { |
| 186 | 181 | close(conf.shutdownNotifier) |
| 187 | 182 | } |
| 188 | 183 | close(p.done) |
| 189 | 184 | }() |
| 185 | ||
| 186 | numP, numA := -1, -1 | |
| 190 | 187 | |
| 191 | 188 | for { |
| 192 | 189 | select { |
| 193 | 190 | case op := <-p.ops: |
| 194 | 191 | op(&conf) |
| 195 | 192 | case <-conf.ticker.C: |
| 196 | numBars := len(conf.bars) | |
| 197 | if numBars == 0 { | |
| 193 | if len(conf.bars) == 0 { | |
| 198 | 194 | runtime.Gosched() |
| 199 | 195 | break |
| 200 | 196 | } |
| 201 | ||
| 202 | if conf.beforeRender != nil { | |
| 203 | conf.beforeRender(conf.bars) | |
| 204 | } | |
| 205 | ||
| 206 | wSyncTimeout := make(chan struct{}) | |
| 207 | time.AfterFunc(conf.rr, func() { | |
| 208 | close(wSyncTimeout) | |
| 209 | }) | |
| 210 | ||
| 211 | 197 | b0 := conf.bars[0] |
| 212 | prependWs := newWidthSync(wSyncTimeout, numBars, b0.NumOfPrependers()) | |
| 213 | appendWs := newWidthSync(wSyncTimeout, numBars, b0.NumOfAppenders()) | |
| 214 | ||
| 215 | tw, _, _ := cwriter.GetTermSize() | |
| 216 | ||
| 217 | sequence := make([]<-chan []byte, numBars) | |
| 218 | for i, b := range conf.bars { | |
| 219 | sequence[i] = b.render(tw, prependWs, appendWs) | |
| 220 | } | |
| 221 | ||
| 222 | for buf := range fanIn(sequence...) { | |
| 223 | conf.cw.Write(buf) | |
| 224 | } | |
| 225 | ||
| 226 | for _, interceptor := range conf.interceptors { | |
| 227 | interceptor(conf.cw) | |
| 228 | } | |
| 229 | ||
| 230 | conf.cw.Flush() | |
| 198 | if numP == -1 { | |
| 199 | numP = b0.NumOfPrependers() | |
| 200 | } | |
| 201 | if numA == -1 { | |
| 202 | numA = b0.NumOfAppenders() | |
| 203 | } | |
| 204 | err := conf.writeAndFlush(numP, numA) | |
| 205 | if err != nil { | |
| 206 | fmt.Fprintln(os.Stderr, err) | |
| 207 | } | |
| 231 | 208 | case <-conf.cancel: |
| 232 | 209 | conf.ticker.Stop() |
| 233 | 210 | conf.cancel = nil |
| 277 | 254 | return ws |
| 278 | 255 | } |
| 279 | 256 | |
| 280 | func fanIn(inputs ...<-chan []byte) <-chan []byte { | |
| 281 | ch := make(chan []byte) | |
| 257 | func (p *pConf) writeAndFlush(numP, numA int) (err error) { | |
| 258 | if p.beforeRender != nil { | |
| 259 | p.beforeRender(p.bars) | |
| 260 | } | |
| 261 | ||
| 262 | wSyncTimeout := make(chan struct{}) | |
| 263 | time.AfterFunc(p.rr, func() { | |
| 264 | close(wSyncTimeout) | |
| 265 | }) | |
| 266 | ||
| 267 | prependWs := newWidthSync(wSyncTimeout, len(p.bars), numP) | |
| 268 | appendWs := newWidthSync(wSyncTimeout, len(p.bars), numA) | |
| 269 | ||
| 270 | tw, _, _ := cwriter.TermSize() | |
| 271 | ||
| 272 | sequence := make([]<-chan *writeBuf, len(p.bars)) | |
| 273 | for i, b := range p.bars { | |
| 274 | sequence[i] = b.render(tw, prependWs, appendWs) | |
| 275 | } | |
| 276 | ||
| 277 | var i int | |
| 278 | for b := range fanIn(sequence...) { | |
| 279 | _, err = p.cw.Write(b.buf) | |
| 280 | defer func(bar *Bar, complete bool) { | |
| 281 | if complete { | |
| 282 | bar.Complete() | |
| 283 | } | |
| 284 | }(p.bars[i], b.completeAfterFlush) | |
| 285 | i++ | |
| 286 | } | |
| 287 | ||
| 288 | for _, interceptor := range p.interceptors { | |
| 289 | interceptor(p.cw) | |
| 290 | } | |
| 291 | ||
| 292 | if e := p.cw.Flush(); err == nil { | |
| 293 | err = e | |
| 294 | } | |
| 295 | return | |
| 296 | } | |
| 297 | ||
| 298 | func fanIn(inputs ...<-chan *writeBuf) <-chan *writeBuf { | |
| 299 | ch := make(chan *writeBuf) | |
| 282 | 300 | |
| 283 | 301 | go func() { |
| 284 | 302 | defer close(ch) |