callAPIDealWithLimit calls the Github GraphQL API and if the Github API returns a rate limiting error, then it waits until the rate limit is reset, and it repeats the request to the API. The parameter `apiCall` is intended to be a closure containing a query or a mutation to the Github GraphQL API.
(ctx context.Context, apiCall func(context.Context) error, rateLimitCallback func(msg string))
| 122 | // parameter `apiCall` is intended to be a closure containing a query or a mutation to the Github |
| 123 | // GraphQL API. |
| 124 | func (c *rateLimitHandlerClient) callAPIDealWithLimit(ctx context.Context, apiCall func(context.Context) error, rateLimitCallback func(msg string)) error { |
| 125 | qctx, cancel := context.WithTimeout(ctx, defaultTimeout) |
| 126 | defer cancel() |
| 127 | // call the function fun() |
| 128 | err := apiCall(qctx) |
| 129 | if err == nil { |
| 130 | return nil |
| 131 | } |
| 132 | // matching the error string |
| 133 | if strings.Contains(err.Error(), "API rate limit exceeded") || |
| 134 | strings.Contains(err.Error(), "was submitted too quickly") { |
| 135 | // a rate limit error |
| 136 | qctx, cancel = context.WithTimeout(ctx, defaultTimeout) |
| 137 | defer cancel() |
| 138 | // Use a separate query to get Github rate limiting information. |
| 139 | limitQuery := rateLimitQuery{} |
| 140 | if err := c.sc.Query(qctx, &limitQuery, map[string]interface{}{}); err != nil { |
| 141 | return err |
| 142 | } |
| 143 | // Get the time when Github will reset the rate limit of their API. |
| 144 | resetTime := limitQuery.RateLimit.ResetAt.Time |
| 145 | msg := fmt.Sprintf( |
| 146 | "Github GraphQL API rate limit. This process will sleep until %s.", |
| 147 | resetTime.String(), |
| 148 | ) |
| 149 | // Send message about rate limiting event. |
| 150 | rateLimitCallback(msg) |
| 151 | |
| 152 | // sanitize the reset time, in case the local clock is wrong |
| 153 | waitTime := time.Until(resetTime) |
| 154 | if waitTime < 0 { |
| 155 | waitTime = 10 * time.Second |
| 156 | } |
| 157 | if waitTime > 30*time.Second { |
| 158 | waitTime = 30 * time.Second |
| 159 | } |
| 160 | |
| 161 | // Pause current goroutine |
| 162 | timer := time.NewTimer(waitTime) |
| 163 | select { |
| 164 | case <-ctx.Done(): |
| 165 | stop(timer) |
| 166 | return ctx.Err() |
| 167 | case <-timer.C: |
| 168 | } |
| 169 | // call the function apiCall() again |
| 170 | qctx, cancel = context.WithTimeout(ctx, defaultTimeout) |
| 171 | defer cancel() |
| 172 | err = apiCall(qctx) |
| 173 | return err // might be nil |
| 174 | } else { |
| 175 | return err |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | func stop(t *time.Timer) { |
| 180 | if !t.Stop() { |
no test coverage detected