Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Retry retryable 403 (rate limit) #2387

Merged
merged 35 commits into from
Jun 28, 2023

Conversation

EnricoMi
Copy link
Collaborator

@EnricoMi EnricoMi commented Dec 21, 2022

GitHub response with rate limit errors (403 Forbidden) when request occur too frequently. While they can be mitigated by throttling request rate (see #1989), they can still occur.

Not all 403 errors are retry-able (e.g. access forbidden if not authenticated), so retrying based on status code (403) only would be a bad idea. The urllib3.util.retry.Retry interface does not allow for checking the response message or body content, only response code: urllib3/urllib3#2500

This PR introduces a bespoke PyGithub retry class that is able to determine whether to retry based on the response content. With that, 403 responses that refer to RateLimitExceeded are retried, other 403 responses are not retried. The base Retry implementation respects Retry-After headers if they exist.

Primary rate limit errors are retried after the primary rate reset occurs, secondary rate limit errors are retried after 60 seconds (configurable).

Log for a primary error rate limit:

2022-12-16 13:56:47 +0000 - urllib3.connectionpool - DEBUG - https://api.github.com:443 "GET /repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150 HTTP/1.1" 403 None
2022-12-16 13:56:47 +0000 - github.GithubRetry -  INFO - Request GET /repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150 failed with 403: Forbidden
2022-12-16 13:56:47 +0000 - github.GithubRetry - DEBUG - Response body indicates retry-able primary rate limit error: API rate limit exceeded for installation ID 5425010.
2022-12-16 13:56:47 +0000 - urllib3.util.retry - DEBUG - Incremented Retry for (url='/repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150'): GithubRetry(total=9, connect=None, read=None, redirect=None, status=None)
2022-12-16 13:56:47 +0000 - github.GithubRetry - DEBUG - Reset occurs in 0:24:24.940607 (1671200472 / 2022-12-16 14:21:12)
2022-12-16 13:56:47 +0000 - github.GithubRetry - INFO - Setting next backoff to 1465.940607s
2022-12-16 14:21:13 +0000 - urllib3.connectionpool - DEBUG - Retry: /repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150
2022-12-16 14:21:13 +0000 - urllib3.connectionpool - DEBUG - Resetting dropped connection: api.github.com
2022-12-16 14:21:13 +0000 - urllib3.connectionpool - DEBUG - https://api.github.com:443 "GET /repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150 HTTP/1.1" 200 None
2022-12-16 14:21:13 +0000 - github.Requester - DEBUG - GET https://api.github.com/repositories/290227969/commits/71ca27c4a9d74fa7ec2898304ee75909e4a57124/check-runs?per_page=1&page=150 {'Accept': 'application/vnd.github.v3+json', 'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 200

Log for a secondary error rate limit:

2022-12-17 09:48:56 +0000 - urllib3.connectionpool - DEBUG - https://api.github.com:443 "GET /search/issues?q=type%3Apr+repo%3A%22EnricoMi%2Fpublish-unit-test-result-action%22+df23eea893bc760a02ff52d63d9ce4c6a06c381b&per_page=10 HTTP/1.1" 403 None
2022-12-17 09:48:56 +0000 - github.GithubRetry -  INFO - Request GET /search/issues?q=type%3Apr+repo%3A%22EnricoMi%2Fpublish-unit-test-result-action%22+df23eea893bc760a02ff52d63d9ce4c6a06c381b&per_page=10 failed with 403: Forbidden
2022-12-17 09:48:56 +0000 - github.GithubRetry - DEBUG - Response body indicates retry-able secondary rate limit error: You have exceeded a secondary rate limit. Please wait a few minutes before you try again.
2022-12-17 09:48:56 +0000 - urllib3.util.retry - DEBUG - Incremented Retry for (url='/search/issues?q=type%3Apr+repo%3A%22EnricoMi%2Fpublish-unit-test-result-action%22+df23eea893bc760a02ff52d63d9ce4c6a06c381b&per_page=10'): GithubRetry(total=8, connect=None, read=None, redirect=None, status=None)
2022-12-17 09:48:56 +0000 - github.GithubRetry - DEBUG - Setting next backoff to 60s
2022-12-17 09:49:56 +0000 - urllib3.connectionpool - DEBUG - Retry: /search/issues?q=type%3Apr+repo%3A%22EnricoMi%2Fpublish-unit-test-result-action%22+df23eea893bc760a02ff52d63d9ce4c6a06c381b&per_page=10
2022-12-17 09:49:56 +0000 - urllib3.connectionpool - DEBUG - https://api.github.com:443 "GET /search/issues?q=type%3Apr+repo%3A%22EnricoMi%2Fpublish-unit-test-result-action%22+df23eea893bc760a02ff52d63d9ce4c6a06c381b&per_page=10 HTTP/1.1" 200 None

@codecov-commenter
Copy link

codecov-commenter commented Dec 21, 2022

Codecov Report

Patch coverage: 100.00% and project coverage change: +0.01 🎉

Comparison is base (094538e) 98.35% compared to head (9735c1d) 98.36%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2387      +/-   ##
==========================================
+ Coverage   98.35%   98.36%   +0.01%     
==========================================
  Files         131      132       +1     
  Lines       13178    13280     +102     
==========================================
+ Hits        12961    13063     +102     
  Misses        217      217              
Impacted Files Coverage Δ
github/GithubRetry.py 100.00% <100.00%> (ø)
github/MainClass.py 99.30% <100.00%> (+<0.01%) ⬆️
github/Requester.py 97.43% <100.00%> (+0.10%) ⬆️
github/__init__.py 94.11% <100.00%> (+0.36%) ⬆️

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@EnricoMi EnricoMi changed the title Retry retryable 403 errors (rate limit errors) Retry retryable 403 (rate limit) errors Dec 21, 2022
@EnricoMi EnricoMi force-pushed the branch-retry-retryable-403 branch 2 times, most recently from 5225698 to 8b9d62d Compare December 21, 2022 14:00
@hellkite500
Copy link

hellkite500 commented Jan 5, 2023

I have browsed through this PR and it looks practical to me, but I'm not fully up to speed with the code review requirements of this project to offer an official review. I'm about to pull this branch though and do some testing to see if this works for my use case.

Update:
Pulled this PR and ran a large workload against the API. Worked seamlessly! Thanks for your effort on this!

@dkujawski
Copy link

This works great in my testing. Implementation is clean and contained. Very similar to what I have implemented for my internal tools. I have a framework that iterates over many repositories(hundreds) using concurrent.futures to create branches, perform change operations and create PRs. I swapped out my implementation with a build from this branch and it performed as expected. In my tests, reducing the secondaryRateWait to 5s demonstrated the retries and eventual exceptions. For my use case secondaryRateWait=15 seems to be ideal.

Happy to help and/or provide any additional feedback where needed.

Thanks for the work on this @EnricoMi !

@EnricoMi
Copy link
Collaborator Author

EnricoMi commented Jan 9, 2023

@dkujawski thanks for testing this!

@mikeNG
Copy link

mikeNG commented Jan 13, 2023

I can confirm this also works fine in my scripts that work with the entire https://github.com/LineageOS org (around 2500 repositories)

@EnricoMi EnricoMi changed the title Retry retryable 403 (rate limit) errors Retry retryable 403 (rate limit) and 5xx errors Jan 21, 2023
@EnricoMi EnricoMi changed the title Retry retryable 403 (rate limit) and 5xx errors Retry retryable 403 (rate limit) Jan 21, 2023
@EnricoMi
Copy link
Collaborator Author

@s-t-e-v-e-n-k @sfdye rebased with latest master, what do you think?

@JosephTLyons
Copy link

This seems so practical - would love to see this land.

)

# we backoff primary rate limit at least until X-RateLimit-Reset,
# we backoff secondary rate limit at for secondaryRateWait seconds
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my experience we can use x-ratelimit-reset for secondary wait as well, we don't have to guess

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could still potentially use secondaryRateWait as the max time we are willing to wait

@RomainC-lab
Copy link

Hello,
I’m interested in this feature and I appreciate your work on it.
Do you have an estimate of when it will be ready to merge?
Thank you.

@EnricoMi
Copy link
Collaborator Author

@JLLeitschuh can I get a review here as well, please?

Comment on lines +57 to +59
# used to mock datetime, mock.patch("github.GithubRetry.date") does not work as this
# references the class, not the module (due to re-exporting in github/__init__.py)
__datetime = datetime
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you mention mocking here, but the changes don't seem to actually do any mocking. Am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@JLLeitschuh JLLeitschuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@JLLeitschuh JLLeitschuh merged commit 0bb72ca into PyGithub:main Jun 28, 2023
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants