thin 1.8.0 → 2.0.0.pre

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 (108) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +29 -107
  3. data/Gemfile +8 -0
  4. data/README.md +44 -78
  5. data/Rakefile +28 -18
  6. data/bin/thin +4 -4
  7. data/examples/async.ru +21 -0
  8. data/examples/thin.conf.rb +39 -0
  9. data/lib/thin.rb +2 -44
  10. data/lib/thin/async.rb +108 -0
  11. data/lib/thin/backends/prefork.rb +44 -0
  12. data/lib/thin/backends/single_process.rb +28 -0
  13. data/lib/thin/chunked_body.rb +28 -0
  14. data/lib/thin/configurator.rb +118 -0
  15. data/lib/thin/connection.rb +246 -172
  16. data/lib/thin/listener.rb +114 -0
  17. data/lib/thin/request.rb +94 -76
  18. data/lib/thin/response.rb +112 -45
  19. data/lib/thin/runner.rb +134 -197
  20. data/lib/thin/server.rb +203 -252
  21. data/lib/thin/system.rb +49 -0
  22. data/lib/thin/version.rb +11 -26
  23. data/man/index.txt +3 -0
  24. data/man/thin-conf.5.ronn +121 -0
  25. data/man/thin.1.ronn +105 -0
  26. data/site/.gitignore +2 -0
  27. data/site/README.md +21 -0
  28. data/site/Rakefile +20 -0
  29. data/site/config.ru +4 -0
  30. data/site/public/images/grid.png +0 -0
  31. data/site/public/javascripts/dd_belatedpng.js +13 -0
  32. data/site/public/javascripts/modernizr-1.6.min.js +30 -0
  33. data/site/public/man/thin-conf.5.html +220 -0
  34. data/site/public/man/thin.1.html +177 -0
  35. data/site/site/assets/javascripts/main.coffee +2 -0
  36. data/site/site/assets/stylesheets/_config.scss +55 -0
  37. data/site/site/assets/stylesheets/main.scss +24 -0
  38. data/site/site/helpers.rb +17 -0
  39. data/site/site/layouts/base.erb +55 -0
  40. data/site/site/layouts/default.erb +17 -0
  41. data/site/site/pages/about.md +5 -0
  42. data/site/site/pages/index.erb +10 -0
  43. data/site/site/partials/.gitkeep +0 -0
  44. data/test/fixtures/big.txt +1 -0
  45. data/test/fixtures/small.txt +1 -0
  46. data/test/fixtures/thin.conf.rb +15 -0
  47. data/test/integration/async_test.rb +35 -0
  48. data/test/integration/big_request_test.rb +30 -0
  49. data/test/integration/config.ru +57 -0
  50. data/test/integration/daemonize_test.rb +26 -0
  51. data/test/integration/env_test.rb +44 -0
  52. data/test/integration/error_test.rb +37 -0
  53. data/test/integration/file_sending_test.rb +24 -0
  54. data/test/integration/keep_alive_test.rb +35 -0
  55. data/test/integration/robustness_test.rb +37 -0
  56. data/test/integration/single_process_test.rb +15 -0
  57. data/test/integration/socket_family_test.rb +38 -0
  58. data/test/integration/worker_test.rb +22 -0
  59. data/test/test_helper.rb +195 -0
  60. data/test/unit/configurator_test.rb +43 -0
  61. data/test/unit/connection_test.rb +94 -0
  62. data/test/unit/listener_test.rb +74 -0
  63. data/test/unit/request_test.rb +74 -0
  64. data/test/unit/response_test.rb +90 -0
  65. data/test/unit/server_test.rb +29 -0
  66. data/test/unit/system_test.rb +17 -0
  67. data/thin.gemspec +26 -0
  68. data/v2.todo +21 -0
  69. metadata +138 -93
  70. checksums.yaml +0 -7
  71. data/example/adapter.rb +0 -32
  72. data/example/async_app.ru +0 -126
  73. data/example/async_chat.ru +0 -247
  74. data/example/async_tailer.ru +0 -100
  75. data/example/config.ru +0 -22
  76. data/example/monit_sockets +0 -20
  77. data/example/monit_unixsock +0 -20
  78. data/example/myapp.rb +0 -1
  79. data/example/ramaze.ru +0 -12
  80. data/example/thin.god +0 -80
  81. data/example/thin_solaris_smf.erb +0 -36
  82. data/example/thin_solaris_smf.readme.txt +0 -150
  83. data/example/vlad.rake +0 -72
  84. data/ext/thin_parser/common.rl +0 -59
  85. data/ext/thin_parser/ext_help.h +0 -14
  86. data/ext/thin_parser/extconf.rb +0 -6
  87. data/ext/thin_parser/parser.c +0 -1447
  88. data/ext/thin_parser/parser.h +0 -49
  89. data/ext/thin_parser/parser.rl +0 -152
  90. data/ext/thin_parser/thin.c +0 -435
  91. data/lib/rack/adapter/loader.rb +0 -75
  92. data/lib/rack/adapter/rails.rb +0 -178
  93. data/lib/rack/handler/thin.rb +0 -38
  94. data/lib/thin/backends/base.rb +0 -169
  95. data/lib/thin/backends/swiftiply_client.rb +0 -56
  96. data/lib/thin/backends/tcp_server.rb +0 -34
  97. data/lib/thin/backends/unix_server.rb +0 -56
  98. data/lib/thin/command.rb +0 -53
  99. data/lib/thin/controllers/cluster.rb +0 -178
  100. data/lib/thin/controllers/controller.rb +0 -189
  101. data/lib/thin/controllers/service.rb +0 -76
  102. data/lib/thin/controllers/service.sh.erb +0 -39
  103. data/lib/thin/daemonizing.rb +0 -199
  104. data/lib/thin/headers.rb +0 -40
  105. data/lib/thin/logging.rb +0 -174
  106. data/lib/thin/stats.html.erb +0 -216
  107. data/lib/thin/stats.rb +0 -52
  108. data/lib/thin/statuses.rb +0 -48
@@ -1,56 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
4
- class SwiftiplyClient < Base
5
- attr_accessor :key
6
-
7
- attr_accessor :host, :port
8
-
9
- def initialize(host, port, options = {})
10
- @host = host
11
- @port = port.to_i
12
- @key = options[:swiftiply].to_s
13
- super()
14
- end
15
-
16
- # Connect the server
17
- def connect
18
- EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
19
- end
20
-
21
- # Stops the server
22
- def disconnect
23
- EventMachine.stop
24
- end
25
-
26
- def to_s
27
- "#{@host}:#{@port} swiftiply"
28
- end
29
- end
30
- end
31
-
32
- class SwiftiplyConnection < Connection
33
- def connection_completed
34
- send_data swiftiply_handshake(@backend.key)
35
- end
36
-
37
- def persistent?
38
- true
39
- end
40
-
41
- def unbind
42
- super
43
- EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
44
- end
45
-
46
- protected
47
- def swiftiply_handshake(key)
48
- 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i) }.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
49
- end
50
-
51
- # For some reason Swiftiply request the current host
52
- def host_ip
53
- Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0, 0, 0, 0]
54
- end
55
- end
56
- end
@@ -1,34 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a TCP socket server.
4
- class TcpServer < Base
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
- binary_name = EventMachine.get_sockname( @signature )
18
- port_name = Socket.unpack_sockaddr_in( binary_name )
19
- @port = port_name[0]
20
- @host = port_name[1]
21
- @signature
22
- end
23
-
24
- # Stops the server
25
- def disconnect
26
- EventMachine.stop_server(@signature)
27
- end
28
-
29
- def to_s
30
- "#{@host}:#{@port}"
31
- end
32
- end
33
- end
34
- end
@@ -1,56 +0,0 @@
1
- module Thin
2
- module Backends
3
- # Backend to act as a UNIX domain socket server.
4
- class UnixServer < Base
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 domain 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
- old_umask = File.umask(0)
18
- begin
19
- EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
20
- # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
21
- # so we have to go in the internal stuff to find it.
22
- @signature = EventMachine.instance_eval{@acceptors.keys.first}
23
- ensure
24
- File.umask(old_umask)
25
- end
26
- end
27
-
28
- # Stops the server
29
- def disconnect
30
- EventMachine.stop_server(@signature)
31
- end
32
-
33
- # Free up resources used by the backend.
34
- def close
35
- remove_socket_file
36
- end
37
-
38
- def to_s
39
- @socket
40
- end
41
-
42
- protected
43
- def remove_socket_file
44
- File.delete(@socket) if @socket && File.exist?(@socket)
45
- end
46
- end
47
- end
48
-
49
- # Connection through a UNIX domain socket.
50
- class UnixConnection < Connection
51
- protected
52
- def socket_address
53
- '127.0.0.1' # Unix domain sockets can only be local
54
- end
55
- end
56
- end
@@ -1,53 +0,0 @@
1
- require 'open3'
2
-
3
- module Thin
4
- # Run a command through the +thin+ command-line script.
5
- class Command
6
- include Logging
7
-
8
- class << self
9
- # Path to the +thin+ script used to control the servers.
10
- # Leave this to default to use the one in the path.
11
- attr_accessor :script
12
- end
13
-
14
- def initialize(name, options={})
15
- @name = name
16
- @options = options
17
- end
18
-
19
- def self.run(*args)
20
- new(*args).run
21
- end
22
-
23
- # Send the command to the +thin+ script
24
- def run
25
- shell_cmd = shellify
26
- trace shell_cmd
27
- trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
28
- Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
29
- log_info stdout.gets until stdout.eof?
30
- log_info stderr.gets until stderr.eof?
31
- end
32
- end
33
-
34
- # Turn into a runnable shell command
35
- def shellify
36
- shellified_options = @options.inject([]) do |args, (name, value)|
37
- option_name = name.to_s.tr("_", "-")
38
- case value
39
- when NilClass,
40
- TrueClass then args << "--#{option_name}"
41
- when FalseClass
42
- when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
43
- else args << "--#{option_name}=#{value.inspect}"
44
- end
45
- args
46
- end
47
-
48
- raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
49
-
50
- "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
51
- end
52
- end
53
- end
@@ -1,178 +0,0 @@
1
- require 'socket'
2
-
3
- module Thin
4
- # An exception class to handle the event that server didn't start on time
5
- class RestartTimeout < RuntimeError; end
6
-
7
- module Controllers
8
- # Control a set of servers.
9
- # * Generate start and stop commands and run them.
10
- # * Inject the port or socket number in the pid and log filenames.
11
- # Servers are started throught the +thin+ command-line script.
12
- class Cluster < Controller
13
- # Cluster only options that should not be passed in the command sent
14
- # to the indiviual servers.
15
- CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
16
-
17
- # Maximum wait time for the server to be restarted
18
- DEFAULT_WAIT_TIME = 30 # seconds
19
-
20
- # Create a new cluster of servers launched using +options+.
21
- def initialize(options)
22
- super
23
- # Cluster can only contain daemonized servers
24
- @options.merge!(:daemonize => true)
25
- end
26
-
27
- def first_port; @options[:port] end
28
- def address; @options[:address] end
29
- def socket; @options[:socket] end
30
- def pid_file; @options[:pid] end
31
- def log_file; @options[:log] end
32
- def size; @options[:servers] end
33
- def only; @options[:only] end
34
- def onebyone; @options[:onebyone] end
35
- def wait; @options[:wait] end
36
-
37
- def swiftiply?
38
- @options.has_key?(:swiftiply)
39
- end
40
-
41
- # Start the servers
42
- def start
43
- with_each_server { |n| start_server n }
44
- end
45
-
46
- # Start a single server
47
- def start_server(number)
48
- log_info "Starting server on #{server_id(number)} ... "
49
-
50
- run :start, number
51
- end
52
-
53
- # Stop the servers
54
- def stop
55
- with_each_server { |n| stop_server n }
56
- end
57
-
58
- # Stop a single server
59
- def stop_server(number)
60
- log_info "Stopping server on #{server_id(number)} ... "
61
-
62
- run :stop, number
63
- end
64
-
65
- # Stop and start the servers.
66
- def restart
67
- unless onebyone
68
- # Let's do a normal restart by defaults
69
- stop
70
- sleep 0.1 # Let's breath a bit shall we ?
71
- start
72
- else
73
- with_each_server do |n|
74
- stop_server(n)
75
- sleep 0.1 # Let's breath a bit shall we ?
76
- start_server(n)
77
- wait_until_server_started(n)
78
- end
79
- end
80
- end
81
-
82
- def test_socket(number)
83
- if socket
84
- UNIXSocket.new(socket_for(number))
85
- else
86
- TCPSocket.new(address, number)
87
- end
88
- rescue
89
- nil
90
- end
91
-
92
- # Make sure the server is running before moving on to the next one.
93
- def wait_until_server_started(number)
94
- log_info "Waiting for server to start ..."
95
- STDOUT.flush # Need this to make sure user got the message
96
-
97
- tries = 0
98
- loop do
99
- if test_socket = test_socket(number)
100
- test_socket.close
101
- break
102
- elsif tries < wait
103
- sleep 1
104
- tries += 1
105
- else
106
- raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
107
- "for more information, or set the value of 'wait' in your config " +
108
- "file to be higher (defaults: 30)."
109
- end
110
- end
111
- end
112
-
113
- def server_id(number)
114
- if socket
115
- socket_for(number)
116
- elsif swiftiply?
117
- [address, first_port, number].join(':')
118
- else
119
- [address, number].join(':')
120
- end
121
- end
122
-
123
- def log_file_for(number)
124
- include_server_number log_file, number
125
- end
126
-
127
- def pid_file_for(number)
128
- include_server_number pid_file, number
129
- end
130
-
131
- def socket_for(number)
132
- include_server_number socket, number
133
- end
134
-
135
- def pid_for(number)
136
- File.read(pid_file_for(number)).chomp.to_i
137
- end
138
-
139
- private
140
- # Send the command to the +thin+ script
141
- def run(cmd, number)
142
- cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
143
- cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
144
- if socket
145
- cmd_options.merge!(:socket => socket_for(number))
146
- elsif swiftiply?
147
- cmd_options.merge!(:port => first_port)
148
- else
149
- cmd_options.merge!(:port => number)
150
- end
151
- Command.run(cmd, cmd_options)
152
- end
153
-
154
- def with_each_server
155
- if only
156
- if first_port && only < 80
157
- # interpret +only+ as a sequence number
158
- yield first_port + only
159
- else
160
- # interpret +only+ as an absolute port number
161
- yield only
162
- end
163
- elsif socket || swiftiply?
164
- size.times { |n| yield n }
165
- else
166
- size.times { |n| yield first_port + n }
167
- end
168
- end
169
-
170
- # Add the server port or number in the filename
171
- # so each instance get its own file
172
- def include_server_number(path, number)
173
- ext = File.extname(path)
174
- path.gsub(/#{ext}$/, ".#{number}#{ext}")
175
- end
176
- end
177
- end
178
- end
@@ -1,189 +0,0 @@
1
- require 'yaml'
2
-
3
- module Thin
4
- # Error raised that will abort the process and print not backtrace.
5
- class RunnerError < RuntimeError; end
6
-
7
- # Raised when a mandatory option is missing to run a command.
8
- class OptionRequired < RunnerError
9
- def initialize(option)
10
- super("#{option} option required")
11
- end
12
- end
13
-
14
- # Raised when an option is not valid.
15
- class InvalidOption < RunnerError; end
16
-
17
- # Build and control Thin servers.
18
- # Hey Controller pattern is not only for web apps yo!
19
- module Controllers
20
- # Controls one Thin server.
21
- # Allow to start, stop, restart and configure a single thin server.
22
- class Controller
23
- include Logging
24
-
25
- # Command line options passed to the thin script
26
- attr_accessor :options
27
-
28
- def initialize(options)
29
- @options = options
30
-
31
- if @options[:socket]
32
- @options.delete(:address)
33
- @options.delete(:port)
34
- end
35
- end
36
-
37
- def start
38
- # Constantize backend class
39
- @options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
40
-
41
- server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
42
- @options[:port], # Port ignored on UNIX socket
43
- @options)
44
-
45
- # Set options
46
- server.pid_file = @options[:pid]
47
- server.log_file = @options[:log]
48
- server.timeout = @options[:timeout]
49
- server.maximum_connections = @options[:max_conns]
50
- server.maximum_persistent_connections = @options[:max_persistent_conns]
51
- server.threaded = @options[:threaded]
52
- server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
53
- server.threadpool_size = @options[:threadpool_size] if server.threaded?
54
-
55
- # ssl support
56
- if @options[:ssl]
57
- server.ssl = true
58
- server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
59
- end
60
-
61
- # Detach the process, after this line the current process returns
62
- server.daemonize if @options[:daemonize]
63
-
64
- # +config+ must be called before changing privileges since it might require superuser power.
65
- server.config
66
-
67
- server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
68
-
69
- # If a Rack config file is specified we eval it inside a Rack::Builder block to create
70
- # a Rack adapter from it. Or else we guess which adapter to use and load it.
71
- if @options[:rackup]
72
- server.app = load_rackup_config
73
- else
74
- server.app = load_adapter
75
- end
76
-
77
- # If a prefix is required, wrap in Rack URL mapper
78
- server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
79
-
80
- # If a stats URL is specified, wrap in Stats adapter
81
- server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
82
-
83
- # Register restart procedure which just start another process with same options,
84
- # so that's why this is done here.
85
- server.on_restart { Command.run(:start, @options) }
86
-
87
- server.start
88
- end
89
-
90
- def stop
91
- raise OptionRequired, :pid unless @options[:pid]
92
-
93
- tail_log(@options[:log]) do
94
- if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
95
- wait_for_file :deletion, @options[:pid]
96
- end
97
- end
98
- end
99
-
100
- def restart
101
- raise OptionRequired, :pid unless @options[:pid]
102
-
103
- tail_log(@options[:log]) do
104
- if Server.restart(@options[:pid])
105
- wait_for_file :creation, @options[:pid]
106
- end
107
- end
108
- end
109
-
110
- def config
111
- config_file = @options.delete(:config) || raise(OptionRequired, :config)
112
-
113
- # Stringify keys
114
- @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
115
-
116
- File.open(config_file, 'w') { |f| f << @options.to_yaml }
117
- log_info "Wrote configuration to #{config_file}"
118
- end
119
-
120
- protected
121
- # Wait for a pid file to either be created or deleted.
122
- def wait_for_file(state, file)
123
- Timeout.timeout(@options[:timeout] || 30) do
124
- case state
125
- when :creation then sleep 0.1 until File.exist?(file)
126
- when :deletion then sleep 0.1 while File.exist?(file)
127
- end
128
- end
129
- end
130
-
131
- # Tail the log file of server +number+ during the execution of the block.
132
- def tail_log(log_file)
133
- if log_file
134
- tail_thread = tail(log_file)
135
- yield
136
- tail_thread.kill
137
- else
138
- yield
139
- end
140
- end
141
-
142
- # Acts like GNU tail command. Taken from Rails.
143
- def tail(file)
144
- cursor = File.exist?(file) ? File.size(file) : 0
145
- last_checked = Time.now
146
- tail_thread = Thread.new do
147
- Thread.pass until File.exist?(file)
148
- File.open(file, 'r') do |f|
149
- loop do
150
- f.seek cursor
151
- if f.mtime > last_checked
152
- last_checked = f.mtime
153
- contents = f.read
154
- cursor += contents.length
155
- print contents
156
- STDOUT.flush
157
- end
158
- sleep 0.1
159
- end
160
- end
161
- end
162
- sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
163
- tail_thread
164
- end
165
-
166
- private
167
- def load_adapter
168
- adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
169
- log_info "Using #{adapter} adapter"
170
- Rack::Adapter.for(adapter, @options)
171
- rescue Rack::AdapterNotFound => e
172
- raise InvalidOption, e.message
173
- end
174
-
175
- def load_rackup_config
176
- ENV['RACK_ENV'] = @options[:environment]
177
- case @options[:rackup]
178
- when /\.rb$/
179
- Kernel.load(@options[:rackup])
180
- Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
181
- when /\.ru$/
182
- Rack::Adapter.load(@options[:rackup])
183
- else
184
- raise "Invalid rackup file. please specify either a .ru or .rb file"
185
- end
186
- end
187
- end
188
- end
189
- end