Skip to content

Instantly share code, notes, and snippets.

@brainopia
Created February 3, 2013 08:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brainopia/4700962 to your computer and use it in GitHub Desktop.
Save brainopia/4700962 to your computer and use it in GitHub Desktop.
Octokit pool
class Octokit::Mirror
attr_reader :remaining, :reserved
def initialize(options={})
@client = Octokit::Client.new options
@remaining = Atomic.new Float::INFINITY
@reserved = Atomic.new 0
@monitor = Atomic.new 0
end
# выполнить блок кода, если зеркало доступно
# то есть осталось больше запросов, чем зарезервировано
#
# перед выполнением блока увеличиваем зарезервированное кол-во
# после выполнения – уменьшаем
#
# если лимит внезапно закончился, то возвращаем ошибку
# и запускаем мониторилку клиента в фоне
def reserve!
return unless available?
begin
@reserved.update {|x| x + 1 }
yield @client
rescue Octokit::Forbidden => error
@remaining.value = 0
monitor_rate!
error
ensure
@reserved.update {|x| x - 1 }
end
end
# обновить кол-во доступных запросов
# основываясь на респонсе от гитхаба
#
# респонс может не содержать кол-во доступных запросов, если
# - респонс nil (когда успешный conditional request)
# - респонс число (например когда мы запрашиваем rate_limit)
#
# обновляем кол-во доступных запросов только по убывающей
# чтобы не зависеть от порядка выполнения запросов
#
# если лимит закончился, запускаем мониторилку клиента в фоне
def update_stats!(response)
return unless response.respond_to? :rate_remaining
@remaining.update do |x|
x > response.rate_remaining ? response.rate_remaining : x
end
monitor_rate! if @remaining.value.zero?
end
private
def available?
@remaining.value > @reserved.value
end
def monitor_rate!
if @monitor.compare_and_swap 0, 1
Thread.new do
loop do
@remaining.value = @client.rate_limit_remaining
@remaining.value > 0 ? break : sleep(120)
end
@monitor.value = 0
end
end
end
end
Gem::Specification.new do |s|
s.name = 'octokit_pool'
s.version = '0.5'
s.author = 'brainopia'
s.summary = 'Octokit pool'
s.files = ['octokit_mirror.rb', 'octokit_pool.rb']
s.require_path = '.'
s.add_dependency 'octokit'
s.add_dependency 'atomic'
end
require 'atomic'
require 'octokit'
require 'octokit_mirror'
module Octokit
@mirrors = [ Octokit::Mirror.new ]
class << self
attr_reader :mirrors
# по циклу пытаемся зарезервировать зеркало
# и выполнить через него требуемый метод
#
# - если метод возвращает ошибку (лимит закончился)
# переходим к следующему зеркалу
# - если ни одно зеркало не подошло
# ждем 10 секунд и повторяем заново
def method_missing(method, *args)
loop do
mirrors.each do |mirror|
mirror.reserve! do |client|
response = client.send method, *args
next if response.is_a? Exception
mirror.update_stats! response
return response
end
end or sleep 10
end
end
def mirrors
Thread.current[:mirrors] ||= @mirrors.shuffle
end
def create_mirror(options={})
@mirrors << Octokit::Mirror.new(options)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment