Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 Copyright 2016 The LUCI Authors. All rights reserved. | 2 Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 Use of this source code is governed under the Apache License, Version 2.0 | 3 Use of this source code is governed under the Apache License, Version 2.0 |
| 4 that can be found in the LICENSE file. | 4 that can be found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 ///<reference path="../luci-sleep-promise/promise.ts" /> | 7 ///<reference path="../luci-sleep-promise/promise.ts" /> |
| 8 | 8 |
| 9 namespace luci { | 9 namespace luci { |
| 10 /** | 10 /** |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 66 * @param service the RPC service name to call. | 66 * @param service the RPC service name to call. |
| 67 * @param method the RPC method name to call. | 67 * @param method the RPC method name to call. |
| 68 * @param request optional request body content. | 68 * @param request optional request body content. |
| 69 * | 69 * |
| 70 * @returns A Promise that resolves to the RPC response, or errors with an | 70 * @returns A Promise that resolves to the RPC response, or errors with an |
| 71 * error, including HttpError or GrpcError on HTTP/gRPC failure | 71 * error, including HttpError or GrpcError on HTTP/gRPC failure |
| 72 * respectively. | 72 * respectively. |
| 73 */ | 73 */ |
| 74 call<T, R>(service: string, method: string, request?: T): Promise<R> { | 74 call<T, R>(service: string, method: string, request?: T): Promise<R> { |
| 75 let transientRetry = new RetryIterator(this.transientRetry); | 75 let transientRetry = new RetryIterator(this.transientRetry); |
| 76 let doCall = (): Promise<R> => { | 76 return transientRetry |
| 77 // Configure the client for this request. | 77 .do( |
| 78 this.pc.service = service; | 78 () => { |
| 79 this.pc.method = method; | 79 // Configure the client for this request. |
| 80 this.pc.request = request; | 80 this.pc.service = service; |
| 81 this.pc.method = method; | |
| 82 this.pc.request = request; | |
| 81 | 83 |
| 82 // Execute the configured request. | 84 // Execute the configured request. |
| 83 let callPromise: Promise<R> = this.pc.call().completes; | 85 return this.pc.call().completes; |
| 84 return callPromise.then((resp: any) => resp.response) | 86 }, |
| 85 .catch((err: Error) => { | 87 (err: Error, delay: number) => { |
| 86 // Is this a transient error? | 88 // Is this a transient error? |
| 87 if (isTransientError(err)) { | 89 if (!isTransientError(err)) { |
| 88 let delay = transientRetry.next(); | 90 throw err; |
| 89 if (delay >= 0) { | |
| 90 console.warn( | |
| 91 `Transient error calling ` + | |
| 92 `${service}.${method} with params:`, | |
| 93 request, `:`, err, `; retrying after ${delay}ms.`); | |
| 94 return luci.sleepPromise(delay).then(doCall); | |
| 95 } | 91 } |
| 96 } | 92 console.warn( |
| 97 | 93 `Transient error calling ` + |
| 98 // Non-transient, throw the error. | 94 `${service}.${method} with params:`, |
| 99 throw err; | 95 request, `:`, err, `; retrying after ${delay}ms.`); |
| 100 }); | 96 }) |
| 101 }; | 97 .then((resp: any) => resp.response); |
| 102 return doCall(); | |
| 103 } | 98 } |
| 104 } | 99 } |
| 105 | 100 |
| 106 /** | 101 /** |
| 107 * gRPC Codes | 102 * gRPC Codes |
| 108 * | 103 * |
| 109 * Copied from "rpc-code.html" and, more directly, | 104 * Copied from "rpc-code.html" and, more directly, |
| 110 * https://github.com/grpc/grpc-go/blob/972dbd2/codes/codes.go#L43 | 105 * https://github.com/grpc/grpc-go/blob/972dbd2/codes/codes.go#L43 |
| 111 */ | 106 */ |
| 112 export enum Code { | 107 export enum Code { |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 209 | 204 |
| 210 /** | 205 /** |
| 211 * RetryIterator configuration class. | 206 * RetryIterator configuration class. |
| 212 * | 207 * |
| 213 * A user will define the retry parameters using a Retry instance, then create | 208 * A user will define the retry parameters using a Retry instance, then create |
| 214 * a RetryIterator with them. | 209 * a RetryIterator with them. |
| 215 */ | 210 */ |
| 216 export type Retry = { | 211 export type Retry = { |
| 217 // The number of retries to perform before failing. If undefined, will retry | 212 // The number of retries to perform before failing. If undefined, will retry |
| 218 // indefinitely. | 213 // indefinitely. |
| 219 retries: number | undefined; | 214 retries?: number; |
| 220 | 215 |
| 221 // The amount of time to delay in between retry attempts, in milliseconds. | 216 // The amount of time to delay in between retry attempts, in milliseconds. |
| 222 // If undefined or < 0, no delay will be imposed. | 217 // If undefined or < 0, no delay will be imposed. |
| 223 delay: number; | 218 delay: number; |
| 224 // The maximum delay to apply, in milliseconds. If > 0 and delay scales past | 219 // The maximum delay to apply, in milliseconds. If > 0 and delay scales past |
| 225 // "maxDelay", it will be capped at "maxDelay". | 220 // "maxDelay", it will be capped at "maxDelay". |
| 226 maxDelay: number | undefined; | 221 maxDelay?: number; |
| 227 | 222 |
| 228 // delayScaling is the multiplier applied to "delay" in between retries. If | 223 // delayScaling is the multiplier applied to "delay" in between retries. If |
| 229 // undefined or <= 1, DEFAULT_DELAY_SCALING will be used. | 224 // undefined or <= 1, DEFAULT_DELAY_SCALING will be used. |
| 230 delayScaling?: number; | 225 delayScaling?: number; |
| 231 }; | 226 }; |
| 232 | 227 |
| 228 /** RetryCallback is an optional callback type used in "do". */ | |
| 229 export type RetryCallback = (err: Error, delay: number) => void; | |
| 230 | |
| 231 /** | |
| 232 * Stopped is a sentinel error thrown by RetryIterator when it runs out of | |
| 233 * retries. | |
| 234 */ | |
| 235 export const STOPPED = new Error('retry stopped'); | |
| 236 | |
| 233 /** | 237 /** |
| 234 * Generic exponential backoff retry delay generator. | 238 * Generic exponential backoff retry delay generator. |
| 235 * | 239 * |
| 236 * A RetryIterator is a specific configured instance of a Retry. Each call to | 240 * A RetryIterator is a specific configured instance of a Retry. Each call to |
| 237 * "next()" returns the next delay in the retry sequence. | 241 * "next()" returns the next delay in the retry sequence. |
| 238 */ | 242 */ |
| 239 export class RetryIterator { | 243 export class RetryIterator { |
| 240 // Default scaling if no delay is specified. | 244 // Default scaling if no delay is specified. |
| 241 static readonly DEAFULT_DELAY_SCALING = 2; | 245 static readonly DEAFULT_DELAY_SCALING = 2; |
| 242 | 246 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 253 this.delayScaling = (config.delayScaling || 0); | 257 this.delayScaling = (config.delayScaling || 0); |
| 254 if (this.delayScaling < 1) { | 258 if (this.delayScaling < 1) { |
| 255 this.delayScaling = RetryIterator.DEAFULT_DELAY_SCALING; | 259 this.delayScaling = RetryIterator.DEAFULT_DELAY_SCALING; |
| 256 } | 260 } |
| 257 } | 261 } |
| 258 | 262 |
| 259 /** | 263 /** |
| 260 * @returns the next delay, in milliseconds. If there are no more retries, | 264 * @returns the next delay, in milliseconds. If there are no more retries, |
| 261 * returns undefined. | 265 * returns undefined. |
| 262 */ | 266 */ |
| 263 next(): number|undefined { | 267 next(): number { |
| 264 // Apply retries, if they have been enabled. | 268 // Apply retries, if they have been enabled. |
| 265 if (this.retries !== undefined) { | 269 if (this.retries !== undefined) { |
| 266 if (this.retries <= 0) { | 270 if (this.retries <= 0) { |
| 267 // No more retries remaining. | 271 // No more retries remaining. |
| 268 return undefined; | 272 throw STOPPED; |
| 269 } | 273 } |
| 270 this.retries--; | 274 this.retries--; |
| 271 } | 275 } |
| 272 | 276 |
| 273 let delay = this.delay; | 277 let delay = this.delay; |
| 274 this.delay *= this.delayScaling; | 278 this.delay *= this.delayScaling; |
| 275 if (this.maxDelay > 0 && delay > this.maxDelay) { | 279 if (this.maxDelay > 0 && delay > this.maxDelay) { |
| 276 this.delay = delay = this.maxDelay; | 280 this.delay = delay = this.maxDelay; |
| 277 } | 281 } |
| 278 return delay; | 282 return delay; |
| 279 } | 283 } |
| 284 | |
| 285 /** | |
| 286 * Executes a Promise, retrying if the Promise raises an error. | |
| 287 * | |
| 288 * "do" iteratively tries to execute a Promise, generated by "gen". If that | |
| 289 * Promise raises an error, "do" will retry until it either runs out of | |
| 290 * retries, or the Promise does not return an error. Each retry, "do" will | |
| 291 * invoke "gen" again to generate a new Promise. | |
| 292 * | |
| 293 * An optional "onError" callback can be supplied. If it is, it will be | |
| 294 * invoked in between each retry. The callback may, itself, throw, in which | |
| 295 * case the retry loop will stop. This can be used for reporting and/or | |
| 296 * selective retries. | |
| 297 * | |
| 298 * @param gen Promise generator function for retries. | |
| 299 * @param onError optional callback to be invoked in between retries. | |
| 300 * | |
| 301 * @throws any the error generated by "gen"'s Promise, if out of retries, | |
|
nodir
2017/03/08 07:41:28
any error
dnj
2017/03/08 08:50:15
"any" is the type
nodir
2017/03/13 19:48:21
Acknowledged.
| |
| 302 * or the error raised by onError if it chooses to throw. | |
| 303 */ | |
| 304 do | |
|
nodir
2017/03/08 07:41:28
why line break?
this causes extra indentation in t
dnj
2017/03/08 08:50:15
clang-format put it here.
nodir
2017/03/13 19:48:21
Acknowledged.
| |
| 305 <T>(gen: () => Promise<T>, onError?: RetryCallback): Promise<T> { | |
| 306 let onErr = (err: Error): Promise<T> => { | |
| 307 let delay: number; | |
| 308 try { | |
| 309 delay = this.next(); | |
| 310 } catch (e) { | |
| 311 if (e !== STOPPED) { | |
| 312 console.warn('Unexpected error generating next delay:', e); | |
| 313 } | |
| 314 | |
| 315 // If we could not generate another retry delay, raise the initial | |
| 316 // Promise's error. | |
| 317 throw err; | |
| 318 } | |
| 319 | |
| 320 if (onError) { | |
| 321 // Note: this may throw. | |
| 322 onError(err, delay); | |
| 323 } | |
| 324 | |
| 325 return luci.sleepPromise(delay).then(() => { | |
|
nodir
2017/03/08 07:41:28
return luci.sleepPromise(delay).then(() => gen().c
dnj
2017/03/08 08:50:15
clang-format
| |
| 326 return gen().catch(onErr); | |
| 327 }); | |
| 328 }; | |
| 329 | |
| 330 return gen().catch(onErr); | |
| 331 } | |
| 280 } | 332 } |
| 281 } | 333 } |
| OLD | NEW |