3

I have a daemonized ruby script running on my server that looks like this:

@server = TCPServer.open(61101)                         
loop do                                                 
  @thr = Thread.new(@server.accept) do |sock|
    Thread.current[:myArrayOfHashes] = []   # hashes containing attributes of myObject
    SystemTimer.timeout_after(5) do
      Thread.current[:string] = sock.gets
      sock.close

      # parse the string and load the data into myArrayOfHashes

      Myobject.transaction do           # Update the myObjects Table
        Thread.current[:myArrayOfHashes].each do |h|
          Thread.current[:newMyObject] = Myobject.new
          # load up the new object with data
          Thread.current[:newMyObject].save
        end
      end

    end
  end
  @thr.join
end

This server receives and manages data for my rails application which is all running on Mac OS 10.6. The clients call the server every 15 minutes on the 15 and while I currently only have 16 or so clients calling every 15 min on the 15, I'm wondering about the following:

  1. If two clients call at close enough to the same time, will one client's connection attempt fail?
  2. How I can figure out how many client connections my server can accommodate at the same time?
  3. How can I monitor how much memory my server is using?

Also, is there an article you can point me toward that discusses the best way to implement this kind of a server? I mean can I have multiple instances of the server listening on the same port? Would that even help?

I am using Bluepill to monitor my server daemons.

pitachip
  • 965
  • 3
  • 7
  • 24
  • 1
    Curious: why do you use `Thread.new` if you just `join` it afterward? This is equivalent to `sock = @server.accept`, then just removing all the references to threads. – Asherah May 31 '12 at 00:22
  • 1
    The real answer to this I think is that I may not have any idea what I'm doing. What I do know is that the @thr.join made everything start working reliably. I will update my post with enough of the code in the middle for you to maybe be able to tell where my gaps in understanding are. – pitachip May 31 '12 at 14:42
  • if you use `Thread.new` but not `join`, then yes, that may be weird if your code isn't thread-safe. You're safer yet just removing all `Thread` references, though; something like this: [http://pastebin.com/8m6CnbU4](http://pastebin.com/8m6CnbU4). – Asherah May 31 '12 at 14:51
  • That method definitely did not work for me. From the searching around I did on threads and how different systems handle them differently I was not sure the why of it, but I'm running on Mac OS and it was clear to me (from seeing what was ending up in my db) that the variables were getting all jumbled among my threads when I just had a simple `sock=@server.accept` up top. So really I'm just not sure what @thr.join is really doing. The thing is before I had the @thr.join in there, a lot of the threads would simply not go to completion. How would I solve that problem without @thr.join? – pitachip May 31 '12 at 15:07
  • O…kay, so I see. There's no real advantage to using threads, still, because you're synchronising them whenever you use `@thr.join`. I'd suggest reviewing what you're trying to do. – Asherah Jun 01 '12 at 00:12

1 Answers1

2

1 and 2
The answer is no, two clients connecting close to each other will not make the connection fail (however multiple clients connecting may fail, see below).

The reason is the operating system has a default so called listening queue built into all server sockets. So even if you are not calling accept fast enough in your program, the OS will still keep buffering incoming connections for you. It will buffer these connections for as long as the listening queue does not get filled.

Now what is the size of this queue then?

In most cases the default size typically used is 5. The size is set after you create the socket and you call listen on this socket (see man page for listen here).

For Ruby TCPSocket automatically calls listen for you, and if you look at the C-source code for TCPSocket you will find that it indeed sets the size to 5:

https://github.com/ruby/ruby/blob/trunk/ext/socket/ipsocket.c#L108

SOMAXCONN is defined as 5 here:

https://github.com/ruby/ruby/blob/trunk/ext/socket/mkconstants.rb#L693

Now what happens if you don't call accept fast enough and the queue gets filled? The answer is found in the man page of listen:

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.

In your code however there is one problem which can make the queue fill up if more than 5 clients try to connect at the same time: you're calling @thr.join at the end of the loop.

What effectively happens when you do this is that your server will not accept any new incoming connections until all your stuff inside your accept-thread has finished executing.

So if the database stuff and the other things you are doing inside the accept-thread takes a long time, the listening queue may fill up in the meantime. It depends on how long your processing takes, and how many clients could potentially be connecting at the exact same time.

3
You didn't say which platform you are running on, but on linux/osx the easiest way is to just run top in your console. For more advanced memory monitoring options you might want to check these out:

ruby/ruby on rails memory leak detection
track application memory usage on heroku

Community
  • 1
  • 1
Casper
  • 33,403
  • 4
  • 84
  • 79
  • Hi Casper. So I am panicking a little the way you do when you ask a question and then realize that you don't just need the answer to your question anymore, but have no real idea what you're doing in general. Ok #1 is I need to be able to accommodate around 80 connections happening at the same time for now. As a temporary fix so my server doesn't refuse any connection attempts while I'm figuring out a better solution, would it be alright for me to just set the SOMAXCONN variable to 100? – pitachip May 31 '12 at 14:38
  • @pitachip No, changing SOMAXCONN is not the solution here. And I doubt the OS will allow you to set it so high. It looks like you need to take a time out and read up on Threads a little bit. You can't create proper programs when you don't understand what you are doing. One solution is for you to have one thread that all it does is accept new connections. Then this thread fires of "subthreads" to handle the incoming client request. Kind of what you had before you added `@thr.join`. But you need to understand thread safety and shared variables properly first. Time to study! – Casper May 31 '12 at 15:16
  • I may have overstated my lack of understanding as I tend to do:). I have read a lot on threads and thread safety and articles on why threads are bad on this system versus that. What I have yet to find is an explanation that recommends a way to implement a simple multi client TCP server that is "correct". What I have above is recommended by many and is currently working, but apparently grossly incorrect. I am a GREAT study and would LOVE to read authoritative material on the subject. Could you point me toward a definitive source on this? Because google doesn't seem to helping in this case. – pitachip May 31 '12 at 15:32