How to Spawn an Eventmachine "Inside" a Rails App

How do you spawn an EventMachine inside a Rails app?

I'd try using em-synchrony to start a reactor in a fiber. In a rails app you can probably start it in an initializer since it sounds like you just want to leave the reactor running to respond to websocket requests. As suggested by the other answers I think you want to either setup socket communication with your reactor or use one of the asynchronous clients to a data store which both your reactor and rails code can read from and write to to exchange data.

Some of my coworkers put together some examples of starting EM reactors on demand in ruby code to run their tests within EventMachine. I'd try using that as a possible example; raking and testing with eventmachine

EventMachine in Rails - where to put run loop?

You'll probably want to toss your code into an initializer. John Nunemaker posted some really interesting info about firing up EventMachine under Passenger in a Sinatra app, and I'd imagine it would work fairly similarly for you.

That said, it sounds like you're just trying to get a web sockets server working with Heroku. In theory youd think running on the Cedar stack and adding a new process type to your Procfile for your EM based Websockets server would get it working. That would look something like:

web:         bundle exec rails server -p $PORT
websocket: bundle exec script/websocket_server $PORT

With script/websocket_server being your app's socket server script that can handle a custom port argument.

Unfortunately, though, Heroku doesn't support Websockets yet, as outlined here, so a Procfile solution most likely wouldn't work.

EventMachine and Ruby Threads - what's really going on here?

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.

Connecting to background EventMachine application for unit testing

Not sure if there's a better way, but I solved this by using retry:

@thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
begin
@socket = TCPSocket.open('localhost', PORT)
rescue Errno::ECONNREFUSED
sleep 0.1
retry
end

This will indefinitely keep trying to establish the connection 10 times a second until it works. A more robust solution might be to use a counter or timer to eventually give up, just in case something is seriously awry.

The full test code looks like this:

require 'socket'
require 'minitest/autorun'

PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)

class TestServer < MiniTest::Unit::TestCase
def setup
@pid = Process.spawn "#{CMD} -D --port #{PORT}"
begin
@socket = TCPSocket.open('localhost', PORT)
rescue Errno::ECONNREFUSED
sleep 0.1
retry
end
end

def teardown
if @socket && !@socket.closed?
@socket.puts("quit") # try for a nice shutdown
@socket.close
end
Process.kill("HUP",@pid)
end

def test_aaa
# my test code
end

def test_bbb
# more test code
end
end

How to log with Ruby and eventmachine?

I ended up wrapping the logger in a singleton class that started a thread and had a FIFO queue. Logging would dump log info into the queue and the thread just looped, pulling stuff out of the queue and using the real logger to log it. Not really reactor pattern but it won't chew up my EM thread pool either.

With this singleton, you can only ever have one logger but here is what I did up on github.

Event Machine: How to receive file uploads?

First here is how to build a simple file upload with sinatra: File upload with Sinatra

So now to run your web server with thin you can do this in your code:

class MyWebApp < Sinatra::Base
# here goes the sinatra app code
post '/something' do
# ...
end
end

EM::run do
Thin::Server.start('0.0.0.0', 8000) do
map('/'){ run MyWebApp.new }
end
end

thin uses eventmachine internally, I suppose webrick uses threads but honestly I never really looked into it.

You should use apache or nginx in front of your ruby process at least for the file upload (I think websockets won't work through it).
I can include a simple nginx config if you need (just need to find it on my disk xD).

Edit: Another solution is to use goliath as web server, you have an example here: https://github.com/postrank-labs/goliath/blob/master/examples/async_upload.rb
If you don't need to display upload progress you should stay with sinatra+nginx/apache.



Related Topics



Leave a reply



Submit