21

I've got a Rails application, and am looking to add some sort of WebSocket support to it. From various googling, it appears that the best Ruby based WebSocket solution is em-websocket running on EventMachine.

I was wondering if there was a way to "integrate" an EventMachine reactor into Rails? Where do I put the initialization code? Is this the proper way to accomplish this?

I've seen this example that falls back on Sinatra to do an EventMachine GET request, but that isn't quite what I'm looking for.

Any help is appreciated.

Mike Trpcic
  • 25,305
  • 8
  • 78
  • 114

8 Answers8

9

You cannot run the Eventmachine engine inside of Rails itself as it is a persistent run loop that would block one of your Rails processes permanently. What is usually done is there's a side-process that uses Eventmachine and Rails communicates with it through sockets to send notifications.

Juggernaut serves as an example of this kind of thing where it implements a Websocket client and a Rails hook to send notifications to it. The project has since deprecated the Ruby version in favor of a JavaScript Node.js version but this still serves as a very thorough example of how Eventmachine can be used.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • What do you mean "side-process"? You mean that you spawn a process in an initializer file? – Daniel Viglione Sep 13 '16 at 23:15
  • @Donato I mean you need to maintain an independent process usually using something like [Daemons](http://daemons.rubyforge.org) to make background processes and a process launcher like [Eye](https://github.com/kostya/eye) to keep it running. – tadman Sep 14 '16 at 15:58
  • I do not like Daemons because you have to manage it separately from your Rails application. I want Passenger to use its Preloader process to fork a new process (using ruby 2's copy-on-write semantics and thus using the shared memory space) in an initializer, and I want that new process to be managed by Passenger. I want that process to poll IMAP using idle, yet having access to Rails environment. I want to use EventMachine because there will be a lot of idleness and do not want to waste memory resources on separate thread stacks. – Daniel Viglione Sep 14 '16 at 19:39
  • Essentially, I just want to know how to fork this separate Passenger process in an initializer and have EventMachine running in this new process. – Daniel Viglione Sep 14 '16 at 19:39
  • What you like and how you solve this problem seem to be at odds here. Passenger is free to kill your Rails process if nothing's happening traffic wise, something it *will* do. That means during idle times you have no background processes. You need something external from Rails to stay running, and the best plan for that is an independent process outside the control of Passenger. Hoping that your Passenger process doesn't get killed, or preventing Passenger from doing it is going to be nothing but trouble. – tadman Sep 14 '16 at 19:40
6

If you run rails application in a thin server (bundle exec thin start) thin server run EventMachine for you and then your rails application can execute EM code wherever you need.

By example:

A library o initializer with that code:

EM.next_tick do
  EM.add_periodic_timer(20) do
    puts 'from Event Machine in rails code'
  end
end

not blocks rails processes application.

5

Don't know if this is what you are after. But if you would like to do provide some kind of socket-messaging system.

Have a look at Faye. It provides message servers for Node.js and Rack. There is also a rails cast for this by Ryan Bates which should simplify the implementation.

Hope that helps.

Christian Fazzini
  • 19,613
  • 21
  • 110
  • 215
4

I spent a considerable amount of time looking into this. EventMachine need to run as a thread in your rails install (unless you are using Thin,) and there are some special considerations for Passenger. I wrote our implementation up here: http://www.hiringthing.com/2011/11/04/eventmachine-with-rails.html

UPDATE

We pulled this configuration out into a gem called Momentarily. Source is here https://github.com/eatenbyagrue/momentarily

Joshua
  • 5,336
  • 1
  • 28
  • 42
2

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

Jonah
  • 17,918
  • 1
  • 43
  • 70
2

I'd consider looking into Cramp. It's an async framework built on top of EventMachine, and it supports Thin server:

Rack Middlewares support + Rainbows! and Thin web servers

John K. Chow
  • 1,651
  • 16
  • 24
  • 1
    Sadly that site's broken and [Cramp](https://github.com/lifo/cramp) has been abandoned. Something to consider if approaching this problem today. – tadman Sep 14 '16 at 19:41
0

You probably shouldn't use EM any more if you can help it, it seems to no longer be maintained - if you encounter a bug - you're on your own.

Most of the answers above don't work in JRuby due to https://github.com/eventmachine/eventmachine/issues/479 - namely the pattern:

Thread.new{ EM.run }

used by EM::Synchrony and various answers found around the internet (such as EventMachine and Ruby Threads - what's really going on here?) are broken under JRuby eventmachine implementation (fibers are threads in jruby and there's currently no roadmap on when this will change).

JRuby messaging options would be

  1. deploy with TorqueBox (which comes bundled with HornetQ), http://torquebox.org/news/2011/08/15/websockets-stomp-and-torquebox/, impressive and enterprisey but not really elegant unless you're coming from a Java background
  2. newer versions of Faye should work with JRuby, Faye in jruby on rails
  3. note for the future, keep an eye on the celluloid community, some interesting distributed solutions are coming from there https://github.com/celluloid/reel/wiki/WebSockets, https://github.com/celluloid/dcell
  4. ?
Community
  • 1
  • 1
bbozo
  • 7,075
  • 3
  • 30
  • 56
0

I had same problem and found solution. First, put your code in lib dir (for example /lib/listener/init.rb) and create one class method that run EM, for example Listener.run.

#!/usr/bin/env ruby

require File.expand_path('../../config/environment', File.dirname(__FILE__))

class Listener
  def self.run
  # your code here
  # you can access your models too
  end
end

After that I used dante gem. Create /init/listener file. The code may be like that:

#!/usr/bin/env ruby

require File.expand_path('../../lib/listener/init.rb', __FILE__)

log_file = File.expand_path('../../log/listener.stdout.log', __FILE__)
pid_file = File.expand_path('../../tmp/listener.pid', __FILE__)

listener = Dante::Runner.new('listener')

if ARGV[0] === 'start'
  listener.execute(daemonize: true,
                   pid_path: pid_file,
                   log_path: log_file) { Listener.run }
elsif ARGV[0] === 'restart'
  listener.execute(daemonize: true,
                   restart: true,
                   pid_path: pid_file,
                   log_path: log_file) { Listener.run }
elsif ARGV[0] === 'stop'
  listener.execute(kill: true, pid_path: pid_file)
end

Now you can run you code like that: ./bin/listener start, ./bin/listener restart, ./bin/listener stop

You can use god for monitoring your listener is running. But make sure you're using same pid file (/tmp/listener.pid).

Artem P
  • 5,198
  • 5
  • 40
  • 44