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  }