michaelyta-thin 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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