Skip to content

Commit 790ee9a

Browse files
committed
Enhance ThrottleMiddleware to dynamically manage GitHub API rate limits
1 parent fb55e2e commit 790ee9a

File tree

1 file changed

+63
-9
lines changed

1 file changed

+63
-9
lines changed

api/ruby/find-inactive-members/find_inactive_members.rb

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def initialize(app, options = {})
1616
@last_request_time = Time.now
1717
@mutex = Mutex.new
1818
@debug_enabled = !ENV['THROTTLE_DEBUG'].nil? && !ENV['THROTTLE_DEBUG'].empty?
19+
@github_rate_limit_remaining = nil
20+
@github_rate_limit_reset = nil
1921
end
2022

2123
def call(env)
@@ -24,11 +26,43 @@ def call(env)
2426
log_throttle_status
2527
end
2628

27-
@app.call(env)
29+
response = @app.call(env)
30+
31+
# Update GitHub rate limit info from response headers
32+
@mutex.synchronize do
33+
update_github_rate_limit(response)
34+
end
35+
36+
response
2837
end
2938

3039
private
3140

41+
def update_github_rate_limit(response)
42+
if response.headers['x-ratelimit-remaining']
43+
@github_rate_limit_remaining = response.headers['x-ratelimit-remaining'].to_i
44+
@github_rate_limit_reset = response.headers['x-ratelimit-reset'].to_i if response.headers['x-ratelimit-reset']
45+
end
46+
end
47+
48+
def calculate_dynamic_delay
49+
return MIN_DELAY_SECONDS unless @github_rate_limit_remaining && @github_rate_limit_reset
50+
51+
# Calculate time until rate limit resets
52+
current_time = Time.now.to_i
53+
time_until_reset = [@github_rate_limit_reset - current_time, 1].max
54+
55+
# Calculate required delay to not exceed remaining requests
56+
if @github_rate_limit_remaining > 0
57+
required_delay = time_until_reset.to_f / @github_rate_limit_remaining
58+
# Use the more conservative delay (either our standard delay or the calculated one)
59+
[MIN_DELAY_SECONDS, required_delay].max
60+
else
61+
# No requests remaining, wait until reset
62+
time_until_reset
63+
end
64+
end
65+
3266
def throttle_request
3367
current_time = Time.now
3468

@@ -39,11 +73,16 @@ def throttle_request
3973
@last_request_time = current_time
4074
end
4175

42-
# Ensure minimum delay between requests to maintain steady rate under 5000/hour
76+
# Use dynamic delay based on actual GitHub rate limit if available
77+
required_delay = @github_rate_limit_remaining ? calculate_dynamic_delay : MIN_DELAY_SECONDS
78+
79+
# Ensure minimum delay between requests
4380
time_since_last = current_time - @last_request_time
44-
if time_since_last < MIN_DELAY_SECONDS
45-
sleep_time = MIN_DELAY_SECONDS - time_since_last
81+
if time_since_last < required_delay
82+
sleep_time = required_delay - time_since_last
4683
if sleep_time > 0
84+
#delay_reason = @github_rate_limit_remaining ? "dynamic" : "standard"
85+
#$stderr.print "Throttling: waiting #{sleep_time.round(2)}s (#{delay_reason} delay)\n"
4786
sleep(sleep_time)
4887
end
4988
end
@@ -55,7 +94,8 @@ def throttle_request
5594
if @request_count % 1000 == 0
5695
elapsed_hour = @last_request_time - @hour_start_time
5796
current_rate = elapsed_hour > 0 ? (@request_count / elapsed_hour * 3600).round(1) : 0
58-
$stderr.print "Throttling status: #{@request_count} requests in #{elapsed_hour.round(1)}s (#{current_rate}/hour rate)\n"
97+
github_info = @github_rate_limit_remaining ? " GitHub: #{@github_rate_limit_remaining} remaining" : ""
98+
$stderr.print "Throttling status: #{@request_count} requests in #{elapsed_hour.round(1)}s (#{current_rate}/hour rate)#{github_info}\n"
5999
end
60100
end
61101

@@ -264,8 +304,22 @@ def member_activity
264304

265305
# for each repo
266306
@repositories.each do |repo|
267-
rate_limit = @client.rate_limit
268-
info "rate limit remaining: #{rate_limit.remaining}/#{rate_limit.limit} "
307+
# Show rate limit from last response headers (more efficient than API call)
308+
if @client.last_response
309+
remaining = @client.last_response.headers['x-ratelimit-remaining']
310+
limit = @client.last_response.headers['x-ratelimit-limit']
311+
if remaining && limit
312+
reset_time = @client.last_response.headers['x-ratelimit-reset']
313+
if reset_time
314+
minutes_until_reset = [(reset_time.to_i - Time.now.to_i) / 60.0, 0].max.round(1)
315+
reset_info = " (resets in #{minutes_until_reset}min)"
316+
else
317+
reset_info = ""
318+
end
319+
info "#{remaining} requests remaining#{reset_info} "
320+
end
321+
end
322+
269323
info "analyzing #{repo}"
270324

271325
commit_activity(repo)
@@ -342,13 +396,13 @@ def member_activity
342396
builder.use Octokit::Middleware::FollowRedirects
343397
builder.use Octokit::Response::RaiseError
344398
builder.use Octokit::Response::FeedParser
345-
builder.response :logger
399+
builder.response :logger if @debug
346400
builder.adapter Faraday.default_adapter
347401
end
348402

349403
Octokit.configure do |kit|
350404
kit.auto_paginate = true
351-
kit.middleware = stack if @debug
405+
kit.middleware = stack
352406
end
353407

354408
options[:client] = Octokit::Client.new

0 commit comments

Comments
 (0)