puma 3.10.0 → 3.11.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: ae7ad36143f718016374d543d84e480b182d0684
4
- data.tar.gz: 4209350d304e18ee7144ff07461041479259a205
3
+ metadata.gz: fc80f23863511d940c0cd4139b67605fda3e943a
4
+ data.tar.gz: 26362306ddc252d008e729e517ec6821a62c6f4c
5
5
  SHA512:
6
- metadata.gz: 534b17d2b7ed7ad4917f7587707f7088d8b917d5ce0a08c5b31688088d0447fc013ca084adea61447c6c6463b99cada93ade7457fcd543b4240c72787b07d2dc
7
- data.tar.gz: 24cd789a7a1233972c4b52d28743ababe5d3cee3cba963c45ec013cf3cd40f9c0388e40e9145538b5e414a2dc05e97a48a729fa5db3a0cf3f1838dc19da23aa9
6
+ metadata.gz: d103e80ccccc54908cee56769221c8533b6d0ab366e1d4b749a309a082be23be8440656058f39e39fc59b9ae8f33d957f954c734b5badbfd7c90d056f9e45136
7
+ data.tar.gz: e0f2dd434998331662ac42e38694eecebf32f836769aef6e6d3815ff3cdb8089de3b743aae44f72a92bd86292a5b1e189cb176aa824a426a7e6e41047f91cf7f
data/History.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 3.11.0 / 2017-11-20
2
+
3
+ * 2 features:
4
+ * HTTP 203 Early Hints (#1403)
5
+ * 421/451 status codes now have correct status messages attached (#1435)
6
+
7
+ * 9 bugfixes:
8
+ * Environment config files (/config/puma/<ENV>.rb) load correctly (#1340)
9
+ * Specify windows dependencies correctly (#1434, #1436)
10
+ * puma/events required in test helper (#1418)
11
+ * Correct control CLI's option help text (#1416)
12
+ * Remove a warning for unused variable in mini_ssl (#1409)
13
+ * Correct pumactl docs argument ordering (#1427)
14
+ * Fix an uninitialized variable warning in server.rb (#1430)
15
+ * Fix docs typo/error in Launcher init (#1429)
16
+ * Deal with leading spaces in RUBYOPT (#1455)
17
+
18
+ * 2 other:
19
+ * Add docs about internals (#1425, #1452)
20
+ * Tons of test fixes from @MSP-Greg (#1439, #1442, #1464)
21
+
1
22
  ## 3.10.0 / 2017-08-17
2
23
 
3
24
  * 3 features:
data/README.md CHANGED
@@ -74,6 +74,8 @@ $ puma -t 8:32
74
74
 
75
75
  Puma will automatically scale the number of threads, from the minimum until it caps out at the maximum, based on how much traffic is present. The current default is `0:16`. Feel free to experiment, but be careful not to set the number of maximum threads to a large number, as you may exhaust resources on the system (or hit resource limits).
76
76
 
77
+ Be aware that additionally Puma creates threads on its own for internal purposes (e.g. handling slow clients). So even if you specify -t 1:1, expect around 7 threads created in your application.
78
+
77
79
  ### Clustered mode
78
80
 
79
81
  Puma also offers "clustered mode". Clustered mode `fork`s workers from a master process. Each child process still has its own thread pool. You can tune the number of workers with the `-w` (or `--workers`) flag:
@@ -173,7 +175,7 @@ Puma will start the control server on localhost port 9293. All requests to the c
173
175
  You can also interact with the control server via `pumactl`. This command will restart Puma:
174
176
 
175
177
  ```
176
- $ pumactl restart --control-token foo
178
+ $ pumactl -C 'tcp://127.0.0.1:9293' --control-token foo restart
177
179
  ```
178
180
 
179
181
  To see a list of `pumactl` options, use `pumactl --help`.
@@ -215,11 +217,14 @@ Some platforms do not support all Puma features.
215
217
 
216
218
  ## Known Bugs
217
219
 
218
- For MRI versions 2.2.7, 2.3.4 and 2.4.1, you may see ```stream closed in another thread (IOError)```. It may be caused by a [Ruby bug](https://bugs.ruby-lang.org/issues/13632). It can be fixed with the gem https://rubygems.org/gems/stopgap_13632:
220
+ For MRI versions 2.2.7, 2.2.8, 2.3.4 and 2.4.1, you may see ```stream closed in another thread (IOError)```. It may be caused by a [Ruby bug](https://bugs.ruby-lang.org/issues/13632). It can be fixed with the gem https://rubygems.org/gems/stopgap_13632:
219
221
 
220
222
  ```ruby
221
- if %w(2.2.7 2.3.4 2.4.1).include? RUBY_VERSION
222
- gem "stopgap_13632", "~> 1.0", :platforms => ["mri", "mingw", "x64_mingw"]
223
+ if %w(2.2.7 2.2.8 2.3.4 2.4.1).include? RUBY_VERSION
224
+ begin
225
+ require 'stopgap_13632'
226
+ rescue LoadError
227
+ end
223
228
  end
224
229
  ```
225
230
 
@@ -0,0 +1,36 @@
1
+ # Architecture
2
+
3
+ ## Overview
4
+
5
+ ![http://bit.ly/2iJuFky](images/puma-general-arch.png)
6
+
7
+ Puma is a threaded web server, processing requests across a TCP or UNIX socket.
8
+
9
+ Workers accept connections from the socket and a thread in the worker's thread pool processes the client's request.
10
+
11
+ Clustered mode is shown/discussed here. Single mode is analogous to having a single worker process.
12
+
13
+ ## Connection pipeline
14
+
15
+ ![http://bit.ly/2zwzhEK](images/puma-connection-flow.png)
16
+
17
+ * Upon startup, Puma listens on a TCP or UNIX socket.
18
+ * The backlog of this socket is configured (with a default of 1024), determining how many established but unaccepted connections can exist concurrently.
19
+ * This socket backlog is distinct from the "backlog" of work as reported by the control server stats. The latter is the number of connections in that worker's "todo" set waiting for a worker thread.
20
+ * By default, a single, separate thread is used to receive HTTP requests across the socket.
21
+ * When at least one worker thread is available for work, a connection is accepted and placed in this request buffer
22
+ * This thread waits for entire HTTP requests to be received over the connection
23
+ * Once received, the connection is pushed into the "todo" set
24
+ * Worker threads pop work off the "todo" set for processing
25
+ * The thread processes the request via the rack application (which generates the HTTP response)
26
+ * The thread writes the response to the connection
27
+ * Finally, the thread become available to process another connection in the "todo" set
28
+
29
+ ### Disabling `queue_requests`
30
+
31
+ ![http://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png)
32
+
33
+ The `queue_requests` option is `true` by default, enabling the separate thread used to buffer requests as described above.
34
+
35
+ If set to `false`, this buffer will not be used for connections while waiting for the request to arrive.
36
+ In this mode, when a connection is accepted, it is added to the "todo" queue immediately, and a worker will syncronously do any waiting necessarry to read the HTTP request from the socket.
@@ -0,0 +1,91 @@
1
+ # Deployment engineering for puma
2
+
3
+ Puma is software that is expected to be run in a deployed environment eventually.
4
+ You can certainly use it as your dev server only, but most people look to use
5
+ it in their production deployments as well.
6
+
7
+ To that end, this is meant to serve as a foundation of wisdom how to do that
8
+ in a way that increases happiness and decreases downtime.
9
+
10
+ ## Specifying puma
11
+
12
+ Most people want to do this by putting `gem "puma"` into their Gemfile, so we'll
13
+ go ahead and assume that. Go add it now... we'll wait.
14
+
15
+
16
+ Welcome back!
17
+
18
+ ## Single vs Cluster mode
19
+
20
+ Puma was originally conceived as a thread-only webserver, but grew the ability to
21
+ also use processes in version 2.
22
+
23
+ Here are some rules of thumb:
24
+
25
+ ### MRI
26
+
27
+ * Use cluster mode and set the number of workers to 1.5x the number of cpu cores
28
+ in the machine, minimum 2.
29
+ * Set the number of threads to desired concurrent requests / number of workers.
30
+ Puma defaults to 16 and that's a decent number.
31
+
32
+ #### Migrating from Unicorn
33
+
34
+ * If you're migrating from unicorn though, here are some settings to start with:
35
+ * Set workers to half the number of unicorn workers you're using
36
+ * Set threads to 2
37
+ * Enjoy 50% memory savings
38
+ * As you grow more confident in the thread safety of your app, you can tune the
39
+ workers down and the threads up.
40
+
41
+ #### Worker utilization
42
+
43
+ **How do you know if you're got enough (or too many workers)?**
44
+
45
+ A good question. Due to MRI's GIL, only one thread can be executing Ruby code at a time.
46
+ But since so many apps are waiting on IO from DBs, etc., they can utilize threads
47
+ to make better use of the process.
48
+
49
+ The rule of thumb is you never want processes that are pegged all the time. This
50
+ means that there is more work to do that the process can get through. On the other
51
+ hand, if you have processes that sit around doing nothing, then they're just eating
52
+ up resources.
53
+
54
+ Watching your CPU utilization over time and aim for about 70% on average. This means
55
+ you've got capacity still but aren't starving threads.
56
+
57
+ ## Daemonizing
58
+
59
+ I prefer to not daemonize my servers and use something like `runit` or `upstart` to
60
+ monitor them as child processes. This gives them fast response to crashes and
61
+ makes it easy to figure out what is going on. Additionally, unlike `unicorn`,
62
+ puma does not require daemonization to do zero-downtime restarts.
63
+
64
+ I see people using daemonization because they start puma directly via capistrano
65
+ task and thus want it to live on past the `cap deploy`. To this people I said:
66
+ You need to be using a process monitor. Nothing is making sure puma stays up in
67
+ this scenario! You're just waiting for something weird to happen, puma to die,
68
+ and to get paged at 3am. Do yourself a favor, at least the process monitoring
69
+ your OS comes with, be it `sysvinit`, `upstart`, or `systemd`. Or branch out
70
+ and use `runit` or hell, even `monit`.
71
+
72
+ ## Restarting
73
+
74
+ You probably will want to deploy some new code at some point, and you'd like
75
+ puma to start running that new code. Minimizing the amount of time the server
76
+ is unavailable would be nice as well. Here's how to do it:
77
+
78
+ 1. Don't use `preload!`. This dirties the master process and means it will have
79
+ to shutdown all the workers and re-exec itself to get your new code. It is not compatible with phased-restart and `prune_bundler` as well.
80
+
81
+ 1. Use `prune_bundler`. This makes it so that the cluster master will detach itself
82
+ from a Bundler context on start. This allows the cluster workers to load your app
83
+ and start a brand new Bundler context within the worker only. This means your
84
+ master remains pristine and can live on between new releases of your code.
85
+
86
+ 1. Use phased-restart (`SIGUSR1` or `pumactl phased-restart`). This tells the master
87
+ to kill off one worker at a time and restart them in your new code. This minimizes
88
+ downtime and staggers the restart nicely. **WARNING** This means that both your
89
+ old code and your new code will be running concurrently. Most deployment solutions
90
+ already cause that, but it's worth warning you about it again. Be careful with your
91
+ migrations, etc!
@@ -411,6 +411,11 @@ VALUE noop(VALUE self) {
411
411
  void Init_mini_ssl(VALUE puma) {
412
412
  VALUE mod, eng;
413
413
 
414
+ /* Fake operation for documentation (RDoc, YARD) */
415
+ #if 0 == 1
416
+ puma = rb_define_module("Puma");
417
+ #endif
418
+
414
419
  SSL_library_init();
415
420
  OpenSSL_add_ssl_algorithms();
416
421
  SSL_load_error_strings();
@@ -447,7 +452,7 @@ VALUE raise_error(VALUE self) {
447
452
  }
448
453
 
449
454
  void Init_mini_ssl(VALUE puma) {
450
- VALUE mod, eng;
455
+ VALUE mod;
451
456
 
452
457
  mod = rb_define_module_under(puma, "MiniSSL");
453
458
  rb_define_class_under(mod, "SSLError", rb_eStandardError);
@@ -181,6 +181,10 @@ module Puma
181
181
  user_config.tcp_mode!
182
182
  end
183
183
 
184
+ o.on "--early-hints", "Enable early hints support" do
185
+ user_config.early_hints
186
+ end
187
+
184
188
  o.on "-V", "--version", "Print the version information" do
185
189
  puts "puma version #{Puma::Const::VERSION}"
186
190
  exit 0
@@ -24,7 +24,7 @@ module Puma
24
24
  @workers.each { |x| x.term }
25
25
 
26
26
  begin
27
- Process.waitall
27
+ @workers.each { |w| Process.waitpid(w.pid) }
28
28
  rescue Interrupt
29
29
  log "! Cancelled waiting for workers"
30
30
  end
@@ -189,22 +189,22 @@ module Puma
189
189
  end
190
190
 
191
191
  def load
192
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
193
+
194
+ @options
195
+ end
196
+
197
+ def config_files
192
198
  files = @options.all_of(:config_files)
193
199
 
194
- if files.empty?
195
- imp = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find { |f|
196
- File.exist?(f)
197
- }
200
+ return [] if files == ['-']
201
+ return files if files.any?
198
202
 
199
- files << imp
200
- elsif files == ["-"]
201
- files = []
203
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
204
+ File.exist?(f)
202
205
  end
203
206
 
204
- files.each do |f|
205
- @file_dsl._load_from(f)
206
- end
207
- @options
207
+ [first_default_file]
208
208
  end
209
209
 
210
210
  # Call once all configuration (included from rackup files)
@@ -264,6 +264,10 @@ module Puma
264
264
  @options[:environment]
265
265
  end
266
266
 
267
+ def environment_str
268
+ environment.respond_to?(:call) ? environment.call : environment
269
+ end
270
+
267
271
  def load_plugin(name)
268
272
  @plugins.create name
269
273
  end
@@ -54,6 +54,7 @@ module Puma
54
54
  416 => 'Range Not Satisfiable',
55
55
  417 => 'Expectation Failed',
56
56
  418 => 'I\'m A Teapot',
57
+ 421 => 'Misdirected Request',
57
58
  422 => 'Unprocessable Entity',
58
59
  423 => 'Locked',
59
60
  424 => 'Failed Dependency',
@@ -61,6 +62,7 @@ module Puma
61
62
  428 => 'Precondition Required',
62
63
  429 => 'Too Many Requests',
63
64
  431 => 'Request Header Fields Too Large',
65
+ 451 => 'Unavailable For Legal Reasons',
64
66
  500 => 'Internal Server Error',
65
67
  501 => 'Not Implemented',
66
68
  502 => 'Bad Gateway',
@@ -96,8 +98,8 @@ module Puma
96
98
  # too taxing on performance.
97
99
  module Const
98
100
 
99
- PUMA_VERSION = VERSION = "3.10.0".freeze
100
- CODE_NAME = "Russell's Teapot".freeze
101
+ PUMA_VERSION = VERSION = "3.11.0".freeze
102
+ CODE_NAME = "Love Song".freeze
101
103
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
102
104
 
103
105
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -221,5 +223,7 @@ module Puma
221
223
  HIJACK_P = "rack.hijack?".freeze
222
224
  HIJACK = "rack.hijack".freeze
223
225
  HIJACK_IO = "rack.hijack_io".freeze
226
+
227
+ EARLY_HINTS = "rack.early_hints".freeze
224
228
  end
225
229
  end
@@ -245,7 +245,7 @@ module Puma
245
245
  run_args += ["-S", @state] if @state
246
246
  run_args += ["-q"] if @quiet
247
247
  run_args += ["--pidfile", @pidfile] if @pidfile
248
- run_args += ["--control", @control_url] if @control_url
248
+ run_args += ["--control-url", @control_url] if @control_url
249
249
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
250
250
  run_args += ["-C", @config_file] if @config_file
251
251
 
@@ -241,6 +241,10 @@ module Puma
241
241
  @options[:mode] = :tcp
242
242
  end
243
243
 
244
+ def early_hints(answer=true)
245
+ @options[:early_hints] = answer
246
+ end
247
+
244
248
  # Redirect STDOUT and STDERR to files specified.
245
249
  def stdout_redirect(stdout=nil, stderr=nil, append=false)
246
250
  @options[:redirect_stdout] = stdout
@@ -40,7 +40,7 @@ module Puma
40
40
  # [200, {}, ["hello world"]]
41
41
  # end
42
42
  # end
43
- # Puma::Launcher.new(conf, argv: Puma::Events.stdio).run
43
+ # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
44
44
  def initialize(conf, launcher_args={})
45
45
  @runner = nil
46
46
  @events = launcher_args[:events] || Events::DEFAULT
@@ -168,7 +168,7 @@ module Puma
168
168
  env = Bundler::ORIGINAL_ENV.dup
169
169
  # add -rbundler/setup so we load from Gemfile when restarting
170
170
  bundle = "-rbundler/setup"
171
- env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ") unless env["RUBYOPT"].to_s.include?(bundle)
171
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
172
172
  env
173
173
  else
174
174
  ENV.to_h
@@ -1,3 +1,8 @@
1
+ begin
2
+ require 'io/wait'
3
+ rescue LoadError
4
+ end
5
+
1
6
  module Puma
2
7
  module MiniSSL
3
8
  class Socket
@@ -43,7 +48,20 @@ module Puma
43
48
  output = engine_read_all
44
49
  return output if output
45
50
 
46
- data = @socket.read_nonblock(size)
51
+ begin
52
+ data = @socket.read_nonblock(size, exception: false)
53
+ if data == :wait_readable || data == :wait_writable
54
+ if @socket.to_io.respond_to?(data)
55
+ @socket.to_io.__send__(data)
56
+ elsif data == :wait_readable
57
+ IO.select([@socket.to_io])
58
+ else
59
+ IO.select(nil, [@socket.to_io])
60
+ end
61
+ else
62
+ break
63
+ end
64
+ end while true
47
65
 
48
66
  @engine.inject(data)
49
67
  output = engine_read_all
@@ -161,6 +161,10 @@ module Puma
161
161
  server.tcp_mode!
162
162
  end
163
163
 
164
+ if @options[:early_hints]
165
+ server.early_hints = true
166
+ end
167
+
164
168
  unless development?
165
169
  server.leak_stack_on_error = false
166
170
  end
@@ -62,6 +62,7 @@ module Puma
62
62
 
63
63
  @thread = nil
64
64
  @thread_pool = nil
65
+ @early_hints = nil
65
66
 
66
67
  @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
67
68
  @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
@@ -81,7 +82,7 @@ module Puma
81
82
  @precheck_closing = true
82
83
  end
83
84
 
84
- attr_accessor :binder, :leak_stack_on_error
85
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
85
86
 
86
87
  forward :add_tcp_listener, :@binder
87
88
  forward :add_ssl_listener, :@binder
@@ -595,6 +596,24 @@ module Puma
595
596
  env[RACK_INPUT] = body
596
597
  env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
597
598
 
599
+ if @early_hints
600
+ env[EARLY_HINTS] = lambda { |headers|
601
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
602
+
603
+ headers.each_pair do |k, vs|
604
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
605
+ vs.to_s.split(NEWLINE).each do |v|
606
+ fast_write client, "#{k}: #{v}\r\n"
607
+ end
608
+ else
609
+ fast_write client, "#{k}: #{v}\r\n"
610
+ end
611
+ end
612
+
613
+ fast_write client, "\r\n".freeze
614
+ }
615
+ end
616
+
598
617
  # A rack extension. If the app writes #call'ables to this
599
618
  # array, we will invoke them when the request is done.
600
619
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.11.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: 2017-08-17 00:00:00.000000000 Z
11
+ date: 2017-11-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
14
14
  for Ruby/Rack applications. Puma is intended for use in both development and production
@@ -29,6 +29,11 @@ files:
29
29
  - bin/puma
30
30
  - bin/puma-wild
31
31
  - bin/pumactl
32
+ - docs/architecture.md
33
+ - docs/deployment.md
34
+ - docs/images/puma-connection-flow-no-reactor.png
35
+ - docs/images/puma-connection-flow.png
36
+ - docs/images/puma-general-arch.png
32
37
  - docs/nginx.md
33
38
  - docs/plugins.md
34
39
  - docs/restart.md
@@ -98,7 +103,8 @@ files:
98
103
  homepage: http://puma.io
99
104
  licenses:
100
105
  - BSD-3-Clause
101
- metadata: {}
106
+ metadata:
107
+ msys2_mingw_dependencies: openssl
102
108
  post_install_message:
103
109
  rdoc_options: []
104
110
  require_paths:
@@ -115,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
121
  version: '0'
116
122
  requirements: []
117
123
  rubyforge_project:
118
- rubygems_version: 2.6.11
124
+ rubygems_version: 2.6.13
119
125
  signing_key:
120
126
  specification_version: 4
121
127
  summary: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for