1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package wait 18 19 import ( 20 "context" 21 "time" 22 ) 23 24 // PollUntilContextCancel tries a condition func until it returns true, an error, or the context 25 // is cancelled or hits a deadline. condition will be invoked after the first interval if the 26 // context is not cancelled first. The returned error will be from ctx.Err(), the condition's 27 // err return value, or nil. If invoking condition takes longer than interval the next condition 28 // will be invoked immediately. When using very short intervals, condition may be invoked multiple 29 // times before a context cancellation is detected. If immediate is true, condition will be 30 // invoked before waiting and guarantees that condition is invoked at least once, regardless of 31 // whether the context has been cancelled. 32 func PollUntilContextCancel(ctx context.Context, interval time.Duration, immediate bool, condition ConditionWithContextFunc) error { 33 return loopConditionUntilContext(ctx, Backoff{Duration: interval}.Timer(), immediate, false, condition) 34 } 35 36 // PollUntilContextTimeout will terminate polling after timeout duration by setting a context 37 // timeout. This is provided as a convenience function for callers not currently executing under 38 // a deadline and is equivalent to: 39 // 40 // deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) 41 // err := PollUntilContextCancel(deadlineCtx, interval, immediate, condition) 42 // 43 // The deadline context will be cancelled if the Poll succeeds before the timeout, simplifying 44 // inline usage. All other behavior is identical to PollUntilContextCancel. 45 func PollUntilContextTimeout(ctx context.Context, interval, timeout time.Duration, immediate bool, condition ConditionWithContextFunc) error { 46 deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) 47 defer deadlineCancel() 48 return loopConditionUntilContext(deadlineCtx, Backoff{Duration: interval}.Timer(), immediate, false, condition) 49 } 50 51 // Poll tries a condition func until it returns true, an error, or the timeout 52 // is reached. 53 // 54 // Poll always waits the interval before the run of 'condition'. 55 // 'condition' will always be invoked at least once. 56 // 57 // Some intervals may be missed if the condition takes too long or the time 58 // window is too short. 59 // 60 // If you want to Poll something forever, see PollInfinite. 61 // 62 // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. 63 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 64 // defined by the context package. Will be removed in a future release. 65 func Poll(interval, timeout time.Duration, condition ConditionFunc) error { 66 return PollWithContext(context.Background(), interval, timeout, condition.WithContext()) 67 } 68 69 // PollWithContext tries a condition func until it returns true, an error, 70 // or when the context expires or the timeout is reached, whichever 71 // happens first. 72 // 73 // PollWithContext always waits the interval before the run of 'condition'. 74 // 'condition' will always be invoked at least once. 75 // 76 // Some intervals may be missed if the condition takes too long or the time 77 // window is too short. 78 // 79 // If you want to Poll something forever, see PollInfinite. 80 // 81 // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. 82 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 83 // defined by the context package. Will be removed in a future release. 84 func PollWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { 85 return poll(ctx, false, poller(interval, timeout), condition) 86 } 87 88 // PollUntil tries a condition func until it returns true, an error or stopCh is 89 // closed. 90 // 91 // PollUntil always waits interval before the first run of 'condition'. 92 // 'condition' will always be invoked at least once. 93 // 94 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 95 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 96 // defined by the context package. Will be removed in a future release. 97 func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { 98 return PollUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) 99 } 100 101 // PollUntilWithContext tries a condition func until it returns true, 102 // an error or the specified context is cancelled or expired. 103 // 104 // PollUntilWithContext always waits interval before the first run of 'condition'. 105 // 'condition' will always be invoked at least once. 106 // 107 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 108 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 109 // defined by the context package. Will be removed in a future release. 110 func PollUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { 111 return poll(ctx, false, poller(interval, 0), condition) 112 } 113 114 // PollInfinite tries a condition func until it returns true or an error 115 // 116 // PollInfinite always waits the interval before the run of 'condition'. 117 // 118 // Some intervals may be missed if the condition takes too long or the time 119 // window is too short. 120 // 121 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 122 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 123 // defined by the context package. Will be removed in a future release. 124 func PollInfinite(interval time.Duration, condition ConditionFunc) error { 125 return PollInfiniteWithContext(context.Background(), interval, condition.WithContext()) 126 } 127 128 // PollInfiniteWithContext tries a condition func until it returns true or an error 129 // 130 // PollInfiniteWithContext always waits the interval before the run of 'condition'. 131 // 132 // Some intervals may be missed if the condition takes too long or the time 133 // window is too short. 134 // 135 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 136 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 137 // defined by the context package. Will be removed in a future release. 138 func PollInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { 139 return poll(ctx, false, poller(interval, 0), condition) 140 } 141 142 // PollImmediate tries a condition func until it returns true, an error, or the timeout 143 // is reached. 144 // 145 // PollImmediate always checks 'condition' before waiting for the interval. 'condition' 146 // will always be invoked at least once. 147 // 148 // Some intervals may be missed if the condition takes too long or the time 149 // window is too short. 150 // 151 // If you want to immediately Poll something forever, see PollImmediateInfinite. 152 // 153 // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. 154 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 155 // defined by the context package. Will be removed in a future release. 156 func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error { 157 return PollImmediateWithContext(context.Background(), interval, timeout, condition.WithContext()) 158 } 159 160 // PollImmediateWithContext tries a condition func until it returns true, an error, 161 // or the timeout is reached or the specified context expires, whichever happens first. 162 // 163 // PollImmediateWithContext always checks 'condition' before waiting for the interval. 164 // 'condition' will always be invoked at least once. 165 // 166 // Some intervals may be missed if the condition takes too long or the time 167 // window is too short. 168 // 169 // If you want to immediately Poll something forever, see PollImmediateInfinite. 170 // 171 // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. 172 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 173 // defined by the context package. Will be removed in a future release. 174 func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { 175 return poll(ctx, true, poller(interval, timeout), condition) 176 } 177 178 // PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed. 179 // 180 // PollImmediateUntil runs the 'condition' before waiting for the interval. 181 // 'condition' will always be invoked at least once. 182 // 183 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 184 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 185 // defined by the context package. Will be removed in a future release. 186 func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { 187 return PollImmediateUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) 188 } 189 190 // PollImmediateUntilWithContext tries a condition func until it returns true, 191 // an error or the specified context is cancelled or expired. 192 // 193 // PollImmediateUntilWithContext runs the 'condition' before waiting for the interval. 194 // 'condition' will always be invoked at least once. 195 // 196 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 197 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 198 // defined by the context package. Will be removed in a future release. 199 func PollImmediateUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { 200 return poll(ctx, true, poller(interval, 0), condition) 201 } 202 203 // PollImmediateInfinite tries a condition func until it returns true or an error 204 // 205 // PollImmediateInfinite runs the 'condition' before waiting for the interval. 206 // 207 // Some intervals may be missed if the condition takes too long or the time 208 // window is too short. 209 // 210 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 211 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 212 // defined by the context package. Will be removed in a future release. 213 func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error { 214 return PollImmediateInfiniteWithContext(context.Background(), interval, condition.WithContext()) 215 } 216 217 // PollImmediateInfiniteWithContext tries a condition func until it returns true 218 // or an error or the specified context gets cancelled or expired. 219 // 220 // PollImmediateInfiniteWithContext runs the 'condition' before waiting for the interval. 221 // 222 // Some intervals may be missed if the condition takes too long or the time 223 // window is too short. 224 // 225 // Deprecated: This method does not return errors from context, use PollUntilContextCancel. 226 // Note that the new method will no longer return ErrWaitTimeout and instead return errors 227 // defined by the context package. Will be removed in a future release. 228 func PollImmediateInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { 229 return poll(ctx, true, poller(interval, 0), condition) 230 } 231 232 // Internally used, each of the public 'Poll*' function defined in this 233 // package should invoke this internal function with appropriate parameters. 234 // ctx: the context specified by the caller, for infinite polling pass 235 // a context that never gets cancelled or expired. 236 // immediate: if true, the 'condition' will be invoked before waiting for the interval, 237 // in this case 'condition' will always be invoked at least once. 238 // wait: user specified WaitFunc function that controls at what interval the condition 239 // function should be invoked periodically and whether it is bound by a timeout. 240 // condition: user specified ConditionWithContextFunc function. 241 // 242 // Deprecated: will be removed in favor of loopConditionUntilContext. 243 func poll(ctx context.Context, immediate bool, wait waitWithContextFunc, condition ConditionWithContextFunc) error { 244 if immediate { 245 done, err := runConditionWithCrashProtectionWithContext(ctx, condition) 246 if err != nil { 247 return err 248 } 249 if done { 250 return nil 251 } 252 } 253 254 select { 255 case <-ctx.Done(): 256 // returning ctx.Err() will break backward compatibility, use new PollUntilContext* 257 // methods instead 258 return ErrWaitTimeout 259 default: 260 return waitForWithContext(ctx, wait, condition) 261 } 262 } 263 264 // poller returns a WaitFunc that will send to the channel every interval until 265 // timeout has elapsed and then closes the channel. 266 // 267 // Over very short intervals you may receive no ticks before the channel is 268 // closed. A timeout of 0 is interpreted as an infinity, and in such a case 269 // it would be the caller's responsibility to close the done channel. 270 // Failure to do so would result in a leaked goroutine. 271 // 272 // Output ticks are not buffered. If the channel is not ready to receive an 273 // item, the tick is skipped. 274 // 275 // Deprecated: Will be removed in a future release. 276 func poller(interval, timeout time.Duration) waitWithContextFunc { 277 return waitWithContextFunc(func(ctx context.Context) <-chan struct{} { 278 ch := make(chan struct{}) 279 280 go func() { 281 defer close(ch) 282 283 tick := time.NewTicker(interval) 284 defer tick.Stop() 285 286 var after <-chan time.Time 287 if timeout != 0 { 288 // time.After is more convenient, but it 289 // potentially leaves timers around much longer 290 // than necessary if we exit early. 291 timer := time.NewTimer(timeout) 292 after = timer.C 293 defer timer.Stop() 294 } 295 296 for { 297 select { 298 case <-tick.C: 299 // If the consumer isn't ready for this signal drop it and 300 // check the other channels. 301 select { 302 case ch <- struct{}{}: 303 default: 304 } 305 case <-after: 306 return 307 case <-ctx.Done(): 308 return 309 } 310 } 311 }() 312 313 return ch 314 }) 315 } 316