github.com/cilium/cilium@v1.16.2/pkg/lock/stoppable_waitgroup.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package lock 5 6 import ( 7 "sync" 8 "sync/atomic" 9 ) 10 11 // A StoppableWaitGroup waits for a collection of goroutines to finish. 12 type StoppableWaitGroup struct { 13 noopDone chan struct{} 14 noopAdd chan struct{} 15 // i is the internal counter which can store tolerate negative values 16 // as opposed the golang's library WaitGroup. 17 i atomic.Int64 18 doneOnce, stopOnce sync.Once 19 } 20 21 // NewStoppableWaitGroup returns a new StoppableWaitGroup. When the 'Stop' is 22 // executed, following 'Add()' calls won't have any effect. 23 func NewStoppableWaitGroup() *StoppableWaitGroup { 24 return &StoppableWaitGroup{ 25 noopDone: make(chan struct{}), 26 noopAdd: make(chan struct{}), 27 doneOnce: sync.Once{}, 28 stopOnce: sync.Once{}, 29 } 30 } 31 32 // Stop makes following 'Add()' to be considered a no-op. 33 // If all goroutines that have called Add also called Done, 'Wait()' will 34 // be immediately unblocked. 35 func (l *StoppableWaitGroup) Stop() { 36 l.stopOnce.Do(func() { 37 // We will do an Add here so we can perform a Done after we close 38 // the l.noopAdd channel. 39 l.Add() 40 close(l.noopAdd) 41 // Calling Done() here so we know that in case 'l.i' will become zero 42 // it will trigger a close of l.noopDone channel. 43 l.Done() 44 }) 45 } 46 47 // Wait will return once all goroutines that have called Add also called 48 // Done and StoppableWaitGroup was stopped. 49 // Internally, Wait() returns once the internal counter becomes negative. 50 func (l *StoppableWaitGroup) Wait() { 51 <-l.noopDone 52 } 53 54 // WaitChannel will return a channel that will be closed once all goroutines 55 // that have called Add also called Done and StoppableWaitGroup was stopped. 56 func (l *StoppableWaitGroup) WaitChannel() <-chan struct{} { 57 return l.noopDone 58 } 59 60 // Add adds the goroutine to the list of routines to that Wait() will have 61 // to wait before it returns. 62 // If the StoppableWaitGroup was stopped this will be a no-op. 63 func (l *StoppableWaitGroup) Add() { 64 select { 65 case <-l.noopAdd: 66 default: 67 l.i.Add(1) 68 } 69 } 70 71 // Done will decrement the number of goroutines the Wait() will have to wait 72 // before it returns. 73 // This function is a no-op once all goroutines that have called 'Add()' have 74 // also called 'Done()' and the StoppableWaitGroup was stopped. 75 func (l *StoppableWaitGroup) Done() { 76 select { 77 case <-l.noopDone: 78 return 79 default: 80 select { 81 case <-l.noopAdd: 82 a := l.i.Add(-1) 83 if a <= 0 { 84 l.doneOnce.Do(func() { 85 close(l.noopDone) 86 }) 87 } 88 default: 89 a := l.i.Add(-1) 90 select { 91 // in case the channel was close while we where in this default 92 // case we will need to check if 'a' is less than zero and close 93 // l.noopDone channel. 94 case <-l.noopAdd: 95 if a <= 0 { 96 l.doneOnce.Do(func() { 97 close(l.noopDone) 98 }) 99 } 100 default: 101 } 102 } 103 } 104 }