sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/github/internal/retry.go (about) 1 /* 2 Copyright 2019 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 internal 18 19 import ( 20 "crypto/rand" 21 "math/big" 22 "strings" 23 "time" 24 25 "github.com/google/go-github/v60/github" 26 "github.com/sirupsen/logrus" 27 ) 28 29 const ( 30 // MaxGithubRetries is the maximum amount of times we flag a GitHub error as 31 // retryable before we give up and do not flag the same call as retryable 32 // anymore. 33 MaxGithubRetries = 3 34 35 // defaultGithubSleep is the amount of time we wait between two consecutive 36 // GitHub calls in case we cannot extract that information from the error 37 // itself. 38 defaultGithubSleep = time.Minute 39 ) 40 41 // DefaultGithubErrChecker is a GithubErrChecker set up with a default amount 42 // of retries and the default sleep function. 43 func DefaultGithubErrChecker() func(error) bool { 44 return GithubErrChecker(MaxGithubRetries, time.Sleep) 45 } 46 47 // GithubErrChecker returns a function that checks errors from GitHub and 48 // decides if they can / should be retried. 49 // It needs to be called with `maxTries`, a number of retries a single call 50 // should be retried at max, and `sleeper`, a function which implements the 51 // sleeping. 52 // 53 // Currently only the `github.RateLimitError` and `github.AbuseRateLimitError` 54 // return values are supported. If one of those errors occur, then we sleep for 55 // a default duration or the amount of time the error told us to wait. 56 // 57 // Other special errors should be easy to implement too. 58 // 59 // It can be used like this: 60 // 61 // for shouldRetry := GithubErrChecker(10, time.Sleep); ; { 62 // commit, res, err := github_client.GetCommit(...) 63 // if !shouldRetry(err) { 64 // return commit, res, err 65 // } 66 // } 67 func GithubErrChecker(maxTries int, sleeper func(time.Duration)) func(error) bool { 68 try := 0 69 70 return func(err error) bool { 71 if err == nil { 72 return false 73 } 74 if try >= maxTries { 75 logrus.Errorf("Max retries (%d) reached, not retrying anymore: %v", maxTries, err) 76 return false 77 } 78 79 try++ 80 81 if err, ok := err.(*github.RateLimitError); ok { 82 waitDuration := defaultGithubSleep 83 until := time.Until(err.Rate.Reset.Time) 84 if until > 0 { 85 waitDuration = until 86 } 87 logrus. 88 WithField("err", err). 89 Infof("Hit the rate limit on try %d, sleeping for %s", try, waitDuration) 90 sleeper(waitDuration) 91 return true 92 } 93 94 if aerr, ok := err.(*github.AbuseRateLimitError); ok { 95 waitDuration := defaultGithubSleep 96 if d := aerr.RetryAfter; d != nil { 97 waitDuration = *d 98 } 99 logrus. 100 WithField("err", aerr). 101 Infof("Hit the abuse rate limit on try %d, sleeping for %s", try, waitDuration) 102 sleeper(waitDuration) 103 return true 104 } 105 106 if strings.Contains(err.Error(), "secondary rate limit. Please wait") { 107 rtime, err := rand.Int(rand.Reader, big.NewInt(30)) 108 if err != nil { 109 logrus.Error(err) 110 return false 111 } 112 waitDuration := time.Duration(rtime.Int64()*int64(time.Second)) + defaultGithubSleep 113 logrus. 114 WithField("err", err). 115 Infof("Hit the GitHub secondary rate limit on try %d, sleeping for %s", try, waitDuration) 116 sleeper(waitDuration) 117 return true 118 } 119 120 return false 121 } 122 }