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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a0e83ce4d5a44643fe10cd8df04b2f0a69af34d
4
- data.tar.gz: 9435b67b6349bca3263b273568201da196c83e50
3
+ metadata.gz: 898a1954d5ebcc2e339fa1b455a0f772922550d9
4
+ data.tar.gz: ed25f3dcea9438077229cfbfc9e4feca966a1190
5
5
  SHA512:
6
- metadata.gz: 08a358ed0f8a67e416482cc5a285749fd4d9cfb4bf96f6ed35dfccd7b3023a8ece29cb640f01310775e63fc70b476e4f22f199724a3c379a5249797ebb9a8813
7
- data.tar.gz: 57f8b9c308dfc1abd2c1a569c758a4be50423056dff554ee7f10a04911cdbfab2412dd0a2ac724320095abe6b39fa56a6251812fdfd413136eb42a56ed80cd81
6
+ metadata.gz: 81e27d75599ba7fe68b342c375ba5bf1f80f8e6859072950ea20beb3de14a5c9c18272231c3d66734bab76622a258489a8e9bad6c8bedaaf7aae67e22dcfd980
7
+ data.tar.gz: 17251b4e6c9af4e7af501e3974e7c42e836ac95798793e8b05df01cdb19cd8d9178ed960b84adb2030fb475d584fb0dee6fc01ff07e0ae5be40dd98199dba079
data/Gemfile CHANGED
@@ -10,3 +10,8 @@ gem "rack"
10
10
  gem 'minitest', '~> 4.0'
11
11
 
12
12
  gem "jruby-openssl", :platform => "jruby"
13
+
14
+ platforms :rbx do
15
+ gem 'rubysl', '~> 2.0'
16
+ end
17
+
@@ -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:
@@ -9,6 +9,7 @@ bin/puma
9
9
  bin/pumactl
10
10
  docs/config.md
11
11
  docs/nginx.md
12
+ docs/signals.md
12
13
  ext/puma_http11/PumaHttp11Service.java
13
14
  ext/puma_http11/ext_help.h
14
15
  ext/puma_http11/extconf.rb
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 advantate 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:
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
@@ -12,6 +12,7 @@ HOE = Hoe.spec "puma" do
12
12
  self.readme_file = "README.md"
13
13
  self.urls = %w!http://puma.io https://github.com/puma/puma!
14
14
 
15
+ license "BSD"
15
16
  developer 'Evan Phoenix', 'evan@phx.io'
16
17
 
17
18
  spec_extras[:extensions] = ["ext/puma_http11/extconf.rb"]
@@ -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`
@@ -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.exists? path
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
@@ -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
@@ -99,7 +99,8 @@ module Puma
99
99
  :binds => [],
100
100
  :workers => 0,
101
101
  :daemon => false,
102
- :worker_boot => []
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, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
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.exists? path
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.exists?($0)
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
@@ -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
@@ -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
- pid = fork { worker(upgrade, master) }
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[:worker_boot]
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 == "b"
296
- pid = read.gets.to_i
297
- w = @workers.find { |x| x.pid == pid }
298
- if w
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
- else
302
- log "! Out-of-sync worker list, no #{pid} worker"
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
 
@@ -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[:worker_boot] ||= []
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.exists?(rackup)
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.exists?(rackup)
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.exists?("/dev/urandom")
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[:worker_boot] << block
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
@@ -28,8 +28,8 @@ module Puma
28
28
  # too taxing on performance.
29
29
  module Const
30
30
 
31
- PUMA_VERSION = VERSION = "2.7.1".freeze
32
- CODE_NAME = "Earl of Sandwich Partition"
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.
@@ -98,8 +98,9 @@ module Puma
98
98
 
99
99
  while @timeouts.first.timeout_at < now
100
100
  c = @timeouts.shift
101
- sockets.delete c
101
+ c.write_408 if c.in_data_phase
102
102
  c.close
103
+ sockets.delete c
103
104
 
104
105
  break if @timeouts.empty?
105
106
  end
@@ -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
 
@@ -17,7 +17,7 @@ require 'puma/rack_patch'
17
17
 
18
18
  require 'puma/puma_http11'
19
19
 
20
- unless Puma.const_defined?("IOBuffer", false)
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
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
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, 1)
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
- return false
792
+ raise ConnectionError, "Socket timeout writing data"
782
793
  end
783
794
 
784
795
  return if n == str.bytesize
@@ -84,7 +84,7 @@ module Puma
84
84
  @waiting -= 1
85
85
  end
86
86
 
87
- work = todo.pop if continue
87
+ work = todo.shift if continue
88
88
  end
89
89
 
90
90
  break unless continue
@@ -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")
@@ -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
@@ -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(2048, 100)
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)
@@ -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
@@ -21,7 +21,7 @@ unless defined?(JRUBY_VERSION) || RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
21
21
 
22
22
  def teardown
23
23
  @server.stop(true)
24
- File.unlink Path if File.exists? Path
24
+ File.unlink Path if File.exist? Path
25
25
  end
26
26
 
27
27
  def test_server
@@ -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.7.1
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: 2013-12-05 00:00:00.000000000 Z
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.7'
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.7'
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
- - MIT
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.1.10
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