puma 3.11.4 → 3.12.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
  SHA256:
3
- metadata.gz: 7f451a7278034b23b2a1537d6bde82ae318cd58750dbab2c0a6aa4eb0e72efb0
4
- data.tar.gz: 3841d52680598006e72b92c37c358d0f185980bfca81cdc55c19e25673156f61
3
+ metadata.gz: bff4687b24c136075e00b45d5314fd6326b6240733fff22c706096d10d8ec965
4
+ data.tar.gz: db2020f983ba02f7403e50f9f9391bd2f20e811fa42121147d4cbaf619e1d8d3
5
5
  SHA512:
6
- metadata.gz: 6273588bb47a3f85b40a358dc445733de687707111dd5aaffe4004be32a82eef00c227495abd9e68bd0b3f398395b64b685e8b78f5b428e3aee87be50f91cb1f
7
- data.tar.gz: 167dc9b6bdcf05050cff4d80af977ede8c4cd9b5fb0dd3397f8075e4657402cf5e7e084c248c2e23d06669cbce52cd13a84155612a3451c0ac5df6d0cff36730
6
+ metadata.gz: d6d7efcb9aeb437c07f49cb5a589ed016d888acab08f130bb382f2d470b301ec40ce3df90fbe4cf989bc84468633b180894c60daca377346d283210e6fedba98
7
+ data.tar.gz: 2d45380434e8d88d534a27db1a1f843d70b925a64065d55ee637153669b3c78f1539bf4c84ee166ec90636ba3ee948a97540c0bdbac5cc3d68809c86874038f8
data/History.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 3.12.0 / 2018-07-13
2
+
3
+ * 5 features:
4
+ * You can now specify which SSL ciphers the server should support, default is unchanged (#1478)
5
+ * The setting for Puma's `max_threads` is now in `Puma.stats` (#1604)
6
+ * Pool capacity is now in `Puma.stats` (#1579)
7
+ * Installs restricted to Ruby 2.2+ (#1506)
8
+ * `--control` is now deprecated in favor of `--control-url` (#1487)
9
+
10
+ * 2 bugfixes:
11
+ * Workers will no longer accept more web requests than they have capacity to process. This prevents an issue where one worker would accept lots of requests while starving other workers (#1563)
12
+ * In a test env puma now emits the stack on an exception (#1557)
13
+
1
14
  ## 3.11.4 / 2018-04-12
2
15
 
3
16
  * 2 features:
@@ -7,7 +20,7 @@
7
20
  * Fix parsing CLI options (#1482)
8
21
  * Order of stderr and stdout is made before redirecting to a log file (#1511)
9
22
  * Init.d fix of `ps -p` to check if pid exists (#1545)
10
- * Early hits bugfix (#1550)
23
+ * Early hints bugfix (#1550)
11
24
  * Purge interrupt queue when closing socket fails (#1553)
12
25
 
13
26
  ## 3.11.3 / 2018-03-05
data/README.md CHANGED
@@ -157,17 +157,27 @@ $ puma -b 'unix:///var/run/puma.sock?umask=0111'
157
157
  ```
158
158
 
159
159
  Need a bit of security? Use SSL sockets:
160
-
161
160
  ```
162
161
  $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'
163
162
  ```
163
+ #### Controlling SSL Cipher Suites
164
+ Need to use or avoid specific SSL cipher suites? Use ssl_cipher_filter or ssl_cipher_list options.
165
+ #####Ruby:
166
+ ```
167
+ $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&ssl_cipher_filter=!aNULL:AES+SHA'
168
+ ```
169
+ #####JRuby:
170
+ ```
171
+ $ puma -b 'ssl://127.0.0.1:9292?keystore=path_to_keystore&keystore-pass=keystore_password&ssl_cipher_list=TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA'
172
+ ```
173
+ See https://www.openssl.org/docs/man1.0.2/apps/ciphers.html for cipher filter format and full list of cipher suites.
164
174
 
165
175
  ### Control/Status Server
166
176
 
167
177
  Puma has a built-in status/control app that can be used to query and control Puma itself.
168
178
 
169
179
  ```
170
- $ puma --control tcp://127.0.0.1:9293 --control-token foo
180
+ $ puma --control-url tcp://127.0.0.1:9293 --control-token foo
171
181
  ```
172
182
 
173
183
  Puma will start the control server on localhost port 9293. All requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out [status.rb](https://github.com/puma/puma/blob/master/lib/puma/app/status.rb) to see what the app has available.
@@ -175,7 +185,7 @@ Puma will start the control server on localhost port 9293. All requests to the c
175
185
  You can also interact with the control server via `pumactl`. This command will restart Puma:
176
186
 
177
187
  ```
178
- $ pumactl -C 'tcp://127.0.0.1:9293' --control-token foo restart
188
+ $ pumactl --control-url 'tcp://127.0.0.1:9293' --control-token foo restart
179
189
  ```
180
190
 
181
191
  To see a list of `pumactl` options, use `pumactl --help`.
@@ -217,10 +227,10 @@ Some platforms do not support all Puma features.
217
227
 
218
228
  ## Known Bugs
219
229
 
220
- For MRI versions 2.2.7, 2.2.8, 2.2.9, 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:
230
+ For MRI versions 2.2.7, 2.2.8, 2.2.9, 2.2.10 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:
221
231
 
222
232
  ```ruby
223
- if %w(2.2.7 2.2.8 2.2.9 2.3.4 2.4.1).include? RUBY_VERSION
233
+ if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION
224
234
  begin
225
235
  require 'stopgap_13632'
226
236
  rescue LoadError
@@ -161,6 +161,9 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
161
161
  ID sym_verify_mode = rb_intern("verify_mode");
162
162
  VALUE verify_mode = rb_funcall(mini_ssl_ctx, sym_verify_mode, 0);
163
163
 
164
+ ID sym_ssl_cipher_filter = rb_intern("ssl_cipher_filter");
165
+ VALUE ssl_cipher_filter = rb_funcall(mini_ssl_ctx, sym_ssl_cipher_filter, 0);
166
+
164
167
  ctx = SSL_CTX_new(SSLv23_server_method());
165
168
  conn->ctx = ctx;
166
169
 
@@ -175,7 +178,13 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
175
178
  SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION);
176
179
  SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
177
180
 
178
- SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
181
+ if (!NIL_P(ssl_cipher_filter)) {
182
+ StringValue(ssl_cipher_filter);
183
+ SSL_CTX_set_cipher_list(ctx, RSTRING_PTR(ssl_cipher_filter));
184
+ }
185
+ else {
186
+ SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
187
+ }
179
188
 
180
189
  DH *dh = get_dh1024();
181
190
  SSL_CTX_set_tmp_dh(ctx, dh);
@@ -170,6 +170,12 @@ public class MiniSSL extends RubyObject {
170
170
  engine.setNeedClientAuth(true);
171
171
  }
172
172
 
173
+ IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
174
+ if (!sslCipherListObject.isNil()) {
175
+ String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
176
+ engine.setEnabledCipherSuites(sslCipherList);
177
+ }
178
+
173
179
  SSLSession session = engine.getSession();
174
180
  inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
175
181
  outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
@@ -162,6 +162,7 @@ module Puma
162
162
  end
163
163
 
164
164
  ctx.keystore_pass = params['keystore-pass']
165
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
165
166
  else
166
167
  unless params['key']
167
168
  @events.error "Please specify the SSL key via 'key='"
@@ -182,6 +183,7 @@ module Puma
182
183
  end
183
184
 
184
185
  ctx.ca = params['ca'] if params['ca']
186
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
185
187
  end
186
188
 
187
189
  if params['verify_mode']
@@ -313,6 +315,7 @@ module Puma
313
315
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
314
316
  s.listen backlog
315
317
 
318
+
316
319
  ssl = MiniSSL::Server.new s, ctx
317
320
  env = @proto_env.dup
318
321
  env[HTTPS_KEY] = HTTPS
@@ -84,6 +84,14 @@ module Puma
84
84
  raise UnsupportedOption
85
85
  end
86
86
 
87
+ def configure_control_url(command_line_arg)
88
+ if command_line_arg
89
+ @control_url = command_line_arg
90
+ elsif Puma.jruby?
91
+ unsupported "No default url available on JRuby"
92
+ end
93
+ end
94
+
87
95
  # Build the OptionParser object to handle the available options.
88
96
  #
89
97
 
@@ -98,13 +106,13 @@ module Puma
98
106
  file_config.load arg
99
107
  end
100
108
 
101
- o.on "--control URL", "The bind url to use for the control server",
102
- "Use 'auto' to use temp unix server" do |arg|
103
- if arg
104
- @control_url = arg
105
- elsif Puma.jruby?
106
- unsupported "No default url available on JRuby"
107
- end
109
+ o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
110
+ configure_control_url(arg)
111
+ end
112
+
113
+ # alias --control-url for backwards-compatibility
114
+ o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
115
+ configure_control_url(arg)
108
116
  end
109
117
 
110
118
  o.on "--control-token TOKEN",
@@ -21,6 +21,17 @@ module Puma
21
21
 
22
22
  class ConnectionError < RuntimeError; end
23
23
 
24
+ # An instance of this class represents a unique request from a client.
25
+ # For example a web request from a browser or from CURL. This
26
+ #
27
+ # An instance of `Puma::Client` can be used as if it were an IO object
28
+ # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
29
+ # This is accomplished by the `to_io` method which gets called on any
30
+ # non-IO objects being used with the IO api such as `IO.select.
31
+ #
32
+ # Instances of this class are responsible for knowing if
33
+ # the header and body are fully buffered via the `try_to_finish` method.
34
+ # They can be used to "time out" a response via the `timeout_at` reader.
24
35
  class Client
25
36
  include Puma::Const
26
37
  extend Puma::Delegation
@@ -5,6 +5,17 @@ require 'puma/plugin'
5
5
  require 'time'
6
6
 
7
7
  module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when puma "workers" are needed
10
+ # i.e. when using multi-processes. For example `$ puma -w 5`
11
+ #
12
+ # At the core of this class is running an instance of `Puma::Server` which
13
+ # gets created via the `start_server` method from the `Puma::Runner` class
14
+ # that this inherits from.
15
+ #
16
+ # An instance of this class will spawn the number of processes passed in
17
+ # via the `spawn_workers` method call. Each worker will have it's own
18
+ # instance of a `Puma::Server`.
8
19
  class Cluster < Runner
9
20
  WORKER_CHECK_INTERVAL = 5
10
21
 
@@ -281,7 +292,9 @@ module Puma
281
292
  begin
282
293
  b = server.backlog || 0
283
294
  r = server.running || 0
284
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
295
+ t = server.pool_capacity || 0
296
+ m = server.max_threads || 0
297
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
285
298
  io << payload
286
299
  rescue IOError
287
300
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@@ -98,8 +98,8 @@ module Puma
98
98
  # too taxing on performance.
99
99
  module Const
100
100
 
101
- PUMA_VERSION = VERSION = "3.11.4".freeze
102
- CODE_NAME = "Love Song".freeze
101
+ PUMA_VERSION = VERSION = "3.12.0".freeze
102
+ CODE_NAME = "Llamas in Pajamas".freeze
103
103
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
104
104
 
105
105
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -220,7 +220,7 @@ module Puma
220
220
  end
221
221
 
222
222
  def run
223
- start if @command == "start"
223
+ return start if @command == "start"
224
224
 
225
225
  prepare_configuration
226
226
 
@@ -124,7 +124,7 @@ module Puma
124
124
 
125
125
  def read_and_drop(timeout = 1)
126
126
  return :timeout unless IO.select([@socket], nil, nil, timeout)
127
- read_nonblock(1024)
127
+ return :eof unless read_nonblock(1024)
128
128
  :drop
129
129
  rescue Errno::EAGAIN
130
130
  # do nothing
@@ -141,7 +141,7 @@ module Puma
141
141
  # Don't let this socket hold this loop forever.
142
142
  # If it can't send more packets within 1s, then give up.
143
143
  while should_drop_bytes?
144
- return if read_and_drop(1) == :timeout
144
+ return if [:timeout, :eof].include?(read_and_drop(1))
145
145
  end
146
146
  rescue IOError, SystemCallError
147
147
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@@ -180,6 +180,7 @@ module Puma
180
180
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
181
181
  attr_reader :keystore
182
182
  attr_accessor :keystore_pass
183
+ attr_accessor :ssl_cipher_list
183
184
 
184
185
  def keystore=(keystore)
185
186
  raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
@@ -195,6 +196,7 @@ module Puma
195
196
  attr_reader :key
196
197
  attr_reader :cert
197
198
  attr_reader :ca
199
+ attr_accessor :ssl_cipher_filter
198
200
 
199
201
  def key=(key)
200
202
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -2,15 +2,54 @@ require 'puma/util'
2
2
  require 'puma/minissl'
3
3
 
4
4
  module Puma
5
+ # Internal Docs, Not a public interface.
6
+ #
7
+ # The Reactor object is responsible for ensuring that a request has been
8
+ # completely received before it starts to be processed. This may be known as read buffering.
9
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
10
+ # such as nginx) then the application would be subject to a slow client attack.
11
+ #
12
+ # Each Puma "worker" process has its own Reactor. For example if you start puma with `$ puma -w 5` then
13
+ # it will have 5 workers and each worker will have it's own reactor.
14
+ #
15
+ # For a graphical representation of how the reactor works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
16
+ #
17
+ # ## Reactor Flow
18
+ #
19
+ # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
20
+ # The reactor stores the request in an array and calls `IO.select` on the array in a loop.
21
+ #
22
+ # When the request is written to by the client then the `IO.select` will "wake up" and
23
+ # return the references to any objects that caused it to "wake". The reactor
24
+ # then loops through each of these request objects, and sees if they're complete. If they
25
+ # have a full header and body then the reactor passes the request to a thread pool.
26
+ # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
27
+ #
28
+ # If the request is not complete, then it stays in the array, and the next time any
29
+ # data is written to that socket reference, then the loop is woken up and it is checked for completeness again.
30
+ #
31
+ # A detailed example is given in the docs for `run_internal` which is where the bulk
32
+ # of this logic lives.
5
33
  class Reactor
6
34
  DefaultSleepFor = 5
7
35
 
36
+ # Creates an instance of Puma::Reactor
37
+ #
38
+ # The `server` argument is an instance of `Puma::Server`
39
+ # this is used to write a response for "low level errors"
40
+ # when there is an exception inside of the reactor.
41
+ #
42
+ # The `app_pool` is an instance of `Puma::ThreadPool`.
43
+ # Once a request is fully formed (header and body are received)
44
+ # it will be passed to the `app_pool`.
8
45
  def initialize(server, app_pool)
9
46
  @server = server
10
47
  @events = server.events
11
48
  @app_pool = app_pool
12
49
 
13
50
  @mutex = Mutex.new
51
+
52
+ # Read / Write pipes to wake up internal while loop
14
53
  @ready, @trigger = Puma::Util.pipe
15
54
  @input = []
16
55
  @sleep_for = DefaultSleepFor
@@ -21,6 +60,64 @@ module Puma
21
60
 
22
61
  private
23
62
 
63
+
64
+ # Until a request is added via the `add` method this method will internally
65
+ # loop, waiting on the `sockets` array objects. The only object in this
66
+ # array at first is the `@ready` IO object, which is the read end of a pipe
67
+ # connected to `@trigger` object. When `@trigger` is written to, then the loop
68
+ # will break on `IO.select` and return an array.
69
+ #
70
+ # ## When a request is added:
71
+ #
72
+ # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
73
+ # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
74
+ #
75
+ # When that happens, the internal loop stops blocking at `IO.select` and returns a reference
76
+ # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
77
+ # When `@trigger` is written-to, the loop "wakes" and the `ready`
78
+ # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
79
+ # first IO object is the `@ready` object. This first array `[#<IO:fd 10>]`
80
+ # is saved as a `reads` variable.
81
+ #
82
+ # The `reads` variable is iterated through. In the case that the object
83
+ # is the same as the `@ready` input pipe, then we know that there was a `trigger` event.
84
+ #
85
+ # If there was a trigger event, then one byte of `@ready` is read into memory. In the case of the first request,
86
+ # the reactor sees that it's a `"*"` value and the reactor adds the contents of `@input` into the `sockets` array.
87
+ # The while then loop continues to iterate again, but now the `sockets` array contains a `Puma::Client` instance in addition
88
+ # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
89
+ #
90
+ # Since the `Puma::Client` in this example has data that has not been read yet,
91
+ # the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
92
+ # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
93
+ #
94
+ # Each element in the first entry is iterated over. The `Puma::Client` object is not
95
+ # the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
96
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
97
+ # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
98
+ # can pick up the request and begin to execute application logic. This is done
99
+ # via `@app_pool << c`. The `Puma::Client` is then removed from the `sockets` array.
100
+ #
101
+ # If the request body is not present then nothing will happen, and the loop will iterate
102
+ # again. When the client sends more data to the socket the `Puma::Client` object will
103
+ # wake up the `IO.select` and it can again be checked to see if it's ready to be
104
+ # passed to the thread pool.
105
+ #
106
+ # ## Time Out Case
107
+ #
108
+ # In addition to being woken via a write to one of the sockets the `IO.select` will
109
+ # periodically "time out" of the sleep. One of the functions of this is to check for
110
+ # any requests that have "timed out". At the end of the loop it's checked to see if
111
+ # the first element in the `@timeout` array has exceed it's allowed time. If so,
112
+ # the client object is removed from the timeout aray, a 408 response is written.
113
+ # Then it's connection is closed, and the object is removed from the `sockets` array
114
+ # that watches for new data.
115
+ #
116
+ # This behavior loops until all the objects that have timed out have been removed.
117
+ #
118
+ # Once all the timeouts have been processed, the next duration of the `IO.select` sleep
119
+ # will be set to be equal to the amount of time it will take for the next timeout to occur.
120
+ # This calculation happens in `calculate_sleep`.
24
121
  def run_internal
25
122
  sockets = @sockets
26
123
 
@@ -163,6 +260,16 @@ module Puma
163
260
  end
164
261
  end
165
262
 
263
+ # The `calculate_sleep` sets the value that the `IO.select` will
264
+ # sleep for in the main reactor loop when no sockets are being written to.
265
+ #
266
+ # The values kept in `@timeouts` are sorted so that the first timeout
267
+ # comes first in the array. When there are no timeouts the default timeout is used.
268
+ #
269
+ # Otherwise a sleep value is set that is the same as the amount of time it
270
+ # would take for the first element to time out.
271
+ #
272
+ # If that value is in the past, then a sleep value of zero is used.
166
273
  def calculate_sleep
167
274
  if @timeouts.empty?
168
275
  @sleep_for = DefaultSleepFor
@@ -177,6 +284,31 @@ module Puma
177
284
  end
178
285
  end
179
286
 
287
+ # This method adds a connection to the reactor
288
+ #
289
+ # Typically called by `Puma::Server` the value passed in
290
+ # is usually a `Puma::Client` object that responds like an IO
291
+ # object.
292
+ #
293
+ # The main body of the reactor loop is in `run_internal` and it
294
+ # will sleep on `IO.select`. When a new connection is added to the
295
+ # reactor it cannot be added directly to the `sockets` aray, because
296
+ # the `IO.select` will not be watching for it yet.
297
+ #
298
+ # Instead what needs to happen is that `IO.select` needs to be woken up,
299
+ # the contents of `@input` added to the `sockets` array, and then
300
+ # another call to `IO.select` needs to happen. Since the `Puma::Client`
301
+ # object can be read immediately, it does not block, but instead returns
302
+ # right away.
303
+ #
304
+ # This behavior is accomplished by writing to `@trigger` which wakes up
305
+ # the `IO.select` and then there is logic to detect the value of `*`,
306
+ # pull the contents from `@input` and add them to the sockets array.
307
+ #
308
+ # If the object passed in has a timeout value in `timeout_at` then
309
+ # it is added to a `@timeouts` array. This array is then re-arranged
310
+ # so that the first element to timeout will be at the front of the
311
+ # array. Then a value to sleep for is derived in the call to `calculate_sleep`
180
312
  def add(c)
181
313
  @mutex.synchronize do
182
314
  @input << c
@@ -2,6 +2,9 @@ require 'puma/server'
2
2
  require 'puma/const'
3
3
 
4
4
  module Puma
5
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
6
+ # serve requests. This class spawns a new instance of `Puma::Server` via
7
+ # a call to `start_server`.
5
8
  class Runner
6
9
  def initialize(cli, events)
7
10
  @launcher = cli
@@ -19,6 +22,10 @@ module Puma
19
22
  @options[:environment] == "development"
20
23
  end
21
24
 
25
+ def test?
26
+ @options[:environment] == "test"
27
+ end
28
+
22
29
  def log(str)
23
30
  @events.log str
24
31
  end
@@ -165,7 +172,7 @@ module Puma
165
172
  server.early_hints = true
166
173
  end
167
174
 
168
- unless development?
175
+ unless development? || test?
169
176
  server.leak_stack_on_error = false
170
177
  end
171
178
 
@@ -23,6 +23,15 @@ require 'socket'
23
23
  module Puma
24
24
 
25
25
  # The HTTP Server itself. Serves out a single Rack app.
26
+ #
27
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
28
+ # to generate one or more `Puma::Server` instances capable of handling requests.
29
+ # Each Puma process will contain one `Puma::Server` instacne.
30
+ #
31
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
32
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
33
+ #
34
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
35
  class Server
27
36
 
28
37
  include Puma::Const
@@ -159,6 +168,18 @@ module Puma
159
168
  @thread_pool and @thread_pool.spawned
160
169
  end
161
170
 
171
+
172
+ # This number represents the number of requests that
173
+ # the server is capable of taking right now.
174
+ #
175
+ # For example if the number is 5 then it means
176
+ # there are 5 threads sitting idle ready to take
177
+ # a request. If one request comes in, then the
178
+ # value would be 4 until it finishes processing.
179
+ def pool_capacity
180
+ @thread_pool and @thread_pool.pool_capacity
181
+ end
182
+
162
183
  # Lopez Mode == raw tcp apps
163
184
 
164
185
  def run_lopez_mode(background=true)
@@ -241,7 +262,12 @@ module Puma
241
262
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
242
263
  STDERR.puts e.backtrace
243
264
  ensure
244
- @check.close
265
+ begin
266
+ @check.close
267
+ rescue
268
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
269
+ end
270
+
245
271
  @notify.close
246
272
 
247
273
  if @status != :restart and @own_binder
@@ -3,11 +3,20 @@ require 'puma/detect'
3
3
  require 'puma/plugin'
4
4
 
5
5
  module Puma
6
+ # This class is instantiated by the `Puma::Launcher` and used
7
+ # to boot and serve a Ruby application when no puma "workers" are needed
8
+ # i.e. only using "threaded" mode. For example `$ puma -t 1:5`
9
+ #
10
+ # At the core of this class is running an instance of `Puma::Server` which
11
+ # gets created via the `start_server` method from the `Puma::Runner` class
12
+ # that this inherits from.
6
13
  class Single < Runner
7
14
  def stats
8
15
  b = @server.backlog || 0
9
16
  r = @server.running || 0
10
- %Q!{ "backlog": #{b}, "running": #{r} }!
17
+ t = @server.pool_capacity || 0
18
+ m = @server.max_threads || 0
19
+ %Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
11
20
  end
12
21
 
13
22
  def restart
@@ -1,8 +1,17 @@
1
1
  require 'thread'
2
2
 
3
3
  module Puma
4
- # A simple thread pool management object.
4
+ # Internal Docs for A simple thread pool management object.
5
5
  #
6
+ # Each Puma "worker" has a thread pool to process requests.
7
+ #
8
+ # First a connection to a client is made in `Puma::Server`. It is wrapped in a
9
+ # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
10
+ # the whole request is buffered into memory. Once the request is ready, it is passed into
11
+ # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
12
+ #
13
+ # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
14
+ # and proceses it.
6
15
  class ThreadPool
7
16
  class ForceShutdown < RuntimeError
8
17
  end
@@ -49,7 +58,7 @@ module Puma
49
58
  @clean_thread_locals = false
50
59
  end
51
60
 
52
- attr_reader :spawned, :trim_requested
61
+ attr_reader :spawned, :trim_requested, :waiting
53
62
  attr_accessor :clean_thread_locals
54
63
 
55
64
  def self.clean_thread_locals
@@ -64,6 +73,10 @@ module Puma
64
73
  @mutex.synchronize { @todo.size }
65
74
  end
66
75
 
76
+ def pool_capacity
77
+ waiting + (@max - spawned)
78
+ end
79
+
67
80
  # :nodoc:
68
81
  #
69
82
  # Must be called with @mutex held!
@@ -153,16 +166,42 @@ module Puma
153
166
  end
154
167
  end
155
168
 
169
+ # This method is used by `Puma::Server` to let the server know when
170
+ # the thread pool can pull more requests from the socket and
171
+ # pass to the reactor.
172
+ #
173
+ # The general idea is that the thread pool can only work on a fixed
174
+ # number of requests at the same time. If it is already processing that
175
+ # number of requests then it is at capacity. If another Puma process has
176
+ # spare capacity, then the request can be left on the socket so the other
177
+ # worker can pick it up and process it.
178
+ #
179
+ # For example: if there are 5 threads, but only 4 working on
180
+ # requests, this method will not wait and the `Puma::Server`
181
+ # can pull a request right away.
182
+ #
183
+ # If there are 5 threads and all 5 of them are busy, then it will
184
+ # pause here, and wait until the `not_full` condition variable is
185
+ # signaled, usually this indicates that a request has been processed.
186
+ #
187
+ # It's important to note that even though the server might accept another
188
+ # request, it might not be added to the `@todo` array right away.
189
+ # For example if a slow client has only sent a header, but not a body
190
+ # then the `@todo` array would stay the same size as the reactor works
191
+ # to try to buffer the request. In tha scenario the next call to this
192
+ # method would not block and another request would be added into the reactor
193
+ # by the server. This would continue until a fully bufferend request
194
+ # makes it through the reactor and can then be processed by the thread pool.
156
195
  def wait_until_not_full
157
196
  @mutex.synchronize do
158
197
  while true
159
198
  return if @shutdown
160
- return if @waiting > 0
161
199
 
162
200
  # If we can still spin up new threads and there
163
- # is work queued, then accept more work until we would
201
+ # is work queued that cannot be handled by waiting
202
+ # threads, then accept more work until we would
164
203
  # spin up the max number of threads.
165
- return if @todo.size < @max - @spawned
204
+ return if @todo.size - @waiting < @max - @spawned
166
205
 
167
206
  @not_full.wait @mutex
168
207
  end
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.11.4
4
+ version: 3.12.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: 2018-04-12 00:00:00.000000000 Z
11
+ date: 2018-07-13 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
@@ -116,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- version: 1.9.3
119
+ version: '2.2'
120
120
  required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="