Jan 15 2009

Playing with EventMachine

img_5305I’ve had a bit of opportunity to play with Ruby EventMachine over the last few weeks at work. While the RDoc is very helpful it’s still a bit of work to figure things out. To that end, I figured I might try to write some stuff down.

The first question is typically, what is this EventMachine thing and how come when I use it the rest of my application doesn’t execute. Well, simply put, EM (EventMachine) is an event loop. Once you kick off EventMachine it takes over the execution of your main Ruby thread and does its own thing. EM will listen on sockets, on file descriptors, set timers and various other procedures. When activity happens on the things EM is listening too it will trigger event callbacks to your code. You handle the event then return control to EventMachine.

It’s also possible to have EventMachine execute a block of long running code on another thread so you don’t hold up the entire run loop with your work. When the process is done a callback can be executed on the main thread to handle any responses to the original connection.

Hopefully this will all become more clear as we look at some code.

Timer Example


#!/usr/bin/ruby

require 'rubygems'
require 'eventmachine'

EventMachine.run do
    EM.add_periodic_timer(1) { puts "Tick ..." }

    EM.add_timer(3) do
        puts "I waited 3 seconds"
        EM.stop_event_loop
    end
end

puts "All done."

Nothing too complicated here. Let’s, as they say, start at the start. I’ve installed Eventmachine through rubygems (gem install eventmachine) so I need the proper requires to get everything available. You’ll notice I’m using both EventMachine and EM, they’re interchangeable. I prefer EM as it’s shorter but sometimes use EventMachine anyway. The call to EventMachine#run is what kicks off the main event loop. Before the event loop is started the block provided to EM#run will be executed. This is where you can setup your various servers, timers and other handlers as needed.

In this example I creating two timers. Using EM#add_timer I’ve created a timer that will execute once in at least three seconds. The EM#add_periodic_timer creates a timer that will execute every one second. In both cases when the event is fired it will execute the block attached to the timer creation call.

For the timer which fires each second we do a simple puts call. In the single shot timer we have a call to EM#stop_event_loop. EM#stop_event_loop will cause the EventMachine event loop to stop executing. Once the event loop is terminated the control flow of the application will pickup again after the EM#run block and execute the final puts statement.

So, as you can see, any code we have after the call to EM#run will not get executed until the event loop is shutdown. Typically when your application is terminating.

Server Example


#!/usr/bin/ruby

require 'rubygems'
require 'eventmachine'

module Server
    def receive_data(data)
        puts data
        send_data("helo\n")
    end
end

EM.run { EM.start_server 'localhost', 8080, Server }

Here we’re calling EM#start_server in our run block. EM#start_server will tell EventMachine to start listening on IP address defined by localhost, port 8080 and will will create an anonymous class and include the module Server for each connection. You could also define Server as a class which inherits from EventMachine::Connection.

It’s worth pointing out, the instantiated class will only exist for a single connection. You won’t be able to store information between requests in the object.

What you can do is pass data into the object when it’s created.

Server Example 2


#!/usr/bin/ruby

require 'rubygems'
require 'eventmachine'

class Server < EventMachine::Connection
    attr_accessor :options, :status

    def receive_data(data)
        puts "#{@status} -- #{data}"
        send_data("helo\n")
    end
end

EM.run do
    EM.start_server 'localhost', 8080, Server do |conn|
        conn.options = {:my => 'options'}
        conn.status = :OK
    end
end

The EM#start_server method will yield to a block passing in the object that will handle the connection. You can then assign any values into that object that you wish.

That’s it for now, my very quick introduction to EventMachine. Hopefully it was useful.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Jan 02 2009

Fighting Review Board

Categories: Computers, Work
Tags:

img_5528We’ve been talking a bit about code reviews at work, I do a bit of this now by reading commit emails, and have been wondering if there was an easier solution. To that end, I started looking around for software that could help us out.

I ended up beating on Review Board for about six hours before getting it installed. Now that it’s running it looks pretty nice. I think we’ll end up doing more post-commits then pre-commits but hopefully it’ll fit into our workflow.

Anyway, I though I’d put up a quick post on what I needed to do it get it running. The docs do a good job of getting the base install done. So give that a gander but come back here before you start running rb-site install.

We need to setup our MySQL database before running the rb-site. I did this by initializing a reviewboard MySQL database with a reviewboard user. I then granted all priviledges by executing:


GRANT ALL PRIVILEGES ON reviewboard.* TO 'reviewboard'@'localhost'
            IDENTIFIED BY 'some_pass' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON reviewboard.* TO 'reviewboard'@'% '
            IDENTIFIED BY 'some_pass' WITH GRANT OPTION;

Once rb-site install is run you’ll need to configure your webserver. In my case I ended up using Apache with FastCGI. When I tried to use the mod_python option the webserver would get a Segmentation Violation and terminate. Not so good.

The provided Apache FastCGI configuration script didn’t work for me and I ended up using the following.


AddHandler fastcgi-script fcgi

FastCGIExternalServer "/var/www/reviews/htdocs/reviewboard.fcgi" -host 127.0.0.1:3033 -idle-timeout 60

<VirtualHost *:8888>
    ServerName reviews.local
    DocumentRoot /var/www/reviews/htdocs

    # Alias static media requests to filesystem
    Alias /media /var/www/reviews/htdocs/media
    Alias /errordocs /var/www/reviews/htdocs/errordocs

    # Error handlers
    ErrorDocument 500 /errordocs/500.html

    # Direct all other requests to the fastcgi server
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^/(.*)$ /reviewboard.fcgi/$1 [QSA,L]
</VirtualHost>

The first parameter to FastCGIExternalServer needs to be the full path to reviewboard.fcgi as if it existed in your DocumentRoot. Nothing else seemed to work for me.

Note, if you’re using VirtualHosts you’ll also need to make sure the NamedVirtualHost option is enabled in your httpd.conf file (or whatever your main Apache config file is named).

Now, this will run, but it won’t work. The reason it won’t work is that it needs an external server to send the FastCGI requests too. You can run this server by executing: rb-site manage /var/www/reviews/ runfcgi method=threaded port=3033 host=127.0.0.1 protocol=fcgi. You’ll notice the host and port match up to those specified in the Apache config file.

I ended up creating a simple shell script to handle starting and stopping the rb-site server.

#! /bin/sh

# chkconfig: 2345 90 90

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="reviewboard daemon"
NAME=reviewboard

case "$1" in
  start)
    echo -n "Starting $DESC: $NAME"
    /usr/bin/rb-site manage /var/www/reviews/ runfcgi method=threaded port=3033 host=127.0.0.1 protocol=fcgi
    echo "."
    ;;
  stop)
    echo -n "Stopping $DESC: $NAME"
    pkill rb-site
    echo "."
    ;;
  *)
    echo "Usage: NAME {start|stop}" >&2
    exit 3
    ;;
esac

exit 0

With that I was able to login and start playing with Review Board. I ended up doing a bit of extra work to disable registration (which is kind of nasty as I had to edit the base HTML templates and urls.py) but everything seems to be working well now.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]