Search code examples
rubythin

How do I daemonize a ruby server from code?


handler = Rack::Handler::Thin
#driver = Selenium::WebDriver.for :firefox

class RackApp
  def call(env)
    req = Rack::Request.new(env)
    case req.path_info
    when /hello/
      [200, {"Content-Type" => "text/html"}, [render_erb]]
    when /goodbye/
      [500, {"Content-Type" => "text/html"}, ["Goodbye Cruel World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["I'm Lost!"]]
    end
  end

  def render_erb
    raw = File.read('template/tipsters.html.erb')
    ERB.new(raw).result(binding)
  end
end
handler.run(RackApp.new, {server: 'sfsefsfsef', daemonize: true, pid: "/piddycent.pid"})

puts "hellllllllllllllllllllllllllllllllllllllllllo"

I'm trying to create a simple app for my pc that outputs graphs in a browser. I'm trying to use selenium to navigate to localhost after the thin server has been demonized. Unfortunately it will not deamonize, and "hellllllllllllllllllllo" is only printed to the terminal when I cancel the process from the terminal, that is to say the thin server is not daemonizing.

The problem is that the options hash appears to be getting ignored.

So yeah any input on why handler.run is ignoring my options hash is appreciated. No errors at all, it justs ignores my options hash.

According to documentation https://www.rubydoc.info/gems/rack/Rack/Server daemonize is an option?


Solution

  • I think that the issue here has to do with a misunderstanding. When we talk about daemonizing a process we are essentially saying "This process will now run itself in the background independent of any other process." This can be accomplished, for example, by forking a process to create a new process.

    It sounds like that's what you're intending to do here: start your Ruby app which starts a new Rack app that forks into a separate process, then return control to your Ruby app so that you now have your Rack app and your Ruby app running simultaneously so you can use Selenium to browse your Rack app.

    That doesn't happen automatically. The daemonize options in Thin can be accessed if you're starting your app using the command line. (because you're starting a new process) But there is no automatic process forking, likely because that would lead to a host of confusing problems. (i.e., if you run your app once and fork a new Thin server on port 3000, and then your Ruby app terminates and you try to run it again, you will already have a forked Thin server running on port 3000 and attempting to start a new one will cause an error because port 3000 is in use)

    Essentially, if you want to run a daemonized Thin server then launch it using the thin CLI. If you want to run an ad-hoc Thin server within another app then launch it the way you have above.

    And that seems to lead to the real answer: your explanation and code sample indicate that you don't actually want a daemonized Thin server running in the background. You want a Thin server that runs in a separate thread and returns control to the main thread so you can use Selenium, and when your app terminates the Thin server terminates as well.

    If I'm wrong about that, and if you actually want a daemonized Thin server, then you're going about it the wrong way. You should tread the Thin server as one app and the Selenium app as another app. (launch and manage them separately, not as a single Ruby app)

    But if I'm right about that, then it should be as simple as:

    require 'rack'
    require 'thin'
    require 'httparty' # used for demonstration purposes only
    
    class RackApp
      def call(env)
        case Rack::Request.new(env).path_info
        when //
          [200, {"Content-Type" => "text/html"}, 'Hello World']
        end
      end
    end
    
    threads = []
    
    # First start the Thin server
    threads << Thread.new { Rack::Handler::Thin.run(RackApp.new) }
    
    # Then sleep long enough for it to start before making an HTTP request
    threads << Thread.new { sleep 2; puts HTTParty.get('http://localhost:8080/').response.body }
    
    threads.each(&:join)
    
    puts 'Back in the main thread'
    

    The output in the console is:

    Thin web server (v1.7.2 codename Bachmanity)
    Maximum connections set to 1024
    Listening on localhost:8080, CTRL+C to stop
    Hello World
    

    You would need to replace the HTTParty.get call with your calls to Selenium.

    This may not be the exact solution you want because you won't see Back in the main thread until you hit ^C, even if the Selenium thread has completed all the work it needs to do, because the Thin thread is still running:

    Thin web server (v1.7.2 codename Bachmanity)
    Maximum connections set to 1024
    Listening on localhost:8080, CTRL+C to stop
    Hello World
    ^CStopping ...
    Back in the main thread
    

    If you would prefer that the Ruby app terminates after the Selenium work is complete, you can modify it to something like this:

    Thread.new { Rack::Handler::Thin.run(RackApp.new) }
    
    selenium_thread = Thread.new { sleep 2; puts HTTParty.get('http://localhost:8080/').response.body }
    selenium_thread.join
    
    puts 'Back in the main thread'
    

    And then you'll see something like this where the Ruby app terminates immediately after printing Back in the main thread:

    Thin web server (v1.7.2 codename Bachmanity)
    Maximum connections set to 1024
    Listening on localhost:8080, CTRL+C to stop
    Hello World
    Back in the main thread