1 // Copyright 2024 Google LLC 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 // https://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 cloudsql 16 17 import ( 18 "context" 19 "math" 20 "math/rand" 21 "time" 22 23 "google.golang.org/api/googleapi" 24 ) 25 26 // exponentialBackoff calculates a duration based on the attempt i. 27 // 28 // The formula is: 29 // 30 // base * multi^(attempt + 1 + random) 31 // 32 // With base = 200ms and multi = 1.1618, and random = [0.0, 1.0), 33 // the backoff values would fall between the following low and high ends: 34 // 35 // Attempt Low (ms) High (ms) 36 // 37 // 0 324 524 38 // 1 524 847 39 // 2 847 1371 40 // 3 1371 2218 41 // 4 2218 3588 42 // 43 // The theoretical worst case scenario would have a client wait 8.5s in total 44 // for an API request to complete (with the first four attempts failing, and 45 // the fifth succeeding). 46 // 47 // This backoff strategy matches the behavior of the Cloud SQL Proxy v1. 48 func exponentialBackoff(attempt int) time.Duration { 49 const ( 50 base = float64(200 * time.Millisecond) 51 multi = 1.618 52 ) 53 exp := float64(attempt+1) + rand.Float64() 54 return time.Duration(base * math.Pow(multi, exp)) 55 } 56 57 // retry50x will retry any 50x HTTP response up to maxRetries times. The 58 // backoffFunc determines the duration to wait between attempts. 59 func retry50x[T any]( 60 ctx context.Context, 61 f func(context.Context) (*T, error), 62 waitDuration func(int) time.Duration, 63 ) (*T, error) { 64 const maxRetries = 5 65 var ( 66 resp *T 67 err error 68 ) 69 for i := 0; i < maxRetries; i++ { 70 resp, err = f(ctx) 71 // If err is nil, break and return the response. 72 if err == nil { 73 break 74 } 75 76 gErr, ok := err.(*googleapi.Error) 77 // If err is not a googleapi.Error, don't retry. 78 if !ok { 79 return nil, err 80 } 81 // If the error code is not a 50x error, don't retry. 82 if gErr.Code < 500 { 83 return nil, err 84 } 85 86 if wErr := wait(ctx, waitDuration(i)); wErr != nil { 87 err = wErr 88 break 89 } 90 91 } 92 return resp, err 93 } 94 95 // wait will block until the provided duration passes or the context is 96 // canceled, whatever happens first. 97 func wait(ctx context.Context, d time.Duration) error { 98 timer := time.NewTimer(d) 99 select { 100 case <-ctx.Done(): 101 if !timer.Stop() { 102 <-timer.C 103 } 104 return ctx.Err() 105 case <-timer.C: 106 return nil 107 } 108 } 109