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 -- Posted via http://www.ruby-forum.com/.
> 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. -- Posted via http://www.ruby-forum.com/.
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... -----Original Message----- From: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org [mailto:rubyonrails-talk@googlegroups.com] On Behalf Of Steve Hull Sent: Thursday, May 21, 2009 11:41 AM To: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org Subject: [Rails] Re: How to keep persistent socket connections?> 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. -- Posted via http://www.ruby-forum.com/. GHC Confidentiality Statement This message and any attached files might contain confidential information protected by federal and state law. The information is intended only for the use of the individual(s) or entities originally named as addressees. The improper disclosure of such information may be subject to civil or criminal penalties. If this message reached you in error, please contact the sender and destroy this message. Disclosing, copying, forwarding, or distributing the information by unauthorized individuals or entities is strictly prohibited by law.
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:www.continuousthinking.com/2006/11/17/ruby-class-variable-or-class-instance-variables+ruby-class-variable-or-class-instance-variables&cd=5&hl=en&ct=clnk (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 -- Posted via http://www.ruby-forum.com/.
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!") -----Original Message----- From: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org [mailto:rubyonrails-talk@googlegroups.com] On Behalf Of Steve Hull Sent: Thursday, May 21, 2009 2:12 PM To: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org Subject: [Rails] Re: How to keep persistent socket connections? 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:www.continuousthinking.com/2006/11/17/ruby-class-variable-or-class-instance-variables+ruby-class-variable-or-class-instance-variables&cd=5&hl=en&ct=clnk (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 -- Posted via http://www.ruby-forum.com/. GHC Confidentiality Statement This message and any attached files might contain confidential information protected by federal and state law. The information is intended only for the use of the individual(s) or entities originally named as addressees. The improper disclosure of such information may be subject to civil or criminal penalties. If this message reached you in error, please contact the sender and destroy this message. Disclosing, copying, forwarding, or distributing the information by unauthorized individuals or entities is strictly prohibited by law.
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 -- Posted via http://www.ruby-forum.com/.
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 ;) -- Posted via http://www.ruby-forum.com/.
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 -----Original Message----- From: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org [mailto:rubyonrails-talk@googlegroups.com] On Behalf Of Steve Hull Sent: Thursday, May 21, 2009 5:06 PM To: rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org Subject: [Rails] Re: How to keep persistent socket connections? 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 -- Posted via http://www.ruby-forum.com/. GHC Confidentiality Statement This message and any attached files might contain confidential information protected by federal and state law. The information is intended only for the use of the individual(s) or entities originally named as addressees. The improper disclosure of such information may be subject to civil or criminal penalties. If this message reached you in error, please contact the sender and destroy this message. Disclosing, copying, forwarding, or distributing the information by unauthorized individuals or entities is strictly prohibited by law.
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? -- Posted via http://www.ruby-forum.com/.
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. -- Posted via http://www.ruby-forum.com/.