github.com/fafucoder/cilium@v1.6.11/pkg/completion/completion.go (about)

     1  // Copyright 2018 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package completion
    16  
    17  import (
    18  	"context"
    19  
    20  	"github.com/cilium/cilium/pkg/lock"
    21  )
    22  
    23  // WaitGroup waits for a collection of Completions to complete.
    24  type WaitGroup struct {
    25  	// ctx is the context of all the Completions in the wait group.
    26  	ctx context.Context
    27  
    28  	// cancel is the function to call if any pending operation fails
    29  	cancel context.CancelFunc
    30  
    31  	// counterLocker locks all calls to AddCompletion and Wait, which must not
    32  	// be called concurrently.
    33  	counterLocker lock.Mutex
    34  
    35  	// pendingCompletions is the list of Completions returned by
    36  	// AddCompletion() which have not yet been completed.
    37  	pendingCompletions []*Completion
    38  }
    39  
    40  // NewWaitGroup returns a new WaitGroup using the given context.
    41  func NewWaitGroup(ctx context.Context) *WaitGroup {
    42  	ctx2, cancel := context.WithCancel(ctx)
    43  	return &WaitGroup{ctx: ctx2, cancel: cancel}
    44  }
    45  
    46  // Context returns the context of all the Completions in the wait group.
    47  func (wg *WaitGroup) Context() context.Context {
    48  	return wg.ctx
    49  }
    50  
    51  // AddCompletionWithCallback creates a new completion, adds it to the wait
    52  // group, and returns it. The callback will be called upon completion.
    53  // Completion can complete in a failure (err != nil)
    54  func (wg *WaitGroup) AddCompletionWithCallback(callback func(err error)) *Completion {
    55  	wg.counterLocker.Lock()
    56  	defer wg.counterLocker.Unlock()
    57  	c := NewCompletion(wg.cancel, callback)
    58  	wg.pendingCompletions = append(wg.pendingCompletions, c)
    59  	return c
    60  }
    61  
    62  // AddCompletion creates a new completion, adds it into the wait group, and
    63  // returns it.
    64  func (wg *WaitGroup) AddCompletion() *Completion {
    65  	return wg.AddCompletionWithCallback(nil)
    66  }
    67  
    68  // updateError updates the error value to be returned from Wait()
    69  // so that we return the most severe or consequential error
    70  // encountered. The order of importance of error values is (from
    71  // highest to lowest):
    72  // 1. Non-context errors
    73  // 2. context.Canceled
    74  // 3. context.DeadlineExceeded
    75  // 4. nil
    76  func updateError(old, new error) error {
    77  	if new == nil {
    78  		return old
    79  	}
    80  	// 'old' error is overridden by a non-nil 'new' error value if
    81  	// 1. 'old' is nil, or
    82  	// 2. 'old' is a timeout, or
    83  	// 3. 'old' is a cancel and the 'new' error value is not a timeout
    84  	if old == nil || old == context.DeadlineExceeded || (old == context.Canceled && new != context.DeadlineExceeded) {
    85  		return new
    86  	}
    87  	return old
    88  }
    89  
    90  // Wait blocks until all completions added by calling AddCompletion are
    91  // completed, or the context is canceled, whichever happens first.
    92  // Returns the context's error if it is cancelled, nil otherwise.
    93  // No callbacks of the completions in this wait group will be called after
    94  // this returns.
    95  // Returns the error value of one of the completions, if available, or the
    96  // error value of the Context otherwise.
    97  func (wg *WaitGroup) Wait() error {
    98  	wg.counterLocker.Lock()
    99  	defer wg.counterLocker.Unlock()
   100  
   101  	var err error
   102  Loop:
   103  	for i, comp := range wg.pendingCompletions {
   104  		select {
   105  		case <-comp.Completed():
   106  			err = updateError(err, comp.Err()) // Keep the most severe error value we encounter
   107  			continue Loop
   108  		case <-wg.ctx.Done():
   109  			// Complete the remaining completions (if any) to make sure their completed
   110  			// channels are closed.
   111  			for _, comp := range wg.pendingCompletions[i:] {
   112  				// 'comp' may have already completed on a different error
   113  				compErr := comp.Complete(wg.ctx.Err())
   114  				err = updateError(err, compErr) // Keep the most severe error value we encounter
   115  			}
   116  			break Loop
   117  		}
   118  	}
   119  	wg.pendingCompletions = nil
   120  	return err
   121  }
   122  
   123  // Completion provides the Complete callback to be called when an asynchronous
   124  // computation is completed.
   125  type Completion struct {
   126  	// cancel is used to cancel the WaitGroup the completion belongs in case of an error
   127  	cancel context.CancelFunc
   128  
   129  	// lock is used to check and close the completed channel atomically.
   130  	lock lock.Mutex
   131  
   132  	// completed is the channel to be closed when Complete is called the first
   133  	// time.
   134  	completed chan struct{}
   135  
   136  	// callback is called when Complete is called the first time.
   137  	callback func(err error)
   138  
   139  	// err is the error the completion completed with
   140  	err error
   141  }
   142  
   143  // Err returns a non-nil error if the completion ended in error
   144  func (c *Completion) Err() error {
   145  	c.lock.Lock()
   146  	defer c.lock.Unlock()
   147  	return c.err
   148  }
   149  
   150  // Complete notifies of the completion of the asynchronous computation.
   151  // Idempotent.
   152  // If the operation completed successfully 'err' is passed as nil.
   153  // Returns the error state the completion completed with, which is
   154  // gnerally different from 'err' if already completed.
   155  func (c *Completion) Complete(err error) error {
   156  	c.lock.Lock()
   157  	select {
   158  	case <-c.completed:
   159  		err = c.err // return the error 'c' completed with
   160  	default:
   161  		c.err = err
   162  		if c.callback != nil {
   163  			// We must call the callbacks synchronously to guarantee
   164  			// that they are actually called before Wait() returns.
   165  			c.callback(err)
   166  		}
   167  		// Cancel the WaitGroup on failure
   168  		if err != nil && c.cancel != nil {
   169  			c.cancel()
   170  		}
   171  		close(c.completed)
   172  	}
   173  	c.lock.Unlock()
   174  	return err
   175  }
   176  
   177  // Completed returns a channel that's closed when the completion is completed,
   178  // i.e. when Complete is called the first time, or when the call to the parent
   179  // WaitGroup's Wait terminated because the context was canceled.
   180  func (c *Completion) Completed() <-chan struct{} {
   181  	return c.completed
   182  }
   183  
   184  // NewCompletion creates a Completion which calls a function upon Complete().
   185  // 'cancel' is called if the associated operation fails for any reason.
   186  func NewCompletion(cancel context.CancelFunc, callback func(err error)) *Completion {
   187  	return &Completion{
   188  		cancel:    cancel,
   189  		completed: make(chan struct{}),
   190  		callback:  callback,
   191  	}
   192  }