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 }