puma 3.11.3 → 4.0.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.

Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +61 -0
  3. data/README.md +41 -11
  4. data/docs/architecture.md +2 -1
  5. data/docs/deployment.md +24 -4
  6. data/docs/restart.md +5 -3
  7. data/docs/systemd.md +37 -9
  8. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  9. data/ext/puma_http11/mini_ssl.c +42 -5
  10. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  11. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +17 -4
  12. data/lib/puma.rb +8 -0
  13. data/lib/puma/app/status.rb +3 -2
  14. data/lib/puma/binder.rb +22 -10
  15. data/lib/puma/cli.rb +18 -7
  16. data/lib/puma/client.rb +54 -22
  17. data/lib/puma/cluster.rb +54 -15
  18. data/lib/puma/commonlogger.rb +2 -0
  19. data/lib/puma/configuration.rb +4 -1
  20. data/lib/puma/const.rb +8 -2
  21. data/lib/puma/control_cli.rb +23 -11
  22. data/lib/puma/convenient.rb +2 -0
  23. data/lib/puma/daemon_ext.rb +2 -0
  24. data/lib/puma/delegation.rb +2 -0
  25. data/lib/puma/detect.rb +2 -0
  26. data/lib/puma/dsl.rb +63 -11
  27. data/lib/puma/events.rb +2 -0
  28. data/lib/puma/io_buffer.rb +3 -6
  29. data/lib/puma/jruby_restart.rb +2 -0
  30. data/lib/puma/launcher.rb +15 -13
  31. data/lib/puma/minissl.rb +20 -4
  32. data/lib/puma/null_io.rb +2 -0
  33. data/lib/puma/plugin.rb +2 -0
  34. data/lib/puma/rack/builder.rb +2 -1
  35. data/lib/puma/reactor.rb +215 -30
  36. data/lib/puma/runner.rb +11 -2
  37. data/lib/puma/server.rb +63 -26
  38. data/lib/puma/single.rb +14 -3
  39. data/lib/puma/state_file.rb +2 -0
  40. data/lib/puma/tcp_logger.rb +2 -0
  41. data/lib/puma/thread_pool.rb +50 -5
  42. data/lib/puma/util.rb +2 -6
  43. data/lib/rack/handler/puma.rb +4 -0
  44. data/tools/jungle/README.md +10 -4
  45. data/tools/jungle/init.d/README.md +2 -0
  46. data/tools/jungle/init.d/puma +7 -7
  47. data/tools/jungle/init.d/run-puma +1 -1
  48. data/tools/jungle/rc.d/README.md +74 -0
  49. data/tools/jungle/rc.d/puma +61 -0
  50. data/tools/jungle/rc.d/puma.conf +10 -0
  51. metadata +23 -9
  52. data/lib/puma/compat.rb +0 -14
  53. data/lib/puma/java_io_buffer.rb +0 -45
  54. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/runner.rb CHANGED
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/server'
2
4
  require 'puma/const'
3
5
 
4
6
  module Puma
7
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
+ # serve requests. This class spawns a new instance of `Puma::Server` via
9
+ # a call to `start_server`.
5
10
  class Runner
6
11
  def initialize(cli, events)
7
12
  @launcher = cli
@@ -19,6 +24,10 @@ module Puma
19
24
  @options[:environment] == "development"
20
25
  end
21
26
 
27
+ def test?
28
+ @options[:environment] == "test"
29
+ end
30
+
22
31
  def log(str)
23
32
  @events.log str
24
33
  end
@@ -46,7 +55,7 @@ module Puma
46
55
  app = Puma::App::Status.new @launcher
47
56
 
48
57
  if token = @options[:control_auth_token]
49
- app.auth_token = token unless token.empty? or token == :none
58
+ app.auth_token = token unless token.empty? || token == 'none'
50
59
  end
51
60
 
52
61
  control = Puma::Server.new app, @launcher.events
@@ -165,7 +174,7 @@ module Puma
165
174
  server.early_hints = true
166
175
  end
167
176
 
168
- unless development?
177
+ unless development? || test?
169
178
  server.leak_stack_on_error = false
170
179
  end
171
180
 
data/lib/puma/server.rb CHANGED
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
 
3
5
  require 'puma/thread_pool'
4
6
  require 'puma/const'
5
7
  require 'puma/events'
6
8
  require 'puma/null_io'
7
- require 'puma/compat'
8
9
  require 'puma/reactor'
9
10
  require 'puma/client'
10
11
  require 'puma/binder'
@@ -14,15 +15,20 @@ require 'puma/util'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
17
- unless Puma.const_defined? "IOBuffer"
18
- require 'puma/io_buffer'
19
- end
20
-
21
18
  require 'socket'
22
19
 
23
20
  module Puma
24
21
 
25
22
  # The HTTP Server itself. Serves out a single Rack app.
23
+ #
24
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
25
+ # to generate one or more `Puma::Server` instances capable of handling requests.
26
+ # Each Puma process will contain one `Puma::Server` instance.
27
+ #
28
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
29
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
30
+ #
31
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
32
  class Server
27
33
 
28
34
  include Puma::Const
@@ -68,7 +74,6 @@ module Puma
68
74
  @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
69
75
 
70
76
  @binder = Binder.new(events)
71
- @own_binder = true
72
77
 
73
78
  @leak_stack_on_error = true
74
79
 
@@ -91,7 +96,6 @@ module Puma
91
96
 
92
97
  def inherit_binder(bind)
93
98
  @binder = bind
94
- @own_binder = false
95
99
  end
96
100
 
97
101
  def tcp_mode!
@@ -159,6 +163,18 @@ module Puma
159
163
  @thread_pool and @thread_pool.spawned
160
164
  end
161
165
 
166
+
167
+ # This number represents the number of requests that
168
+ # the server is capable of taking right now.
169
+ #
170
+ # For example if the number is 5 then it means
171
+ # there are 5 threads sitting idle ready to take
172
+ # a request. If one request comes in, then the
173
+ # value would be 4 until it finishes processing.
174
+ def pool_capacity
175
+ @thread_pool and @thread_pool.pool_capacity
176
+ end
177
+
162
178
  # Lopez Mode == raw tcp apps
163
179
 
164
180
  def run_lopez_mode(background=true)
@@ -220,7 +236,11 @@ module Puma
220
236
  # nothing
221
237
  rescue Errno::ECONNABORTED
222
238
  # client closed the socket even before accept
223
- io.close rescue nil
239
+ begin
240
+ io.close
241
+ rescue
242
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
+ end
224
244
  end
225
245
  end
226
246
  end
@@ -237,11 +257,17 @@ module Puma
237
257
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
238
258
  STDERR.puts e.backtrace
239
259
  ensure
240
- @check.close
241
- @notify.close
260
+ begin
261
+ @check.close
262
+ rescue
263
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
+ end
242
265
 
243
- if @status != :restart and @own_binder
244
- @binder.close
266
+ # Prevent can't modify frozen IOError (RuntimeError)
267
+ begin
268
+ @notify.close
269
+ rescue IOError
270
+ # no biggy
245
271
  end
246
272
  end
247
273
 
@@ -366,13 +392,20 @@ module Puma
366
392
  end
367
393
 
368
394
  pool << client
369
- pool.wait_until_not_full
395
+ busy_threads = pool.wait_until_not_full
396
+ if busy_threads == 0
397
+ @options[:out_of_band].each(&:call) if @options[:out_of_band]
398
+ end
370
399
  end
371
400
  rescue SystemCallError
372
401
  # nothing
373
402
  rescue Errno::ECONNABORTED
374
403
  # client closed the socket even before accept
375
- io.close rescue nil
404
+ begin
405
+ io.close
406
+ rescue
407
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
408
+ end
376
409
  end
377
410
  end
378
411
  end
@@ -394,10 +427,6 @@ module Puma
394
427
  ensure
395
428
  @check.close
396
429
  @notify.close
397
-
398
- if @status != :restart and @own_binder
399
- @binder.close
400
- end
401
430
  end
402
431
 
403
432
  @events.fire :state, :done
@@ -563,15 +592,19 @@ module Puma
563
592
  env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
564
593
  end
565
594
 
566
- # Given the request +env+ from +client+ and a partial request body
567
- # in +body+, finish reading the body if there is one and invoke
568
- # the rack app. Then construct the response and write it back to
569
- # +client+
595
+ # Takes the request +req+, invokes the Rack application to construct
596
+ # the response and writes it back to +req.io+.
597
+ #
598
+ # The second parameter +lines+ is a IO-like object unique to this thread.
599
+ # This is normally an instance of Puma::IOBuffer.
600
+ #
601
+ # It'll return +false+ when the connection is closed, this doesn't mean
602
+ # that the response wasn't successful.
570
603
  #
571
- # +cl+ is the previously fetched Content-Length header if there
572
- # was one. This is an optimization to keep from having to look
573
- # it up again.
604
+ # It'll return +:async+ if the connection remains open but will be handled
605
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
574
606
  #
607
+ # Finally, it'll return +true+ on keep-alive connections.
575
608
  def handle_request(req, lines)
576
609
  env = req.env
577
610
  client = req.io
@@ -606,7 +639,7 @@ module Puma
606
639
  fast_write client, "#{k}: #{v}\r\n"
607
640
  end
608
641
  else
609
- fast_write client, "#{k}: #{v}\r\n"
642
+ fast_write client, "#{k}: #{vs}\r\n"
610
643
  end
611
644
  end
612
645
 
@@ -906,6 +939,10 @@ module Puma
906
939
  @events.debug "Drained #{count} additional connections."
907
940
  end
908
941
 
942
+ if @status != :restart
943
+ @binder.close
944
+ end
945
+
909
946
  if @thread_pool
910
947
  if timeout = @options[:force_shutdown_after]
911
948
  @thread_pool.shutdown timeout.to_i
data/lib/puma/single.rb CHANGED
@@ -1,13 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/runner'
2
4
  require 'puma/detect'
3
5
  require 'puma/plugin'
4
6
 
5
7
  module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when no puma "workers" are needed
10
+ # i.e. only using "threaded" mode. For example `$ puma -t 1: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.
6
15
  class Single < Runner
7
16
  def stats
8
17
  b = @server.backlog || 0
9
18
  r = @server.running || 0
10
- %Q!{ "backlog": #{b}, "running": #{r} }!
19
+ t = @server.pool_capacity || 0
20
+ m = @server.max_threads || 0
21
+ %Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
11
22
  end
12
23
 
13
24
  def restart
@@ -15,7 +26,7 @@ module Puma
15
26
  end
16
27
 
17
28
  def stop
18
- @server.stop false
29
+ @server.stop(false) if @server
19
30
  end
20
31
 
21
32
  def halt
@@ -25,7 +36,7 @@ module Puma
25
36
  def stop_blocked
26
37
  log "- Gracefully stopping, waiting for requests to finish"
27
38
  @control.stop(true) if @control
28
- @server.stop(true)
39
+ @server.stop(true) if @server
29
40
  end
30
41
 
31
42
  def jruby_daemon?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Puma
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class TCPLogger
3
5
  def initialize(logger, app, quiet=false)
@@ -1,8 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
 
3
5
  module Puma
4
- # A simple thread pool management object.
6
+ # Internal Docs for A simple thread pool management object.
7
+ #
8
+ # Each Puma "worker" has a thread pool to process requests.
9
+ #
10
+ # First a connection to a client is made in `Puma::Server`. It is wrapped in a
11
+ # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
12
+ # the whole request is buffered into memory. Once the request is ready, it is passed into
13
+ # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
5
14
  #
15
+ # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
16
+ # and proceses it.
6
17
  class ThreadPool
7
18
  class ForceShutdown < RuntimeError
8
19
  end
@@ -49,7 +60,7 @@ module Puma
49
60
  @clean_thread_locals = false
50
61
  end
51
62
 
52
- attr_reader :spawned, :trim_requested
63
+ attr_reader :spawned, :trim_requested, :waiting
53
64
  attr_accessor :clean_thread_locals
54
65
 
55
66
  def self.clean_thread_locals
@@ -64,6 +75,10 @@ module Puma
64
75
  @mutex.synchronize { @todo.size }
65
76
  end
66
77
 
78
+ def pool_capacity
79
+ waiting + (@max - spawned)
80
+ end
81
+
67
82
  # :nodoc:
68
83
  #
69
84
  # Must be called with @mutex held!
@@ -153,16 +168,46 @@ module Puma
153
168
  end
154
169
  end
155
170
 
171
+ # This method is used by `Puma::Server` to let the server know when
172
+ # the thread pool can pull more requests from the socket and
173
+ # pass to the reactor.
174
+ #
175
+ # The general idea is that the thread pool can only work on a fixed
176
+ # number of requests at the same time. If it is already processing that
177
+ # number of requests then it is at capacity. If another Puma process has
178
+ # spare capacity, then the request can be left on the socket so the other
179
+ # worker can pick it up and process it.
180
+ #
181
+ # For example: if there are 5 threads, but only 4 working on
182
+ # requests, this method will not wait and the `Puma::Server`
183
+ # can pull a request right away.
184
+ #
185
+ # If there are 5 threads and all 5 of them are busy, then it will
186
+ # pause here, and wait until the `not_full` condition variable is
187
+ # signaled, usually this indicates that a request has been processed.
188
+ #
189
+ # It's important to note that even though the server might accept another
190
+ # request, it might not be added to the `@todo` array right away.
191
+ # For example if a slow client has only sent a header, but not a body
192
+ # then the `@todo` array would stay the same size as the reactor works
193
+ # to try to buffer the request. In tha scenario the next call to this
194
+ # method would not block and another request would be added into the reactor
195
+ # by the server. This would continue until a fully bufferend request
196
+ # makes it through the reactor and can then be processed by the thread pool.
197
+ #
198
+ # Returns the current number of busy threads, or +nil+ if shutting down.
199
+ #
156
200
  def wait_until_not_full
157
201
  @mutex.synchronize do
158
202
  while true
159
203
  return if @shutdown
160
- return if @waiting > 0
161
204
 
162
205
  # If we can still spin up new threads and there
163
- # is work queued, then accept more work until we would
206
+ # is work queued that cannot be handled by waiting
207
+ # threads, then accept more work until we would
164
208
  # spin up the max number of threads.
165
- return if @todo.size < @max - @spawned
209
+ busy_threads = @spawned - @waiting + @todo.size
210
+ return busy_threads if @max > busy_threads
166
211
 
167
212
  @not_full.wait @mutex
168
213
  end
data/lib/puma/util.rb CHANGED
@@ -1,10 +1,6 @@
1
- major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
1
+ # frozen_string_literal: true
2
2
 
3
- if major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
4
- require 'puma/rack/backports/uri/common_193'
5
- else
6
- require 'uri/common'
7
- end
3
+ require 'uri/common'
8
4
 
9
5
  module Puma
10
6
  module Util
@@ -9,6 +9,7 @@ module Rack
9
9
  }
10
10
 
11
11
  def self.config(app, options = {})
12
+ require 'puma'
12
13
  require 'puma/configuration'
13
14
  require 'puma/events'
14
15
  require 'puma/launcher'
@@ -48,6 +49,9 @@ module Rack
48
49
  self.set_host_port_to_config(host, port, user_config)
49
50
  end
50
51
 
52
+ if default_options[:Host]
53
+ file_config.set_default_host(default_options[:Host])
54
+ end
51
55
  self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
52
56
 
53
57
  user_config.app app
@@ -1,9 +1,5 @@
1
1
  # Puma as a service
2
2
 
3
- ## Init.d
4
-
5
- See `/tools/jungle/init.d` for tools to use with init.d and start-stop-daemon.
6
-
7
3
  ## Upstart
8
4
 
9
5
  See `/tools/jungle/upstart` for Ubuntu's upstart scripts.
@@ -11,3 +7,13 @@ See `/tools/jungle/upstart` for Ubuntu's upstart scripts.
11
7
  ## Systemd
12
8
 
13
9
  See [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md).
10
+
11
+ ## Init.d
12
+
13
+ Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
14
+
15
+ See `/tools/jungle/init.d` for tools to use with init.d and start-stop-daemon.
16
+
17
+ ## rc.d
18
+
19
+ See `/tools/jungle/rc.d` for FreeBSD's rc.d scripts
@@ -1,5 +1,7 @@
1
1
  # Puma daemon service
2
2
 
3
+ Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
4
+
3
5
  Init script to manage multiple Puma servers on the same box using start-stop-daemon.
4
6
 
5
7
  ## Installation
@@ -47,11 +47,11 @@ do_start_one() {
47
47
  PIDFILE=$1/tmp/puma/pid
48
48
  if [ -e $PIDFILE ]; then
49
49
  PID=`cat $PIDFILE`
50
- # If the puma isn't running, run it, otherwise restart it.
51
- if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then
52
- do_start_one_do $1
53
- else
50
+ # If the puma is running, restart it, otherwise run it.
51
+ if ps -p $PID > /dev/null; then
54
52
  do_restart_one $1
53
+ else
54
+ do_start_one_do $1
55
55
  fi
56
56
  else
57
57
  do_start_one_do $1
@@ -105,9 +105,7 @@ do_stop_one() {
105
105
  STATEFILE=$1/tmp/puma/state
106
106
  if [ -e $PIDFILE ]; then
107
107
  PID=`cat $PIDFILE`
108
- if [ "`ps -A -o pid= | grep -c $PID`" -eq 0 ]; then
109
- log_daemon_msg "---> Puma $1 isn't running."
110
- else
108
+ if ps -p $PID > /dev/null; then
111
109
  log_daemon_msg "---> About to kill PID `cat $PIDFILE`"
112
110
  if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then
113
111
  cd $1 && bundle exec pumactl --state $STATEFILE stop
@@ -116,6 +114,8 @@ do_stop_one() {
116
114
  fi
117
115
  # Many daemons don't delete their pidfiles when they exit.
118
116
  rm -f $PIDFILE $STATEFILE
117
+ else
118
+ log_daemon_msg "---> Puma $1 isn't running."
119
119
  fi
120
120
  else
121
121
  log_daemon_msg "---> No puma here..."