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

Adjust GHRateLimit to system time instead of depending on synchronization #595

Merged
merged 8 commits into from
Nov 12, 2019

Conversation

bitwiseman
Copy link
Member

@bitwiseman bitwiseman commented Nov 7, 2019

Fixes #383
Related to #591

@martinvanzijl @PauloMigAlmeida
if you have a little time to look at this over, any feedback would be appreciated.

Summary

If the client time is out of sync with global time, GHRateLimit could report a resetDate that was completely wrong - accurate to global time, but disconnected from local system time. This wouldn't be so bad, but some clients use the resetDate to budget API calls. If they believe they have 10 hours until reset and only 4000 calls available, they effective lock up.

Further, public fields and having a "Date" field that is holding unconverted EpochSeconds just bothers me.

I deprecated the old public fields and added private fields with getters. I then added awareness of the Date header returned with the Http response. Using that we can generate calculatedSecondsUntilReset and the create the resetDate by adding that to System-time that his instance was created.

As a final step, I made it so rateLimit() will update automatically when then GHRateLimit instance expires.

Improves the way reset date is calculated - uses server date if possible.

Fixes hub4j#383
@PauloMigAlmeida
Copy link
Contributor

Cool! I will take a look at it during lunchtime :)

Sometimes adding tests finds broken product code.  Sometimes it finds broken test environment.
This was the latter.

The wiremock templating returns dates in UTC format not RFC-1123 format.
Introduces hub4j#597

This does not appear to be a bug in github-api but in OkHttp 2.7.5
(and/or the Cache implementation in that version).
@PauloMigAlmeida
Copy link
Contributor

Hi @bitwiseman,

If the client time is out of sync with global time, GHRateLimit could report a resetDate that was completely wrong

Good catch! That is a nice way of fixing the problem. 👍

[Re: refactoring/improvements you made]
The only thing I would possibly change is the way that the content from /rate_limit response is parsed.

Currently, we're using the JsonRateLimit containing only the rate attribute which has been deprecated. Ideally, we should add the newer JSON attributes listed at https://developer.github.com/v3/rate_limit/#response and refactor all references to the rate attribute to resources.core.

In case you want to go down that road, the Requester.noteRateLimit() method will have to be reworked a bit. https://github.com/github-api/github-api/blob/f28edbcf8fcb57a291aaa537bc57ea79c9bcda64/src/main/java/org/kohsuke/github/Requester.java#L354-L362

same here https://github.com/github-api/github-api/blob/f28edbcf8fcb57a291aaa537bc57ea79c9bcda64/src/main/java/org/kohsuke/github/GitHub.java#L329-L338

|| headerRateLimit.getResetDate().getTime() < observed.getResetDate().getTime()
|| headerRateLimit.remaining > observed.remaining) {
|| headerRateLimit.getRemaining() > observed.getRemaining()
|| headerRateLimit.getResetEpochSeconds() < observed.getResetEpochSeconds()) {
Copy link
Contributor

@PauloMigAlmeida PauloMigAlmeida Nov 9, 2019

Choose a reason for hiding this comment

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

I must be missing something really simple but... I took a look at the git history and the issues used to discuss the original implementation (back in 2017) and I still don't get why we wouldn't update the headerRateLimit variable every time updateRateLimit method is invoked.

Would you mind sharing with me your understanding of that if statement and/or any idea of when this would be applicable?

Copy link
Member Author

@bitwiseman bitwiseman Nov 9, 2019

Choose a reason for hiding this comment

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

Hm, good question. Ah, I remember now. Multi-threading.
If you have 10 threads all make queries and then all try to update the rate limit. We want to pick the one with the lowest remaining count, of if a reset occurs we want to pick the one with the later reset date.

I need to write a test that covers this and add comments to the code explaining.

Copy link
Contributor

Choose a reason for hiding this comment

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

ahhhhh, that makes sense :)

@bitwiseman
Copy link
Member Author

@bitwiseman
Copy link
Member Author

bitwiseman commented Nov 10, 2019

@PauloMigAlmeida
So, I'm making some changes but it looks like making a new design that really includes the other rate limit info aside from core will be another bit of work. I think the change I've made is sufficient for this PR. And we can add an issue to do some further deprecation and redesign to expose the other limit information.

@PauloMigAlmeida
Copy link
Contributor

I agree with you, all of that seems a bit too much for a single PR.

If you don't mind, after you finish this refactor I would love to try implementing the newer rate limits myself. That should be fun 😄

On the other hand, if you want to implement it, please count on me for reviewing it too

} else {
// records of the same type compare to each other as normal.
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds() && current.getRemaining() > candidate.getRemaining());
Copy link
Member Author

@bitwiseman bitwiseman Nov 11, 2019

Choose a reason for hiding this comment

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

@PauloMigAlmeida
This is the check that your question got me to fix.
A recorder with a later reset is for sure newer that one with an earlier one.
Only if they have the same reset should we take the one with the lower remaining count.
Previous implementation would take the lower remaining count candidate even if it had an earlier (older) reset date.
Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense! I like the train of thought you used for solving it.

@bitwiseman
Copy link
Member Author

@PauloMigAlmeida
I've created a task #605 for exposing the new limit records and integrating the header update behavior.
Assigning to you. Enjoy!

Created GHRateLimit.Record
Add the four rate limit records to GHRateLimit
Moved getLimt(), getRemaining(), and so on to point to core record for ease of use.
Fixed update check for header to not replace existing with older when remaining count is lower.

NOTE: Did not expose records other than core and did not resolve header update behavior to respect non-core records.
@bitwiseman
Copy link
Member Author

bitwiseman commented Nov 12, 2019

@PauloMigAlmeida How does this look to you now?

Copy link
Contributor

@PauloMigAlmeida PauloMigAlmeida left a comment

Choose a reason for hiding this comment

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

Looks great! Can't wait to see it on the next version of the library 😃

} else {
// records of the same type compare to each other as normal.
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds() && current.getRemaining() > candidate.getRemaining());
Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense! I like the train of thought you used for solving it.

@bitwiseman bitwiseman merged commit 7036423 into hub4j:master Nov 12, 2019
@bitwiseman bitwiseman deleted the issue/rate-limit branch November 12, 2019 21:02
@jglick jglick mentioned this pull request Mar 30, 2020
4 tasks
@bitwiseman bitwiseman mentioned this pull request Mar 30, 2020
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.

Parse the response Date header and expose in the Rate Limit information
2 participants