michaelyta-thin 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +257 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +13 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +79 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +23 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +452 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +175 -0
  31. data/lib/thin.rb +49 -0
  32. data/lib/thin/backends/base.rb +141 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +68 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +214 -0
  38. data/lib/thin/controllers/cluster.rb +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +158 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/spec/backends/swiftiply_client_spec.rb +66 -0
  54. data/spec/backends/tcp_server_spec.rb +33 -0
  55. data/spec/backends/unix_server_spec.rb +37 -0
  56. data/spec/command_spec.rb +25 -0
  57. data/spec/configs/cluster.yml +9 -0
  58. data/spec/configs/single.yml +9 -0
  59. data/spec/connection_spec.rb +105 -0
  60. data/spec/controllers/cluster_spec.rb +235 -0
  61. data/spec/controllers/controller_spec.rb +129 -0
  62. data/spec/controllers/service_spec.rb +50 -0
  63. data/spec/daemonizing_spec.rb +192 -0
  64. data/spec/headers_spec.rb +40 -0
  65. data/spec/logging_spec.rb +46 -0
  66. data/spec/perf/request_perf_spec.rb +50 -0
  67. data/spec/perf/response_perf_spec.rb +19 -0
  68. data/spec/perf/server_perf_spec.rb +39 -0
  69. data/spec/rack/loader_spec.rb +29 -0
  70. data/spec/rack/rails_adapter_spec.rb +106 -0
  71. data/spec/rails_app/app/controllers/application.rb +10 -0
  72. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  73. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  74. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  75. data/spec/rails_app/config/boot.rb +109 -0
  76. data/spec/rails_app/config/environment.rb +64 -0
  77. data/spec/rails_app/config/environments/development.rb +18 -0
  78. data/spec/rails_app/config/environments/production.rb +19 -0
  79. data/spec/rails_app/config/environments/test.rb +22 -0
  80. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  81. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  82. data/spec/rails_app/config/routes.rb +35 -0
  83. data/spec/rails_app/public/404.html +30 -0
  84. data/spec/rails_app/public/422.html +30 -0
  85. data/spec/rails_app/public/500.html +30 -0
  86. data/spec/rails_app/public/dispatch.cgi +10 -0
  87. data/spec/rails_app/public/dispatch.fcgi +24 -0
  88. data/spec/rails_app/public/dispatch.rb +10 -0
  89. data/spec/rails_app/public/favicon.ico +0 -0
  90. data/spec/rails_app/public/images/rails.png +0 -0
  91. data/spec/rails_app/public/index.html +277 -0
  92. data/spec/rails_app/public/javascripts/application.js +2 -0
  93. data/spec/rails_app/public/javascripts/controls.js +963 -0
  94. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  95. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  96. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  97. data/spec/rails_app/public/robots.txt +5 -0
  98. data/spec/rails_app/script/about +3 -0
  99. data/spec/rails_app/script/console +3 -0
  100. data/spec/rails_app/script/destroy +3 -0
  101. data/spec/rails_app/script/generate +3 -0
  102. data/spec/rails_app/script/performance/benchmarker +3 -0
  103. data/spec/rails_app/script/performance/profiler +3 -0
  104. data/spec/rails_app/script/performance/request +3 -0
  105. data/spec/rails_app/script/plugin +3 -0
  106. data/spec/rails_app/script/process/inspector +3 -0
  107. data/spec/rails_app/script/process/reaper +3 -0
  108. data/spec/rails_app/script/process/spawner +3 -0
  109. data/spec/rails_app/script/runner +3 -0
  110. data/spec/rails_app/script/server +3 -0
  111. data/spec/request/mongrel_spec.rb +39 -0
  112. data/spec/request/parser_spec.rb +215 -0
  113. data/spec/request/persistent_spec.rb +35 -0
  114. data/spec/request/processing_spec.rb +45 -0
  115. data/spec/response_spec.rb +91 -0
  116. data/spec/runner_spec.rb +168 -0
  117. data/spec/server/builder_spec.rb +44 -0
  118. data/spec/server/pipelining_spec.rb +110 -0
  119. data/spec/server/robustness_spec.rb +34 -0
  120. data/spec/server/stopping_spec.rb +55 -0
  121. data/spec/server/swiftiply.yml +6 -0
  122. data/spec/server/swiftiply_spec.rb +32 -0
  123. data/spec/server/tcp_spec.rb +57 -0
  124. data/spec/server/threaded_spec.rb +27 -0
  125. data/spec/server/unix_socket_spec.rb +26 -0
  126. data/spec/server_spec.rb +96 -0
  127. data/spec/spec_helper.rb +219 -0
  128. data/tasks/announce.rake +22 -0
  129. data/tasks/deploy.rake +16 -0
  130. data/tasks/email.erb +30 -0
  131. data/tasks/ext.rake +42 -0
  132. data/tasks/gem.rake +108 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +48 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +254 -0
@@ -0,0 +1,68 @@
1
+ require 'neverblock'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../connection')
3
+
4
+ module Thin
5
+ module Backends
6
+ # Backend to act as a TCP socket server.
7
+ class TcpServer < Base
8
+
9
+ # Allow using fibers in the backend.
10
+ def fibered?; true; end
11
+
12
+ def initialize(host, port, options={})
13
+ @host = host
14
+ @port = port.to_i
15
+ @timeout = 30
16
+ end
17
+
18
+ def start
19
+ @reactor = NB.reactor
20
+ @server_socket = TCPServer.new(@host, @port)
21
+ @server_socket.listen(511)
22
+ @reactor.attach(:read, @server_socket) do |server, reactor|
23
+ begin
24
+ loop do
25
+ connection = accept_connection
26
+ end
27
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
28
+ rescue Exception => e
29
+ end
30
+ end
31
+ loop do
32
+ begin
33
+ @reactor.run
34
+ break unless @reactor.running?
35
+ rescue Exception => e
36
+ end
37
+ end
38
+ @server_socket.close
39
+ end
40
+
41
+ def stop;@reactor.stop;end
42
+
43
+ alias :stop! :stop
44
+
45
+ def trace=(trace);@trace = trace;end
46
+
47
+ def maxfds=(maxfds);raise "not implemented";end
48
+
49
+ def maxfds;raise "not implemented";end
50
+
51
+ def to_s;"#{@host}:#{@port} (NeverBlock)";end
52
+
53
+ def running?;@reactor.running?;end
54
+
55
+ protected
56
+
57
+ def accept_connection
58
+ socket = @server_socket.accept_nonblock
59
+ connection = ::Thin::ReactorConnection.new(socket, @reactor)
60
+ connection.backend = self
61
+ connection.app = @server.app
62
+ connection.threaded = false
63
+ connection.post_init
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,51 @@
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
+ EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
18
+ # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
19
+ # so we have to go in the internal stuff to find it.
20
+ @signature = EventMachine.instance_eval{@acceptors.keys.first}
21
+ end
22
+
23
+ # Stops the server
24
+ def disconnect
25
+ EventMachine.stop_server(@signature)
26
+ end
27
+
28
+ # Free up resources used by the backend.
29
+ def close
30
+ remove_socket_file
31
+ end
32
+
33
+ def to_s
34
+ @socket
35
+ end
36
+
37
+ protected
38
+ def remove_socket_file
39
+ File.delete(@socket) if @socket && File.exist?(@socket)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Connection through a UNIX domain socket.
45
+ class UnixConnection < Connection
46
+ protected
47
+ def socket_address
48
+ '127.0.0.1' # Unix domain sockets can only be local
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
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 stdout.gets until stdout.eof?
30
+ log 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
@@ -0,0 +1,214 @@
1
+ require 'neverblock/io/io'
2
+
3
+ module Thin
4
+ # Connection between the server and client.
5
+ # This class is instanciated by EventMachine on each new connection
6
+ # that is opened.
7
+ class ReactorConnection
8
+
9
+ CONTENT_LENGTH = 'Content-Length'.freeze
10
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
11
+ CHUNKED_REGEXP = /\bchunked\b/i.freeze
12
+
13
+ # whenever we accumulate this buffer size we flush to the socket
14
+ # when flush is called the buffer is emptied (and the conn is blocked)
15
+ BUFFER_SIZE = 256*1024
16
+
17
+ include Logging
18
+
19
+ # Rack application (adapter) served by this connection.
20
+ attr_accessor :app
21
+
22
+ # Backend to the server
23
+ attr_accessor :backend
24
+
25
+ # Current request served by the connection
26
+ attr_accessor :request
27
+
28
+ # Next response sent through the connection
29
+ attr_accessor :response
30
+
31
+ # Calling the application in a threaded allowing
32
+ # concurrent processing of requests.
33
+ attr_writer :threaded
34
+
35
+ # Calling the application in a fiber allowing
36
+ # concurrent processing of requests.
37
+ attr_writer :fibered
38
+ attr_writer :reactor
39
+ attr_writer :socket
40
+
41
+ def initialize(socket, reactor)
42
+ @socket = socket
43
+ @reactor = reactor
44
+ @data = ""
45
+ @sends = 0
46
+ @t = Time.now
47
+ @request = Request.new
48
+ @response = Response.new
49
+ end
50
+
51
+ def post_init
52
+ @request = Request.new
53
+ @response = Response.new
54
+ @reactor.attach(:read, @socket) do |socket, reactor|
55
+ begin
56
+ receive_data(@socket.read_nonblock(8*1024))
57
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
58
+ rescue Exception => e
59
+ close_connection
60
+ end
61
+ end
62
+ end
63
+
64
+ def close_connection(after_writing=false)
65
+ unless after_writing
66
+ @reactor.detach(:read, @socket)
67
+ end
68
+ if after_writing
69
+ @reactor.detach(:read, @socket) unless @reactor.attached?(:read, @socket)
70
+ @socket.close unless @socket.closed?
71
+ end
72
+ end
73
+
74
+ def close_connection_after_writing
75
+ close_connection(true)
76
+ end
77
+
78
+ def send_data(data)
79
+ @data << data
80
+ flush_data if @data.length >= BUFFER_SIZE
81
+ end
82
+
83
+ def flush_data
84
+ return if @data.blank?
85
+ begin
86
+ @socket.syswrite(@data)
87
+ rescue Exception => e
88
+ puts e
89
+ close_connection
90
+ return
91
+ end
92
+ @data = ''
93
+ end
94
+
95
+ # Called when data is received from the client.
96
+ def receive_data(data)
97
+ trace { data }
98
+ post_process(pre_process) if @request.parse(data)
99
+ rescue InvalidRequest => e
100
+ log "!! Invalid request"
101
+ log_error e
102
+ close_connection
103
+ end
104
+
105
+ # Called when all data was received and the request
106
+ # is ready to be processed.
107
+
108
+ def pre_process
109
+ @app.call(@request.env)
110
+ rescue Exception
111
+ handle_error
112
+ terminate_request
113
+ nil # Signal to post_process that the request could not be processed
114
+ end
115
+
116
+ def post_process(result)
117
+ begin
118
+ return unless result
119
+ result = result.to_a
120
+ # Set the Content-Length header if possible
121
+ set_content_length(result) if need_content_length?(result)
122
+ @response.status, @response.headers, @response.body = *result
123
+ log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil?
124
+ # Send the response
125
+ @response.each do |chunk|
126
+ trace { chunk }
127
+ send_data chunk
128
+ end
129
+ flush_data
130
+ rescue Exception
131
+ handle_error
132
+ ensure
133
+ terminate_request
134
+ end
135
+ end
136
+
137
+ # Logs catched exception and closes the connection.
138
+ def handle_error
139
+ log "!! Unexpected error while processing request: #{$!.message}"
140
+ log_error
141
+ close_connection rescue nil
142
+ end
143
+
144
+ def close_request_response
145
+ @request.close rescue nil
146
+ @response.close rescue nil
147
+ end
148
+
149
+ def terminate_request
150
+ close_connection_after_writing rescue nil
151
+ close_request_response
152
+ end
153
+
154
+ # Allows this connection to be persistent.
155
+ def can_persist!
156
+ end
157
+
158
+ # Return +true+ if this connection is allowed to stay open and be persistent.
159
+ def can_persist?
160
+ false
161
+ end
162
+
163
+ # Return +true+ if the connection must be left open
164
+ # and ready to be reused for another request.
165
+ def persistent?
166
+ false
167
+ end
168
+
169
+ def threaded?
170
+ false
171
+ end
172
+
173
+ # IP Address of the remote client.
174
+ def remote_address
175
+ @request.forwarded_for || socket_address
176
+ rescue Exception
177
+ log_error
178
+ nil
179
+ end
180
+
181
+ protected
182
+
183
+ # Returns IP address of peer as a string.
184
+ def socket_address
185
+ Socket.unpack_sockaddr_in(get_peername)[1]
186
+ end
187
+
188
+ private
189
+ def need_content_length?(result)
190
+ status, headers, body = result
191
+ return false if status == -1
192
+ return false if headers.has_key?(CONTENT_LENGTH)
193
+ return false if (100..199).include?(status) || status == 204 || status == 304
194
+ return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
195
+ return false unless body.kind_of?(String) || body.kind_of?(Array)
196
+ true
197
+ end
198
+
199
+ def set_content_length(result)
200
+ headers, body = result[1..2]
201
+ case body
202
+ when String
203
+ # See http://redmine.ruby-lang.org/issues/show/203
204
+ headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
205
+ when Array
206
+ bytes = 0
207
+ body.each do |p|
208
+ bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
209
+ end
210
+ headers[CONTENT_LENGTH] = bytes.to_s
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,127 @@
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
+ # Cluster only options that should not be passed in the command sent
9
+ # to the indiviual servers.
10
+ CLUSTER_OPTIONS = [:servers, :only]
11
+
12
+ # Create a new cluster of servers launched using +options+.
13
+ def initialize(options)
14
+ super
15
+ # Cluster can only contain daemonized servers
16
+ @options.merge!(:daemonize => true)
17
+ end
18
+
19
+ def first_port; @options[:port] end
20
+ def address; @options[:address] end
21
+ def socket; @options[:socket] end
22
+ def pid_file; @options[:pid] end
23
+ def log_file; @options[:log] end
24
+ def size; @options[:servers] end
25
+ def only; @options[:only] end
26
+
27
+ def swiftiply?
28
+ @options.has_key?(:swiftiply)
29
+ end
30
+
31
+ # Start the servers
32
+ def start
33
+ with_each_server { |n| start_server n }
34
+ end
35
+
36
+ # Start a single server
37
+ def start_server(number)
38
+ log "Starting server on #{server_id(number)} ... "
39
+
40
+ run :start, number
41
+ end
42
+
43
+ # Stop the servers
44
+ def stop
45
+ with_each_server { |n| stop_server n }
46
+ end
47
+
48
+ # Stop a single server
49
+ def stop_server(number)
50
+ log "Stopping server on #{server_id(number)} ... "
51
+
52
+ run :stop, number
53
+ end
54
+
55
+ # Stop and start the servers.
56
+ def restart
57
+ stop
58
+ sleep 0.1 # Let's breath a bit shall we ?
59
+ start
60
+ end
61
+
62
+ def server_id(number)
63
+ if socket
64
+ socket_for(number)
65
+ elsif swiftiply?
66
+ [address, first_port, number].join(':')
67
+ else
68
+ [address, number].join(':')
69
+ end
70
+ end
71
+
72
+ def log_file_for(number)
73
+ include_server_number log_file, number
74
+ end
75
+
76
+ def pid_file_for(number)
77
+ include_server_number pid_file, number
78
+ end
79
+
80
+ def socket_for(number)
81
+ include_server_number socket, number
82
+ end
83
+
84
+ def pid_for(number)
85
+ File.read(pid_file_for(number)).chomp.to_i
86
+ end
87
+
88
+ private
89
+ # Send the command to the +thin+ script
90
+ def run(cmd, number)
91
+ cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
92
+ cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
93
+ if socket
94
+ cmd_options.merge!(:socket => socket_for(number))
95
+ elsif swiftiply?
96
+ cmd_options.merge!(:port => first_port)
97
+ else
98
+ cmd_options.merge!(:port => number)
99
+ end
100
+ Command.run(cmd, cmd_options)
101
+ end
102
+
103
+ def with_each_server
104
+ if only
105
+ if first_port && only < 80
106
+ # interpret +only+ as a sequence number
107
+ yield first_port + only
108
+ else
109
+ # interpret +only+ as an absolute port number
110
+ yield only
111
+ end
112
+ elsif socket || swiftiply?
113
+ size.times { |n| yield n }
114
+ else
115
+ size.times { |n| yield first_port + n }
116
+ end
117
+ end
118
+
119
+ # Add the server port or number in the filename
120
+ # so each instance get its own file
121
+ def include_server_number(path, number)
122
+ ext = File.extname(path)
123
+ path.gsub(/#{ext}$/, ".#{number}#{ext}")
124
+ end
125
+ end
126
+ end
127
+ end