thin 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of thin might be problematic. Click here for more details.

Files changed (49) hide show
  1. data/CHANGELOG +34 -0
  2. data/bin/thin +2 -164
  3. data/example/config.ru +4 -1
  4. data/example/ramaze.ru +12 -0
  5. data/example/thin.god +70 -66
  6. data/example/vlad.rake +61 -0
  7. data/lib/rack/adapter/rails.rb +0 -3
  8. data/lib/rack/handler/thin.rb +6 -1
  9. data/lib/thin.rb +13 -4
  10. data/lib/thin/command.rb +9 -5
  11. data/lib/thin/connection.rb +5 -14
  12. data/lib/thin/connectors/connector.rb +61 -0
  13. data/lib/thin/connectors/tcp_server.rb +29 -0
  14. data/lib/thin/connectors/unix_server.rb +48 -0
  15. data/lib/thin/controllers/cluster.rb +115 -0
  16. data/lib/thin/controllers/controller.rb +85 -0
  17. data/lib/thin/controllers/service.rb +73 -0
  18. data/lib/thin/controllers/service.sh.erb +39 -0
  19. data/lib/thin/daemonizing.rb +9 -4
  20. data/lib/thin/headers.rb +2 -2
  21. data/lib/thin/runner.rb +166 -0
  22. data/lib/thin/server.rb +109 -89
  23. data/lib/thin/stats.html.erb +216 -0
  24. data/lib/thin/stats.rb +1 -249
  25. data/lib/thin/version.rb +10 -3
  26. data/spec/command_spec.rb +0 -1
  27. data/spec/configs/cluster.yml +9 -0
  28. data/spec/configs/single.yml +9 -0
  29. data/spec/{cluster_spec.rb → controllers/cluster_spec.rb} +22 -10
  30. data/spec/controllers/controller_spec.rb +85 -0
  31. data/spec/controllers/service_spec.rb +51 -0
  32. data/spec/daemonizing_spec.rb +73 -9
  33. data/spec/request/mongrel_spec.rb +39 -0
  34. data/spec/{request_spec.rb → request/parser_spec.rb} +11 -143
  35. data/spec/request/perf_spec.rb +50 -0
  36. data/spec/request/processing_spec.rb +46 -0
  37. data/spec/runner_spec.rb +135 -0
  38. data/spec/server/builder_spec.rb +38 -0
  39. data/spec/server/stopping_spec.rb +45 -0
  40. data/spec/server/tcp_spec.rb +54 -0
  41. data/spec/server/unix_socket_spec.rb +30 -0
  42. data/spec/spec_helper.rb +49 -16
  43. data/tasks/announce.rake +7 -3
  44. data/tasks/email.erb +8 -18
  45. data/tasks/gem.rake +8 -1
  46. data/tasks/stats.rake +21 -8
  47. metadata +33 -6
  48. data/lib/thin/cluster.rb +0 -123
  49. data/spec/server_spec.rb +0 -200
data/lib/thin.rb CHANGED
@@ -11,10 +11,6 @@ require 'thin/version'
11
11
  require 'thin/statuses'
12
12
 
13
13
  module Thin
14
- NAME = 'thin'.freeze
15
- SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze
16
-
17
- autoload :Cluster, 'thin/cluster'
18
14
  autoload :Command, 'thin/command'
19
15
  autoload :Connection, 'thin/connection'
20
16
  autoload :Daemonizable, 'thin/daemonizing'
@@ -22,8 +18,21 @@ module Thin
22
18
  autoload :Headers, 'thin/headers'
23
19
  autoload :Request, 'thin/request'
24
20
  autoload :Response, 'thin/response'
21
+ autoload :Runner, 'thin/runner'
25
22
  autoload :Server, 'thin/server'
26
23
  autoload :Stats, 'thin/stats'
24
+
25
+ module Connectors
26
+ autoload :Connector, 'thin/connectors/connector'
27
+ autoload :TcpServer, 'thin/connectors/tcp_server'
28
+ autoload :UnixServer, 'thin/connectors/unix_server'
29
+ end
30
+
31
+ module Controllers
32
+ autoload :Cluster, 'thin/controllers/cluster'
33
+ autoload :Controller, 'thin/controllers/controller'
34
+ autoload :Service, 'thin/controllers/service'
35
+ end
27
36
  end
28
37
 
29
38
  require 'rack'
data/lib/thin/command.rb CHANGED
@@ -3,14 +3,15 @@ module Thin
3
3
  class Command
4
4
  include Logging
5
5
 
6
- # Path to the +thin+ script used to control the servers.
7
- # Leave this to default to use the one in the path.
8
- attr_accessor :script
6
+ class << self
7
+ # Path to the +thin+ script used to control the servers.
8
+ # Leave this to default to use the one in the path.
9
+ attr_accessor :script
10
+ end
9
11
 
10
12
  def initialize(name, options={})
11
13
  @name = name
12
14
  @options = options
13
- @script = $PROGRAM_NAME
14
15
  end
15
16
 
16
17
  def self.run(*args)
@@ -34,7 +35,10 @@ module Thin
34
35
  else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
35
36
  end
36
37
  end
37
- "#{@script} #{@name} #{shellified_options.compact.join(' ')}"
38
+
39
+ raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
40
+
41
+ "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
38
42
  end
39
43
  end
40
44
  end
@@ -1,17 +1,15 @@
1
1
  require 'socket'
2
2
 
3
3
  module Thin
4
+ # Connection between the server and client.
4
5
  class Connection < EventMachine::Connection
5
6
  include Logging
6
7
 
7
8
  # Rack application served by this connection.
8
9
  attr_accessor :app
9
10
 
10
- # +true+ if the connection is on a UNIX domain socket.
11
- attr_accessor :unix_socket
12
-
13
- # Server owning the connection
14
- attr_accessor :server
11
+ # Connector to the server
12
+ attr_accessor :connector
15
13
 
16
14
  def post_init
17
15
  @request = Request.new
@@ -52,19 +50,12 @@ module Thin
52
50
  end
53
51
 
54
52
  def unbind
55
- @server.connection_finished(self)
53
+ @connector.connection_finished(self)
56
54
  end
57
55
 
58
56
  protected
59
57
  def remote_address
60
- if remote_addr = @request.env[Request::FORWARDED_FOR]
61
- remote_addr
62
- elsif @unix_socket
63
- # FIXME not sure about this, does it even make sense on a UNIX socket?
64
- Socket.unpack_sockaddr_un(get_peername)
65
- else
66
- Socket.unpack_sockaddr_in(get_peername)[1]
67
- end
58
+ @request.env[Request::FORWARDED_FOR] || Socket.unpack_sockaddr_in(get_peername)[1]
68
59
  end
69
60
  end
70
61
  end
@@ -0,0 +1,61 @@
1
+ module Thin
2
+ module Connectors
3
+ # A Connector connect the server to the client. It handles:
4
+ # * connection/disconnection to the server
5
+ # * initialization of the connections
6
+ # * manitoring of the active connections.
7
+ class Connector
8
+ include Logging
9
+
10
+ # Server serving the connections throught the connector
11
+ attr_reader :server
12
+
13
+ # Maximum time for incoming data to arrive
14
+ attr_accessor :timeout
15
+
16
+ def initialize
17
+ @connections = []
18
+ @timeout = 60 # sec
19
+ end
20
+
21
+ # Free up resources used by the connector.
22
+ def close
23
+ end
24
+
25
+ def server=(server)
26
+ @server = server
27
+ @silent = @server.silent
28
+ end
29
+
30
+ # Initialize a new connection to a client.
31
+ def initialize_connection(connection)
32
+ connection.connector = self
33
+ connection.app = @server.app
34
+ connection.comm_inactivity_timeout = @timeout
35
+ connection.silent = @silent
36
+
37
+ @connections << connection
38
+ end
39
+
40
+ # Close all active connections.
41
+ def close_connections
42
+ @connections.each { |connection| connection.close_connection }
43
+ end
44
+
45
+ # Called by a connection when it's unbinded.
46
+ def connection_finished(connection)
47
+ @connections.delete(connection)
48
+ end
49
+
50
+ # Returns +true+ if no active connection.
51
+ def empty?
52
+ @connections.empty?
53
+ end
54
+
55
+ # Number of active connections.
56
+ def size
57
+ @connections.size
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ module Thin
2
+ module Connectors
3
+ # Connectior to act as a TCP socket server.
4
+ class TcpServer < Connector
5
+ # Address and port on which the server is listening for connections.
6
+ attr_accessor :host, :port
7
+
8
+ def initialize(host, port)
9
+ @host = host
10
+ @port = port
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
17
+ end
18
+
19
+ # Stops the server
20
+ def disconnect
21
+ EventMachine.stop_server(@signature)
22
+ end
23
+
24
+ def to_s
25
+ "#{@host}:#{@port}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ module Thin
2
+ module Connectors
3
+ # Connectior to act as a UNIX domain socket server.
4
+ class UnixServer < Connector
5
+ # UNIX domain socket on which the server is listening for connections.
6
+ attr_accessor :socket
7
+
8
+ def initialize(socket)
9
+ raise PlatformNotSupported, 'UNIX sockets not available on Windows' if Thin.win?
10
+ @socket = socket
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ at_exit { remove_socket_file } # In case it crashes
17
+ @signature = EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
18
+ end
19
+
20
+ # Stops the server
21
+ def disconnect
22
+ EventMachine.stop_server(@signature)
23
+ end
24
+
25
+ # Free up resources used by the connector.
26
+ def close
27
+ remove_socket_file
28
+ end
29
+
30
+ def to_s
31
+ @socket
32
+ end
33
+
34
+ protected
35
+ def remove_socket_file
36
+ File.delete(@socket) if @socket && File.exist?(@socket)
37
+ end
38
+ end
39
+ end
40
+
41
+ class UnixConnection < Connection
42
+ protected
43
+ def remote_address
44
+ # FIXME not sure about this, does it even make sense on a UNIX socket?
45
+ Socket.unpack_sockaddr_un(get_peername)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,115 @@
1
+ module Thin
2
+ module Controllers
3
+ # Control a set of servers.
4
+ # * Generate start and stop commands and run them.
5
+ # * Inject the port or socket number in the pid and log filenames.
6
+ # Servers are started throught the +thin+ command-line script.
7
+ class Cluster < Controller
8
+ # Number of servers in the cluster.
9
+ attr_accessor :size
10
+
11
+ # Create a new cluster of servers launched using +options+.
12
+ def initialize(options)
13
+ @options = options.merge(:daemonize => true)
14
+ @size = @options.delete(:servers)
15
+ @only = @options.delete(:only)
16
+
17
+ if socket
18
+ @options.delete(:address)
19
+ @options.delete(:port)
20
+ end
21
+ end
22
+
23
+ def first_port; @options[:port] end
24
+ def address; @options[:address] end
25
+ def socket; @options[:socket] end
26
+ def pid_file; @options[:pid] end
27
+ def log_file; @options[:log] end
28
+
29
+ # Start the servers
30
+ def start
31
+ with_each_server { |port| start_server port }
32
+ end
33
+
34
+ # Start a single server
35
+ def start_server(number)
36
+ log "Starting server on #{server_id(number)} ... "
37
+
38
+ run :start, @options, number
39
+ end
40
+
41
+ # Stop the servers
42
+ def stop
43
+ with_each_server { |n| stop_server n }
44
+ end
45
+
46
+ # Stop a single server
47
+ def stop_server(number)
48
+ log "Stopping server on #{server_id(number)} ... "
49
+
50
+ run :stop, @options, number
51
+ end
52
+
53
+ # Stop and start the servers.
54
+ def restart
55
+ stop
56
+ sleep 0.1 # Let's breath a bit shall we ?
57
+ start
58
+ end
59
+
60
+ def server_id(number)
61
+ if socket
62
+ socket_for(number)
63
+ else
64
+ [address, number].join(':')
65
+ end
66
+ end
67
+
68
+ def log_file_for(number)
69
+ include_server_number log_file, number
70
+ end
71
+
72
+ def pid_file_for(number)
73
+ include_server_number pid_file, number
74
+ end
75
+
76
+ def socket_for(number)
77
+ include_server_number socket, number
78
+ end
79
+
80
+ def pid_for(number)
81
+ File.read(pid_file_for(number)).chomp.to_i
82
+ end
83
+
84
+ private
85
+ # Send the command to the +thin+ script
86
+ def run(cmd, options, number)
87
+ cmd_options = options.dup
88
+ cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
89
+ if socket
90
+ cmd_options.merge!(:socket => socket_for(number))
91
+ else
92
+ cmd_options.merge!(:port => number)
93
+ end
94
+ Command.run(cmd, cmd_options)
95
+ end
96
+
97
+ def with_each_server
98
+ if @only
99
+ yield @only
100
+ else
101
+ @size.times do |n|
102
+ yield socket ? n : (first_port + n)
103
+ end
104
+ end
105
+ end
106
+
107
+ # Add the server port or number in the filename
108
+ # so each instance get its own file
109
+ def include_server_number(path, number)
110
+ ext = File.extname(path)
111
+ path.gsub(/#{ext}$/, ".#{number}#{ext}")
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,85 @@
1
+ require 'yaml'
2
+
3
+ module Thin
4
+ module Controllers
5
+ # Raised when a mandatory option is missing to run a command.
6
+ class OptionRequired < RuntimeError
7
+ def initialize(option)
8
+ super("#{option} option required")
9
+ end
10
+ end
11
+
12
+ # Controls a Thin server.
13
+ # Allow to start, stop, restart and configure a single thin server.
14
+ class Controller
15
+ include Logging
16
+
17
+ # Command line options passed to the thin script
18
+ attr_accessor :options
19
+
20
+ def initialize(options)
21
+ @options = options
22
+ end
23
+
24
+ def start
25
+ if @options[:socket]
26
+ server = Server.new(@options[:socket])
27
+ else
28
+ server = Server.new(@options[:address], @options[:port])
29
+ end
30
+
31
+ server.pid_file = @options[:pid]
32
+ server.log_file = @options[:log]
33
+ server.timeout = @options[:timeout]
34
+
35
+ if @options[:daemonize]
36
+ server.daemonize
37
+ server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
38
+ end
39
+
40
+ # If a Rack config file is specified we eval it inside a Rack::Builder block to create
41
+ # a Rack adapter from it. DHH was hacker of the year a couple years ago so we default
42
+ # to Rails adapter.
43
+ if @options[:rackup]
44
+ rackup_code = File.read(@options[:rackup])
45
+ server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", nil, @options[:rackup])
46
+ else
47
+ server.app = Rack::Adapter::Rails.new(@options.merge(:root => @options[:chdir]))
48
+ end
49
+
50
+ # If a prefix is required, wrap in Rack URL mapper
51
+ server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
52
+
53
+ # If a stats are required, wrap in Stats adapter
54
+ server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
55
+
56
+ # Register restart procedure
57
+ server.on_restart { Command.run(:start, @options) }
58
+
59
+ server.start
60
+ end
61
+
62
+ def stop
63
+ raise OptionRequired, :pid unless @options[:pid]
64
+
65
+ Server.kill(@options[:pid], @options[:timeout] || 60)
66
+ end
67
+
68
+ def restart
69
+ raise OptionRequired, :pid unless @options[:pid]
70
+
71
+ Server.restart(@options[:pid])
72
+ end
73
+
74
+ def config
75
+ config_file = @options.delete(:config) || raise(OptionRequired, :config)
76
+
77
+ # Stringify keys
78
+ @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
79
+
80
+ File.open(config_file, 'w') { |f| f << @options.to_yaml }
81
+ log ">> Wrote configuration to #{config_file}"
82
+ end
83
+ end
84
+ end
85
+ end