I have been getting more and more interested in high-performance Ruby apps, and in EventMachine in particular. First of all, super props to Aman Gupta for EM, and to some other Ruby devs out there who have been writing libraries and drivers on top if it, such as Ilya Grigorik, and Carbon Five’s own Mike Perham.
However one area that has not gotten a lot of attention within the EventMachine world is that of testing and tools support. It would be ideal for evented codebases if all tests and all rakes were automatically run inside an EventMachine reactor. I realize that many of the EM-enabled libraries out there, like mysql2, work whether they are in a reactor or not, so this may seem unnecessary. But this means that your tests are exercising a different code path than your production app, which is a bad idea.
So how can we get our tools running within EventMachine?
I am using rake, rspec and cucumber in an EM-enabled project, and I monkey-patched each of these gems within my project to run inside a reactor. The strategy for each gem is very similar: I look for the first method called within the gem in question that is executed after the project files have been loaded, and I override it, wrapping it in a reactor.
Let’s take a look at how this works for Rake:
lib/tasks/em.rake
module Rake class Application alias_method :top_level_alias, :top_level def top_level EM.synchrony do top_level_alias EM.stop end end end end
In this case, I override Rake::Application.top_level. I first alias it to top_level_alias, and then I wrap a call to that aliased method with a call to EM.synchrony.[1] And of course I stop the reactor in the end of my block.
For RSpec, the code is a little more complex, but the idea is the same.
spec/spec_helper.rb
module RSpec module Core class ExampleGroup class << self alias_method :run_alias, :run def run(reporter) if EM.reactor_running? run_alias reporter else out = nil EM.synchrony do out = run_alias reporter EM.stop end out end end end end end end
I would like to call a method that wraps the entire test suite, but the best I can do is a method wrapping a single example group. This means that I start and stop a reactor for each spec file in my project. This is not ideal, but it works just fine.
Also notice that I check whether or not the reactor is running. Because of the recursive way RSpec works, we are often already in a reactor loop when we wind up calling this method.
Cucumber is straightforward, just like Rake.
features/support/em.rb
module Cucumber module Ast class TreeWalker alias_method :visit_features_alias, :visit_features def visit_features(features) EM.synchrony do visit_features_alias features EM.stop end end end end end
My hope is that each of these gems (and the other similar ones out there) will add the ability to run themselves inside an EM reactor. I see this as a configuration option, much the way ‘-drb’ is used by many gems to enable Spork. It is my plan to fork these gems and implement it myself, so the gem owners out there should expect a pull request some time soon.
[1] If you don’t already know, EM.synchrony is part of Ilya’s excellent em-synchrony gem. It elegantly starts a reactor within a Ruby Fiber (which also means you need to be using Ruby 1.9).
Mike is the President and CEO of Carbon Five.