summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPablo Carranza <pcarranza@gmail.com>2015-12-26 00:27:02 +0100
committerPablo Carranza <pcarranza@gmail.com>2015-12-27 22:19:55 +0100
commit07a0950a8d191b6dd69598e38986dca9880eec5f (patch)
tree48ae3e70998896d7779d86de601ded551a91b017
parent06d5594c82655734455e5c9b2f8c94db9829aeb2 (diff)
downloadbundler-07a0950a8d191b6dd69598e38986dca9880eec5f.tar.gz
Add TCP Mirror probe object to verify that the mirror is reachable
The implementation is done using nonblocking IO and then using select to probe that the socket is writable once it is open. This is not covering the case of the mirror host not being resolved, this would fail with a nasty error. There are at least 3 possible cases of probing: * the socket is writtable just fine, which probes as true * the socket fails to `select`, this means that the packets are being dropped or that there is nothing listening there * the socket fails to get a valid tcp connection, there is something listening, but it is rejecting packets: closed connection
-rw-r--r--lib/bundler/mirror.rb64
-rw-r--r--spec/bundler/mirror_spec.rb36
2 files changed, 92 insertions, 8 deletions
diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb
index 0a3e34801d..a18d3528f4 100644
--- a/lib/bundler/mirror.rb
+++ b/lib/bundler/mirror.rb
@@ -91,12 +91,6 @@ module Bundler
end
end
- class TCPSocketProbe
- def replies?(mirror)
- true
- end
- end
-
private
class MirrorConfig
@@ -127,5 +121,63 @@ module Bundler
end
end
end
+
+ class TCPSocketProbe
+ def replies?(mirror)
+ MirrorSocket.new(mirror).with_socket do |socket, address, timeout|
+ begin
+ socket.connect_nonblock(address)
+ rescue IO::WaitWritable
+ wait_for_writtable_socket(socket, address, timeout)
+ rescue # Connection failed somehow, again
+ false
+ end
+ end
+ end
+
+ private
+
+ def wait_for_writtable_socket(socket, address, timeout)
+ if IO.select(nil, [socket], nil, timeout)
+ probe_writtable_socket(socket, address)
+ else # TCP Handshake timed out, or there is something dropping packets
+ false
+ end
+ end
+
+ def probe_writtable_socket(socket, address)
+ begin
+ socket.connect_nonblock(address)
+ rescue Errno::EISCONN
+ true
+ rescue => e # Connection failed
+ false
+ end
+ end
+ end
+ end
+
+ class MirrorSocket
+ def initialize(mirror)
+ addr_info = Socket.getaddrinfo(mirror.uri.host, mirror.uri.port)
+ @timeout = mirror.fallback_timeout
+ @type = addr_info[0][0]
+ @port = addr_info[0][1]
+ @host = addr_info[0][3]
+ end
+
+ def with_socket
+ socket = Socket.new(Socket.const_get(@type), Socket::SOCK_STREAM, 0)
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ value = yield socket, socket_address, @timeout
+ socket.close unless socket.closed?
+ value
+ end
+
+ private
+
+ def socket_address
+ Socket.pack_sockaddr_in(@port, @host)
+ end
end
end
diff --git a/spec/bundler/mirror_spec.rb b/spec/bundler/mirror_spec.rb
index faa16b7aa8..6e5cede084 100644
--- a/spec/bundler/mirror_spec.rb
+++ b/spec/bundler/mirror_spec.rb
@@ -136,8 +136,12 @@ end
describe Bundler::Settings::Mirrors do
let(:localhost_uri) { URI("http://localhost:9292") }
- context "with a not configured mirror" do
- let(:mirrors) { Bundler::Settings::Mirrors.new }
+ context "with a just created mirror" do
+ let(:mirrors) do
+ probe = double()
+ allow(probe).to receive(:replies?).and_return(true)
+ Bundler::Settings::Mirrors.new(probe)
+ end
it "returns a mirror that contains the source uri for an unknown uri" do
mirror = mirrors.for("http://rubygems.org/")
@@ -267,3 +271,31 @@ describe Bundler::Settings::Mirrors do
end
end
end
+
+describe Bundler::Settings::TCPSocketProbe do
+ let(:probe) { Bundler::Settings::TCPSocketProbe.new }
+
+ context "with a listening TCP Server" do
+ let(:server) { TCPServer.new("127.0.0.1", 0) }
+ let(:port) { server.addr[1] }
+ let(:mirror) { Bundler::Settings::Mirror.new(uri="http://localhost:#{port}", fallback_timeout=true) }
+
+ it "probes the server correctly" do
+ expect(probe.replies?(mirror)).to be_truthy
+ end
+
+ it "probes falsey when the server is down" do
+ my_mirror = mirror
+ server.close
+ expect(probe.replies?(my_mirror)).to be_falsey
+ end
+ end
+
+ context "with an invalid mirror" do
+ let(:mirror) { Bundler::Settings::Mirror.new(uri="http://127.0.0.127:#{9292}", fallback_timeout=true) }
+
+ it "fails with a timeout when there is nothing to tcp handshake" do
+ expect(probe.replies?(mirror)).to be_falsey
+ end
+ end
+end