How to keep persistent socket connections?

Hello ruby experts!

I've been working in Ruby on Rails for awhile now (like 3 months), I and
have *thoroughly* enjoyed it thus far.

But my team and I have come up against a problem that I don't think
Rails can address. I know Ruby itself can implement a multithreaded
socket server, but I don't know how to make that happen within the
context of rails.

We have 2 distinct (and similar problems) that center around the need to
keep persistent socket connections open (as a socket server, not
client).

For the first, we need to be able to open a socket to a 3rd party server
and keep it open. Today I'm going to try opening the socket and then
storing the socket in the Rails cache, in order to see if that socket
remains open and useable even while just sitting in the cache (I'm kind
of thinking it won't).

For the second, we need to build a game server with built-in chat. The
clients need to be able to open a socket and then send and receive
game-related messages on that socket. I have done some digging and
found TCPServer class that Ruby provides, which could definitely fit the
bill. I've also seen Gserver and the "basic scrappy little chat server"
example provided at
http://www.rubyinside.com/advent2006/10-gserver.html. I'm thinking --
is there any way I could run a Gserver or TCPServer on top of rails?
Have it listen on a different port, but still have all the code inside
be able to access my rails classes/models/etc? Then I could have a
multithreaded server, but with all the convenience and power of RoR.

Let me know what you folks think. I'm really looking forward to having
my mind blown by your solutions. :slight_smile:

-Steve

Today I'm going to try opening the socket and then
storing the socket in the Rails cache, in order to see if that socket
remains open and useable even while just sitting in the cache (I'm kind
of thinking it won't).

Yeah, that didn't work because caching the connection makes it
immutable, which means you can no longer write data to it. But you know
what I think *will* work? Storing the connection(s) as class vars. So
I'm trying that next. I will report back here afterward for anyone who
might be interested.

If I was you I'd ask how to do this over on comp.lang.ruby. My guess is you'll have a better time doing this entirely outside of rails.

BTW--the first appendix of the pickaxe book covers ruby's socket library. If you haven't already looked at that, there could be some useful stuff in there...

FWIW--you can use AR in a straight ruby program. Frx--something like this should work I think:

  require "rubygems"
  require "activerecord"
  require "sqlite3"

  ActiveRecord::Base.establish_connection(:adapter => 'sqlite', :database => '~/my_db.db')

  c = ::ActiveRecord::Base.connection

  unless c.tables.member?("features")
    c.create_table(:features) do |t|
      t.column :feature, :string
      t.column :category, :string
      t.column :count, :integer
    end
    c.add_index(:features, [:category, :feature], :unique => true)
  end

  class Feature < ActiveRecord::Base
  end

  f = Feature.new(:feature => "bibbity", :category => "bobbity", :count => 1)
  f.save!
  puts("finished!")

Roy --

Thank you for that example!! I guess I'll give that a shot :slight_smile:

It still hurts my heart that this doesn't appear to be possible with
Rails. :frowning:

One followup question, if I may:
Why is it that class variables don't behave as I expect them to? So
since it appears that adding class attributes (inheritable or not) to a
descendent of ActiveRecord is unsupported, I added a new Class
(inheriting from Object) and put it in /lib:

class ConnectionManager
  HOST = "the.host.com"
  PATH = '/'
  PORT = 2195

  PASSPHRASE = "foobar"
  CACERT = File.expand_path(File.dirname(__FILE__) +
"certs/ca.the.host.com.crt")
  USERAGENT = 'Mozilla/5.0 (ConnectionManager Ruby on Rails 0.1)'
  cattr_accessor :connection, :cert_name, :cert
  self.cert_name = "my_pem_file.pem"

  def initialize
    super
    cert = File.read("config/#{cert_name}") if
File.exists?("config/#{cert_name}")
    puts "cert = #{cert.inspect}"
    if connection.nil?
      puts "Connecting now!!"
      ctx = OpenSSL::SSL::SSLContext.new
      ctx.key = OpenSSL::PKey::RSA.new(cert, PASSPHRASE)
      ctx.cert = OpenSSL::X509::Certificate.new(cert)

      s = TCPSocket.new(HOST, PORT)
      connection = OpenSSL::SSL::SSLSocket.new(s, ctx)
      connection.sync = true
      connection.connect
    end
  end
end

But when I try to use this in my class that inherits from ActiveRecord,
like this:

  def send
    ssl = cm.class.connection
    logger.info "ssl = #{ssl.inspect}"
    ssl.write(self.message_for_sending)

  rescue SocketError => error
    raise "Error while sending: #{error}"
  end

First I got an error and I realized that my ConnectionManager's
initialize method wasn't being called. So I added a line to get an
instance of ConnectionManager, just so that initialize would be called:

  def send_notification
    cm = ConnectionManager.new
    ssl = cm.class.connection
    logger.info "ssl = #{ssl.inspect}"
    ssl.write(self.message_for_sending)

  rescue SocketError => error
    raise "Error while sending notifications: #{error}"
  end

I could tell from my log ("Connecting Now!!" appeared) that initialize
had been called. But I'm still getting an error:

You have a nil object when you didn't expect it! The error occurred
while evaluating nil.write

Clearly ConnectionManager.connection is returning null. In Java, once
you've used a class, its static vars are instantiated and hold their
values. It seems that in Ruby, the class's static vars are
instantiated/sandboxed in each .rb file. Am I correct here, or no?

Thanks again for the example code Roy!

-Steve

Heh--you're welcome. But please don't let me bully you into dropping rails--I don't understand your goals as well as you do. :wink: I'm just trying to explain options. I frankly don't understand rails well enough to know when/how objects live beyond individual http requesst/response cycles. My guess is that your socket server thingy needs to be independent of that in order to handle incoming connections reliably.

I can say that it is possible to add class *methods* to a descendant of AR::Base--frx, in the below find_by_fragment is a class method.

  class SpecificTherapeuticClass < ActiveRecord::Base
    has_many :ingredients
    belongs_to :general_therapeutic_class
    def self.find_by_fragment(frag)
      frag += "%"
      self.find(:all, :conditions => ["description like :frag", {:frag => frag}])
    end
  end

I'm not totally sure I follow your code below, but one thing jumps out--this line:

  cattr_accessor :connection, :cert_name, :cert

Defines *class* methods & vars for those attributes. So you'd say, e.g. ConnectionManager.cert = "/bibbity/bobbity/boo.pem" and that bit of data is entirely independent of any particular instance of ConnectionManager. But initialize is an *instance* method--it gets called in the context of a new instance of ConnectionManager. So the line "cert = File.read..." in your initialize method is just setting a local var 'cert' which will die as soon as the initialize call is over. If you want to define instance methods connection, cert_name & cert, you'd use the (native ruby) attr_accessor method (and you may want to call it as self.cert, just to make it totally clear to the interpreter that you intend to call a method rather than set a local var).

Maybe that helps?

Cheers,

-Roy

Roy,

thanks!! That did help. I also found that a solution to my problem: I
used a singleton connection manager. This avoided all my problems. :slight_smile:

I have a question regarding thread safety, but I think I'll post a new
thread for it. The general gist is: if I have a singleton class (by
using the Singleton mixin), I understand it's thread-safe in that it
ensures only a single instance of this class will be created. BUT (in
theory), the instance methods of my singleton instance could be accessed
concurrently by various threads, thus causing a concurrency issue.
Right?

BUT Rails is essentially single-threaded (only a single request
processed at a time), so if I have only a single call to my singleton
instance per http request, then it should be impossible to get
concurrency problems with it. Right?

Steve Hull wrote:

Yeah, that didn't work because caching the connection makes it
immutable, which means you can no longer write data to it. But you know
what I think *will* work? Storing the connection(s) as class vars. So
I'm trying that next. I will report back here afterward for anyone who
might be interested.

I'd use a singleton object (ruby Singleton module lets you do this
easily, or it's easy enough to do yourself) that encapsulates logic and
state for your 'connection pool', rather than class variables. But
you're on the right track.

Note though that you've got to make sure all your logic (in this case in
that hypothetical Singleton object), is concurrency-safe, if your rails
app can possibly be deployed in any kind of a concurrent-request
environment (or if you plan to create Threads yourself in your rails
app, but hardly anyone ever does that).

You could look at the newish Rails 2.2 ActiveRecord ConnectionPool for a
model, since it's doing basically what you want, but specifically for db
connections. The ConnectionPool code might just end up being too
confusing and complicated though, since it has to deal with Rails
backwards compatibility issues and stuff. I haven't actually looked at
the ConnectionPool code myself much yet.