Skip to content

Commit 88efa73

Browse files
Merge #7200
7200: Bump net-http-persistent to 3.1.0 r=deivid-rodriguez a=deivid-rodriguez ### What was the end-user problem that led to this PR? The problem was that I wanted to propose some changes to this vendored gem, but I found out that we are using an old version of it. ### What was your diagnosis of the problem? My diagnosis was that we should upgrade. ### What is your fix for the problem, implemented in this PR? My fix is to upgrade the dependency. Since it's a major update, it required some changes. Also, I had to: * Add a new artifice task to vendorize new `connection_pool` dependency. This is the main downside of this PR, that the new version adds a dependency on this gem. But this gem is very stable, and rarely changes and releases new versions, as can be seen by its [releases](https://github.com/mperham/connection_pool/releases). * Cherry-pick a Windows fix not yet merged into master branch of `net-http-persistent`: drbrain/net-http-persistent#90. Co-authored-by: David Rodríguez <[email protected]>
2 parents e945efa + e167e74 commit 88efa73

File tree

15 files changed

+860
-452
lines changed

15 files changed

+860
-452
lines changed

bundler/Rakefile

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ rescue Gem::LoadError => e
251251

252252
desc "Vendor a specific version of net-http-persistent"
253253
task(:"net-http-persistent") { abort msg }
254+
255+
desc "Vendor a specific version of connection_pool"
256+
task(:connection_pool) { abort msg }
254257
end
255258
else
256259
desc "Vendor a specific version of molinillo"
@@ -261,6 +264,9 @@ else
261264
lib.vendor_lib = "lib/bundler/vendor/molinillo"
262265
end
263266

267+
# We currently cherry-pick changes to use `require_relative` internally
268+
# instead of regular `require`. They are already in thor's master branch but
269+
# still need to be released.
264270
desc "Vendor a specific version of thor"
265271
Automatiek::RakeTask.new("thor") do |lib|
266272
lib.download = { :github => "https://github.com/erikhuda/thor" }
@@ -269,6 +275,9 @@ else
269275
lib.vendor_lib = "lib/bundler/vendor/thor"
270276
end
271277

278+
# We currently cherry-pick changes to use `require_relative` internally
279+
# instead of regular `require`. They are already in fileutils' master branch
280+
# but still need to be released.
272281
desc "Vendor a specific version of fileutils"
273282
Automatiek::RakeTask.new("fileutils") do |lib|
274283
lib.download = { :github => "https://github.com/ruby/fileutils" }
@@ -277,22 +286,28 @@ else
277286
lib.vendor_lib = "lib/bundler/vendor/fileutils"
278287
end
279288

289+
# Currently `net-http-persistent` and it's dependency `connection_pool` are
290+
# vendored separately, but `connection_pool` references inside the vendored
291+
# copy of `net-http-persistent` are not properly updated to refer to the
292+
# vendored copy of `connection_pool`, so they need to be manually updated.
293+
# This will be automated once https://github.com/segiddins/automatiek/pull/3
294+
# is included in `automatiek` and we start using the new API for vendoring
295+
# subdependencies.
296+
280297
desc "Vendor a specific version of net-http-persistent"
281298
Automatiek::RakeTask.new("net-http-persistent") do |lib|
282299
lib.download = { :github => "https://github.com/drbrain/net-http-persistent" }
283300
lib.namespace = "Net::HTTP::Persistent"
284301
lib.prefix = "Bundler::Persistent"
285302
lib.vendor_lib = "lib/bundler/vendor/net-http-persistent"
303+
end
286304

287-
mixin = Module.new do
288-
def namespace_files
289-
super
290-
require_target = vendor_lib.sub(%r{^(.+?/)?lib/}, "") << "/lib"
291-
relative_files = files.map {|f| Pathname.new(f).relative_path_from(Pathname.new(vendor_lib) / "lib").sub_ext("").to_s }
292-
process_files(/require (['"])(#{Regexp.union(relative_files)})/, "require \\1#{require_target}/\\2")
293-
end
294-
end
295-
lib.send(:extend, mixin)
305+
desc "Vendor a specific version of connection_pool"
306+
Automatiek::RakeTask.new("connection_pool") do |lib|
307+
lib.download = { :github => "https://github.com/mperham/connection_pool" }
308+
lib.namespace = "ConnectionPool"
309+
lib.prefix = "Bundler"
310+
lib.vendor_lib = "lib/bundler/vendor/connection_pool"
296311
end
297312
end
298313

bundler/lib/bundler/fetcher.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def connection
242242
Bundler.settings[:ssl_client_cert]
243243
raise SSLError if needs_ssl && !defined?(OpenSSL::SSL)
244244

245-
con = PersistentHTTP.new "bundler", :ENV
245+
con = PersistentHTTP.new :name => "bundler", :proxy => :ENV
246246
if gem_proxy = Bundler.rubygems.configuration[:http_proxy]
247247
con.proxy = URI.parse(gem_proxy) if gem_proxy != :no_proxy
248248
end
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
require_relative 'connection_pool/version'
2+
require_relative 'connection_pool/timed_stack'
3+
4+
5+
# Generic connection pool class for e.g. sharing a limited number of network connections
6+
# among many threads. Note: Connections are lazily created.
7+
#
8+
# Example usage with block (faster):
9+
#
10+
# @pool = Bundler::ConnectionPool.new { Redis.new }
11+
#
12+
# @pool.with do |redis|
13+
# redis.lpop('my-list') if redis.llen('my-list') > 0
14+
# end
15+
#
16+
# Using optional timeout override (for that single invocation)
17+
#
18+
# @pool.with(timeout: 2.0) do |redis|
19+
# redis.lpop('my-list') if redis.llen('my-list') > 0
20+
# end
21+
#
22+
# Example usage replacing an existing connection (slower):
23+
#
24+
# $redis = Bundler::ConnectionPool.wrap { Redis.new }
25+
#
26+
# def do_work
27+
# $redis.lpop('my-list') if $redis.llen('my-list') > 0
28+
# end
29+
#
30+
# Accepts the following options:
31+
# - :size - number of connections to pool, defaults to 5
32+
# - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds
33+
#
34+
class Bundler::ConnectionPool
35+
DEFAULTS = {size: 5, timeout: 5}
36+
37+
class Error < RuntimeError
38+
end
39+
40+
def self.wrap(options, &block)
41+
Wrapper.new(options, &block)
42+
end
43+
44+
def initialize(options = {}, &block)
45+
raise ArgumentError, 'Connection pool requires a block' unless block
46+
47+
options = DEFAULTS.merge(options)
48+
49+
@size = options.fetch(:size)
50+
@timeout = options.fetch(:timeout)
51+
52+
@available = TimedStack.new(@size, &block)
53+
@key = :"current-#{@available.object_id}"
54+
@key_count = :"current-#{@available.object_id}-count"
55+
end
56+
57+
if Thread.respond_to?(:handle_interrupt)
58+
59+
# MRI
60+
def with(options = {})
61+
Thread.handle_interrupt(Exception => :never) do
62+
conn = checkout(options)
63+
begin
64+
Thread.handle_interrupt(Exception => :immediate) do
65+
yield conn
66+
end
67+
ensure
68+
checkin
69+
end
70+
end
71+
end
72+
73+
else
74+
75+
# jruby 1.7.x
76+
def with(options = {})
77+
conn = checkout(options)
78+
begin
79+
yield conn
80+
ensure
81+
checkin
82+
end
83+
end
84+
85+
end
86+
87+
def checkout(options = {})
88+
if ::Thread.current[@key]
89+
::Thread.current[@key_count]+= 1
90+
::Thread.current[@key]
91+
else
92+
::Thread.current[@key_count]= 1
93+
::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
94+
end
95+
end
96+
97+
def checkin
98+
if ::Thread.current[@key]
99+
if ::Thread.current[@key_count] == 1
100+
@available.push(::Thread.current[@key])
101+
::Thread.current[@key]= nil
102+
else
103+
::Thread.current[@key_count]-= 1
104+
end
105+
else
106+
raise Bundler::ConnectionPool::Error, 'no connections are checked out'
107+
end
108+
109+
nil
110+
end
111+
112+
def shutdown(&block)
113+
@available.shutdown(&block)
114+
end
115+
116+
# Size of this connection pool
117+
def size
118+
@size
119+
end
120+
121+
# Number of pool entries available for checkout at this instant.
122+
def available
123+
@available.length
124+
end
125+
126+
private
127+
128+
class Wrapper < ::BasicObject
129+
METHODS = [:with, :pool_shutdown]
130+
131+
def initialize(options = {}, &block)
132+
@pool = options.fetch(:pool) { ::Bundler::ConnectionPool.new(options, &block) }
133+
end
134+
135+
def with(&block)
136+
@pool.with(&block)
137+
end
138+
139+
def pool_shutdown(&block)
140+
@pool.shutdown(&block)
141+
end
142+
143+
def pool_size
144+
@pool.size
145+
end
146+
147+
def pool_available
148+
@pool.available
149+
end
150+
151+
def respond_to?(id, *args)
152+
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
153+
end
154+
155+
def method_missing(name, *args, &block)
156+
with do |connection|
157+
connection.send(name, *args, &block)
158+
end
159+
end
160+
end
161+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Global monotonic clock from Concurrent Ruby 1.0.
2+
# Copyright (c) Jerry D'Antonio -- released under the MIT license.
3+
# Slightly modified; used with permission.
4+
# https://github.com/ruby-concurrency/concurrent-ruby
5+
6+
require 'thread'
7+
8+
class Bundler::ConnectionPool
9+
10+
class_definition = Class.new do
11+
12+
if defined?(Process::CLOCK_MONOTONIC)
13+
14+
# @!visibility private
15+
def get_time
16+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
17+
end
18+
19+
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
20+
21+
# @!visibility private
22+
def get_time
23+
java.lang.System.nanoTime() / 1_000_000_000.0
24+
end
25+
26+
else
27+
28+
# @!visibility private
29+
def initialize
30+
@mutex = Mutex.new
31+
@last_time = Time.now.to_f
32+
end
33+
34+
# @!visibility private
35+
def get_time
36+
@mutex.synchronize do
37+
now = Time.now.to_f
38+
if @last_time < now
39+
@last_time = now
40+
else # clock has moved back in time
41+
@last_time += 0.000_001
42+
end
43+
end
44+
end
45+
end
46+
end
47+
48+
##
49+
# Clock that cannot be set and represents monotonic time since
50+
# some unspecified starting point.
51+
#
52+
# @!visibility private
53+
GLOBAL_MONOTONIC_CLOCK = class_definition.new
54+
private_constant :GLOBAL_MONOTONIC_CLOCK
55+
56+
class << self
57+
##
58+
# Returns the current time a tracked by the application monotonic clock.
59+
#
60+
# @return [Float] The current monotonic time when `since` not given else
61+
# the elapsed monotonic time between `since` and the current time
62+
def monotonic_time
63+
GLOBAL_MONOTONIC_CLOCK.get_time
64+
end
65+
end
66+
end

0 commit comments

Comments
 (0)