Ruby Thread with "watchdog" - ruby

I'm implementing a ruby server for handling sockets being created from GPRS modules. The thing is that when the module powers down, there's no indication that the socket closed.
I'm doing threads to handle multiple sockets with the same server. What I'm asking is this: Is there a way to use a timer inside a thread, reset it after every socket input, and that if it hits the timeout, closes the thread? Where can I find more information about this?
EDIT: Code example that doesn't detect the socket closing
require 'socket'
server = TCPServer.open(41000)
loop do
Thread.start(server.accept) do |client|
puts "Client connected"
begin
loop do
line = client.readline
open('log.txt', 'a') { |f|
f.puts line.strip
}
end
rescue
puts "Client disconnected"
end
end
end

I think you need a heartbeat mechanism.

At a guess, your sockets are inexplably closing because you're not catching exceptions that are raised when they are closed by the remote end.
you need to wrap the connection handler in an exception catching block.
Without knowing what module/model you're using I will just fudge it and say you have a process_connection routine. So you need to do something like this:
def process_connection(conn)
begin
# do stuff
rescue Exception => e
STDERR.print "Caught exception #{e}: #{e.message}\n#{e.backtrace}\n"
ensure
conn.close
end
end
This will catch all exceptions and dump them to stderr with a stack trace. From there you can see what is causing them, and possibly handle them more gracefully elsewhere.

Just check the standar API Timeout:
require 'timeout'
status = Timeout::timeout(3){sleep(1)}
puts status.inspect
status = Timeout::timeout(1){sleep(2)}

Related

Ruby Socket Programming Waiting for Client Input

I am trying to implement a small script that will create a server, wait for a client to connect, create a new thread for the client and then process client requests. The first request from the client will be a HELO text and the server must respond with the HELO text followed by IP Address, Port Number and a Student Number. I have got this bit working.
The next request will be any random string and this must not return anything. I have got this bit working.
The last request will be a KILL_SERVICE request which must close the server. I am not sure how to do this. The other issue is that my program will only work with one command at a time. I do not know how to deal with one command and then wait for the next command from the client. My server code is below. Any help would be appreciated.
require 'socket'
port = 8888
puts "Starting Up Server"
server = TCPServer.open(port)
while (client = server.accept)
Thread.start do
input = client.gets
if input.start_with?("HELO")
client.puts "#{input}IP:#{client.peeraddr[2]}\nPort:#{port}\nStudentID:[2]\n"
elsif input == "KILL_SERVICE\n"
client.puts "KILL"
else
puts input
end
end
end
As soon as your code leaves the Thread.start block that thread is considered finished. If you want it to stick around you need to wrap it in a loop:
while (client = server.accept)
Thread.start do
client_running = true
while (client_running)
# ... Your code
end
end
end
Then, if any of those commands should stop the thread, set client_running = false and the loop will exit. To force shut-down the whole thing, exit(0) will end the process.
Update: Added block on how to handle shutting down the server itself, not just the connection.
You need to continue waiting for input from the client after accepting the connection.
while (client = server.accept)
Thread.start do
while input = client.gets
# process input
end
end
end
When you receive KILL_SERVICE you need to close the client using client.close and break out of the while loop using break. Once you've broken out of the while loop, the thread handling that client will exit.

prevent "No buffer space available" error in ruby

I have a small script which scans all the ips ranging from 192.168.190.xxx to 192.168.220.xxx on port 411.
The script works fine sometimes, but sometimes I get the error "No buffer space available"
dcport.rb:8:ininitialize': No buffer space available - connect(2) (Errno::ENOBUFS)`
I have read that this occurs when the socket were not closed properly, but I have used mysocket.close to prevent that which I suppose does not work properly.
How to prevent this from happening, I mean how to close the socket properly?
My code is as follows
require 'socket'
require 'timeout'
(190...216).each do |i|
(0...255).each do |j|
begin
#puts "Scanning 192.168.#{i}.#{j}"
scan=Timeout::timeout(10/1000.0) {
s=TCPSocket.new("192.168.#{i}.#{j}",411)
s.close
puts "192.168.#{i}.#{j} => Hub running"
}
rescue Timeout::Error
rescue Errno::ENETUNREACH
rescue Errno::ECONNREFUSED
end
end
end
My guess is that, sometimes, the timeout fires between the socket creation and the socket closing, which makes you leak some sockets. Since (as far as a quick google search told me), ENOBUFS happens by default after 1024 sockets opened, that could definitely be it.
Timeout, as well as Thread.raise, is very harmful in situations where you need to be sure that something happens (in your case, s.close), as you actually cannot guarantee it anymore: the exception could be raised anywhere, even within an ensure block.
In your case, I think that you could fix it by adding an ensure clause outside the timeout block (untested code follows):
require 'socket'
require 'timeout'
(190...216).each do |i|
(0...255).each do |j|
begin
#puts "Scanning 192.168.#{i}.#{j}"
s = nil
scan=Timeout::timeout(10/1000.0) do
s=TCPSocket.new("192.168.#{i}.#{j}",411)
puts "192.168.#{i}.#{j} => Hub running"
end
rescue Timeout::Error
rescue Errno::ENETUNREACH
rescue Errno::ECONNREFUSED
ensure
s.close if s
end
end
end

Ruby socket server thread question: how to send to all clients?

I'm making a TCP socket server(ruby). A thread is created for each connected client. At some point I try to send data to all connected clients. The thread aborts on exception while trying.(ruby 1.8.7)
require 'socket'
# I test it home right now
server = TCPServer.new('localhost', 12345);
while(session = server.accept)
#Here is the thread being created
Thread.new(session) do |s|
while(msg = s.gets)
#Here is the part that causes the error
Thread.list.each { |aThread|
if aThread != Thread.current
#So what I want it to do is to echo the message from one client to all others
#But for some reason it doesn't, and aborts on the following string
aThread.print "#{msg}\0"
end
}
end
end
Thread.abort_on_exception = true
end
What am I doing wrong?
The following PDF discusses socket programming in ruby in detail. It includes asynchronous handling and goes through a functioning demonstration of a small chat program.
IBM guide to ruby socket programming

Ruby TCPSocket doesn't notice it when server is killed

I've this ruby code that connects to a TCP server (namely, netcat). It loops 20 times, and sends "ABCD ". If I kill netcat, it takes TWO iterations of the loop for an exception to be triggered. On the first loop after netcat is killed, no exception is triggered, and "send" reports that 5 bytes have been correctly written... Which in the end is not true, since of course the server never received them.
Is there a way to work around this issue ? Right now I'm losing data : since I think it's been correctly transfered, I'm not replaying it.
#!/usr/bin/env ruby
require 'rubygems'
require 'socket'
sock = TCPSocket.new('192.168.0.10', 5443)
sock.sync = true
20.times do
sleep 2
begin
count = sock.write("ABCD ")
puts "Wrote #{count} bytes"
rescue Exception => myException
puts "Exception rescued : #{myException}"
end
end
When you're sending data your blocking call will return when the data is written to the TCP output buffer. It would only block if the buffer was full, waiting for the server to acknowledge receipt of previous data that was sent.
Once this data is in the buffer, the network drivers try to send the data. If the connection is lost, on the second attempt to write, your application discovers the broken state of the connection.
Also, how does the connection close? Is the server actively closing the connection? In which case client socket would be notified at its next socket call. Or has it crashed? Or perhaps there's a network fault which means you can no longer communicate.
Discovering a broken connection only occurs when you try to send or receive data over the socket. This is different from having the connection actively closed. You simply can't determine if the connection is still alive without doing something with it.
So try doing sock.recv(0) after the write - if the socket has failed this would raise "Errno::ECONNRESET: Connection reset by peer - recvfrom(2)". You could also try sock.sendmsg "", 0 (not sock.write, or sock.send), and this would report a "Errno::EPIPE: Broken pipe - sendmsg(2)".
Even if you got your hands on the TCP packets and get acknowledgement that the data had been received at the other end, there's still no guarantee that the server will have processed this data - it might in its input buffer but not yet processed.
All of this might help identify a broken connection earlier, but it still won't guarantee that the data was received and processed by the server. The only sure way to know that the application has processed your message is with an application level response.
I tried without the sleep function (just to make sure it wasn't putting on hold anything) and still no luck:
#!/usr/bin/env ruby
require 'rubygems'
require 'socket'
require 'activesupport' # Fixnum.seconds
sock = TCPSocket.new('127.0.0.1', 5443)
sock.sync = true
will_restart_at = Time.now + 2.seconds
should_continue = true
while should_continue
if will_restart_at <= Time.now
will_restart_at = Time.now + 2.seconds
begin
count = sock.write("ABCD ")
puts "Wrote #{count} bytes"
rescue Exception => myException
puts "Exception rescued : #{myException}"
should_continue = false
end
end
end
I analyzed with Wireshark and the two solutions are exactly behaving identically.
I think (and can't be sure) that until you actually call your_socket.write (which will not fail as the socket is still opened because you weren't probing for its possible destruction), the socket won't raise any error.
I tried to simulate this with nginx and manual TCP sockets. And look at that:
irb> sock = TCPSocket.new('127.0.0.1', 80)
=> #<TCPSocket:0xb743b824>
irb> sock.write("salut")
=> 5
irb> sock.read
=> "<html>\r\n<head><title>400 Bad Request</title></head>\r\n<body>\r\n</body>\r\n</html>\r\n"
# Here, I kill nginx
irb> sock.write("salut")
=> 5
irb> sock.read
=> ""
irb> sock.write("salut")
Errno::EPIPE: Broken pipe
So what's the conclusion from here? Unless you're actually expecting some data from the server, you're screwed to detect that you've lost the connection :)
To detect a gracefully close, you'll have to read from the socket - read returning 0 indicates the socket has closed.
If you do need know if data got sent successfully though, there's no way other than implementing ACKs of the data at the application level.

'Who's online?' Ruby Network Program

I have several embedded linux systems that I want to write a 'Who's Online?' network service in Ruby. Below is related part of my code:
mySocket = UDPSocket.new
mySocket.bind("<broadcast>", 50050)
loop do
begin
text, sender = mySocket.recvfrom(1024)
puts text
if text =~ /KNOCK KNOCK/ then
begin
sock = UDPSocket.open
sock.send(r.ipaddress, 0, sender[3], 50051)
sock.close
rescue
retry
end
end
rescue Exception => inLoopEx
puts inLoopEx.message
puts inLoopEx.backtrace.inspect
retry
end
end
I send the 'KNOCK KNOCK' command from a PC. Now, the problem is since they all receive the message at the same time, they try to respond at the same time too, which causes a Broken Pipe exception (which is the reason of my 'rescue retry' code). This code works OK sometimes but; other times the rescue retry part of the code (which is waked by Broken Pipe exception from sock.send) causes one or more systems to respond after 5 seconds or so.
Is there a better way of doing this since I assume I cant escape the Broken Pipe exception?
I have found that exception was caused by the 'r.ipaddress' part in the send command, which is related to my embedded system's internals...

Resources