Skip to content

Commit f15ee02

Browse files
Copilotthedave42
andcommitted
Implement API request throttling to limit requests to 5000/hour
Co-authored-by: thedave42 <[email protected]>
1 parent 70e825a commit f15ee02

File tree

1 file changed

+70
-2
lines changed

1 file changed

+70
-2
lines changed

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

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,69 @@
33
require 'optparse'
44
require 'optparse/date'
55

6+
# Custom Faraday middleware for API request throttling
7+
class ThrottleMiddleware < Faraday::Middleware
8+
# Throttle to 5000 requests per hour (approximately 1.39 requests per second)
9+
MAX_REQUESTS_PER_HOUR = 5000
10+
MIN_DELAY_SECONDS = 3600.0 / MAX_REQUESTS_PER_HOUR # 0.72 seconds
11+
12+
def initialize(app, options = {})
13+
super(app)
14+
@request_count = 0
15+
@hour_start_time = Time.now
16+
@last_request_time = Time.now
17+
@mutex = Mutex.new
18+
end
19+
20+
def call(env)
21+
@mutex.synchronize do
22+
throttle_request
23+
log_throttle_status
24+
end
25+
26+
@app.call(env)
27+
end
28+
29+
private
30+
31+
def throttle_request
32+
current_time = Time.now
33+
34+
# Reset counter if we've moved to a new hour (sliding window)
35+
if current_time - @hour_start_time >= 3600
36+
@request_count = 0
37+
@hour_start_time = current_time
38+
@last_request_time = current_time
39+
end
40+
41+
# Ensure minimum delay between requests to maintain steady rate under 5000/hour
42+
time_since_last = current_time - @last_request_time
43+
if time_since_last < MIN_DELAY_SECONDS
44+
sleep_time = MIN_DELAY_SECONDS - time_since_last
45+
if sleep_time > 0
46+
sleep(sleep_time)
47+
end
48+
end
49+
50+
@request_count += 1
51+
@last_request_time = Time.now
52+
53+
# Log warning if we're approaching the limit
54+
if @request_count % 1000 == 0
55+
elapsed_hour = @last_request_time - @hour_start_time
56+
current_rate = elapsed_hour > 0 ? (@request_count / elapsed_hour * 3600).round(1) : 0
57+
$stderr.print "Throttling status: #{@request_count} requests in #{elapsed_hour.round(1)}s (#{current_rate}/hour rate)\n"
58+
end
59+
end
60+
61+
def log_throttle_status
62+
# This method can be called for detailed debugging if needed
63+
elapsed_hour = Time.now - @hour_start_time
64+
rate_per_hour = elapsed_hour > 0 ? (@request_count / elapsed_hour * 3600).round(1) : 0
65+
$stderr.print "Throttle debug: #{@request_count} requests in last #{elapsed_hour.round(1)}s (#{rate_per_hour}/hour rate)\n" if ENV['THROTTLE_DEBUG']
66+
end
67+
end
68+
669
class InactiveMemberSearch
770
attr_accessor :organization, :members, :repositories, :date, :unrecognized_authors
871

@@ -42,7 +105,10 @@ def check_scopes
42105
end
43106

44107
def check_rate_limit
45-
info "Rate limit: #{@client.rate_limit.remaining}/#{@client.rate_limit.limit}\n"
108+
rate_limit = @client.rate_limit
109+
info "Rate limit: #{rate_limit.remaining}/#{rate_limit.limit}\n"
110+
info "Rate limit resets at: #{rate_limit.resets_at}\n"
111+
info "Throttling: Limited to #{ThrottleMiddleware::MAX_REQUESTS_PER_HOUR} requests/hour (#{ThrottleMiddleware::MIN_DELAY_SECONDS.round(2)}s min delay)\n"
46112
end
47113

48114
def env_help
@@ -195,7 +261,8 @@ def member_activity
195261

196262
# for each repo
197263
@repositories.each do |repo|
198-
info "rate limit remaining: #{@client.rate_limit.remaining} "
264+
rate_limit = @client.rate_limit
265+
info "rate limit remaining: #{rate_limit.remaining}/#{rate_limit.limit} "
199266
info "analyzing #{repo}"
200267

201268
commit_activity(repo)
@@ -268,6 +335,7 @@ def member_activity
268335
end.parse!
269336

270337
stack = Faraday::RackBuilder.new do |builder|
338+
builder.use ThrottleMiddleware
271339
builder.use Octokit::Middleware::FollowRedirects
272340
builder.use Octokit::Response::RaiseError
273341
builder.use Octokit::Response::FeedParser

0 commit comments

Comments
 (0)