refactoring: drop sync.Once
Vladimir Bauer
3 years ago
| 24 | 24 | type Progress struct { |
| 25 | 25 | ctx context.Context |
| 26 | 26 | uwg *sync.WaitGroup |
| 27 | cwg *sync.WaitGroup | |
| 28 | 27 | bwg *sync.WaitGroup |
| 29 | 28 | operateState chan func(*pState) |
| 30 | 29 | interceptIo chan func(io.Writer) |
| 31 | 30 | done chan struct{} |
| 32 | once sync.Once | |
| 31 | shutdown chan struct{} | |
| 33 | 32 | cancel func() |
| 34 | 33 | } |
| 35 | 34 | |
| 72 | 71 | // method has been called. |
| 73 | 72 | func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { |
| 74 | 73 | s := &pState{ |
| 75 | rows: make([]io.Reader, 0, 64), | |
| 76 | pool: make([]*Bar, 0, 64), | |
| 77 | refreshRate: defaultRefreshRate, | |
| 78 | popPriority: math.MinInt32, | |
| 79 | manualRefresh: make(chan interface{}), | |
| 80 | shutdownNotifier: make(chan struct{}), | |
| 81 | queueBars: make(map[*Bar]*Bar), | |
| 82 | output: os.Stdout, | |
| 83 | debugOut: io.Discard, | |
| 74 | rows: make([]io.Reader, 0, 64), | |
| 75 | pool: make([]*Bar, 0, 64), | |
| 76 | refreshRate: defaultRefreshRate, | |
| 77 | popPriority: math.MinInt32, | |
| 78 | manualRefresh: make(chan interface{}), | |
| 79 | queueBars: make(map[*Bar]*Bar), | |
| 80 | output: os.Stdout, | |
| 81 | debugOut: io.Discard, | |
| 84 | 82 | } |
| 85 | 83 | |
| 86 | 84 | for _, opt := range options { |
| 93 | 91 | p := &Progress{ |
| 94 | 92 | ctx: ctx, |
| 95 | 93 | uwg: s.uwg, |
| 96 | cwg: new(sync.WaitGroup), | |
| 97 | 94 | bwg: new(sync.WaitGroup), |
| 98 | 95 | operateState: make(chan func(*pState)), |
| 99 | 96 | interceptIo: make(chan func(io.Writer)), |
| 101 | 98 | cancel: cancel, |
| 102 | 99 | } |
| 103 | 100 | |
| 104 | p.cwg.Add(1) | |
| 101 | if s.shutdownNotifier != nil { | |
| 102 | p.shutdown = s.shutdownNotifier | |
| 103 | s.shutdownNotifier = nil | |
| 104 | } else { | |
| 105 | p.shutdown = make(chan struct{}) | |
| 106 | } | |
| 107 | ||
| 105 | 108 | go p.serve(s, cwriter.New(s.output)) |
| 106 | 109 | return p |
| 107 | 110 | } |
| 224 | 227 | p.uwg.Wait() |
| 225 | 228 | } |
| 226 | 229 | |
| 227 | // wait for bars to quit, if any | |
| 228 | 230 | p.bwg.Wait() |
| 229 | // shutdown | |
| 230 | p.once.Do(p.shutdown) | |
| 231 | // wait for container to quit | |
| 232 | p.cwg.Wait() | |
| 231 | p.Shutdown() | |
| 233 | 232 | } |
| 234 | 233 | |
| 235 | 234 | // Shutdown cancels any running bar immediately and then shutdowns (*Progress) |
| 237 | 236 | // are doing. Proper way to shutdown is to call (*Progress).Wait() instead. |
| 238 | 237 | func (p *Progress) Shutdown() { |
| 239 | 238 | p.cancel() |
| 240 | p.bwg.Wait() | |
| 241 | p.once.Do(p.shutdown) | |
| 242 | p.cwg.Wait() | |
| 243 | } | |
| 244 | ||
| 245 | func (p *Progress) shutdown() { | |
| 246 | close(p.done) | |
| 239 | <-p.shutdown | |
| 240 | } | |
| 241 | ||
| 242 | func (p *Progress) newTicker(s *pState) chan time.Time { | |
| 243 | ch := make(chan time.Time) | |
| 244 | go func() { | |
| 245 | var autoRefresh <-chan time.Time | |
| 246 | if !s.disableAutoRefresh && !s.outputDiscarded { | |
| 247 | if s.renderDelay != nil { | |
| 248 | <-s.renderDelay | |
| 249 | } | |
| 250 | ticker := time.NewTicker(s.refreshRate) | |
| 251 | defer ticker.Stop() | |
| 252 | autoRefresh = ticker.C | |
| 253 | } | |
| 254 | for { | |
| 255 | select { | |
| 256 | case t := <-autoRefresh: | |
| 257 | ch <- t | |
| 258 | case x := <-s.manualRefresh: | |
| 259 | if t, ok := x.(time.Time); ok { | |
| 260 | ch <- t | |
| 261 | } else { | |
| 262 | ch <- time.Now() | |
| 263 | } | |
| 264 | case <-p.ctx.Done(): | |
| 265 | close(p.done) | |
| 266 | return | |
| 267 | } | |
| 268 | } | |
| 269 | }() | |
| 270 | return ch | |
| 247 | 271 | } |
| 248 | 272 | |
| 249 | 273 | func (p *Progress) serve(s *pState, cw *cwriter.Writer) { |
| 250 | defer p.cwg.Done() | |
| 274 | defer close(p.shutdown) | |
| 251 | 275 | |
| 252 | 276 | render := func() error { |
| 253 | 277 | if s.bHeap.Len() == 0 { |
| 256 | 280 | return s.render(cw) |
| 257 | 281 | } |
| 258 | 282 | |
| 259 | refreshCh := s.newTicker(p.done) | |
| 283 | refreshCh := p.newTicker(s) | |
| 260 | 284 | |
| 261 | 285 | for { |
| 262 | 286 | select { |
| 267 | 291 | case <-refreshCh: |
| 268 | 292 | err := render() |
| 269 | 293 | if err != nil { |
| 270 | go func() { | |
| 271 | p.bwg.Wait() | |
| 272 | p.once.Do(p.shutdown) | |
| 273 | }() | |
| 274 | render = func() error { | |
| 275 | s.heapUpdated = false | |
| 276 | return nil | |
| 277 | } | |
| 294 | s.heapUpdated = false | |
| 295 | render = func() error { return nil } | |
| 278 | 296 | _, _ = fmt.Fprintln(s.debugOut, err) |
| 279 | 297 | p.cancel() // cancel all bars |
| 280 | 298 | } |
| 281 | case <-s.shutdownNotifier: | |
| 299 | case <-p.done: | |
| 282 | 300 | for s.heapUpdated { |
| 283 | 301 | err := render() |
| 284 | 302 | if err != nil { |
| 322 | 340 | b := heap.Pop(&s.bHeap).(*Bar) |
| 323 | 341 | frame := <-b.frameCh |
| 324 | 342 | if frame.err != nil { |
| 343 | s.rows = s.rows[:0] | |
| 325 | 344 | return frame.err |
| 326 | 345 | } |
| 327 | 346 | var usedRows int |
| 399 | 418 | return err |
| 400 | 419 | } |
| 401 | 420 | |
| 402 | func (s *pState) newTicker(done <-chan struct{}) chan time.Time { | |
| 403 | ch := make(chan time.Time) | |
| 404 | go func() { | |
| 405 | var autoRefresh <-chan time.Time | |
| 406 | if !s.disableAutoRefresh && !s.outputDiscarded { | |
| 407 | if s.renderDelay != nil { | |
| 408 | <-s.renderDelay | |
| 409 | } | |
| 410 | ticker := time.NewTicker(s.refreshRate) | |
| 411 | defer ticker.Stop() | |
| 412 | autoRefresh = ticker.C | |
| 413 | } | |
| 414 | for { | |
| 415 | select { | |
| 416 | case t := <-autoRefresh: | |
| 417 | ch <- t | |
| 418 | case x := <-s.manualRefresh: | |
| 419 | if t, ok := x.(time.Time); ok { | |
| 420 | ch <- t | |
| 421 | } else { | |
| 422 | ch <- time.Now() | |
| 423 | } | |
| 424 | case <-done: | |
| 425 | close(s.shutdownNotifier) | |
| 426 | return | |
| 427 | } | |
| 428 | } | |
| 429 | }() | |
| 430 | return ch | |
| 431 | } | |
| 432 | ||
| 433 | 421 | func (s *pState) updateSyncMatrix() { |
| 434 | 422 | s.pMatrix = make(map[int][]chan int) |
| 435 | 423 | s.aMatrix = make(map[int][]chan int) |
| 483 | 471 | } |
| 484 | 472 | |
| 485 | 473 | func syncWidth(wg *sync.WaitGroup, matrix map[int][]chan int) { |
| 474 | wg.Add(len(matrix)) | |
| 486 | 475 | for _, column := range matrix { |
| 487 | wg.Add(1) | |
| 488 | 476 | go maxWidthDistributor(wg, column) |
| 489 | 477 | } |
| 490 | 478 | } |