puma 1.5.0 → 1.6.0

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

Potentially problematic release.


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

@@ -1,3 +1,9 @@
1
+ === 1.6.0 / 2012-07-23
2
+
3
+ * 1 major bug fix:
4
+ * Prevent slow clients from starving the server by introducing a
5
+ dedicated IO reactor thread. Credit for reporting goes to @meh.
6
+
1
7
  === 1.5.0 / 2012-07-19
2
8
 
3
9
  * 7 contributers to this release:
@@ -33,6 +33,7 @@ ext/puma_http11/puma_http11.c
33
33
  lib/puma.rb
34
34
  lib/puma/app/status.rb
35
35
  lib/puma/cli.rb
36
+ lib/puma/client.rb
36
37
  lib/puma/compat.rb
37
38
  lib/puma/configuration.rb
38
39
  lib/puma/const.rb
@@ -41,12 +42,14 @@ lib/puma/events.rb
41
42
  lib/puma/jruby_restart.rb
42
43
  lib/puma/null_io.rb
43
44
  lib/puma/rack_patch.rb
45
+ lib/puma/reactor.rb
44
46
  lib/puma/server.rb
45
47
  lib/puma/thread_pool.rb
46
48
  lib/rack/handler/puma.rb
47
49
  puma.gemspec
48
50
  test/ab_rs.rb
49
51
  test/config/app.rb
52
+ test/hello-post.ru
50
53
  test/hello.ru
51
54
  test/lobster.ru
52
55
  test/mime.yaml
@@ -57,7 +57,7 @@ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
57
57
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
58
58
 
59
59
  struct common_field {
60
- const signed long len;
60
+ const size_t len;
61
61
  const char *name;
62
62
  int raw;
63
63
  VALUE value;
@@ -127,7 +127,7 @@ static int common_field_cmp(const void *a, const void *b)
127
127
 
128
128
  static void init_common_fields(void)
129
129
  {
130
- int i;
130
+ unsigned i;
131
131
  struct common_field *cf = common_http_fields;
132
132
  char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
133
133
  memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
@@ -163,7 +163,7 @@ static VALUE find_common_field_value(const char *field, size_t flen)
163
163
  common_field_cmp);
164
164
  return found ? found->value : Qnil;
165
165
  #else /* !HAVE_QSORT_BSEARCH */
166
- int i;
166
+ unsigned i;
167
167
  struct common_field *cf = common_http_fields;
168
168
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
169
169
  if (cf->len == flen && !memcmp(cf->name, field, flen))
@@ -460,6 +460,7 @@ void Init_puma_http11()
460
460
  {
461
461
 
462
462
  VALUE mPuma = rb_define_module("Puma");
463
+ VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
463
464
 
464
465
  DEF_GLOBAL(request_method, "REQUEST_METHOD");
465
466
  DEF_GLOBAL(request_uri, "REQUEST_URI");
@@ -471,7 +472,6 @@ void Init_puma_http11()
471
472
  eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
472
473
  rb_global_variable(&eHttpParserError);
473
474
 
474
- VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
475
475
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
476
476
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
477
477
  rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
@@ -4,6 +4,7 @@ require 'uri'
4
4
  require 'puma/server'
5
5
  require 'puma/const'
6
6
  require 'puma/configuration'
7
+ require 'puma/detect'
7
8
 
8
9
  require 'rack/commonlogger'
9
10
  require 'rack/utils'
@@ -12,8 +13,6 @@ module Puma
12
13
  # Handles invoke a Puma::Server in a command line style.
13
14
  #
14
15
  class CLI
15
- IS_JRUBY = defined?(JRUBY_VERSION)
16
-
17
16
  # Create a new CLI object using +argv+ as the command line
18
17
  # arguments.
19
18
  #
@@ -0,0 +1,233 @@
1
+ class IO
2
+ # We need to use this for a jruby work around on both 1.8 and 1.9.
3
+ # So this either creates the constant (on 1.8), or harmlessly
4
+ # reopens it (on 1.9).
5
+ module WaitReadable
6
+ end
7
+ end
8
+
9
+ require 'puma/detect'
10
+
11
+ if Puma::IS_JRUBY
12
+ # We have to work around some OpenSSL buffer/io-readiness bugs
13
+ # so we pull it in regardless of if the user is binding
14
+ # to an SSL socket
15
+ require 'openssl'
16
+ end
17
+
18
+ module Puma
19
+ class Client
20
+ include Puma::Const
21
+
22
+ def initialize(io, env)
23
+ @io = io
24
+ @to_io = io.to_io
25
+ @proto_env = env
26
+ @env = env.dup
27
+
28
+ @parser = HttpParser.new
29
+ @parsed_bytes = 0
30
+ @read_header = true
31
+ @ready = false
32
+
33
+ @body = nil
34
+ @buffer = nil
35
+
36
+ @timeout_at = nil
37
+
38
+ @requests_served = 0
39
+ end
40
+
41
+ attr_reader :env, :to_io, :body, :io, :timeout_at, :ready
42
+
43
+ def inspect
44
+ "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
45
+ end
46
+
47
+ def set_timeout(val)
48
+ @timeout_at = Time.now + val
49
+ end
50
+
51
+ def reset
52
+ @parser.reset
53
+ @read_header = true
54
+ @env = @proto_env.dup
55
+ @body = nil
56
+ @parsed_bytes = 0
57
+ @ready = false
58
+
59
+ if @buffer
60
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
61
+
62
+ if @parser.finished?
63
+ return setup_body
64
+ elsif @parsed_bytes >= MAX_HEADER
65
+ raise HttpParserError,
66
+ "HEADER is longer than allowed, aborting client early."
67
+ end
68
+
69
+ return false
70
+ end
71
+ end
72
+
73
+ def close
74
+ begin
75
+ @io.close
76
+ rescue IOError
77
+ end
78
+ end
79
+
80
+ # The object used for a request with no body. All requests with
81
+ # no body share this one object since it has no state.
82
+ EmptyBody = NullIO.new
83
+
84
+ def setup_body
85
+ body = @parser.body
86
+ cl = @env[CONTENT_LENGTH]
87
+
88
+ unless cl
89
+ @buffer = body.empty? ? nil : body
90
+ @body = EmptyBody
91
+ @requests_served += 1
92
+ @ready = true
93
+ return true
94
+ end
95
+
96
+ remain = cl.to_i - body.bytesize
97
+
98
+ if remain <= 0
99
+ @body = StringIO.new(body)
100
+ @buffer = nil
101
+ @requests_served += 1
102
+ @ready = true
103
+ return true
104
+ end
105
+
106
+ if remain > MAX_BODY
107
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
108
+ @body.binmode
109
+ else
110
+ # The body[0,0] trick is to get an empty string in the same
111
+ # encoding as body.
112
+ @body = StringIO.new body[0,0]
113
+ end
114
+
115
+ @body.write body
116
+
117
+ @body_remain = remain
118
+
119
+ @read_header = false
120
+
121
+ return false
122
+ end
123
+
124
+ def try_to_finish
125
+ return read_body unless @read_header
126
+
127
+ data = @io.readpartial(CHUNK_SIZE)
128
+
129
+ if @buffer
130
+ @buffer << data
131
+ else
132
+ @buffer = data
133
+ end
134
+
135
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
136
+
137
+ if @parser.finished?
138
+ return setup_body
139
+ elsif @parsed_bytes >= MAX_HEADER
140
+ raise HttpParserError,
141
+ "HEADER is longer than allowed, aborting client early."
142
+ end
143
+
144
+ false
145
+ end
146
+
147
+ if IS_JRUBY
148
+ def jruby_start_try_to_finish
149
+ return read_body unless @read_header
150
+
151
+ begin
152
+ data = @io.sysread_nonblock(CHUNK_SIZE)
153
+ rescue OpenSSL::SSL::SSLError => e
154
+ return false if e.kind_of? IO::WaitReadable
155
+ raise e
156
+ end
157
+
158
+ if @buffer
159
+ @buffer << data
160
+ else
161
+ @buffer = data
162
+ end
163
+
164
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
165
+
166
+ if @parser.finished?
167
+ return setup_body
168
+ elsif @parsed_bytes >= MAX_HEADER
169
+ raise HttpParserError,
170
+ "HEADER is longer than allowed, aborting client early."
171
+ end
172
+
173
+ false
174
+ end
175
+
176
+ def eagerly_finish
177
+ return true if @ready
178
+
179
+ if @io.kind_of? OpenSSL::SSL::SSLSocket
180
+ return true if jruby_start_try_to_finish
181
+ end
182
+
183
+ return false unless IO.select([@to_io], nil, nil, 0)
184
+ try_to_finish
185
+ end
186
+
187
+ else
188
+
189
+ def eagerly_finish
190
+ return true if @ready
191
+ return false unless IO.select([@to_io], nil, nil, 0)
192
+ try_to_finish
193
+ end
194
+ end # IS_JRUBY
195
+
196
+ def read_body
197
+ # Read an odd sized chunk so we can read even sized ones
198
+ # after this
199
+ remain = @body_remain
200
+
201
+ if remain > CHUNK_SIZE
202
+ want = CHUNK_SIZE
203
+ else
204
+ want = remain
205
+ end
206
+
207
+ chunk = @io.readpartial(want)
208
+
209
+ # No chunk means a closed socket
210
+ unless chunk
211
+ @body.close
212
+ @buffer = nil
213
+ @requests_served += 1
214
+ @ready = true
215
+ raise EOFError
216
+ end
217
+
218
+ remain -= @body.write(chunk)
219
+
220
+ if remain <= 0
221
+ @body.rewind
222
+ @buffer = nil
223
+ @requests_served += 1
224
+ @ready = true
225
+ return true
226
+ end
227
+
228
+ @body_remain = remain
229
+
230
+ false
231
+ end
232
+ end
233
+ end
@@ -25,12 +25,16 @@ module Puma
25
25
  # too taxing on performance.
26
26
  module Const
27
27
 
28
- PUMA_VERSION = VERSION = "1.5.0".freeze
28
+ PUMA_VERSION = VERSION = "1.6.0".freeze
29
29
 
30
30
  # The default number of seconds for another request within a persistent
31
31
  # session.
32
32
  PERSISTENT_TIMEOUT = 20
33
33
 
34
+ # The default number of seconds to wait until we get the first data
35
+ # for the request
36
+ FIRST_DATA_TIMEOUT = 30
37
+
34
38
  DATE = "Date".freeze
35
39
 
36
40
  SCRIPT_NAME = "SCRIPT_NAME".freeze
@@ -0,0 +1,124 @@
1
+ module Puma
2
+ class Reactor
3
+ DefaultSleepFor = 5
4
+
5
+ def initialize(server, app_pool)
6
+ @server = server
7
+ @events = server.events
8
+ @app_pool = app_pool
9
+
10
+ @mutex = Mutex.new
11
+ @ready, @trigger = IO.pipe
12
+ @input = []
13
+ @sleep_for = DefaultSleepFor
14
+ @timeouts = []
15
+ end
16
+
17
+ def run
18
+ sockets = [@ready]
19
+
20
+ while true
21
+ ready = IO.select sockets, nil, nil, @sleep_for
22
+
23
+ if ready and reads = ready[0]
24
+ reads.each do |c|
25
+ if c == @ready
26
+ @mutex.synchronize do
27
+ case @ready.read(1)
28
+ when "*"
29
+ sockets += @input
30
+ @input.clear
31
+ when "!"
32
+ return
33
+ end
34
+ end
35
+ else
36
+ # We have to be sure to remove it from the timeout
37
+ # list or we'll accidentally close the socket when
38
+ # it's in use!
39
+ if c.timeout_at
40
+ @timeouts.delete c
41
+ end
42
+
43
+ begin
44
+ if c.try_to_finish
45
+ @app_pool << c
46
+ sockets.delete c
47
+ end
48
+
49
+ # The client doesn't know HTTP well
50
+ rescue HttpParserError => e
51
+ c.close
52
+ sockets.delete c
53
+
54
+ @events.parse_error @server, c.env, e
55
+
56
+ rescue IOError => e
57
+ c.close
58
+ sockets.delete c
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ unless @timeouts.empty?
65
+ now = Time.now
66
+
67
+ while @timeouts.first.timeout_at < now
68
+ c = @timeouts.shift
69
+ sockets.delete c
70
+ c.close
71
+
72
+ break if @timeouts.empty?
73
+ end
74
+
75
+ calculate_sleep
76
+ end
77
+ end
78
+ end
79
+
80
+ def run_in_thread
81
+ @thread = Thread.new {
82
+ begin
83
+ run
84
+ rescue Exception => e
85
+ puts "MAJOR ERROR DETECTED"
86
+ p e
87
+ puts e.backtrace
88
+ end
89
+ }
90
+ end
91
+
92
+ def calculate_sleep
93
+ if @timeouts.empty?
94
+ @sleep_for = DefaultSleepFor
95
+ else
96
+ diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
97
+
98
+ if diff < 0.0
99
+ @sleep_for = 0
100
+ else
101
+ @sleep_for = diff
102
+ end
103
+ end
104
+ end
105
+
106
+ def add(c)
107
+ @mutex.synchronize do
108
+ @input << c
109
+ @trigger << "*"
110
+
111
+ if c.timeout_at
112
+ @timeouts << c
113
+ @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
114
+
115
+ calculate_sleep
116
+ end
117
+ end
118
+ end
119
+
120
+ def shutdown
121
+ @trigger << "!"
122
+ end
123
+ end
124
+ end
@@ -6,6 +6,8 @@ require 'puma/const'
6
6
  require 'puma/events'
7
7
  require 'puma/null_io'
8
8
  require 'puma/compat'
9
+ require 'puma/reactor'
10
+ require 'puma/client'
9
11
 
10
12
  require 'puma/puma_http11'
11
13
 
@@ -54,6 +56,8 @@ module Puma
54
56
  @persistent_timeout = PERSISTENT_TIMEOUT
55
57
  @persistent_check, @persistent_wakeup = IO.pipe
56
58
 
59
+ @first_data_timeout = FIRST_DATA_TIMEOUT
60
+
57
61
  @unix_paths = []
58
62
 
59
63
  @proto_env = {
@@ -61,7 +65,7 @@ module Puma
61
65
  "rack.errors".freeze => events.stderr,
62
66
  "rack.multithread".freeze => true,
63
67
  "rack.multiprocess".freeze => false,
64
- "rack.run_once".freeze => true,
68
+ "rack.run_once".freeze => false,
65
69
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
66
70
 
67
71
  # Rack blows up if this is an empty string, and Rack::Lint
@@ -196,10 +200,30 @@ module Puma
196
200
 
197
201
  @status = :run
198
202
 
199
- @thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client, env|
200
- process_client(client, env)
203
+ @thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client|
204
+ process_now = false
205
+
206
+ begin
207
+ process_now = client.eagerly_finish
208
+ rescue HttpParserError => e
209
+ client.close
210
+ @events.parse_error self, client.env, e
211
+ rescue IOError
212
+ client.close
213
+ else
214
+ if process_now
215
+ process_client client
216
+ else
217
+ client.set_timeout @first_data_timeout
218
+ @reactor.add client
219
+ end
220
+ end
201
221
  end
202
222
 
223
+ @reactor = Reactor.new self, @thread_pool
224
+
225
+ @reactor.run_in_thread
226
+
203
227
  if @auto_trim_time
204
228
  @thread_pool.auto_trim!(@auto_trim_time)
205
229
  end
@@ -225,7 +249,8 @@ module Puma
225
249
  if sock == check
226
250
  break if handle_check
227
251
  else
228
- pool << [sock.accept, @envs.fetch(sock, @proto_env)]
252
+ c = Client.new sock.accept, @envs.fetch(sock, @proto_env)
253
+ pool << c
229
254
  end
230
255
  end
231
256
  rescue Errno::ECONNABORTED
@@ -236,6 +261,7 @@ module Puma
236
261
  end
237
262
  end
238
263
 
264
+ @reactor.shutdown
239
265
  graceful_shutdown if @status == :stop
240
266
  ensure
241
267
  unless @status == :restart
@@ -270,72 +296,34 @@ module Puma
270
296
  # indicates that it supports keep alive, wait for another request before
271
297
  # returning.
272
298
  #
273
- def process_client(client, proto_env)
274
- parser = HttpParser.new
275
- close_socket = true
276
-
299
+ def process_client(client)
277
300
  begin
278
- while true
279
- parser.reset
280
-
281
- env = proto_env.dup
282
- data = client.readpartial(CHUNK_SIZE)
283
- nparsed = 0
284
-
285
- # Assumption: nparsed will always be less since data will get filled
286
- # with more after each parsing. If it doesn't get more then there was
287
- # a problem with the read operation on the client socket.
288
- # Effect is to stop processing when the socket can't fill the buffer
289
- # for further parsing.
290
- while nparsed < data.bytesize
291
- nparsed = parser.execute(env, data, nparsed)
292
-
293
- if parser.finished?
294
- cl = env[CONTENT_LENGTH]
295
-
296
- case handle_request(env, client, parser.body, cl)
297
- when false
298
- return
299
- when :async
300
- close_socket = false
301
- return
302
- end
303
-
304
- nparsed += parser.body.bytesize if cl
305
-
306
- if data.bytesize > nparsed
307
- data.slice!(0, nparsed)
308
- parser.reset
309
- env = @proto_env.dup
310
- nparsed = 0
311
- else
312
- unless ret = IO.select([client, @persistent_check], nil, nil, @persistent_timeout)
313
- raise EOFError, "Timed out persistent connection"
314
- end
301
+ close_socket = true
315
302
 
316
- return if ret.first.include? @persistent_check
317
- end
318
- else
319
- # Parser is not done, queue up more data to read and continue parsing
320
- chunk = client.readpartial(CHUNK_SIZE)
321
- return if !chunk or chunk.length == 0 # read failed, stop processing
322
-
323
- data << chunk
324
- if data.bytesize >= MAX_HEADER
325
- raise HttpParserError,
326
- "HEADER is longer than allowed, aborting client early."
327
- end
303
+ while true
304
+ case handle_request(client)
305
+ when false
306
+ return
307
+ when :async
308
+ close_socket = false
309
+ return
310
+ when true
311
+ unless client.reset
312
+ close_socket = false
313
+ client.set_timeout @persistent_timeout
314
+ @reactor.add client
315
+ return
328
316
  end
329
317
  end
330
318
  end
331
319
 
332
320
  # The client disconnected while we were reading data
333
- rescue EOFError, SystemCallError
321
+ rescue IOError, SystemCallError => e
334
322
  # Swallow them. The ensure tries to close +client+ down
335
323
 
336
324
  # The client doesn't know HTTP well
337
325
  rescue HttpParserError => e
338
- @events.parse_error self, env, e
326
+ @events.parse_error self, client.env, e
339
327
 
340
328
  # Server error
341
329
  rescue StandardError => e
@@ -390,10 +378,6 @@ module Puma
390
378
  env[REMOTE_ADDR] = client.peeraddr.last
391
379
  end
392
380
 
393
- # The object used for a request with no body. All requests with
394
- # no body share this one object since it has no state.
395
- EmptyBody = NullIO.new
396
-
397
381
  # Given the request +env+ from +client+ and a partial request body
398
382
  # in +body+, finish reading the body if there is one and invoke
399
383
  # the rack app. Then construct the response and write it back to
@@ -403,17 +387,15 @@ module Puma
403
387
  # was one. This is an optimization to keep from having to look
404
388
  # it up again.
405
389
  #
406
- def handle_request(env, client, body, cl)
390
+ def handle_request(req)
391
+ env = req.env
392
+ client = req.io
393
+
407
394
  normalize_env env, client
408
395
 
409
396
  env[PUMA_SOCKET] = client
410
397
 
411
- if cl
412
- body = read_body env, client, body, cl
413
- return false unless body
414
- else
415
- body = EmptyBody
416
- end
398
+ body = req.body
417
399
 
418
400
  env[RACK_INPUT] = body
419
401
  env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
@@ -37,7 +37,7 @@ module Puma
37
37
  end
38
38
  end
39
39
 
40
- attr_reader :spawned
40
+ attr_reader :spawned, :trim_requested
41
41
 
42
42
  # How many objects have yet to be processed by the pool?
43
43
  #
@@ -2,23 +2,23 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "puma"
5
- s.version = "1.5.0"
5
+ s.version = "1.6.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Evan Phoenix"]
9
- s.date = "2012-07-19"
9
+ s.date = "2012-08-12"
10
10
  s.description = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications. It can be used with any application that supports Rack, and is considered the replacement for Webrick and Mongrel. It was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. Puma is intended for use in both development and production environments.\n\nUnder the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool (which you can control). This allows Puma to provide real concurrency for your web application!\n\nWith Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning you won't have to spawn multiple processes to increase throughput. You can expect to see a similar benefit from JRuby.\n\nOn MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can be run at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently (EventMachine-based servers such as Thin turn off this ability, requiring you to use special libraries). Your mileage may vary. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org)."
11
11
  s.email = ["evan@phx.io"]
12
12
  s.executables = ["puma", "pumactl"]
13
13
  s.extensions = ["ext/puma_http11/extconf.rb"]
14
14
  s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
15
- s.files = [".travis.yml", "COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "examples/CA/cacert.pem", "examples/CA/newcerts/cert_1.pem", "examples/CA/newcerts/cert_2.pem", "examples/CA/private/cakeypair.pem", "examples/CA/serial", "examples/config.rb", "examples/puma/cert_puma.pem", "examples/puma/csr_puma.pem", "examples/puma/puma_keypair.pem", "examples/qc_config.rb", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/app/status.rb", "lib/puma/cli.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/events.rb", "lib/puma/jruby_restart.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "test/ab_rs.rb", "test/config/app.rb", "test/hello.ru", "test/lobster.ru", "test/mime.yaml", "test/slow.ru", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb"]
15
+ s.files = [".travis.yml", "COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "examples/CA/cacert.pem", "examples/CA/newcerts/cert_1.pem", "examples/CA/newcerts/cert_2.pem", "examples/CA/private/cakeypair.pem", "examples/CA/serial", "examples/config.rb", "examples/puma/cert_puma.pem", "examples/puma/csr_puma.pem", "examples/puma/puma_keypair.pem", "examples/qc_config.rb", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/app/status.rb", "lib/puma/cli.rb", "lib/puma/client.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/events.rb", "lib/puma/jruby_restart.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/reactor.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "test/ab_rs.rb", "test/config/app.rb", "test/hello-post.ru", "test/hello.ru", "test/lobster.ru", "test/mime.yaml", "test/slow.ru", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb"]
16
16
  s.homepage = "http://puma.io"
17
17
  s.rdoc_options = ["--main", "README.md"]
18
18
  s.require_paths = ["lib"]
19
19
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
20
20
  s.rubyforge_project = "puma"
21
- s.rubygems_version = "1.8.18"
21
+ s.rubygems_version = "1.8.24"
22
22
  s.summary = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications"
23
23
  s.test_files = ["test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
24
24
 
@@ -0,0 +1,4 @@
1
+ run lambda { |env|
2
+ p :body => env['rack.input'].read
3
+ [200, {"Content-Type" => "text/plain"}, ["Hello World"]]
4
+ }
@@ -22,8 +22,12 @@ class TestPersistent < Test::Unit::TestCase
22
22
  [status, @headers, @body]
23
23
  end
24
24
 
25
+ @host = "127.0.0.1"
26
+ @port = 9988
27
+
25
28
  @server = Puma::Server.new @simple
26
29
  @server.add_tcp_listener "127.0.0.1", 9988
30
+ @server.max_threads = 1
27
31
  @server.run
28
32
 
29
33
  @client = TCPSocket.new "127.0.0.1", 9988
@@ -212,7 +216,23 @@ class TestPersistent < Test::Unit::TestCase
212
216
 
213
217
  assert_kind_of Puma::NullIO, @inputs[0]
214
218
  assert_kind_of Puma::NullIO, @inputs[1]
219
+ end
220
+
221
+ def test_keepalive_doesnt_starve_clients
222
+ sz = @body[0].size.to_s
223
+
224
+ @client << @valid_request
225
+
226
+ c2 = TCPSocket.new @host, @port
227
+ c2 << @valid_request
228
+
229
+ out = IO.select([c2], nil, nil, 1)
230
+
231
+ assert out, "select returned nil"
232
+ assert_equal c2, out.first.first
215
233
 
234
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4, c2)
235
+ assert_equal "Hello", c2.read(5)
216
236
  end
217
237
 
218
238
  end
@@ -113,11 +113,11 @@ class TestThreadPool < Test::Unit::TestCase
113
113
  pool.trim
114
114
  pool.trim
115
115
 
116
+ assert_equal 0, pool.trim_requested
117
+
116
118
  finish = true
117
119
 
118
120
  pause
119
-
120
- assert_equal 2, pool.spawned
121
121
  end
122
122
 
123
123
  def test_autotrim
metadata CHANGED
@@ -1,102 +1,116 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: puma
3
- version: !ruby/object:Gem::Version
4
- hash: 3
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.6.0
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 5
9
- - 0
10
- version: 1.5.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Evan Phoenix
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-07-19 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rack
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 11
29
- segments:
30
- - 1
31
- - 2
32
- version: "1.2"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rdoc
37
23
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
39
25
  none: false
40
- requirements:
26
+ requirements:
41
27
  - - ~>
42
- - !ruby/object:Gem::Version
43
- hash: 19
44
- segments:
45
- - 3
46
- - 10
47
- version: "3.10"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.10'
48
38
  type: :development
49
- version_requirements: *id002
50
- - !ruby/object:Gem::Dependency
51
- name: rake-compiler
52
39
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
54
41
  none: false
55
- requirements:
42
+ requirements:
56
43
  - - ~>
57
- - !ruby/object:Gem::Version
58
- hash: 63
59
- segments:
60
- - 0
61
- - 8
62
- - 0
44
+ - !ruby/object:Gem::Version
45
+ version: '3.10'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake-compiler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
63
53
  version: 0.8.0
64
54
  type: :development
65
- version_requirements: *id003
66
- - !ruby/object:Gem::Dependency
67
- name: hoe
68
55
  prerelease: false
69
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: hoe
64
+ requirement: !ruby/object:Gem::Requirement
70
65
  none: false
71
- requirements:
66
+ requirements:
72
67
  - - ~>
73
- - !ruby/object:Gem::Version
74
- hash: 7
75
- segments:
76
- - 3
77
- - 0
78
- version: "3.0"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
79
70
  type: :development
80
- version_requirements: *id004
81
- description: |-
82
- Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications. It can be used with any application that supports Rack, and is considered the replacement for Webrick and Mongrel. It was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. Puma is intended for use in both development and production environments.
83
-
84
- Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool (which you can control). This allows Puma to provide real concurrency for your web application!
85
-
86
- With Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning you won't have to spawn multiple processes to increase throughput. You can expect to see a similar benefit from JRuby.
87
-
88
- On MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can be run at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently (EventMachine-based servers such as Thin turn off this ability, requiring you to use special libraries). Your mileage may vary. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org).
89
- email:
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ description: ! 'Puma is a simple, fast, and highly concurrent HTTP 1.1 server for
79
+ Ruby web applications. It can be used with any application that supports Rack, and
80
+ is considered the replacement for Webrick and Mongrel. It was designed to be the
81
+ go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and
82
+ MRI. Puma is intended for use in both development and production environments.
83
+
84
+
85
+ Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited
86
+ from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable
87
+ way. Puma then serves the request in a thread from an internal thread pool (which
88
+ you can control). This allows Puma to provide real concurrency for your web application!
89
+
90
+
91
+ With Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning
92
+ you won''t have to spawn multiple processes to increase throughput. You can expect
93
+ to see a similar benefit from JRuby.
94
+
95
+
96
+ On MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can
97
+ be run at a time. But if you''re doing a lot of blocking IO (such as HTTP calls
98
+ to external APIs like Twitter), Puma still improves MRI''s throughput by allowing
99
+ blocking IO to be run concurrently (EventMachine-based servers such as Thin turn
100
+ off this ability, requiring you to use special libraries). Your mileage may vary.
101
+ In order to get the best throughput, it is highly recommended that you use a Ruby
102
+ implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org).'
103
+ email:
90
104
  - evan@phx.io
91
- executables:
105
+ executables:
92
106
  - puma
93
107
  - pumactl
94
- extensions:
108
+ extensions:
95
109
  - ext/puma_http11/extconf.rb
96
- extra_rdoc_files:
110
+ extra_rdoc_files:
97
111
  - History.txt
98
112
  - Manifest.txt
99
- files:
113
+ files:
100
114
  - .travis.yml
101
115
  - COPYING
102
116
  - Gemfile
@@ -132,6 +146,7 @@ files:
132
146
  - lib/puma.rb
133
147
  - lib/puma/app/status.rb
134
148
  - lib/puma/cli.rb
149
+ - lib/puma/client.rb
135
150
  - lib/puma/compat.rb
136
151
  - lib/puma/configuration.rb
137
152
  - lib/puma/const.rb
@@ -140,12 +155,14 @@ files:
140
155
  - lib/puma/jruby_restart.rb
141
156
  - lib/puma/null_io.rb
142
157
  - lib/puma/rack_patch.rb
158
+ - lib/puma/reactor.rb
143
159
  - lib/puma/server.rb
144
160
  - lib/puma/thread_pool.rb
145
161
  - lib/rack/handler/puma.rb
146
162
  - puma.gemspec
147
163
  - test/ab_rs.rb
148
164
  - test/config/app.rb
165
+ - test/hello-post.ru
149
166
  - test/hello.ru
150
167
  - test/lobster.ru
151
168
  - test/mime.yaml
@@ -168,41 +185,32 @@ files:
168
185
  - tools/trickletest.rb
169
186
  homepage: http://puma.io
170
187
  licenses: []
171
-
172
188
  post_install_message:
173
- rdoc_options:
189
+ rdoc_options:
174
190
  - --main
175
191
  - README.md
176
- require_paths:
192
+ require_paths:
177
193
  - lib
178
- required_ruby_version: !ruby/object:Gem::Requirement
194
+ required_ruby_version: !ruby/object:Gem::Requirement
179
195
  none: false
180
- requirements:
181
- - - ">="
182
- - !ruby/object:Gem::Version
183
- hash: 57
184
- segments:
185
- - 1
186
- - 8
187
- - 7
196
+ requirements:
197
+ - - ! '>='
198
+ - !ruby/object:Gem::Version
188
199
  version: 1.8.7
189
- required_rubygems_version: !ruby/object:Gem::Requirement
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
201
  none: false
191
- requirements:
192
- - - ">="
193
- - !ruby/object:Gem::Version
194
- hash: 3
195
- segments:
196
- - 0
197
- version: "0"
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
198
206
  requirements: []
199
-
200
207
  rubyforge_project: puma
201
- rubygems_version: 1.8.18
208
+ rubygems_version: 1.8.24
202
209
  signing_key:
203
210
  specification_version: 3
204
- summary: Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications
205
- test_files:
211
+ summary: Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web
212
+ applications
213
+ test_files:
206
214
  - test/test_app_status.rb
207
215
  - test/test_cli.rb
208
216
  - test/test_config.rb