grockit-thin 0.8.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 (136) hide show
  1. data/CHANGELOG +220 -0
  2. data/COMMITTERS +3 -0
  3. data/COPYING +18 -0
  4. data/README +77 -0
  5. data/Rakefile +13 -0
  6. data/benchmark/abc +51 -0
  7. data/benchmark/benchmarker.rb +80 -0
  8. data/benchmark/runner +79 -0
  9. data/bin/thin +6 -0
  10. data/example/adapter.rb +32 -0
  11. data/example/config.ru +23 -0
  12. data/example/monit_sockets +20 -0
  13. data/example/monit_unixsock +20 -0
  14. data/example/myapp.rb +1 -0
  15. data/example/ramaze.ru +12 -0
  16. data/example/thin.god +80 -0
  17. data/example/thin_solaris_smf.erb +36 -0
  18. data/example/thin_solaris_smf.readme.txt +150 -0
  19. data/example/vlad.rake +61 -0
  20. data/ext/thin_parser/common.rl +55 -0
  21. data/ext/thin_parser/ext_help.h +14 -0
  22. data/ext/thin_parser/extconf.rb +6 -0
  23. data/ext/thin_parser/parser.c +1218 -0
  24. data/ext/thin_parser/parser.h +49 -0
  25. data/ext/thin_parser/parser.rl +159 -0
  26. data/ext/thin_parser/thin.c +433 -0
  27. data/lib/rack/adapter/loader.rb +78 -0
  28. data/lib/rack/adapter/rails.rb +167 -0
  29. data/lib/rack/handler/thin.rb +18 -0
  30. data/lib/thin.rb +49 -0
  31. data/lib/thin/backends/base.rb +135 -0
  32. data/lib/thin/backends/swiftiply_client.rb +56 -0
  33. data/lib/thin/backends/tcp_server.rb +29 -0
  34. data/lib/thin/backends/unix_server.rb +51 -0
  35. data/lib/thin/command.rb +52 -0
  36. data/lib/thin/connection.rb +178 -0
  37. data/lib/thin/controllers/cluster.rb +121 -0
  38. data/lib/thin/controllers/controller.rb +182 -0
  39. data/lib/thin/controllers/service.rb +75 -0
  40. data/lib/thin/controllers/service.sh.erb +39 -0
  41. data/lib/thin/daemonizing.rb +163 -0
  42. data/lib/thin/headers.rb +31 -0
  43. data/lib/thin/logging.rb +54 -0
  44. data/lib/thin/request.rb +144 -0
  45. data/lib/thin/response.rb +96 -0
  46. data/lib/thin/runner.rb +208 -0
  47. data/lib/thin/server.rb +241 -0
  48. data/lib/thin/stats.html.erb +216 -0
  49. data/lib/thin/stats.rb +52 -0
  50. data/lib/thin/statuses.rb +43 -0
  51. data/lib/thin/version.rb +32 -0
  52. data/spec/backends/swiftiply_client_spec.rb +66 -0
  53. data/spec/backends/tcp_server_spec.rb +33 -0
  54. data/spec/backends/unix_server_spec.rb +37 -0
  55. data/spec/command_spec.rb +20 -0
  56. data/spec/configs/cluster.yml +9 -0
  57. data/spec/configs/single.yml +9 -0
  58. data/spec/connection_spec.rb +105 -0
  59. data/spec/controllers/cluster_spec.rb +179 -0
  60. data/spec/controllers/controller_spec.rb +121 -0
  61. data/spec/controllers/service_spec.rb +50 -0
  62. data/spec/daemonizing_spec.rb +192 -0
  63. data/spec/headers_spec.rb +29 -0
  64. data/spec/logging_spec.rb +46 -0
  65. data/spec/perf/request_perf_spec.rb +50 -0
  66. data/spec/perf/response_perf_spec.rb +19 -0
  67. data/spec/perf/server_perf_spec.rb +39 -0
  68. data/spec/rack/loader_spec.rb +29 -0
  69. data/spec/rack/rails_adapter_spec.rb +106 -0
  70. data/spec/rails_app/app/controllers/application.rb +10 -0
  71. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  72. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  73. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  74. data/spec/rails_app/config/boot.rb +109 -0
  75. data/spec/rails_app/config/environment.rb +64 -0
  76. data/spec/rails_app/config/environments/development.rb +18 -0
  77. data/spec/rails_app/config/environments/production.rb +19 -0
  78. data/spec/rails_app/config/environments/test.rb +22 -0
  79. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  80. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  81. data/spec/rails_app/config/routes.rb +35 -0
  82. data/spec/rails_app/public/404.html +30 -0
  83. data/spec/rails_app/public/422.html +30 -0
  84. data/spec/rails_app/public/500.html +30 -0
  85. data/spec/rails_app/public/dispatch.cgi +10 -0
  86. data/spec/rails_app/public/dispatch.fcgi +24 -0
  87. data/spec/rails_app/public/dispatch.rb +10 -0
  88. data/spec/rails_app/public/favicon.ico +0 -0
  89. data/spec/rails_app/public/images/rails.png +0 -0
  90. data/spec/rails_app/public/index.html +277 -0
  91. data/spec/rails_app/public/javascripts/application.js +2 -0
  92. data/spec/rails_app/public/javascripts/controls.js +963 -0
  93. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  94. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  95. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  96. data/spec/rails_app/public/robots.txt +5 -0
  97. data/spec/rails_app/script/about +3 -0
  98. data/spec/rails_app/script/console +3 -0
  99. data/spec/rails_app/script/destroy +3 -0
  100. data/spec/rails_app/script/generate +3 -0
  101. data/spec/rails_app/script/performance/benchmarker +3 -0
  102. data/spec/rails_app/script/performance/profiler +3 -0
  103. data/spec/rails_app/script/performance/request +3 -0
  104. data/spec/rails_app/script/plugin +3 -0
  105. data/spec/rails_app/script/process/inspector +3 -0
  106. data/spec/rails_app/script/process/reaper +3 -0
  107. data/spec/rails_app/script/process/spawner +3 -0
  108. data/spec/rails_app/script/runner +3 -0
  109. data/spec/rails_app/script/server +3 -0
  110. data/spec/request/mongrel_spec.rb +39 -0
  111. data/spec/request/parser_spec.rb +191 -0
  112. data/spec/request/persistent_spec.rb +35 -0
  113. data/spec/request/processing_spec.rb +45 -0
  114. data/spec/response_spec.rb +76 -0
  115. data/spec/runner_spec.rb +167 -0
  116. data/spec/server/builder_spec.rb +44 -0
  117. data/spec/server/pipelining_spec.rb +109 -0
  118. data/spec/server/robustness_spec.rb +34 -0
  119. data/spec/server/stopping_spec.rb +45 -0
  120. data/spec/server/swiftiply.yml +6 -0
  121. data/spec/server/swiftiply_spec.rb +32 -0
  122. data/spec/server/tcp_spec.rb +57 -0
  123. data/spec/server/threaded_spec.rb +27 -0
  124. data/spec/server/unix_socket_spec.rb +26 -0
  125. data/spec/server_spec.rb +96 -0
  126. data/spec/spec_helper.rb +219 -0
  127. data/tasks/announce.rake +22 -0
  128. data/tasks/deploy.rake +16 -0
  129. data/tasks/email.erb +30 -0
  130. data/tasks/ext.rake +42 -0
  131. data/tasks/gem.rake +102 -0
  132. data/tasks/rdoc.rake +25 -0
  133. data/tasks/site.rake +15 -0
  134. data/tasks/spec.rake +48 -0
  135. data/tasks/stats.rake +28 -0
  136. metadata +240 -0
@@ -0,0 +1,56 @@
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
@@ -0,0 +1,29 @@
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
+ 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,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,52 @@
1
+ require 'open3'
2
+
3
+ module Thin
4
+ # Run a command though 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
+ case value
38
+ when NilClass,
39
+ TrueClass then args << "--#{name}"
40
+ when FalseClass
41
+ when Array then value.each { |v| args << "--#{name}=#{v.inspect}" }
42
+ else args << "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
43
+ end
44
+ args
45
+ end
46
+
47
+ raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
48
+
49
+ "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,178 @@
1
+ require 'socket'
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 Connection < EventMachine::Connection
8
+ CONTENT_LENGTH = 'Content-Length'.freeze
9
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
10
+ CHUNKED_REGEXP = /\bchunked\b/i.freeze
11
+
12
+ include Logging
13
+
14
+ # Rack application (adapter) served by this connection.
15
+ attr_accessor :app
16
+
17
+ # Backend to the server
18
+ attr_accessor :backend
19
+
20
+ # Current request served by the connection
21
+ attr_accessor :request
22
+
23
+ # Next response sent through the connection
24
+ attr_accessor :response
25
+
26
+ # Calling the application in a threaded allowing
27
+ # concurrent processing of requests.
28
+ attr_writer :threaded
29
+
30
+ # Get the connection ready to process a request.
31
+ def post_init
32
+ @request = Request.new
33
+ @response = Response.new
34
+ end
35
+
36
+ # Called when data is received from the client.
37
+ def receive_data(data)
38
+ trace { data }
39
+ process if @request.parse(data)
40
+ rescue InvalidRequest => e
41
+ log "!! Invalid request"
42
+ log_error e
43
+ close_connection
44
+ end
45
+
46
+ # Called when all data was received and the request
47
+ # is ready to be processed.
48
+ def process
49
+ if threaded?
50
+ @request.threaded = true
51
+ EventMachine.defer(method(:pre_process), method(:post_process))
52
+ else
53
+ @request.threaded = false
54
+ post_process(pre_process)
55
+ end
56
+ end
57
+
58
+ def pre_process
59
+ # Add client info to the request env
60
+ @request.remote_address = remote_address
61
+
62
+ # Process the request calling the Rack adapter
63
+ @app.call(@request.env)
64
+ rescue Object
65
+ handle_error
66
+ terminate_request
67
+ nil # Signal to post_process that the request could not be processed
68
+ end
69
+
70
+ def post_process(result)
71
+ return unless result
72
+
73
+ # Set the Content-Length header if possible
74
+ set_content_length(result) if need_content_length?(result)
75
+
76
+ @response.status, @response.headers, @response.body = result
77
+
78
+ # Make the response persistent if requested by the client
79
+ @response.persistent! if @request.persistent?
80
+
81
+ # Send the response
82
+ @response.each do |chunk|
83
+ trace { chunk }
84
+ send_data chunk
85
+ end
86
+
87
+ # If no more request on that same connection, we close it.
88
+ close_connection_after_writing unless persistent?
89
+
90
+ rescue Object
91
+ handle_error
92
+ ensure
93
+ terminate_request
94
+ end
95
+
96
+ def handle_error
97
+ log "!! Unexpected error while processing request: #{$!.message}"
98
+ log_error
99
+ close_connection rescue nil
100
+ end
101
+
102
+ def terminate_request
103
+ @request.close rescue nil
104
+ @response.close rescue nil
105
+
106
+ # Prepare the connection for another request if the client
107
+ # supports HTTP pipelining (persistent connection).
108
+ post_init if persistent?
109
+ end
110
+
111
+ # Called when the connection is unbinded from the socket
112
+ # and can no longer be used to process requests.
113
+ def unbind
114
+ @backend.connection_finished(self)
115
+ end
116
+
117
+ # Allows this connection to be persistent.
118
+ def can_persist!
119
+ @can_persist = true
120
+ end
121
+
122
+ # Return +true+ if this connection is allowed to stay open and be persistent.
123
+ def can_persist?
124
+ @can_persist
125
+ end
126
+
127
+ # Return +true+ if the connection must be left open
128
+ # and ready to be reused for another request.
129
+ def persistent?
130
+ @can_persist && @response.persistent?
131
+ end
132
+
133
+ # +true+ if <tt>app.call</tt> will be called inside a thread.
134
+ # You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
135
+ # or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
136
+ def threaded?
137
+ @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
138
+ end
139
+
140
+ # IP Address of the remote client.
141
+ def remote_address
142
+ @request.forwarded_for || socket_address
143
+ rescue Object
144
+ log_error
145
+ nil
146
+ end
147
+
148
+ protected
149
+ def socket_address
150
+ Socket.unpack_sockaddr_in(get_peername)[1]
151
+ end
152
+
153
+ private
154
+ def need_content_length?(result)
155
+ status, headers, body = result
156
+ return false if headers.has_key?(CONTENT_LENGTH)
157
+ return false if (100..199).include?(status) || status == 204 || status == 304
158
+ return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
159
+ return false unless body.kind_of?(String) || body.kind_of?(Array)
160
+ true
161
+ end
162
+
163
+ def set_content_length(result)
164
+ headers, body = result[1..2]
165
+ case body
166
+ when String
167
+ # See http://redmine.ruby-lang.org/issues/show/203
168
+ headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
169
+ when Array
170
+ bytes = 0
171
+ body.each do |p|
172
+ bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
173
+ end
174
+ headers[CONTENT_LENGTH] = bytes.to_s
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,121 @@
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
+ yield only
106
+ elsif socket || swiftiply?
107
+ size.times { |n| yield n }
108
+ else
109
+ size.times { |n| yield first_port + n }
110
+ end
111
+ end
112
+
113
+ # Add the server port or number in the filename
114
+ # so each instance get its own file
115
+ def include_server_number(path, number)
116
+ ext = File.extname(path)
117
+ path.gsub(/#{ext}$/, ".#{number}#{ext}")
118
+ end
119
+ end
120
+ end
121
+ end