6

we use Rails and EventMachine together, and when using that combo with Passenger there is some very specific setup that needs to be done. After a lot of trial and error, I got EventMachine initialization working well, but I would like to understand the code a little better. As you can see below in this code snippet, our initializer checks for Passenger, and then checks if it's a forked process before restarting EventMachine.

if defined?(PhusionPassenger)
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
  # for passenger, we need to avoid orphaned threads
    if forked && EM.reactor_running?
      EM.stop
    end
    Thread.new {
      EM.run do

My question is related to the EM.reactor_running? and EM.stop commands. If Passenger has forked our process, why do I need to restart the EM reference in a new thread? If EM.reactor_running? returns true, what EM instance am I referencing?

You can see the full initializer code on our blog here http://www.hiringthing.com/2011/11/04/eventmachine-with-rails.html

Joshua
  • 5,336
  • 1
  • 28
  • 42

1 Answers1

14

First of all, there's only one EventMachine instance per Ruby process, so no matter what you'll always reference the same EM instance, independent of the thread your currently in.

You run the reactor in a new, separate thread so that it doesn't block the main thread (whose purpose it is to serve the web request). EM.run would otherwise take over control, entering its run loop, not leaving the EM.run block anymore. EM.reactor_running? returns true, well, if an EM loop is running somewhere. As there is only one per Ruby process it's easy enough for the method to figure out if EM is running or not.

The setup you have here is the simplest way to use EM inside a normal Ruby process without interfering with everything else that's running. I'm assuming you're pushing messages to an AMQP broker from your web app. Whenever you send a message, it will go into EM's run loop in the separate thread, that part is pretty transparent to you, and not affect the main loop, which can continue handling the Rails web request. Be careful though to always push things onto the EM loop using EM.next_tick. Trying to handle sockets opened by EM in different threads could result in bad things happening, which I have seen in production, incidentally by using and building a library called happening ;)

Stopping the EM loop before starting a new one takes care of an EM loop that may be left over from a parent process, which could result in problems with file descriptors opened, using EM, in the parent process. In custom code this can be circumvented by using EM.fork_reactor, but as the parent process is out of your control, it's safest to check if a reactor exists and stop it before starting a new instance.

roidrage
  • 2,341
  • 17
  • 17
  • Great description thanks. I'm clear on the need to run EM in it's own thread, but When you say "EM loop that may be left over", that's where I'm a little unclear. How does an EM loop get "left over"? I'm not shutting down the parent process' EM, correct? – Joshua Nov 07 '11 at 17:35
  • That's exactly what you're doing. You're shutting down a potentially left over EM from the parent process. Should an EM loop run in the parent process, it will be forked too, but you're usually not at all interested in the file descriptors left over from the parent, so you start from scratch instead. Given that Passenger is unlikely to run an EM loop by itself, this is more of a safety measure than anything else. – roidrage Nov 08 '11 at 09:14
  • In a similar vein, I'm working with RabbitMQ via the ruby-amqp gem, within a websocket app running under thin. Thin has it's own EventMachine loop going, and I am currently using EventMachine.next_tick to break in and do my amqp stuff. Is this correct or should I be using EventMachine.fork_reactor to give AMQP it's own EM to use? – wchrisjohnson Jan 24 '13 at 12:46
  • @wchrisjohnson You should be fine to use the reactor created by Thin. `fork_reactor` creates a new process which is not going to have the context of the Thin web server anymore, which I assume you need for the WebSockets integration. – roidrage Mar 18 '13 at 11:48
  • Thanks - that's exactly the case. – wchrisjohnson Mar 19 '13 at 12:30