Forum: Ruby on Rails How to keep persistent socket connections?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-21 19:17
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.  :)

-Steve
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-21 20:41
> 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.
D6434aa0b7b350f8c3ed0119d81b2ead?d=identicon&s=25 Roy Pardee (rpardee)
on 2009-05-21 20:50
(Received via mailing list)
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...
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-21 23:12
Roy Pardee wrote:
> you'll have a better time doing this entirely outside of rails.

Yeah I've definitely considered this...  let's call this "Plan B".  :)

This is because I would love to have access to all the CRUD/easy
persistence provided by ActiveRecord, while keeping open sockets.

Anyway I'm running into a problem when I try to add class instance
variables to a class that inherits from ActiveRecord.  I've tried the
method outlined here:
http://74.125.93.132/search?q=cache:8FvmwfP_gDwJ:w...

(linked through google's cache since the site is down).  I got the
following error:

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

Same error when I tried to define the class instance vars using
:class_inheritable_accessor.

Perhaps it's unsupported to add class inheritable attributes to a
descendent ActiveRecord class?  I also have found very little talk of
class_inheritable_accessor, so perhaps this is no longer maintained?  I
would really appreciate some pointers re:class instance variables in
rails...

Thanks,
Steve
D6434aa0b7b350f8c3ed0119d81b2ead?d=identicon&s=25 Roy Pardee (rpardee)
on 2009-05-21 23:32
(Received via mailing list)
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!")
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-22 02:05
Roy --

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

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

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
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-22 03:01
Also.. it would be nice if I could use ActionController as well...
mostly I don't want to have to parse out the params hash ;)
D6434aa0b7b350f8c3ed0119d81b2ead?d=identicon&s=25 Roy Pardee (rpardee)
on 2009-05-22 19:02
(Received via mailing list)
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. ;-)  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
89992299997cb05046d970aa3d867ff8?d=identicon&s=25 Steve Hull (sdhull)
on 2009-05-27 06:02
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.  :)

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?
0f50b9a2ad85666d537d39bda49327ee?d=identicon&s=25 Jonathan Rochkind (jrochkind)
on 2009-05-30 20:04
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.
This topic is locked and can not be replied to.