Skip to content

Deal with GitHub's secondary rate limit #2355

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
fthomas opened this issue Nov 15, 2021 · 11 comments · May be fixed by #2970
Open

Deal with GitHub's secondary rate limit #2355

fthomas opened this issue Nov 15, 2021 · 11 comments · May be fixed by #2970
Labels
cat:forge enhancement New feature or request

Comments

@fthomas
Copy link
Member

fthomas commented Nov 15, 2021

My public Scala Steward instance has repeatedly hit GitHub's secondary rate limit recently when creating PRs:

org.scalasteward.core.util.UnexpectedResponse: uri: https://api.github.com/repos/***/***/pulls
method: POST
status: 403 Forbidden
headers: Headers(
  access-control-allow-origin: *,
  access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset
  content-security-policy: default-src 'none'
  content-type: application/json; charset=utf-8
  date: Wed, 10 Nov 2021 04:26:48 GMT
  referrer-policy: origin-when-cross-origin
  strict-origin-when-cross-origin
  server: github.com
  strict-transport-security: max-age=31536000; includeSubdomains; preload
  vary: Accept-Encoding, Accept, X-Requested-With
  x-accepted-oauth-scopes: 
  x-content-type-options: nosniff
  x-frame-options: deny
  x-github-media-type: github.v3
  x-github-request-id: B1C8:AD73:2D8A937:2E660AE:618B4A03
  x-oauth-scopes: delete_repo
  notifications, repo, workflow
  x-ratelimit-limit: 5000
  x-ratelimit-remaining: 4290
  x-ratelimit-reset: 1636520514
  x-ratelimit-resource: core
  x-ratelimit-used: 710
  x-xss-protection: 0
  )
body: {
  "message":"You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later.",
  "documentation_url":"https://docs.github.com/rest/overview/resources-in-the-rest-api#secondary-rate-limits"
}

GitHub docs about the secondary rate limit are here:

The response doesn't include a Retry-After header, so we don't know how long Scala Steward should wait before creating the next PR.

fthomas added a commit that referenced this issue Dec 24, 2021
Instead of `(Boolean, List[Update.Single])` where the first element
designate if the second is empty, we use
`Option[Nel[UpdateState.WithUpdate]]` instead. This is a little bit
nicer since we don't need to interpret the `Boolean` but just traverse
the `Option`.

Having the `UpdateState`s available in `StewardAlg` will probably also
come in handy when dealing with #2355. I imagine that some kind of rate
limiter in `StewardAlg` will solve that issue.
@d-g-n
Copy link
Contributor

d-g-n commented Jan 24, 2022

Hello, we're trying to run a local version of scala-steward against approx 30-35 repos, but given how out of date they are, we're frequently getting body: {"message":"You have exceeded a secondary rate limit and have been temporarily blocked from content creation. Please retry your request again later.","documentation_url":"https://docs.github.com/rest/overview/resources-in-the-rest-api#secondary-rate-limits"},

i acknowledge that github isn't returning the correct header but is there any config or setting we can tweak to hopefully mitigate this until a full fix is in place?

@htmldoug
Copy link
Contributor

htmldoug commented Mar 3, 2022

Looks like github returns Retry-After now!

When you have been limited, use the Retry-After response header to slow down. The value of the Retry-After header will always be an integer, representing the number of seconds you should wait before making requests again. For example, Retry-After: 30 means you should wait 30 seconds before sending more requests.

https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits

Actual response I received:

org.scalasteward.core.util.UnexpectedResponse: uri: https://api.github.com/repos/***/***/pulls?head=****/***%3Aupdate/google-api-services-sheets-v4-rev20220221-1.32.1&base=main&state=all
method: GET
status: 403 Forbidden
headers:
  access-control-allow-origin: *
  access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
  content-security-policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.githubapp.com collector.github.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com collector.githubapp.com collector.github.com avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com objects-origin.githubusercontent.com secured-user-images.githubusercontent.com/; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/
  content-type: application/json; charset=utf-8
  date: Thu, 03 Mar 2022 16:14:51 GMT
  expect-ct: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors"
  referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
  retry-after: 60
  server: github.com
  strict-transport-security: max-age=31536000; includeSubdomains; preload
  vary: Accept-Encoding, Accept, X-Requested-With
  x-content-type-options: nosniff
  x-frame-options: deny
  x-github-media-type: github.v3; format=json
  x-github-request-id: AEC9:5D27:43638DF:7FCD679:6220E961
  x-xss-protection: 0
body: {
  "documentation_url": "https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#secondary-rate-limits",
  "message": "You have exceeded a secondary rate limit. Please wait a few minutes before you try again."
}

@fthomas
Copy link
Member Author

fthomas commented Mar 4, 2022

You got this header in response to a GET for listing PRs. I posted a response to a POST for creating a PR. The documentation you cited also mentions that there is no RetryAfter for creating PRs. So I doubt that GitHub has changed anything here.

@htmldoug
Copy link
Contributor

htmldoug commented Mar 4, 2022

Ah, good point. So #2540 would only fix a subset of these issues. The majority of the failures I'm seeing are actually from POST .../forks, so I can at least take care of those. I'll update my PR so it keeps this issue open.

org.scalasteward.core.util.UnexpectedResponse: uri: https://api.github.com/repos/***/***/forks
method: POST
status: 403 Forbidden
headers:
  access-control-allow-origin: *
  access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
  content-security-policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.githubapp.com collector.github.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com collector.githubapp.com collector.github.com avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com objects-origin.githubusercontent.com secured-user-images.githubusercontent.com/; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/
  content-type: application/json; charset=utf-8
  date: Thu, 03 Mar 2022 16:14:51 GMT
  expect-ct: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors"
  referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
  retry-after: 60
  server: github.com
  strict-transport-security: max-age=31536000; includeSubdomains; preload
  vary: Accept-Encoding, Accept, X-Requested-With
  x-content-type-options: nosniff
  x-frame-options: deny
  x-github-media-type: github.v3; format=json
  x-github-request-id: AEC9:5D27:4363908:7FCD6C5:6220E97B
  x-xss-protection: 0
body: {
  "documentation_url": "https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#secondary-rate-limits",
  "message": "You have exceeded a secondary rate limit. Please wait a few minutes before you try again."
}

@exoego
Copy link
Contributor

exoego commented Apr 19, 2022

Regarding #2540, code LGTM 👍
However, secondary rate limit for POST does not return Retry-After header, so I am afraid that adding Retry-After is going to be an overhead.

From the document,

If you're making a large number of POST, PATCH, PUT, or DELETE requests for a single user or client ID, wait at least one second between each request.

I wonder adding sleep(1.second) probably mitigate secondary rate limit.

@htmldoug
Copy link
Contributor

htmldoug commented Apr 20, 2022

Thanks for the review of #2540!

The document also says:

When you have been limited, use the Retry-After response header to slow down. The value of the Retry-After header will always be an integer, representing the number of seconds you should wait before making requests again. For example, Retry-After: 30 means you should wait 30 seconds before sending more requests.

Implementing both of the github best practices seems reasonable to me--probably best as separate PRs.

I am afraid that adding Retry-After is going to be an overhead.

It's true that not all requests would return Retry-After. I haven't benchmarked it, although I expect that checking for the presence of the header should have negligible CPU/memory cost. Additionally, I'd be surprised if this code path were hot enough to optimize.

Did you mean this as a blocker for #2540? Is there anything else I can do to address concerns so that this can be merged?

We have a long list of repos we run scala-steward against and it's currently failing for us at the end of that list. I'd love to see a fix merged one way or another.

@exoego
Copy link
Contributor

exoego commented Jun 15, 2022

I've merged scala-steward-org/scala-steward-action#335
It does not cover all cases but helps some cases definitely.

I keep this issue open until we have solutions for other cases.

@rossabaker
Copy link
Contributor

We are still struggling with this. I wonder if the Retry-After from the other endpoints gives us some empirical guess as to what would be an appropriate time to sleep? Like, is it always a fixed duration, or is it (more likely) some token bucket and varies wildly from run to run?

@rossabaker
Copy link
Contributor

Or if we know whether we're talking on the order of resting for a couple seconds vs "a few minutes" like the message. A couple seconds seems worthwhile. "A few minutes" could really add up on runners where we pay by the minute.

@fthomas
Copy link
Member Author

fthomas commented Jan 27, 2023

I don't maintain an instance that works with GitHub anymore, but still have logs of @scala-steward from March and April 2022. I looked at four occasions where it hit the secondary rate limit and it was able to create PRs again after ~ 40, 30, 8, and 40 minutes.

@fthomas
Copy link
Member Author

fthomas commented Jan 27, 2023

I don't know if waiting for a few seconds or minutes between PRs would have meant that it would have never hit the secondary rate limit.

@fthomas fthomas linked a pull request Feb 11, 2023 that will close this issue
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
cat:forge enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants