Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support running in a single process on platforms where
fork
is not …
…available. Can be forced by setting the number of workers to 0.
- Loading branch information
1 parent
ee9d987
commit 9c29f33
Showing
10 changed files
with
260 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
require "preforker" | ||
|
||
module Thin | ||
# Raised when the pid file already exist starting as a daemon. | ||
class PidFileExist < RuntimeError; end | ||
|
||
module Backends | ||
class Prefork | ||
def initialize(server) | ||
@server = server | ||
end | ||
|
||
def start(daemonize) | ||
if File.file?(@server.pid_path) | ||
raise PidFileExist, "#{@server.pid_path} already exists. Thin is already running or the file is stale. " + | ||
"Stop the process or delete #{@server.pid_path}." | ||
end | ||
|
||
@prefork = Preforker.new( | ||
:app_name => @server.to_s, | ||
:workers => @server.workers, | ||
:timeout => @server.timeout, | ||
:pid_path => @server.pid_path, | ||
:stderr_path => @server.log_path, | ||
:stdout_path => @server.log_path, | ||
:logger => Logger.new(@server.log_path || $stdout) | ||
) do |master| | ||
|
||
EM.run do | ||
EM.add_periodic_timer(4) do | ||
EM.stop_event_loop unless master.wants_me_alive? | ||
end | ||
|
||
yield | ||
end | ||
end | ||
|
||
if daemonize | ||
@prefork.start | ||
else | ||
@prefork.run | ||
end | ||
end | ||
|
||
def stop | ||
@prefork.quit if @prefork | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module Thin | ||
module Backends | ||
class SingleProcess | ||
def initialize(server) | ||
@server = server | ||
end | ||
|
||
def start(daemonize) | ||
raise NotImplementedError, "Daemonization not supported in single process mode" if daemonize | ||
|
||
$0 = @server.to_s | ||
|
||
# Install signals | ||
trap("INT", "EXIT") | ||
|
||
EM.run do | ||
yield | ||
end | ||
end | ||
|
||
def stop | ||
EM.stop_event_loop | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,122 @@ | ||
require "preforker" | ||
require "eventmachine" | ||
require "socket" | ||
|
||
require "thin/system" | ||
require "thin/connection" | ||
require "thin/backends/prefork" | ||
require "thin/backends/single_process" | ||
|
||
module Thin | ||
class Server | ||
attr_accessor :app, :address, :port, :backlog, :workers, :timeout, :pid_path, :log_path, :use_epoll, :maximum_connections | ||
# Application (Rack adapter) called with the request that produces the response. | ||
attr_accessor :app | ||
|
||
# A tag that will show in the process listing | ||
attr_accessor :tag | ||
|
||
# Address on which the server is listening for connections. | ||
attr_accessor :address | ||
alias :host :address | ||
alias :host= :address= | ||
|
||
# Port on which the server is listening for connections. | ||
attr_accessor :port | ||
|
||
attr_accessor :backlog | ||
|
||
attr_accessor :workers | ||
|
||
# Workers are killer if they don't checked in under `timeout` seconds. | ||
attr_accessor :timeout | ||
|
||
attr_accessor :pid_path | ||
|
||
attr_accessor :log_path | ||
|
||
attr_accessor :use_epoll | ||
|
||
# Maximum number of file or socket descriptors that the server may open. | ||
attr_accessor :maximum_connections | ||
|
||
# Backend handling the connections to the clients. | ||
attr_writer :backend | ||
|
||
def initialize(app, address="0.0.0.0", port=3000) | ||
@app = app | ||
@address = address | ||
@port = port | ||
@backlog = 1024 | ||
@workers = nil | ||
@timeout = 30 | ||
@pid_path = "./thin.pid" | ||
@log_path = nil | ||
@use_epoll = true | ||
@maximum_connections = 1024 | ||
|
||
if System.supports_fork? | ||
# One worker per processor | ||
@workers = System.processor_count | ||
else | ||
@workers = 0 | ||
end | ||
end | ||
|
||
def backend | ||
@backend ||= begin | ||
if prefork? | ||
Backends::Prefork.new(self) | ||
else | ||
Backends::SingleProcess.new(self) | ||
end | ||
end | ||
end | ||
|
||
def start(daemonize=false) | ||
# One worker per processor | ||
@workers = System.processor_count unless @workers | ||
|
||
# Configure EventMachine | ||
EM.epoll if @use_epoll | ||
@maximum_connections = EM.set_descriptor_table_size(@maximum_connections) | ||
puts "Maximum connections set to #{@maximum_connections} per worker" | ||
|
||
# Starts and configure the server socket. | ||
socket = TCPServer.new(@address, @port) | ||
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) | ||
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) | ||
socket.listen(@backlog) | ||
|
||
trap("EXIT") { socket.close } | ||
@socket = TCPServer.new(@address, @port) | ||
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) | ||
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) | ||
@socket.listen(@backlog) | ||
|
||
# Prefork! | ||
puts "Starting #{@workers} worker(s) ..." | ||
@prefork = Preforker.new( | ||
:workers => @workers, | ||
:app_name => "Thin", | ||
:timeout => @timeout, | ||
:pid_path => pid_path, | ||
:stderr_path => @log_path, | ||
:stdout_path => @log_path, | ||
:logger => Logger.new(@log_path || $stdout) | ||
) do |master| | ||
|
||
EM.run do | ||
EM.add_periodic_timer(4) do | ||
EM.stop_event_loop unless master.wants_me_alive? | ||
end | ||
|
||
EM.attach_server(socket, Connection) { |c| c.server = self } | ||
end | ||
end | ||
trap("EXIT") { stop } | ||
|
||
puts "Using #{@workers} worker(s) ..." if @workers > 0 | ||
puts "Listening on #{@address}:#{@port}, CTRL+C to stop" | ||
if daemonize | ||
@prefork.start | ||
else | ||
@prefork.run | ||
|
||
backend.start(daemonize) do | ||
EM.attach_server(@socket, Connection) { |c| c.server = self } | ||
@started = true | ||
end | ||
rescue | ||
@socket.close if @socket | ||
raise | ||
end | ||
|
||
def started? | ||
@started | ||
end | ||
|
||
def stop | ||
@prefork.quit if @prefork | ||
if started? | ||
puts "Stopping ..." | ||
backend.stop | ||
@socket.close | ||
@socket = nil | ||
@started = false | ||
end | ||
end | ||
alias :shutdown :stop | ||
|
||
def prefork? | ||
@workers > 0 | ||
end | ||
|
||
def to_s | ||
"Thin" + (@tag ? " [#{@tag}]" : "") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
require 'test_helper' | ||
|
||
class SingleProcessTest < IntegrationTestCase | ||
def test_stop_with_int_signal | ||
@pid = thin :workers => 0 | ||
|
||
Process.kill "INT", @pid | ||
|
||
Process.wait @pid | ||
@pid = nil | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
require 'test_helper' | ||
|
||
class ServerTest < Test::Unit::TestCase | ||
def setup | ||
app = proc { |env| [200, {}, ["ok"]] } | ||
@server = Thin::Server.new(app) | ||
end | ||
|
||
def test_pick_prefork_backend_if_any_workers | ||
@server.workers = 1 | ||
assert_kind_of Thin::Backends::Prefork, @server.backend | ||
end | ||
|
||
def test_pick_single_process_backend_if_no_workers | ||
@server.workers = 0 | ||
assert_kind_of Thin::Backends::SingleProcess, @server.backend | ||
end | ||
|
||
def test_cant_daemonize_single_process | ||
@server.workers = 0 | ||
assert_raise(NotImplementedError) do | ||
silence_streams { @server.start(true) } | ||
end | ||
end | ||
end |
Oops, something went wrong.