I have a modular Sinatra web app running using Thin, with EventMachine running additional tasks.
It works, but there's something a bit odd about the webserver: Any requests, whether successful or 404s, don't appear in the log output from Thin/Sinatra. And when I cancel the process, the server ends twice.
Here's the rough, basic structure of the app:
Procfile:
web: ruby app.rb
app.rb:
require 'thin'
require 'eventmachine'
require 'app/frontend'
EM.run do
# Start some background tasks here...
EM.add_periodic_timer(1200) do
# Do a repeating task here...
end
App::Frontend.run!
end
app/frontend.rb:
require 'sinatra/base'
module App
class Frontend < Sinatra::Base
get '/' do
# Display page
end
# etc
end
end
When I do foreman start then I get:
16:50:00 web.1 | started with pid 76423
16:50:01 web.1 | [messages about EventMachine background tasks starting]
16:50:01 web.1 | == Sinatra/1.4.3 has taken the stage on 5000 for development with backup from Thin
16:50:01 web.1 | >> Thin web server (v1.5.1 codename Straight Razor)
16:50:01 web.1 | >> Maximum connections set to 1024
16:50:01 web.1 | >> Listening on 0.0.0.0:5000, CTRL+C to stop
Nothing more is output when I request existing web pages (which load OK) or not-existing web pages. When I cancel the process I get:
^CSIGINT received
16:50:08 system | sending SIGTERM to all processes
SIGTERM received
16:50:08 web.1 | >> Stopping ...
16:50:08 web.1 | == Sinatra has ended his set (crowd applauds)
16:50:08 web.1 | >> Stopping ...
16:50:08 web.1 | == Sinatra has ended his set (crowd applauds)
16:50:08 web.1 | exited with code 0
That Sinatra finishes twice makes me think I'm somehow running it twice, and the one that's serving the web pages isn't being logged... but I don't know how I'm managing this!
The docs on Modular vs. Classic style mention that there are some changes to the default settings, and one of these is logging, which is turned off by default.
Adding settings.logging = true to the top of class Frontend < Sinatra::Base gives me a log in my terminal window for the localhost:5000 requests.
I don't think the second issue is that it's creating two processes, but rather that it's killing and re-starting the process right before closing the server. This can be solved by following the Sinatra recipe for using EventMachine with Sinatra, which is a little more complicated than what you've done. Here's their code, modified to fit your app:
The new app.rb:
EM.run do
server = 'thin'
host = '0.0.0.0'
port = ENV['PORT'] || '8181'
web_app = App::Frontend.new
# Start some background tasks here...
EM.add_periodic_timer(1200) do
# Do a repeating task here...
end
dispatch = Rack::Builder.app do
map '/' do
run web_app
end
end
Rack::Server.start({
app: dispatch,
server: server,
Host: host,
Port: port
})
end
(original source, from Sinatra Recipes)
The use of ENV['PORT'] in the app.rb lets you use multiple instances in foreman (e.g., foreman start -p 4000 -c web=2, which will run services on ports 4000 and 4001). And both of them appear in the log!
Hope that helps.
Related
I'm writing automation tests which are dependent on two different web services and I've decided to mock these out using two very basic Sinatra applications. However I'm having trouble starting more than one Sinatra instance in my Cucumber env file. The second Sinatra instance stops as soon as its started.
Here's a snippet of the output I get when kicking off a test
== Sinatra/1.4.5 has taken the stage on 9000 for development with backup from Thin
Thin web server (v1.6.2 codename Doc Brown)
Maximum connections set to 1024
Listening on localhost:9000, CTRL+C to stop
== Sinatra/1.4.5 has taken the stage on 8082 for development with backup from Thin
Thin web server (v1.6.2 codename Doc Brown)
Maximum connections set to 1024
Listening on localhost:8082, CTRL+C to stop
Stopping ...
== Sinatra has ended his set (crowd applauds)
As you can see the first service starts up and runs fine but the second service starts up OK but then immediately begins stopping
Sinatra app 1
class MockService1 < Sinatra::Base
get '/some/endpoint' do
response =
{
enabled: false
}
end
end
Sinatra app 2
class MockService2 < Sinatra::Base
get '/some/endpoint' do
response =
{
enabled: false
}
end
end
Inside my Cucumber env file
Thread.new do
MockService1.run! host: 'localhost', port: '9000'
end
Thread.new do
MockService2.run! host: 'localhost', port: '8082'
end
I was able to use the childprocess gem to start a new sinatra server under a new child process.
mock_service = ChildProcess.build('ruby', File.join(File.dirname(__FILE__), 'mock_service.rb'))
mock_service.start
I'm creating an application that has Sinatra running inside of EventMachine and when I run the barebones test app I cannot get the server to end with Ctrl-C, I have to kill it with -9 or -usr2 for example.
I cannot figure out why Sinatra reports it has stopped but continues to serve requests or why I cannot stop the server with Ctrl-C.
Thin 1.6.1 with Sinatra 1.4.4 STOPPED MESSAGE BUT CONTINUES
== Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.1 codename Death Proof)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
Stopping ...
== Sinatra has ended his set (crowd applauds)
Ping!
^CPing!
Stopping ...
Ping!
^CStopping ...
This is the barebones test app I'm using to generate the output
# Run with 'ruby test.rb'
require 'eventmachine'
require 'sinatra/base'
require 'thin'
class NeverStops < Sinatra::Base
settings.logging = true
configure do
set :threaded, true
end
get '/foobar' do
'Foobar'
end
end
EM.run do
# Does nothing
#trap(:INT) { EM::stop(); exit }
#trap(:TERM) { EM::stop(); exit }
#trap(:KILL) { EM::stop(); exit }
EventMachine.add_periodic_timer 2 do
puts 'Ping!'
end
NeverStops.run!
end
Downgrading either Thin or Sinatra has different results
Thin 1.6.1 with Sinatra 1.4.3 NO STOPPED MESSAGE BUT STILL WON'T DIE (DEATH PROOF INDEED)
== Sinatra/1.4.3 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.1 codename Death Proof)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
Ping!
^CPing!
Stopping ...
Ping!
Thin 1.5.1 with Sinatra 1.4.4 JUST STOPS
== Sinatra/1.4.4 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on localhost:4567, CTRL+C to stop
>> Stopping ...
== Sinatra has ended his set (crowd applauds)
Thin 1.5.1 with Sinatra 1.4.3 WORKS
== Sinatra/1.4.3 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on localhost:4567, CTRL+C to stop
Ping!
Ping!
Ping!
^C>> Stopping ...
== Sinatra has ended his set (crowd applauds)
I've updated my gems to the latest versions and have tried downgrading various gems such as EventMachine and Rack to see what results I get and nothing was helpfully different.
Versions
OSX 10.8.5 and Ubuntu 12.04.1
Ruby 2.0.0p247 and 1.9.3p194
eventmachine 1.0.3
rack 1.5.2
sinatra 1.4.4
thin 1.6.1
tilt 1.4.1
This issue is specific to newer versions of Thin (notice that v1.5.1 does not exhibit this behavior). This behavior was introduced in 1.6 and a similar issue is documented here.
The code in question follows the same pattern as mentioned in the upstream issue.
TL;DR version of the issue: Thin will stop the server, but will not stop the reactor loop (because it does not "own" the reactor). It is possible to allow Thin to own its reactor loop, in which case you will get back the desired behavior (as seen in 1.5.1). In order to do this you must start Thin without the enclosing EM#run { } , which will allow Thin to bring up (and subsequently tear down) the reactor loop.
In this case, one can imagine the periodic "ping" as a separate application that shares the reactor loop with Thin. Neither of them can claim ownership of the reactor loop. It would be wrong for Thin to stop all other applications and exit the reactor when it did not start the reactor. It is then the users responsibility to handle signals and terminate individual applications as necessary and finally stop the reactor loop (causing the process to quit).
Hope this explanation helps!
Thin is taking advantage of EM, you should run your app as you would do with Webrick and no EM. Example config.ru:
require 'bundler'
Bundle.require
class UseToStop < Sinatra::Base
get '/foobar' { body "Foobar" }
end
run UseToStop
Are you sure that you need this option? This complicates things, and is last thing you need.
set :threaded, true
I've a very basic test app. When I execute this command the server ignores the port I specify and runs Thin on port 4567. Why is the port I specify ignored?
$ruby xxx.rb start -p 8000
== Sinatra/1.3.3 has taken the stage on 4567 for production with backup from Thin
>> Thin web server (v1.4.1 codename Chromeo)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
xxx.rb file
require 'Thin'
rackup_file = "config.ru"
argv = ARGV
argv << ["-R", rackup_file ] unless ARGV.include?("-R")
argv << ["-e", "production"] unless ARGV.include?("-e")
puts argv.flatten
Thin::Runner.new(argv.flatten).run!
config.ru file
require 'sinatra'
require 'sinatra/base'
class SingingRain < Sinatra::Base
get '/' do
return 'hello'
end
end
SingingRain.run!
#\ -p 8000
put this at the top of the config.ru
Your problem is with the line:
SingingRain.run!
This is Sinatra’s run method, which tells Sinatra to start its own web server which runs on port 4567 by default. This is in your config.ru file, but config.ru is just Ruby, so this line is run as if it was in any other .rb file. This is why you see Sinatra start up on that port.
When you stop this server with CTRL-C, Thin will try to continue loading the config.ru file to determine what app to run. You don’t actually specify an app in your config.ru, so you’ll see something like:
^C>> Stopping ...
== Sinatra has ended his set (crowd applauds)
/Users/matt/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/builder.rb:129:in `to_app': missing run or map statement (RuntimeError)
from config.ru:1:in `<main>'
...
This error is simply telling you that you didn’t actually specify an app to run in your config file.
Instead of SingingRain.run!, use:
run SingingRain
run is a Rack method that specifies which app to run. You could also do run SingingRain.new – Sinatra takes steps to enable you to use just the class itself here, or an instance.
The output to this should now just be:
>> Thin web server (v1.4.1 codename Chromeo)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:8000, CTRL+C to stop
You don’t get the == Sinatra/1.3.2 has taken the stage on 4567 for production with backup from Thin message because Sinatra isn’t running its built in server, it’s just your Thin server as you configured it.
in your config.ru add
set :port=> 8000
Also i would highly suggest using Sinatra with something like passenger+nginx which makes deploying to production a breeze. But You need not worry about this if you are going to deploy to heroku.
I have a really basic Sinatra site working locally. I am using the "rackup" thing, where you define a config.ru like this:
require './web'
use Rack::ShowExceptions
run App.new
And then in the terminal you can run 'rackup' and a web server is fired up and all is well.
However, when I deploy this to heroku I don't get any error messages, but, when I visit the site, it says the standard "Sinatra does not know this ditty" error.
Here is a snippet of my web.rb in case it helps:
require 'sinatra'
require 'maruku'
require 'mustache/sinatra'
require 'nokogiri'
class App < Sinatra::Base
register Mustache::Sinatra
require './views/layout'
set :mustache, {
:views => './views/',
:templates => './templates/'
}
get '/' do
"FUUUUUUUUUUUUU"
end
Edit
Looking at the heroku logs, it appears like sinatra starts and then stops; it doesn't keep running. Then when someone makes a request obviously the server returns a 404
2012-01-20T12:39:23+00:00 app[web.1]: == Sinatra/1.1.0 has taken the stage on 16662 for development with backup from Thin
2012-01-20T12:39:23+00:00 app[web.1]: >> Thin web server (v1.2.7 codename No Hup)
2012-01-20T12:39:23+00:00 app[web.1]: >> Maximum connections set to 1024
2012-01-20T12:39:23+00:00 app[web.1]: >> Listening on 0.0.0.0:16662, CTRL+C to stop
2012-01-20T12:39:23+00:00 app[web.1]: == Sinatra has ended his set (crowd applauds)
2012-01-20T12:39:23+00:00 app[web.1]:
2012-01-20T12:39:23+00:00 app[web.1]: >> Stopping ...
2012-01-20T12:39:23+00:00 heroku[web.1]: Process exited
2012-01-20T12:39:24+00:00 heroku[router]: GET young-river-2245.herokuapp.com/ dyno=web.1 queue=0 wait=0ms service=48ms status=404 bytes=409
Whenever you inherit from Sinatra::Base you must require 'sinatra/base' rather than require 'sinatra' at the top of your web.rb file.
I just ran a simple test using your snippets and was able to replicate and then fix the error by doing this.
I'm new to ruby, learning Sinatra. While creating a Sinatra site by requiring 'sinatra' and setting up the routes directly under is pretty easy and rather well documented, creating an application by requiring 'sinatra/base' and writing a class that inherits from 'Sinatra::Base', while still relatively easy, is very poorly documented (maybe because it's a pretty recent feature of Sinatra).
And that's exactly what I am doing. I am not having too much trouble on the Sinatra part, however I am having a bit of trouble on the rackup/thin/server part. Apparently there are two ways to deploy the application: using Sinatra itself (using the run! method) and using a rackup file (typically config.ru).
Using Sinatra's run! method is extremely intuitive and works like a charm, but apparently it doesn't work if I want to deploy my app on heroku. As a matter of fact, almost all the Sinatra apps that I have encountered on GitHub use a config.ru file.
Using a rackup file might be equally intuitive, but I can't manage to understand how to pass options from the Sinatra app to the server (ir: the port). I tried to merge options to rackup's default options array:
MyApp::App.default_options.merge!(
:run => false,
:env => :production,
:port => 4567
)
run MyApp::App
by adding options directly to the app:
MyApp::App.set :port, 4567
MyApp::App.set :run, false
MyApp::App.set :env, :production
run MyApp::App
by setting options from within the application class:
module MyApp
class App < Sinatra::Base
set :port, 4567
set :run, false
set :env, :production
# ...
# config.ru
require 'app'
run MyApp::App
All the methods above failed, either by showing error messages or by just not taking any of the options into consideration. So is there any way to pass options to rackup/thin/the sever via a Sinatra app when using a rackup file? Or the options in questions should be passed directly to rackup/thin/the sever via command-line options?
As a reference to the problem, here is the little Sinatra application I am building: https://github.com/AzizLight/Wiki/
You're actully going to pass options to thin on the command line directly or via a configuration file. See all options:
$ thin -h
For production, use a configuration file:
$ thin -C thin-production.yml -R config.ru start
Here is an example thin-production.yml file:
---
address: localhost
port: 3020
servers: 4
max_conns: 1024
max_persistent_conns: 512
timeout: 30
environment: production
pid: tmp/pids/thin-production.pid
log: log/thin-production.log
daemonize: true
I know I'm resurrecting an ancient question here, but I came across another useful solution not yet mentioned. As stated in this rack wiki tutorial:
the first line starting with #\ is treated as if it was options, allowing rackup arguments to be specified in the config file.
So if you wanted to set your host to 0.0.0.0 and port to 5656, you would add the following line to the beginning of your config.ru file:
#\ -o 0.0.0.0 -p 5656