r/ruby•
fiveguy

Long-term TCP socket monitoring in Ruby

My day jobs for years have been full(ish) stack developer in Ruby on Rails, so I'm used to very short-term web requests, and occasional longer-running processes in Resque/Sidekiq. I've written plenty of code which connects to remote APIs, authenticates, perform a single action, then disconnect.

I'm working on a project for a 8-hour event later this year. A vendor will be continuously outputting event-related data via a TCP server. I will need to be connected and monitoring nearly 100% of the time (accounting for downtime in case the socket gets disconnected). With a very basic script in IRB, I can successfully connect to their server using the TCPSocket class and get what I need from their feed.

What are typical Ruby tools for this type of longer-term process? Sure, I could just keep an IRB console open all day and grab what I need, but surely there are better tools to do this.

Related, anyone know of a robust TCP client library/gem which might handle reconnections and whatnot?

This will be running on my laptop all day, so I don't necessarily need to worry about hosting this.

Posted Apr 7, 2016, 6:22 PM UTC

1419
moomaka•

A pure ruby solution is to use a daemon wrapper, daemons is probably the most popular: https://github.com/thuehlinger/daemons

Related, anyone know of a robust TCP client library/gem which might handle reconnections and whatnot?

https://github.com/drbrain/net-http-persistent works well, and if you want a higher level API Faraday supports it as a backend.

4
realntl•

Persistent HTTP connections will not avail you if the connection drops or is reset. Servers recycle connections periodically anyways (see max-age).

The network is unreliable, plan around it.

2
moomaka•

Did you look at the library? Part of the point of it's existence is that maintains the connection, detecting failures and reconnecting as needed.

2
realntl•

Even if it detects that failures occurred, it can't detect if the HTTP messages were actually posted. Which means messages will often arrive more than once on the other end.

There is a fair amount of complexity to faithfully reconstituting a bytestream over unstable TCP connections.

1
fiveguyOP•

ah I'd forgotten about daemon - I used it to write a mail client (believe it or not) years ago.

1
realntl•

I would consider pushing events to Kafka or some other log replication broker. You want to be able to recover from network failures, not prevent them.

3
moomaka•

8-hour event, not even enough time to get Zookeeper running happily let alone Kafka

1
realntl•

Zookeeper isn't necessary if you aren't running a consumer group.

I think something similar could be done with a SQL database at any rate.

What is not possible is to guarantee a connection will never fail for 8 hours. Instead, plan on having the connection fail a lot.

1
moomaka•

I think something similar could be done with a SQL database at any rate.

It's an 8hr event that will be running on his laptop, File.write is more than sufficient.

1
fiveguyOP•

I've been considering piping it it to disk or mysql (since I run mysql locally). Either option could work for this case IMO

1
mperham•

When writing your own infrastructure, the simpler the better. Ruby can make ad hoc TCP clients very simple to implement.

while true do
  begin
    sock = TCPSocket.new
    sock.each_line do |line|
      process_event(line)
    end
  rescue => ex
    sleep 1
  end
end
3
scharty•

You're supposed to do loop do not while true do.

1
0x0dea•

For what it's worth, Kernel#loop is considerably slower than a bare while, which is arguably quite relevant to OP's particular use case.

2
rb2k•

Very interesting! even when not doing the while inline the speed difference holds true!

I guess loop does (or used to? haven't checked recently):

  • an implicit rescue for a StopIteration being raised (so you can do .next inside it without having to rescue it manually)
  • it takes a block and thus opens a new variable scope

Those two probably add quite a bit of overhead.

In the end, I think opening the socket and reading from network would take up 99.9% of the time, so optimizing 0.1% probably doesn't matter :)

3
0x0dea•

even when not doing the while inline

Modifier while is syntactic sugar in the purest sense; that is, the generated bytecode is exactly the same.

#loop does indeed do constant exception handling, and that does explain much of the performance difference. You're right, though; the loop mechanism itself is rarely (if ever) the bottleneck.

2
scharty•

I really doubt the speed difference matters. It's a micro-optimization.

https://github.com/bbatsov/ruby-style-guide#infinite-loop

2
rb2k•

If you want to do anything manually, be sure to turn on keepalives.

I think this is simple enough that you don't need additional libraries / etc to handle this.

3
fiveguyOP•

Great tip. Agreed that this appears very simple.

1
[deleted]•

[deleted]

2
[deleted]•

[removed]

1