Files
github-api/jacoco/org.kohsuke.github/GitHubRateLimitChecker.java.html
2021-06-02 11:09:28 -07:00

170 lines
10 KiB
HTML

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GitHubRateLimitChecker.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">GitHub API for Java</a> &gt; <a href="index.source.html" class="el_package">org.kohsuke.github</a> &gt; <span class="el_source">GitHubRateLimitChecker.java</span></div><h1>GitHubRateLimitChecker.java</h1><pre class="source lang-java linenums">package org.kohsuke.github;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Objects;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
/**
* A GitHub API Rate Limit Checker called before each request.
*
* &lt;p&gt;
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
* remaining and the time when the number will be reset is returned in the response header and can also be requested
* using {@link GitHub#getRateLimit()}. The &quot;requests per interval&quot; is referred to as the &quot;rate limit&quot;.
* &lt;/p&gt;
* &lt;p&gt;
* Different parts of the GitHub API have separate rate limits, but most of REST API uses {@link RateLimitTarget#CORE}.
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit. GitHub prefers that
* clients stop before exceeding their rate limit rather than stopping after they exceed it.
* &lt;/p&gt;
* &lt;p&gt;
* This class provides the infrastructure for calling the appropriate {@link RateLimitChecker} before each request and
* retrying than call many times as needed. Each {@link RateLimitChecker} decides whether to wait and for how long. This
* allows for a wide range of {@link RateLimitChecker} implementations, including complex throttling strategies and
* polling.
* &lt;/p&gt;
*/
class GitHubRateLimitChecker {
@Nonnull
private final RateLimitChecker core;
@Nonnull
private final RateLimitChecker search;
@Nonnull
private final RateLimitChecker graphql;
@Nonnull
private final RateLimitChecker integrationManifest;
<span class="fc" id="L44"> private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName());</span>
GitHubRateLimitChecker() {
<span class="fc" id="L47"> this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);</span>
<span class="fc" id="L48"> }</span>
GitHubRateLimitChecker(@Nonnull RateLimitChecker core,
@Nonnull RateLimitChecker search,
@Nonnull RateLimitChecker graphql,
<span class="fc" id="L53"> @Nonnull RateLimitChecker integrationManifest) {</span>
<span class="fc" id="L54"> this.core = Objects.requireNonNull(core);</span>
<span class="fc" id="L55"> this.search = Objects.requireNonNull(search);</span>
<span class="fc" id="L56"> this.graphql = Objects.requireNonNull(graphql);</span>
<span class="fc" id="L57"> this.integrationManifest = Objects.requireNonNull(integrationManifest);</span>
<span class="fc" id="L58"> }</span>
/**
* Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target.
*
* Only one {@link RateLimitChecker} is allowed per target.
*
* @param checker
* the {@link RateLimitChecker} to apply.
* @param rateLimitTarget
* the {@link RateLimitTarget} for this checker. If {@link RateLimitTarget#NONE}, checker will be ignored
* and no change will be made.
* @return a new {@link GitHubRateLimitChecker}
*/
GitHubRateLimitChecker with(@Nonnull RateLimitChecker checker, @Nonnull RateLimitTarget rateLimitTarget) {
<span class="pc bpc" id="L73" title="1 of 2 branches missed."> return new GitHubRateLimitChecker(rateLimitTarget == RateLimitTarget.CORE ? checker : core,</span>
<span class="pc bpc" id="L74" title="1 of 2 branches missed."> rateLimitTarget == RateLimitTarget.SEARCH ? checker : search,</span>
<span class="pc bpc" id="L75" title="1 of 2 branches missed."> rateLimitTarget == RateLimitTarget.GRAPHQL ? checker : graphql,</span>
<span class="pc bpc" id="L76" title="1 of 2 branches missed."> rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST ? checker : integrationManifest);</span>
}
/**
* Checks whether there is sufficient requests remaining within this client's rate limit quota to make the current
* request.
* &lt;p&gt;
* This method does not do the actual check. Instead it selects the appropriate {@link RateLimitChecker} and
* {@link GHRateLimit.Record} for the current request's {@link RateLimitTarget}. It then calls
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}.
* &lt;/p&gt;
* &lt;p&gt;
* It is up to {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to which decide if the rate limit
* has been exceeded. If it has, {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} will sleep for as
* long is it chooses and then return {@code true}. If not, that method will return {@code false}.
* &lt;/p&gt;
* &lt;p&gt;
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
* will request updated rate limit information and call
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows different
* {@link RateLimitChecker} implementations to apply any number of strategies to controlling the speed at which
* requests are made.
* &lt;/p&gt;
* &lt;p&gt;
* When the {@link RateLimitChecker} returns {@code false} this method will return and the request processing will
* continue.
* &lt;/p&gt;
* &lt;p&gt;
* If the {@link RateLimitChecker} for this the current request's urlPath is {@link RateLimitChecker#NONE} the rate
* limit is not checked.
* &lt;/p&gt;
*
* @param client
* the {@link GitHubClient} to check
* @param request
* the {@link GitHubRequest} to check against
* @throws IOException
* if there is an I/O error
*/
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
<span class="fc" id="L116"> RateLimitChecker guard = selectChecker(request.rateLimitTarget());</span>
<span class="fc bfc" id="L117" title="All 2 branches covered."> if (guard == RateLimitChecker.NONE) {</span>
<span class="fc" id="L118"> return;</span>
}
// For the first rate limit, accept the current limit if a valid one is already present.
<span class="fc" id="L122"> GHRateLimit rateLimit = client.rateLimit(request.rateLimitTarget());</span>
<span class="fc" id="L123"> GHRateLimit.Record rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());</span>
<span class="fc" id="L124"> long waitCount = 0;</span>
try {
<span class="fc bfc" id="L126" title="All 2 branches covered."> while (guard.checkRateLimit(rateLimitRecord, waitCount)) {</span>
<span class="fc" id="L127"> waitCount++;</span>
// When rate limit is exceeded, sleep for one additional second beyond when the
// called {@link RateLimitChecker} sleeps.
// Reset time is only accurate to the second, so adding a one second buffer for safety is a good idea.
// This also keeps polling clients from querying too often.
<span class="fc" id="L133"> Thread.sleep(1000);</span>
// After the first wait, always request a new rate limit from the server.
<span class="fc" id="L136"> rateLimit = client.getRateLimit(request.rateLimitTarget());</span>
<span class="fc" id="L137"> rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());</span>
}
<span class="nc" id="L139"> } catch (InterruptedException e) {</span>
<span class="nc" id="L140"> throw (IOException) new InterruptedIOException(e.getMessage()).initCause(e);</span>
<span class="fc" id="L141"> }</span>
<span class="fc" id="L142"> }</span>
/**
* Gets the appropriate {@link RateLimitChecker} for a particular target.
*
* Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}.
*
* @param rateLimitTarget
* the rate limit to check
* @return the {@link RateLimitChecker} for a particular target
*/
@Nonnull
private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) {
<span class="fc bfc" id="L155" title="All 2 branches covered."> if (rateLimitTarget == RateLimitTarget.NONE) {</span>
<span class="fc" id="L156"> return RateLimitChecker.NONE;</span>
<span class="fc bfc" id="L157" title="All 2 branches covered."> } else if (rateLimitTarget == RateLimitTarget.CORE) {</span>
<span class="fc" id="L158"> return core;</span>
<span class="pc bpc" id="L159" title="1 of 2 branches missed."> } else if (rateLimitTarget == RateLimitTarget.SEARCH) {</span>
<span class="fc" id="L160"> return search;</span>
<span class="nc bnc" id="L161" title="All 2 branches missed."> } else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {</span>
<span class="nc" id="L162"> return graphql;</span>
<span class="nc bnc" id="L163" title="All 2 branches missed."> } else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {</span>
<span class="nc" id="L164"> return integrationManifest;</span>
} else {
<span class="nc" id="L166"> throw new IllegalArgumentException(&quot;Unknown rate limit target: &quot; + rateLimitTarget.toString());</span>
}
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>