Skip to content

Commit

Permalink
Pre-bind sockets on creation
Browse files Browse the repository at this point in the history
When sending a datagram, non-bound sockets require a DNS lookup.
Instead, bind the socket to the given addresses when creating the
socket.

Also, lazily create the socket connections, which allows the
notify methods to capture the exceptions as expected.  If the sockets
are created and bound at initialize, an exception would occur then.

Lastly, allow resetting the connections if the addresses are changed.

Fixes graylog-labs#31
  • Loading branch information
markglenn committed May 13, 2015
1 parent 61906a7 commit 6ae0881
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 13 deletions.
33 changes: 28 additions & 5 deletions lib/gelf/ruby_sender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,44 @@ class RubyUdpSender
attr_accessor :addresses

def initialize(addresses)
@addresses = addresses
self.addresses = addresses
@i = 0
@socket = UDPSocket.open
end

def send_datagrams(datagrams)
host, port = @addresses[@i]
# Choose next round robin socket to use to send all datagrams
socket = sockets[@i]
@i = (@i + 1) % @addresses.length

datagrams.each do |datagram|
@socket.send(datagram, 0, host, port)
socket.send(datagram, 0)
end
end

def close
@socket.close
@sockets.each(&:close) if @sockets
@sockets = nil
end

# Set new addresses for the sender
def addresses=(addresses)
# Close any previously bound sockets
close

@addresses = addresses
end

private

# Get UDPSockets that are prebound to the given addresses
def sockets
@sockets ||= addresses.map do |address|
host, port = address

socket = UDPSocket.new
socket.connect(host, port)
socket
end
end
end
end
63 changes: 55 additions & 8 deletions test/test_ruby_sender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,65 @@ class TestRubyUdpSender < Test::Unit::TestCase
end

context "send_datagrams" do
setup do
before_should "be called 3 times with 1st and 2nd datagrams" do
sockets = [mock, mock]

@sender.instance_variable_set('@sockets', sockets)

sockets[0].expects(:send).times(3).with do |datagram, _|
datagram.start_with?('d')
end
sockets[1].expects(:send).times(3).with do |datagram, _|
datagram.start_with?('e')
end

@sender.send_datagrams(@datagrams1)
@sender.send_datagrams(@datagrams2)
end

before_should "be called 3 times with 1st and 2nd address" do
UDPSocket.any_instance.expects(:send).times(3).with do |datagram, _, host, port|
datagram.start_with?('d') && host == 'localhost' && port == 12201
end
UDPSocket.any_instance.expects(:send).times(3).with do |datagram, _, host, port|
datagram.start_with?('e') && host == 'localhost' && port == 12202
end
should "set the proper hostname and ports" do
sockets = @sender.send(:sockets)

assert_equal ['localhost', '12201'], sockets[0].remote_address.getnameinfo
assert_equal ['localhost', '12202'], sockets[1].remote_address.getnameinfo
end
end

context "close" do
should "close all open sockets" do
sockets = @sender.send(:sockets)

sockets[0].expects(:close)
sockets[1].expects(:close)

@sender.close
end

should "not fail if no sockets open" do
@sender.close
end

should 'reset sockets' do
original_sockets = @sender.send(:sockets)

# Sanity check that sockets method returns same socket connections
assert_equal original_sockets, @sender.send(:sockets)
@sender.close

# Check that sockets returns new sockets
assert_not_equal original_sockets, @sender.send(:sockets)
end
end

context "addresses=" do
should "close old connections" do
@sender.expects(:close)
@sender.addresses = ['localhost', 12201]
end

should "set new addresses" do
@sender.addresses = [['localhost', 12201]]
assert_equal [['localhost', 12201]], @sender.addresses
end
end
end
Expand Down

0 comments on commit 6ae0881

Please sign in to comment.