Sinatra/Thin runs and cannot be stopped with Ctrl-C

2.1k views Asked by At

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
2

There are 2 answers

4
Akshay M On BEST ANSWER

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!

0
phil pirozhkov On

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