Ctrl+C not killing Sinatra + EM::WebSocket servers - ruby

I'm building a Ruby app that runs both an EM::WebSocket server as well as a Sinatra server. Individually, I believe both of these are equipped to handle a SIGINT. However, when running both in the same app, the app continues when I press Ctrl+C. My assumption is that one of them is capturing the SIGINT, preventing the other from capturing it as well. I'm not sure how to go about fixing it, though.
Here's the code in a nutshell:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
EventMachine.run do
class Web::Server < Sinatra::Base
get('/') { erb :index }
run!(port: 3000)
end
EM::WebSocket.start(port: 3001) do |ws|
# connect/disconnect handlers
end
end

I had the same issue. The key for me seemed to be to start Thin in the reactor loop with signals: false:
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
This is complete code for a simple chat server:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
class App < Sinatra::Base
# threaded - False: Will take requests on the reactor thread
# True: Will queue request for background thread
configure do
set :threaded, false
end
get '/' do
erb :index
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") {
puts "Shutting down"
EventMachine.stop
}
Signal.trap("TERM") {
puts "Shutting down"
EventMachine.stop
}
#clients = []
EM::WebSocket.start(:host => '0.0.0.0', :port => '3001') do |ws|
ws.onopen do |handshake|
#clients << ws
ws.send "Connected to #{handshake.path}."
end
ws.onclose do
ws.send "Closed."
#clients.delete ws
end
ws.onmessage do |msg|
puts "Received message: #{msg}"
#clients.each do |socket|
socket.send msg
end
end
end
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
end

I downgrade thin to version 1.5.1 and it just works. Wired.

Related

Faye Websocket Ruby not working as expected

I am trying to use haproxy to load balance my websocket rack application.
I publish message in channel rates using redis-cli and this succeeds puts "sent" if ws.send(msg)
The client does receive the 'Welcome! from server' message so I know the initial handshake is done.
But, the client never receives the published message in channel 'rates'.
web_socket.rb
require 'faye/websocket'
module WebSocket
class App
KEEPALIVE_TIME = 15 # in seconds
def initialize(app)
#app = app
#mutex = Mutex.new
#clients = []
# #redis = Redis.new(host: 'rds', port: 6739)
Thread.new do
#redis_sub = Redis.new(host: 'rds', port: 6379)
#redis_sub.subscribe('rates') do |on|
on.message do |channel, msg|
p [msg,#clients.length]
#mutex.synchronize do
#clients.each do |ws|
# ws.ping 'Mic check, one, two' do
p ws
puts "sent" if ws.send(msg)
# end
end
end
end
end
end
end
def call(env)
if Faye::WebSocket.websocket?(env)
# WebSockets logic goes here
ws = Faye::WebSocket.new(env, nil) # {ping: KEEPALIVE_TIME }
ws.on :open do |event|
p [:open, ENV['APPID'], ws.object_id]
ws.ping 'Mic check, one, two' do
# fires when pong is received
puts "Welcome sent" if ws.send('Welcome! from server')
#mutex.synchronize do
#clients << ws
end
p [#clients.length, ' Client Connected']
end
end
ws.on :close do |event|
p [:close, ENV['APPID'], ws.object_id, event.code, event.reason]
#mutex.synchronize do
#clients.delete(ws)
end
p #clients.length
ws = nil
end
ws.on :message do |event|
p [:message, event.data]
# #clients.each {|client| client.send(event.data) }
end
# Return async Rack response
ws.rack_response
else
#app.call(env)
end
end
end
end
My haproxy.cfg
frontend http
bind *:8080
mode http
timeout client 1000s
use_backend all
backend all
mode http
timeout server 1000s
timeout tunnel 1000s
timeout connect 1000s
server s1 app1:8080
server s2 app2:8080
server s3 app3:8080
server s4 app4:8080
Chrome dev tools
Please help me!!!
EDIT:
I have tried Thread running in Middleware is using old version of parent's instance variable but this does not work.
As mentioned earlier. The below code succeeds
puts "sent" if ws.send(msg)
Okay, After a lot of searching and testing.. I found that the issue was with not setting a ping. during websocket initialization in the server.
Change this
ws = Faye::WebSocket.new(env, nil) # {ping: KEEPALIVE_TIME }
to
ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME })
My KEEPALIVE_TIME is 0.5 because I am making a stock application where rates change very quickly. You can keep it per your needs.

Running Sinatra app inside Thread doesn't work

I'm trying to run a Sinatra app in a new Thread in order to also run some other thing inside my script but when I do:
require 'sinatra/base'
class App < Sinatra::Base
...some routes...
end
Thread.new do
App.run!
end
Nothing happens and Sinatra server is not started. Is there anything I'm missing in order to achieve this?
Finally I run the other ruby process in a Thread but from Sinatra application and it works just fine.
class App < Sinatra::Base
threads = []
threads <<
Thread.new do
Some::Other::Thing
rescue StandardError => e
$stderr << e.message
$stderr << e.backtrace.join("\n")
end
trap('INT') do
puts 'trapping'
threads.each do |t|
puts 'killing'
Thread.kill t
end
end
run!
end
And I added a control when Sinatra app is exited also kill the open thread.

EM::WebSocket.run as well as INotify file-watching

This is the code I currently have:
#!/usr/bin/ruby
require 'em-websocket'
$cs = []
EM.run do
EM::WebSocket.run(:host => "::", :port => 8085) do |ws|
ws.onopen do |handshake|
$cs << ws
end
ws.onclose do
$cs.delete ws
end
end
end
I would like to watch a file with rb-inotify and send a message to all connected clients ($cs.each {|c| c.send "File changed"}) when a file changes. The problem is, I do not understand EventMachine, and I can't seem to find a good tutorial.
So if anyone could explain to me where to put the rb-inotify-related code, I would really appreciate it.
Of course! As soon as I post the question, I figure it out!
#!/usr/bin/ruby
require 'em-websocket'
$cs = []
module Handler
def file_modified
$cs.each {|c| c.send "File was modified!" }
end
end
EM.run do
EM.watch_file("/tmp/foo", Handler)
EM::WebSocket.run(:host => "::", :port => 8085) do |ws|
ws.onopen do |handshake|
$cs << ws
end
ws.onclose do
$cs.delete ws
end
end
end

Ruby Event Machine stop or kill deffered operation

I was wondering if I could stop execution of an operation that has been deffered.
require 'rubygems'
require 'em-websocket'
EM.run do
EM::WebSocket.start(:host => '0.0.0.0', :port => 8080) do |ws|
ws.onmessage do |msg|
op = proc do
sleep 5 # Thread safe IO here that is safely killed
true
end
callback = proc do |result|
puts "Done!"
end
EM.defer(op, callback)
end
end
end
This is an example web socket server. Sometimes when I get a message I want to do some IO, later on another message might come in that needs to read the same thing, the next thing always has precedence over the previous thing. So I want to cancel the first op and do the second.
Here is my solution. It is similar to the EM.queue solution, but just uses a hash.
require 'rubygems'
require 'em-websocket'
require 'json'
EM.run do
EM::WebSocket.start(:host => '0.0.0.0', :port => 3333) do |ws|
mutex = Mutex.new # to make thread safe. See https://github.com/eventmachine/eventmachine/blob/master/lib/eventmachine.rb#L981
queue = EM::Queue.new
ws.onmessage do |msg|
message_type = JSON.parse(msg)["type"]
op = proc do
mutex.synchronize do
if message_type == "preferred"
puts "killing non preferred\n"
queue.size.times { queue.pop {|thread| thread.kill } }
end
queue << Thread.current
end
puts "doing the long running process"
sleep 15 # Thread safe IO here that is safely killed
true
end
callback = proc do |result|
puts "Finished #{message_type} #{msg}"
end
EM.defer(op, callback)
end
end
end

Responding to HTTP Requests in Eventmachine

I have a very simple server for use in integration tests, built using eventmachine:
EM.run do
EM::start_server(server, port, HttpRecipient)
end
I can receive HTTP requests and parse them like so:
class HttpRecipient < EM::Connection
def initialize
##stored = ''
end
# Data is received in chunks, so here we wait until we've got a full web request before
# calling spool.
def receive_data(data)
##stored << data
begin
spool(##stored)
EM.stop
rescue WEBrick::HTTPStatus::BadRequest
#Not received a complete request yet
end
end
def spool(data)
#Parse the request
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
req.parse(StringIO.new(##stored))
#Send a response, e.g. HTTP OK
end
end
The question is, how do I send a response? Eventmachine provides a send_data for sending responses, but that doesn't understand http. Similarly there is the em-http-request
module for sending requests, but it's not obvious that this is capable of generating responses.
I can generate HTTP messages manually and then send them using send_data, but I wonder if there is a clean way to use an existing http library, or the functionality built in to eventmachine?
If you want something easy then use Thin or Rainbows. It uses Eventmachine inside and provides Rack interface support.
# config.ru
http_server = proc do |env|
response = "Hello World!"
[200, {"Connection" => "close", "Content-Length" => response.bytesize.to_s}, [response]]
end
run http_server
And then
>> thin start -R config.ru
UPD.
If you need server to run in parallel you could run it in a Thread
require 'thin'
class ThreadedServer
def initialize(*args)
#server = Thin::Server.new(*args)
end
def start
#thread = Thread.start do
#server.start
end
end
def stop
#server.stop
if #thread
#thread.join
#thread = nil
end
end
end
http_server = proc do |env|
response = "Hello World!"
[200, {"Connection" => "close", "Content-Length" => response.bytesize.to_s}, [response]]
end
server = ThreadedServer.new http_server
server.start
# Some job with server
server.stop
# Server is down

Resources