puma 2.7.1 → 2.8.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/History.txt +43 -0
- data/Manifest.txt +1 -0
- data/README.md +10 -2
- data/Rakefile +1 -0
- data/docs/signals.md +42 -0
- data/lib/puma/binder.rb +8 -3
- data/lib/puma/capistrano.rb +7 -3
- data/lib/puma/cli.rb +66 -7
- data/lib/puma/client.rb +12 -0
- data/lib/puma/cluster.rb +81 -16
- data/lib/puma/configuration.rb +42 -5
- data/lib/puma/const.rb +9 -2
- data/lib/puma/reactor.rb +2 -1
- data/lib/puma/runner.rb +9 -1
- data/lib/puma/server.rb +17 -6
- data/lib/puma/thread_pool.rb +1 -1
- data/puma.gemspec +1 -0
- data/test/test_config.rb +10 -0
- data/test/test_http11.rb +1 -1
- data/test/test_puma_server.rb +31 -0
- data/test/test_unix_socket.rb +1 -1
- data/tools/jungle/upstart/puma.conf +1 -1
- metadata +21 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 898a1954d5ebcc2e339fa1b455a0f772922550d9
|
4
|
+
data.tar.gz: ed25f3dcea9438077229cfbfc9e4feca966a1190
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81e27d75599ba7fe68b342c375ba5bf1f80f8e6859072950ea20beb3de14a5c9c18272231c3d66734bab76622a258489a8e9bad6c8bedaaf7aae67e22dcfd980
|
7
|
+
data.tar.gz: 17251b4e6c9af4e7af501e3974e7c42e836ac95798793e8b05df01cdb19cd8d9178ed960b84adb2030fb475d584fb0dee6fc01ff07e0ae5be40dd98199dba079
|
data/Gemfile
CHANGED
data/History.txt
CHANGED
@@ -1,3 +1,46 @@
|
|
1
|
+
=== 2.8.0 / 2014-02-28
|
2
|
+
|
3
|
+
* 8 minor features:
|
4
|
+
* Add ability to autoload a config file. Fixes #438
|
5
|
+
* Add ability to detect and terminate hung workers. Fixes #333
|
6
|
+
* Add booted_workers to stats response
|
7
|
+
* Add config to customize the default error message
|
8
|
+
* Add prune_bundler option
|
9
|
+
* Add worker indexes, expose them via on_worker_boot. Fixes #440
|
10
|
+
* Add pretty process name
|
11
|
+
* Show the ruby version in use
|
12
|
+
|
13
|
+
* 7 bug fixes:
|
14
|
+
* Added 408 status on timeout.
|
15
|
+
* Be more hostile with sockets that write block. Fixes #449
|
16
|
+
* Expect at_exit to exclusively remove the pidfile. Fixes #444
|
17
|
+
* Expose latency and listen backlog via bind query. Fixes #370
|
18
|
+
* JRuby raises IOError if the socket is there. Fixes #377
|
19
|
+
* Process requests fairly. Fixes #406
|
20
|
+
* Rescue SystemCallError as well. Fixes #425
|
21
|
+
|
22
|
+
* 4 doc changes:
|
23
|
+
* Add 2.1.0 to the matrix
|
24
|
+
* Add Code Climate badge to README
|
25
|
+
* Create signals.md
|
26
|
+
* Set the license to BSD. Fixes #432
|
27
|
+
|
28
|
+
* 14 PRs merged:
|
29
|
+
* Merge pull request #428 from alexeyfrank/capistrano_default_hooks
|
30
|
+
* Merge pull request #429 from namusyaka/revert-const_defined
|
31
|
+
* Merge pull request #431 from mrb/master
|
32
|
+
* Merge pull request #433 from alepore/process-name
|
33
|
+
* Merge pull request #437 from ibrahima/master
|
34
|
+
* Merge pull request #446 from sudara/master
|
35
|
+
* Merge pull request #451 from pwiebe/status_408
|
36
|
+
* Merge pull request #453 from joevandyk/patch-1
|
37
|
+
* Merge pull request #470 from arthurnn/fix_458
|
38
|
+
* Merge pull request #472 from rubencaro/master
|
39
|
+
* Merge pull request #480 from jjb/docs-on-running-test-suite
|
40
|
+
* Merge pull request #481 from schneems/master
|
41
|
+
* Merge pull request #482 from prathamesh-sonpatki/signals-doc-cleanup
|
42
|
+
* Merge pull request #483 from YotpoLtd/master
|
43
|
+
|
1
44
|
=== 2.7.1 / 2013-12-05
|
2
45
|
|
3
46
|
* 1 bug fix:
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Puma: A Ruby Web Server Built For Concurrency
|
2
2
|
|
3
|
-
[![Build Status](https://secure.travis-ci.org/puma/puma.png)](http://travis-ci.org/puma/puma) [![Dependency Status](https://gemnasium.com/puma/puma.png)](https://gemnasium.com/puma/puma)
|
3
|
+
[![Build Status](https://secure.travis-ci.org/puma/puma.png)](http://travis-ci.org/puma/puma) [![Dependency Status](https://gemnasium.com/puma/puma.png)](https://gemnasium.com/puma/puma) <a href="https://codeclimate.com/github/puma/puma"><img src="https://codeclimate.com/github/puma/puma.png" /></a>
|
4
4
|
|
5
5
|
## Description
|
6
6
|
|
@@ -82,7 +82,7 @@ Puma 2 offers clustered mode, allowing you to use forked processes to handle mul
|
|
82
82
|
On a ruby implementation that offers native threads, you should tune this number to match the number of cores available.
|
83
83
|
Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will be 32 threads.
|
84
84
|
|
85
|
-
If you're running in Clustered Mode you can optionally choose to preload your application before starting up the workers. This is necessary in order to take
|
85
|
+
If you're running in Clustered Mode you can optionally choose to preload your application before starting up the workers. This is necessary in order to take advantage of the [Copy on Write](http://en.wikipedia.org/wiki/Copy-on-write) feature introduced in [MRI Ruby 2.0](https://blog.heroku.com/archives/2013/3/6/matz_highlights_ruby_2_0_at_waza). To do this simply specify the `--preload` flag in invocation:
|
86
86
|
|
87
87
|
# CLI invocation
|
88
88
|
$ puma -t 8:32 -w 3 --preload
|
@@ -205,6 +205,14 @@ $ bundle exec cap puma:stop
|
|
205
205
|
$ bundle exec cap puma:phased_restart
|
206
206
|
```
|
207
207
|
|
208
|
+
## Contributing
|
209
|
+
|
210
|
+
To run the test suite:
|
211
|
+
|
212
|
+
```bash
|
213
|
+
$ bundle install
|
214
|
+
$ bundle exec rake
|
215
|
+
```
|
208
216
|
|
209
217
|
## License
|
210
218
|
|
data/Rakefile
CHANGED
data/docs/signals.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
The [unix signal](http://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](http://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster.
|
2
|
+
|
3
|
+
## Sending Signals
|
4
|
+
|
5
|
+
If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](http://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file:
|
6
|
+
|
7
|
+
```sh
|
8
|
+
$ echo "foo" >> my.log
|
9
|
+
$ irb
|
10
|
+
> pid = Process.spawn 'tail -f my.log'
|
11
|
+
```
|
12
|
+
|
13
|
+
From here we can see that the tail process is running by using the `ps` command:
|
14
|
+
|
15
|
+
```sh
|
16
|
+
$ ps aux | grep tail
|
17
|
+
schneems 87152 0.0 0.0 2432772 492 s032 S+ 12:46PM 0:00.00 tail -f my.log
|
18
|
+
```
|
19
|
+
|
20
|
+
You can send a signal in Ruby using the [Process module](http://www.ruby-doc.org/core-2.1.1/Process.html#kill-method):
|
21
|
+
|
22
|
+
```
|
23
|
+
$ irb
|
24
|
+
> puts pid
|
25
|
+
=> 87152
|
26
|
+
Process.detach(pid) # http://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
|
27
|
+
Process.kill("TERM", pid)
|
28
|
+
```
|
29
|
+
|
30
|
+
Now you will see via `ps` that there is no more `tail` process. Sometimes when referring to signals the `SIG` prefix will be used for instance `SIGTERM` is equivalent to sending `TERM` via `Process.kill`.
|
31
|
+
|
32
|
+
## Puma Signals
|
33
|
+
|
34
|
+
Puma cluster responds to these signals:
|
35
|
+
|
36
|
+
- `TTIN` increment the worker count by 1
|
37
|
+
- `TTOU` decrement the worker count by 1
|
38
|
+
- `TERM` send `TERM` to worker. Worker will attempt to finish then exit.
|
39
|
+
- `USR2` restart workers
|
40
|
+
- `USR1` restart workers in phases, a rolling restart.
|
41
|
+
- `INT` equivalent of sending Ctrl-C to cluster. Will attempt to finish then exit.
|
42
|
+
- `CHLD`
|
data/lib/puma/binder.rb
CHANGED
@@ -87,8 +87,13 @@ module Puma
|
|
87
87
|
logger.log "* Inherited #{str}"
|
88
88
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
89
89
|
else
|
90
|
+
params = Rack::Utils.parse_query uri.query
|
91
|
+
|
92
|
+
opt = params.key?('low_latency')
|
93
|
+
bak = params.fetch('backlog', 1024).to_i
|
94
|
+
|
90
95
|
logger.log "* Listening on #{str}"
|
91
|
-
io = add_tcp_listener uri.host, uri.port
|
96
|
+
io = add_tcp_listener uri.host, uri.port, opt, bak
|
92
97
|
end
|
93
98
|
|
94
99
|
@listeners << [str, io]
|
@@ -256,10 +261,10 @@ module Puma
|
|
256
261
|
begin
|
257
262
|
old_mask = File.umask(umask)
|
258
263
|
|
259
|
-
if File.
|
264
|
+
if File.exist? path
|
260
265
|
begin
|
261
266
|
old = UNIXSocket.new path
|
262
|
-
rescue SystemCallError
|
267
|
+
rescue SystemCallError, IOError
|
263
268
|
File.unlink path
|
264
269
|
else
|
265
270
|
old.close
|
data/lib/puma/capistrano.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
Capistrano::Configuration.instance.load do
|
2
|
-
after 'deploy:stop', 'puma:stop'
|
3
|
-
after 'deploy:start', 'puma:start'
|
4
|
-
after 'deploy:restart', 'puma:restart'
|
5
2
|
|
6
3
|
# Ensure the tmp/sockets directory is created by the deploy:setup task and
|
7
4
|
# symlinked in by the deploy:update task. This is not handled by Capistrano
|
8
5
|
# v2 but is fixed in v3.
|
9
6
|
shared_children.push('tmp/sockets')
|
10
7
|
|
8
|
+
_cset(:puma_default_hooks) { true }
|
11
9
|
_cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
|
12
10
|
_cset(:pumactl_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec pumactl" }
|
13
11
|
_cset(:puma_env) { fetch(:rack_env, fetch(:rails_env, 'production')) }
|
@@ -15,6 +13,12 @@ Capistrano::Configuration.instance.load do
|
|
15
13
|
_cset(:puma_socket) { "unix://#{shared_path}/sockets/puma.sock" }
|
16
14
|
_cset(:puma_role) { :app }
|
17
15
|
|
16
|
+
if fetch(:puma_default_hooks)
|
17
|
+
after 'deploy:stop', 'puma:stop'
|
18
|
+
after 'deploy:start', 'puma:start'
|
19
|
+
after 'deploy:restart', 'puma:restart'
|
20
|
+
end
|
21
|
+
|
18
22
|
namespace :puma do
|
19
23
|
desc 'Start puma'
|
20
24
|
task :start, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
|
data/lib/puma/cli.rb
CHANGED
@@ -99,7 +99,8 @@ module Puma
|
|
99
99
|
:binds => [],
|
100
100
|
:workers => 0,
|
101
101
|
:daemon => false,
|
102
|
-
:
|
102
|
+
:before_worker_boot => [],
|
103
|
+
:after_worker_boot => []
|
103
104
|
}
|
104
105
|
|
105
106
|
@parser = OptionParser.new do |o|
|
@@ -161,6 +162,10 @@ module Puma
|
|
161
162
|
@options[:preload_app] = true
|
162
163
|
end
|
163
164
|
|
165
|
+
o.on "--prune-bundler", "Prune out the bundler env if possible" do
|
166
|
+
@options[:prune_bundler] = true
|
167
|
+
end
|
168
|
+
|
164
169
|
o.on "-q", "--quiet", "Quiet down the output" do
|
165
170
|
@options[:quiet] = true
|
166
171
|
end
|
@@ -200,6 +205,9 @@ module Puma
|
|
200
205
|
@options[:workers] = arg.to_i
|
201
206
|
end
|
202
207
|
|
208
|
+
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
209
|
+
@options[:tag] = arg
|
210
|
+
end
|
203
211
|
end
|
204
212
|
|
205
213
|
@parser.banner = "puma <options> <rackup file>"
|
@@ -220,7 +228,7 @@ module Puma
|
|
220
228
|
|
221
229
|
cfg = @config.dup
|
222
230
|
|
223
|
-
[ :logger, :
|
231
|
+
[ :logger, :before_worker_boot, :after_worker_boot, :on_restart ].each { |o| cfg.options.delete o }
|
224
232
|
|
225
233
|
state["config"] = cfg
|
226
234
|
|
@@ -263,10 +271,31 @@ module Puma
|
|
263
271
|
|
264
272
|
def delete_pidfile
|
265
273
|
if path = @options[:pidfile]
|
266
|
-
File.unlink path if File.
|
274
|
+
File.unlink path if File.exist? path
|
267
275
|
end
|
268
276
|
end
|
269
277
|
|
278
|
+
def find_config
|
279
|
+
if cfg = @options[:config_file]
|
280
|
+
# Allow - to disable config finding
|
281
|
+
if cfg == "-"
|
282
|
+
@options[:config_file] = nil
|
283
|
+
return
|
284
|
+
end
|
285
|
+
|
286
|
+
return
|
287
|
+
end
|
288
|
+
|
289
|
+
pos = []
|
290
|
+
|
291
|
+
if env = (@options[:environment] || ENV['RACK_ENV'])
|
292
|
+
pos << "config/puma/#{env}.rb"
|
293
|
+
end
|
294
|
+
|
295
|
+
pos << "config/puma.rb"
|
296
|
+
@options[:config_file] = pos.find { |f| File.exist? f }
|
297
|
+
end
|
298
|
+
|
270
299
|
# :nodoc:
|
271
300
|
def parse_options
|
272
301
|
@parser.parse! @argv
|
@@ -275,6 +304,8 @@ module Puma
|
|
275
304
|
@options[:rackup] = @argv.shift
|
276
305
|
end
|
277
306
|
|
307
|
+
find_config
|
308
|
+
|
278
309
|
@config = Puma::Configuration.new @options
|
279
310
|
|
280
311
|
# Advertise the Configuration
|
@@ -328,7 +359,7 @@ module Puma
|
|
328
359
|
# it the same, otherwise add -S on there because it was
|
329
360
|
# picked up in PATH.
|
330
361
|
#
|
331
|
-
if File.
|
362
|
+
if File.exist?($0)
|
332
363
|
arg0 = [Gem.ruby, $0]
|
333
364
|
else
|
334
365
|
arg0 = [Gem.ruby, "-S", $0]
|
@@ -410,6 +441,10 @@ module Puma
|
|
410
441
|
end
|
411
442
|
end
|
412
443
|
|
444
|
+
def prune_bundler?
|
445
|
+
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
446
|
+
end
|
447
|
+
|
413
448
|
# Parse the options, load the rackup, start the server and wait
|
414
449
|
# for it to finish.
|
415
450
|
#
|
@@ -420,6 +455,21 @@ module Puma
|
|
420
455
|
exit 1
|
421
456
|
end
|
422
457
|
|
458
|
+
if prune_bundler? && defined?(Bundler)
|
459
|
+
puma_lib_dir = $:.detect { |d| File.exist? File.join(d, "puma", "const.rb") }
|
460
|
+
|
461
|
+
if puma_lib_dir
|
462
|
+
log "* Pruning Bundler environment"
|
463
|
+
Bundler.with_clean_env do
|
464
|
+
Kernel.exec(Gem.ruby, "-I", puma_lib_dir,
|
465
|
+
File.expand_path(puma_lib_dir + "/../bin/puma"),
|
466
|
+
*@original_argv)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
log "! Unable to prune Bundler environment, continuing"
|
471
|
+
end
|
472
|
+
|
423
473
|
if dir = @options[:directory]
|
424
474
|
Dir.chdir dir
|
425
475
|
end
|
@@ -436,6 +486,7 @@ module Puma
|
|
436
486
|
end
|
437
487
|
|
438
488
|
setup_signals
|
489
|
+
set_process_title
|
439
490
|
|
440
491
|
@status = :run
|
441
492
|
|
@@ -453,9 +504,6 @@ module Puma
|
|
453
504
|
when :exit
|
454
505
|
# nothing
|
455
506
|
end
|
456
|
-
|
457
|
-
ensure
|
458
|
-
delete_pidfile
|
459
507
|
end
|
460
508
|
|
461
509
|
def setup_signals
|
@@ -518,5 +566,16 @@ module Puma
|
|
518
566
|
@status = :halt
|
519
567
|
@runner.halt
|
520
568
|
end
|
569
|
+
|
570
|
+
private
|
571
|
+
def title
|
572
|
+
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
573
|
+
buffer << " [#{@options[:tag]}]" if @options[:tag]
|
574
|
+
buffer
|
575
|
+
end
|
576
|
+
|
577
|
+
def set_process_title
|
578
|
+
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
579
|
+
end
|
521
580
|
end
|
522
581
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -59,6 +59,10 @@ module Puma
|
|
59
59
|
env[HIJACK_IO] ||= @io
|
60
60
|
end
|
61
61
|
|
62
|
+
def in_data_phase
|
63
|
+
!@read_header
|
64
|
+
end
|
65
|
+
|
62
66
|
def set_timeout(val)
|
63
67
|
@timeout_at = Time.now + val
|
64
68
|
end
|
@@ -100,6 +104,7 @@ module Puma
|
|
100
104
|
EmptyBody = NullIO.new
|
101
105
|
|
102
106
|
def setup_body
|
107
|
+
@in_data_phase = true
|
103
108
|
body = @parser.body
|
104
109
|
cl = @env[CONTENT_LENGTH]
|
105
110
|
|
@@ -267,6 +272,13 @@ module Puma
|
|
267
272
|
end
|
268
273
|
end
|
269
274
|
|
275
|
+
def write_408
|
276
|
+
begin
|
277
|
+
@io << ERROR_408_RESPONSE
|
278
|
+
rescue StandardError
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
270
282
|
def write_500
|
271
283
|
begin
|
272
284
|
@io << ERROR_500_RESPONSE
|
data/lib/puma/cluster.rb
CHANGED
@@ -7,6 +7,7 @@ module Puma
|
|
7
7
|
|
8
8
|
@phase = 0
|
9
9
|
@workers = []
|
10
|
+
@next_check = nil
|
10
11
|
|
11
12
|
@phased_state = :idle
|
12
13
|
@phased_restart = false
|
@@ -29,23 +30,34 @@ module Puma
|
|
29
30
|
end
|
30
31
|
|
31
32
|
class Worker
|
32
|
-
def initialize(pid, phase)
|
33
|
+
def initialize(idx, pid, phase)
|
34
|
+
@index = idx
|
33
35
|
@pid = pid
|
34
36
|
@phase = phase
|
35
37
|
@stage = :started
|
36
38
|
@signal = "TERM"
|
39
|
+
@last_checkin = Time.now
|
37
40
|
end
|
38
41
|
|
39
|
-
attr_reader :pid, :phase, :signal
|
42
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin
|
40
43
|
|
41
44
|
def booted?
|
42
45
|
@stage == :booted
|
43
46
|
end
|
44
47
|
|
45
48
|
def boot!
|
49
|
+
@last_checkin = Time.now
|
46
50
|
@stage = :booted
|
47
51
|
end
|
48
52
|
|
53
|
+
def ping!
|
54
|
+
@last_checkin = Time.now
|
55
|
+
end
|
56
|
+
|
57
|
+
def ping_timeout?(which)
|
58
|
+
Time.now - @last_checkin > which
|
59
|
+
end
|
60
|
+
|
49
61
|
def term
|
50
62
|
begin
|
51
63
|
if @first_term_sent && (Time.new - @first_term_sent) > 30
|
@@ -58,6 +70,11 @@ module Puma
|
|
58
70
|
rescue Errno::ESRCH
|
59
71
|
end
|
60
72
|
end
|
73
|
+
|
74
|
+
def kill
|
75
|
+
Process.kill "KILL", @pid
|
76
|
+
rescue Errno::ESRCH
|
77
|
+
end
|
61
78
|
end
|
62
79
|
|
63
80
|
def spawn_workers
|
@@ -68,9 +85,12 @@ module Puma
|
|
68
85
|
master = Process.pid
|
69
86
|
|
70
87
|
diff.times do
|
71
|
-
|
88
|
+
idx = next_worker_index
|
89
|
+
|
90
|
+
pid = fork { worker(idx, upgrade, master) }
|
72
91
|
@cli.debug "Spawned worker: #{pid}"
|
73
|
-
@workers << Worker.new(pid, @phase)
|
92
|
+
@workers << Worker.new(idx, pid, @phase)
|
93
|
+
@options[:after_worker_boot].each { |h| h.call }
|
74
94
|
end
|
75
95
|
|
76
96
|
if diff > 0
|
@@ -78,11 +98,36 @@ module Puma
|
|
78
98
|
end
|
79
99
|
end
|
80
100
|
|
101
|
+
def next_worker_index
|
102
|
+
all_positions = 0...@options[:workers]
|
103
|
+
occupied_positions = @workers.map { |w| w.index }
|
104
|
+
available_positions = all_positions.to_a - occupied_positions
|
105
|
+
available_positions.first
|
106
|
+
end
|
107
|
+
|
81
108
|
def all_workers_booted?
|
82
109
|
@workers.count { |w| !w.booted? } == 0
|
83
110
|
end
|
84
111
|
|
85
112
|
def check_workers
|
113
|
+
return if @next_check && @next_check >= Time.now
|
114
|
+
|
115
|
+
@next_check = Time.now + 5
|
116
|
+
|
117
|
+
any = false
|
118
|
+
|
119
|
+
@workers.each do |w|
|
120
|
+
if w.ping_timeout?(@options[:worker_timeout])
|
121
|
+
log "! Terminating timed out worker: #{w.pid}"
|
122
|
+
w.kill
|
123
|
+
any = true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# If we killed any timed out workers, try to catch them
|
128
|
+
# during this loop by giving the kernel time to kill them.
|
129
|
+
sleep 1 if any
|
130
|
+
|
86
131
|
while @workers.any?
|
87
132
|
pid = Process.waitpid(-1, Process::WNOHANG)
|
88
133
|
break unless pid
|
@@ -118,8 +163,8 @@ module Puma
|
|
118
163
|
end
|
119
164
|
end
|
120
165
|
|
121
|
-
def worker(upgrade, master)
|
122
|
-
$0 = "puma: cluster worker: #{master}"
|
166
|
+
def worker(index, upgrade, master)
|
167
|
+
$0 = "puma: cluster worker #{index}: #{master}"
|
123
168
|
Signal.trap "SIGINT", "IGNORE"
|
124
169
|
|
125
170
|
@master_read.close
|
@@ -140,10 +185,16 @@ module Puma
|
|
140
185
|
end
|
141
186
|
end
|
142
187
|
|
188
|
+
# If we're not running under a Bundler context, then
|
189
|
+
# report the info about the context we will be using
|
190
|
+
if !ENV['BUNDLER_GEMFILE'] and File.exist?("Gemfile")
|
191
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
192
|
+
end
|
193
|
+
|
143
194
|
# Invoke any worker boot hooks so they can get
|
144
195
|
# things in shape before booting the app.
|
145
|
-
hooks = @options[:
|
146
|
-
hooks.each { |h| h.call }
|
196
|
+
hooks = @options[:before_worker_boot]
|
197
|
+
hooks.each { |h| h.call(index) }
|
147
198
|
|
148
199
|
server = start_server
|
149
200
|
|
@@ -158,6 +209,15 @@ module Puma
|
|
158
209
|
return
|
159
210
|
end
|
160
211
|
|
212
|
+
Thread.new(@worker_write) do |io|
|
213
|
+
payload = "p#{Process.pid}\n"
|
214
|
+
|
215
|
+
while true
|
216
|
+
sleep 5
|
217
|
+
io << payload
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
161
221
|
server.run.join
|
162
222
|
|
163
223
|
ensure
|
@@ -196,7 +256,7 @@ module Puma
|
|
196
256
|
end
|
197
257
|
|
198
258
|
def stats
|
199
|
-
%Q!{ "workers": #{@workers.size}, "phase": #{@phase} }!
|
259
|
+
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{@workers.count{|w| w.booted?}} }!
|
200
260
|
end
|
201
261
|
|
202
262
|
def preload?
|
@@ -292,15 +352,20 @@ module Puma
|
|
292
352
|
if res
|
293
353
|
req = read.read_nonblock(1)
|
294
354
|
|
295
|
-
if req == "
|
296
|
-
|
297
|
-
|
298
|
-
|
355
|
+
next if !req || req == "!"
|
356
|
+
|
357
|
+
pid = read.gets.to_i
|
358
|
+
|
359
|
+
if w = @workers.find { |x| x.pid == pid }
|
360
|
+
case req
|
361
|
+
when "b"
|
299
362
|
w.boot!
|
300
|
-
log "- Worker #{pid} booted, phase: #{w.phase}"
|
301
|
-
|
302
|
-
|
363
|
+
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
364
|
+
when "p"
|
365
|
+
w.ping!
|
303
366
|
end
|
367
|
+
else
|
368
|
+
log "! Out-of-sync worker list, no #{pid} worker"
|
304
369
|
end
|
305
370
|
end
|
306
371
|
|
data/lib/puma/configuration.rb
CHANGED
@@ -14,13 +14,16 @@ module Puma
|
|
14
14
|
|
15
15
|
DefaultTCPHost = "0.0.0.0"
|
16
16
|
DefaultTCPPort = 9292
|
17
|
+
DefaultWorkerTimeout = 60
|
17
18
|
|
18
19
|
def initialize(options)
|
19
20
|
@options = options
|
20
21
|
@options[:mode] ||= :http
|
21
22
|
@options[:binds] ||= []
|
22
23
|
@options[:on_restart] ||= []
|
23
|
-
@options[:
|
24
|
+
@options[:before_worker_boot] ||= []
|
25
|
+
@options[:after_worker_boot] ||= []
|
26
|
+
@options[:worker_timeout] ||= DefaultWorkerTimeout
|
24
27
|
end
|
25
28
|
|
26
29
|
attr_reader :options
|
@@ -72,7 +75,7 @@ module Puma
|
|
72
75
|
# Indicate if there is a properly configured app
|
73
76
|
#
|
74
77
|
def app_configured?
|
75
|
-
@options[:app] || File.
|
78
|
+
@options[:app] || File.exist?(rackup)
|
76
79
|
end
|
77
80
|
|
78
81
|
def rackup
|
@@ -86,7 +89,7 @@ module Puma
|
|
86
89
|
app = @options[:app]
|
87
90
|
|
88
91
|
unless app
|
89
|
-
unless File.
|
92
|
+
unless File.exist?(rackup)
|
90
93
|
raise "Missing rackup file '#{rackup}'"
|
91
94
|
end
|
92
95
|
|
@@ -127,7 +130,7 @@ module Puma
|
|
127
130
|
|
128
131
|
if defined? OpenSSL::Random
|
129
132
|
bytes = OpenSSL::Random.random_bytes(count)
|
130
|
-
elsif File.
|
133
|
+
elsif File.exist?("/dev/urandom")
|
131
134
|
File.open("/dev/urandom") do |f|
|
132
135
|
bytes = f.read(count)
|
133
136
|
end
|
@@ -304,7 +307,16 @@ module Puma
|
|
304
307
|
# This can be called multiple times to add hooks.
|
305
308
|
#
|
306
309
|
def on_worker_boot(&block)
|
307
|
-
@options[:
|
310
|
+
@options[:before_worker_boot] << block
|
311
|
+
end
|
312
|
+
|
313
|
+
# *Cluster mode only* Code to run when a worker boots to setup
|
314
|
+
# the process after booting the app.
|
315
|
+
#
|
316
|
+
# This can be called multiple times to add hooks.
|
317
|
+
#
|
318
|
+
def after_worker_boot(&block)
|
319
|
+
@options[:after_worker_boot] << block
|
308
320
|
end
|
309
321
|
|
310
322
|
# The directory to operate out of.
|
@@ -325,6 +337,31 @@ module Puma
|
|
325
337
|
def preload_app!(answer=true)
|
326
338
|
@options[:preload_app] = answer
|
327
339
|
end
|
340
|
+
|
341
|
+
# Use +obj+ or +block+ as the low lever error handler. This allows a config file to
|
342
|
+
# change the default error on the server.
|
343
|
+
#
|
344
|
+
def lowlevel_error_handler(obj=nil, &block)
|
345
|
+
obj ||= block
|
346
|
+
raise "Provide either a #call'able or a block" unless obj
|
347
|
+
@options[:lowlevel_error_handler] = obj
|
348
|
+
end
|
349
|
+
|
350
|
+
# This option is used to allow your app and it's gems to be
|
351
|
+
# properly reloaded when not using preload.
|
352
|
+
#
|
353
|
+
# When set, if puma detects that it's been invoked in the
|
354
|
+
# context of Bundler, it will cleanup the environment and
|
355
|
+
# re-run itself outside the Bundler environment, but directly
|
356
|
+
# using the files that Bundler has setup.
|
357
|
+
#
|
358
|
+
# This means that puma is now decoupled from your Bundler
|
359
|
+
# context and when each worker loads, it will be loading a
|
360
|
+
# new Bundler context and thus can float around as the release
|
361
|
+
# dictates.
|
362
|
+
def prune_bundler(answer=true)
|
363
|
+
@options[:prune_bundler] = answer
|
364
|
+
end
|
328
365
|
end
|
329
366
|
end
|
330
367
|
end
|
data/lib/puma/const.rb
CHANGED
@@ -28,8 +28,8 @@ module Puma
|
|
28
28
|
# too taxing on performance.
|
29
29
|
module Const
|
30
30
|
|
31
|
-
PUMA_VERSION = VERSION = "2.
|
32
|
-
CODE_NAME = "
|
31
|
+
PUMA_VERSION = VERSION = "2.8.0".freeze
|
32
|
+
CODE_NAME = "Sir Edmund Percival Hillary"
|
33
33
|
|
34
34
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
35
35
|
|
@@ -41,6 +41,10 @@ module Puma
|
|
41
41
|
# for the request
|
42
42
|
FIRST_DATA_TIMEOUT = 30
|
43
43
|
|
44
|
+
# How long to wait when getting some write blocking on the socket when
|
45
|
+
# sending data back
|
46
|
+
WRITE_TIMEOUT = 10
|
47
|
+
|
44
48
|
DATE = "Date".freeze
|
45
49
|
|
46
50
|
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
@@ -59,6 +63,9 @@ module Puma
|
|
59
63
|
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
60
64
|
ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
|
61
65
|
|
66
|
+
# The standard empty 408 response for requests that timed out.
|
67
|
+
ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
|
68
|
+
|
62
69
|
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
63
70
|
|
64
71
|
# Indicate that there was an internal error, obviously.
|
data/lib/puma/reactor.rb
CHANGED
data/lib/puma/runner.rb
CHANGED
@@ -62,12 +62,20 @@ module Puma
|
|
62
62
|
@control = control
|
63
63
|
end
|
64
64
|
|
65
|
+
def ruby_engine
|
66
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
67
|
+
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
68
|
+
else
|
69
|
+
"#{RUBY_ENGINE} #{RUBY_VERSION}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
65
73
|
def output_header(mode)
|
66
74
|
min_t = @options[:min_threads]
|
67
75
|
max_t = @options[:max_threads]
|
68
76
|
|
69
77
|
log "Puma starting in #{mode} mode..."
|
70
|
-
log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
|
78
|
+
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
71
79
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
72
80
|
log "* Environment: #{ENV['RACK_ENV']}"
|
73
81
|
|
data/lib/puma/server.rb
CHANGED
@@ -17,7 +17,7 @@ require 'puma/rack_patch'
|
|
17
17
|
|
18
18
|
require 'puma/puma_http11'
|
19
19
|
|
20
|
-
unless Puma.const_defined?
|
20
|
+
unless Puma.const_defined? "IOBuffer"
|
21
21
|
require 'puma/io_buffer'
|
22
22
|
end
|
23
23
|
|
@@ -39,6 +39,7 @@ module Puma
|
|
39
39
|
attr_accessor :max_threads
|
40
40
|
attr_accessor :persistent_timeout
|
41
41
|
attr_accessor :auto_trim_time
|
42
|
+
attr_accessor :first_data_timeout
|
42
43
|
|
43
44
|
# Create a server for the rack app +app+.
|
44
45
|
#
|
@@ -102,13 +103,16 @@ module Puma
|
|
102
103
|
# 3 == TCP_CORK
|
103
104
|
# 1/0 == turn on/off
|
104
105
|
def cork_socket(socket)
|
105
|
-
|
106
|
+
begin
|
107
|
+
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
108
|
+
rescue IOError, SystemCallError
|
109
|
+
end
|
106
110
|
end
|
107
111
|
|
108
112
|
def uncork_socket(socket)
|
109
113
|
begin
|
110
114
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
111
|
-
rescue IOError
|
115
|
+
rescue IOError, SystemCallError
|
112
116
|
end
|
113
117
|
end
|
114
118
|
else
|
@@ -326,7 +330,7 @@ module Puma
|
|
326
330
|
|
327
331
|
# :nodoc:
|
328
332
|
def handle_check
|
329
|
-
cmd = @check.read(1)
|
333
|
+
cmd = @check.read(1)
|
330
334
|
|
331
335
|
case cmd
|
332
336
|
when STOP_COMMAND
|
@@ -703,6 +707,10 @@ module Puma
|
|
703
707
|
# A fallback rack response if +@app+ raises as exception.
|
704
708
|
#
|
705
709
|
def lowlevel_error(e)
|
710
|
+
if handler = @options[:lowlevel_error_handler]
|
711
|
+
return handler.call(e)
|
712
|
+
end
|
713
|
+
|
706
714
|
if @leak_stack_on_error
|
707
715
|
[500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
708
716
|
else
|
@@ -775,10 +783,13 @@ module Puma
|
|
775
783
|
begin
|
776
784
|
n = io.syswrite str
|
777
785
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
778
|
-
IO.select(nil, [io], nil,
|
786
|
+
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
|
787
|
+
raise ConnectionError, "Socket timeout writing data"
|
788
|
+
end
|
789
|
+
|
779
790
|
retry
|
780
791
|
rescue Errno::EPIPE, SystemCallError, IOError
|
781
|
-
|
792
|
+
raise ConnectionError, "Socket timeout writing data"
|
782
793
|
end
|
783
794
|
|
784
795
|
return if n == str.bytesize
|
data/lib/puma/thread_pool.rb
CHANGED
data/puma.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.extensions = ["ext/puma_http11/extconf.rb"]
|
24
24
|
s.files = `git ls-files`.split($/)
|
25
25
|
s.homepage = "http://puma.io"
|
26
|
+
s.license = "BSD"
|
26
27
|
s.rdoc_options = ["--main", "README.md"]
|
27
28
|
s.require_paths = ["lib"]
|
28
29
|
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
data/test/test_config.rb
CHANGED
@@ -13,4 +13,14 @@ class TestConfigFile < Test::Unit::TestCase
|
|
13
13
|
|
14
14
|
assert_equal [200, {}, ["embedded app"]], app.call({})
|
15
15
|
end
|
16
|
+
|
17
|
+
def test_lowleve_error_handler_DSL
|
18
|
+
opts = { :config_file => "test/config/app.rb" }
|
19
|
+
conf = Puma::Configuration.new opts
|
20
|
+
conf.load
|
21
|
+
|
22
|
+
app = conf.options[:lowlevel_error_handler]
|
23
|
+
|
24
|
+
assert_equal [200, {}, ["error page"]], app.call({})
|
25
|
+
end
|
16
26
|
end
|
data/test/test_http11.rb
CHANGED
@@ -94,7 +94,7 @@ class Http11ParserTest < Test::Unit::TestCase
|
|
94
94
|
parser.reset
|
95
95
|
|
96
96
|
# Raise exception if URI path length > 2048
|
97
|
-
path = "/" + rand_data(
|
97
|
+
path = "/" + rand_data(2049, 100)
|
98
98
|
http = "GET #{path} HTTP/1.1\r\n\r\n"
|
99
99
|
assert_raises Puma::HttpParserError do
|
100
100
|
parser.execute(req, http, 0)
|
data/test/test_puma_server.rb
CHANGED
@@ -220,6 +220,23 @@ class TestPumaServer < Test::Unit::TestCase
|
|
220
220
|
data = sock.read
|
221
221
|
|
222
222
|
assert_not_match(/don't leak me bro/, data)
|
223
|
+
assert_match(/HTTP\/1.0 500 Internal Server Error/, data)
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_prints_custom_error
|
227
|
+
@events = Puma::Events.strings
|
228
|
+
re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] }
|
229
|
+
@server = Puma::Server.new @app, @events, {lowlevel_error_handler: re}
|
230
|
+
|
231
|
+
@server.app = proc { |e| raise "don't leak me bro" }
|
232
|
+
@server.add_tcp_listener @host, @port
|
233
|
+
@server.run
|
234
|
+
|
235
|
+
sock = TCPSocket.new @host, @port
|
236
|
+
sock << "GET / HTTP/1.0\r\n\r\n"
|
237
|
+
|
238
|
+
data = sock.read
|
239
|
+
assert_match(/HTTP\/1.0 302 Found/, data)
|
223
240
|
end
|
224
241
|
|
225
242
|
def test_custom_http_codes_10
|
@@ -289,4 +306,18 @@ class TestPumaServer < Test::Unit::TestCase
|
|
289
306
|
|
290
307
|
assert_equal [:booting, :running, :stop, :done], states
|
291
308
|
end
|
309
|
+
|
310
|
+
def test_timeout_in_data_phase
|
311
|
+
@server.first_data_timeout = 2
|
312
|
+
@server.add_tcp_listener @host, @port
|
313
|
+
@server.run
|
314
|
+
|
315
|
+
client = TCPSocket.new @host, @port
|
316
|
+
|
317
|
+
client << "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n"
|
318
|
+
|
319
|
+
data = client.gets
|
320
|
+
|
321
|
+
assert_equal "HTTP/1.1 408 Request Timeout\r\n", data
|
322
|
+
end
|
292
323
|
end
|
data/test/test_unix_socket.rb
CHANGED
@@ -39,7 +39,7 @@ exec /bin/bash <<'EOT'
|
|
39
39
|
export HOME="$(eval echo ~$(id -un))"
|
40
40
|
|
41
41
|
if [ -d "$HOME/.rbenv/bin" ]; then
|
42
|
-
export PATH="$HOME/.rbenv/bin:$PATH"
|
42
|
+
export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
|
43
43
|
elif [ -f /etc/profile.d/rvm.sh ]; then
|
44
44
|
source /etc/profile.d/rvm.sh
|
45
45
|
elif [ -f /usr/local/rvm/scripts/rvm ]; then
|
metadata
CHANGED
@@ -1,77 +1,77 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Phoenix
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.1'
|
20
|
-
- - <
|
20
|
+
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '2.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '1.1'
|
30
|
-
- - <
|
30
|
+
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '2.0'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rdoc
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - ~>
|
37
|
+
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '4.0'
|
40
40
|
type: :development
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - ~>
|
44
|
+
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '4.0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake-compiler
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- - ~>
|
51
|
+
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 0.8.0
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
|
-
- - ~>
|
58
|
+
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 0.8.0
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: hoe
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - ~>
|
65
|
+
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '3.
|
67
|
+
version: '3.9'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
|
-
- - ~>
|
72
|
+
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '3.
|
74
|
+
version: '3.9'
|
75
75
|
description: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
|
76
76
|
for Ruby/Rack applications. Puma is intended for use in both development and production
|
77
77
|
environments. In order to get the best throughput, it is highly recommended that
|
@@ -89,6 +89,7 @@ extra_rdoc_files:
|
|
89
89
|
- README.md
|
90
90
|
- docs/config.md
|
91
91
|
- docs/nginx.md
|
92
|
+
- docs/signals.md
|
92
93
|
- tools/jungle/README.md
|
93
94
|
- tools/jungle/init.d/README.md
|
94
95
|
- tools/jungle/upstart/README.md
|
@@ -104,6 +105,7 @@ files:
|
|
104
105
|
- bin/pumactl
|
105
106
|
- docs/config.md
|
106
107
|
- docs/nginx.md
|
108
|
+
- docs/signals.md
|
107
109
|
- ext/puma_http11/PumaHttp11Service.java
|
108
110
|
- ext/puma_http11/ext_help.h
|
109
111
|
- ext/puma_http11/extconf.rb
|
@@ -177,27 +179,27 @@ files:
|
|
177
179
|
- test/test_ws.rb
|
178
180
|
homepage: http://puma.io
|
179
181
|
licenses:
|
180
|
-
-
|
182
|
+
- BSD
|
181
183
|
metadata: {}
|
182
184
|
post_install_message:
|
183
185
|
rdoc_options:
|
184
|
-
- --main
|
186
|
+
- "--main"
|
185
187
|
- README.md
|
186
188
|
require_paths:
|
187
189
|
- lib
|
188
190
|
required_ruby_version: !ruby/object:Gem::Requirement
|
189
191
|
requirements:
|
190
|
-
- -
|
192
|
+
- - ">="
|
191
193
|
- !ruby/object:Gem::Version
|
192
194
|
version: 1.8.7
|
193
195
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
196
|
requirements:
|
195
|
-
- -
|
197
|
+
- - ">="
|
196
198
|
- !ruby/object:Gem::Version
|
197
199
|
version: '0'
|
198
200
|
requirements: []
|
199
201
|
rubyforge_project: puma
|
200
|
-
rubygems_version: 2.
|
202
|
+
rubygems_version: 2.0.3
|
201
203
|
signing_key:
|
202
204
|
specification_version: 4
|
203
205
|
summary: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for
|