Sunday, November 23, 2008

Using ICE with Ruby on Rails

For those of you who have never heard of it, ICE (Internet Communications Engine) is a really good object oriented middleware platform for communication between applications written in different languages running on different platforms. ZeroC is the company that makes ICE, you can find out more about it at www.zeroc.com.

I've used ICE on mulitple projects for communication of actions to take between the Ruby on Rails web server, and a C++ backend application that performs the actions. I would highly recommend it, if you have any backend application (C++, Java, C#) that you need your Ruby on Rails application to communicate with.

For the most part, setting up ICE to work with Ruby on Rails is pretty simple. If your server is Linux, download the appropriate RPM from ZeroC's site, or download the source code and compile it. For Windows, download and install the ICE for Visual C++ 6.0 file (Ruby support is not in the Visual Studio 2005 or 2008 installation, the C++ 6.0 package can be installed along side either of these).

After this, add the /ruby directory of the ICE install to your RUBYLIB environment variable. This is the directory that Ice.rb gets installed to. For a Linux install, a common location is /opt/Ice-3.3.0/ruby. Then add the /bin directory of the ICE install to your PATH environment variable. Example for Linux installs - /opt/Ice-3.3.0/bin.

After those are added to your environment variables, you should be able to add
require "Ice"
to your Ruby on Rails code to start working with ICE. I typically use slice2rb from the command line to produce a .rb file from my slice definition file before hand, but you can also call slice2rb directly from Ruby. If you do things the way I do, add a require for your slice2rb produced Ruby file.

You must initialize the Ice engine ONCE in your server process. Originally, I was initializing it and destroying it on every action that used Ice. This seemed to work fine for a while, but then I started having strange memory issues in my server related to Ice. I took out the initialize and destroy on every action, and only initialized once, and this cleared up the strange issues.

So what I would recommend is that you make a class variable to hold the Ice runtime in either the controller that calls Ice (do it in application controller if more than one controller calls Ice), or make a separate class to handle Ice. Here is an example of what should be placed at the top of this class (application controller used as an example:
require 'Ice'

class ApplicationController < ActionController::Base

@@ic = nil


Then, for EVERY controller action where you are calling an ICE method, you must do something similar to this
@@ic = Ice::initialize() unless @@ic
proxy = SliceModule::ServerObjectPrx::checkedCast(@@ic.stringToProxy("ServerObject:tcp -h #{server_name} -p #{server_port}"))
# any actions on the server object proxy

1 comment:

Brent said...

An update to this. As I've used ICE more in a production environment, in Ruby on Rails, a C++ backend application running in Linux, and Windows C# GUIs, I've run in to more and more unexplainable issues. On one server in particular, even without much use, the Ruby end starts throws exceptions on any action which requires restarting the web server. When a connection is lost between the client and server but the server doesn't detect the lost connection, issues have arisen. ICE seems great in theory, and it works OK for prototyping (once you figure out how to get it to work in your respective environment), but after the headaches of developing with it for 2+ years in a production environment I can't recommend it.

We're moving to a custom JSON socket interface for the comms between Rails and the backend app, and having the C# GUI make calls directly to the Rails app over http/https, if you're curious.