puma 3.6.0 → 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.

Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +293 -79
  3. data/README.md +143 -227
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +28 -0
  10. data/docs/restart.md +39 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +124 -22
  13. data/ext/puma_http11/extconf.rb +2 -0
  14. data/ext/puma_http11/http11_parser.c +85 -84
  15. data/ext/puma_http11/http11_parser.h +1 -0
  16. data/ext/puma_http11/http11_parser.rl +10 -9
  17. data/ext/puma_http11/io_buffer.c +7 -7
  18. data/ext/puma_http11/mini_ssl.c +62 -6
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  21. data/ext/puma_http11/puma_http11.c +1 -0
  22. data/lib/puma.rb +13 -5
  23. data/lib/puma/app/status.rb +8 -0
  24. data/lib/puma/binder.rb +21 -14
  25. data/lib/puma/cli.rb +49 -33
  26. data/lib/puma/client.rb +39 -4
  27. data/lib/puma/cluster.rb +51 -11
  28. data/lib/puma/commonlogger.rb +19 -20
  29. data/lib/puma/compat.rb +3 -7
  30. data/lib/puma/configuration.rb +133 -130
  31. data/lib/puma/const.rb +13 -37
  32. data/lib/puma/control_cli.rb +38 -35
  33. data/lib/puma/convenient.rb +3 -3
  34. data/lib/puma/detect.rb +3 -1
  35. data/lib/puma/dsl.rb +80 -58
  36. data/lib/puma/events.rb +6 -8
  37. data/lib/puma/io_buffer.rb +1 -1
  38. data/lib/puma/jruby_restart.rb +0 -1
  39. data/lib/puma/launcher.rb +52 -30
  40. data/lib/puma/minissl.rb +73 -4
  41. data/lib/puma/null_io.rb +6 -13
  42. data/lib/puma/plugin/tmp_restart.rb +1 -2
  43. data/lib/puma/rack/builder.rb +3 -0
  44. data/lib/puma/rack/urlmap.rb +9 -8
  45. data/lib/puma/reactor.rb +135 -0
  46. data/lib/puma/runner.rb +23 -1
  47. data/lib/puma/server.rb +117 -34
  48. data/lib/puma/single.rb +14 -3
  49. data/lib/puma/thread_pool.rb +67 -20
  50. data/lib/puma/util.rb +1 -5
  51. data/lib/rack/handler/puma.rb +58 -17
  52. data/tools/jungle/README.md +12 -2
  53. data/tools/jungle/init.d/README.md +9 -2
  54. data/tools/jungle/init.d/puma +32 -62
  55. data/tools/jungle/init.d/run-puma +5 -1
  56. data/tools/jungle/rc.d/README.md +74 -0
  57. data/tools/jungle/rc.d/puma +61 -0
  58. data/tools/jungle/rc.d/puma.conf +10 -0
  59. data/tools/trickletest.rb +1 -1
  60. metadata +22 -92
  61. data/Gemfile +0 -13
  62. data/Manifest.txt +0 -77
  63. data/Rakefile +0 -158
  64. data/lib/puma/rack/backports/uri/common_18.rb +0 -59
  65. data/lib/puma/rack/backports/uri/common_192.rb +0 -55
  66. data/puma.gemspec +0 -52
@@ -1,4 +1,5 @@
1
1
  require 'puma/const'
2
+ require "puma/null_io"
2
3
  require 'stringio'
3
4
 
4
5
  module Puma
@@ -34,8 +35,6 @@ module Puma
34
35
 
35
36
  @debug = ENV.key? 'PUMA_DEBUG'
36
37
 
37
- @on_booted = []
38
-
39
38
  @hooks = Hash.new { |h,k| h[k] = [] }
40
39
  end
41
40
 
@@ -48,7 +47,7 @@ module Puma
48
47
  @hooks[hook].each { |t| t.call(*args) }
49
48
  end
50
49
 
51
- # Register a callbock for a given hook
50
+ # Register a callback for a given hook
52
51
  #
53
52
  def register(hook, obj=nil, &blk)
54
53
  if obj and blk
@@ -92,8 +91,7 @@ module Puma
92
91
  # parsing exception.
93
92
  #
94
93
  def parse_error(server, env, error)
95
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
96
- @stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
94
+ @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
97
95
  end
98
96
 
99
97
  # An SSL error has occurred.
@@ -124,12 +122,12 @@ module Puma
124
122
  end
125
123
  end
126
124
 
127
- def on_booted(&b)
128
- @on_booted << b
125
+ def on_booted(&block)
126
+ register(:on_booted, &block)
129
127
  end
130
128
 
131
129
  def fire_on_booted!
132
- @on_booted.each { |b| b.call }
130
+ fire(:on_booted)
133
131
  end
134
132
 
135
133
  DEFAULT = new(STDOUT, STDERR)
@@ -1,6 +1,6 @@
1
1
  require 'puma/detect'
2
2
 
3
- if Puma::IS_JRUBY
3
+ if Puma.jruby?
4
4
  require 'puma/java_io_buffer'
5
5
  else
6
6
  require 'puma/puma_http11'
@@ -80,4 +80,3 @@ module Puma
80
80
  end
81
81
  end
82
82
  end
83
-
@@ -1,15 +1,12 @@
1
- require 'puma/server'
2
- require 'puma/const'
3
- require 'puma/configuration'
4
- require 'puma/binder'
1
+ require 'puma/events'
5
2
  require 'puma/detect'
6
- require 'puma/daemon_ext'
7
- require 'puma/util'
8
- require 'puma/single'
3
+
9
4
  require 'puma/cluster'
10
- require 'puma/state_file'
5
+ require 'puma/single'
6
+
7
+ require 'puma/const'
11
8
 
12
- require 'puma/commonlogger'
9
+ require 'puma/binder'
13
10
 
14
11
  module Puma
15
12
  # Puma::Launcher is the single entry point for starting a Puma server based on user
@@ -37,13 +34,13 @@ module Puma
37
34
  #
38
35
  # Examples:
39
36
  #
40
- # conf = Puma::Configuration.new do |c|
41
- # c.threads 1, 10
42
- # c.app do |env|
37
+ # conf = Puma::Configuration.new do |user_config|
38
+ # user_config.threads 1, 10
39
+ # user_config.app do |env|
43
40
  # [200, {}, ["hello world"]]
44
41
  # end
45
42
  # end
46
- # Puma::Launcher.new(conf, argv: Puma::Events.stdio).run
43
+ # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
47
44
  def initialize(conf, launcher_args={})
48
45
  @runner = nil
49
46
  @events = launcher_args[:events] || Events::DEFAULT
@@ -62,6 +59,7 @@ module Puma
62
59
  @config.load
63
60
 
64
61
  @options = @config.options
62
+ @config.clamp
65
63
 
66
64
  generate_restart_data
67
65
 
@@ -88,6 +86,7 @@ module Puma
88
86
  else
89
87
  @runner = Single.new(self, @events)
90
88
  end
89
+ Puma.stats_object = @runner
91
90
 
92
91
  @status = :run
93
92
  end
@@ -107,6 +106,8 @@ module Puma
107
106
  path = @options[:state]
108
107
  return unless path
109
108
 
109
+ require 'puma/state_file'
110
+
110
111
  sf = StateFile.new
111
112
  sf.pid = Process.pid
112
113
  sf.control_url = @options[:control_url]
@@ -163,6 +164,17 @@ module Puma
163
164
 
164
165
  # Run the server. This blocks until the server is stopped
165
166
  def run
167
+ previous_env =
168
+ if defined?(Bundler)
169
+ env = Bundler::ORIGINAL_ENV.dup
170
+ # add -rbundler/setup so we load from Gemfile when restarting
171
+ bundle = "-rbundler/setup"
172
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
173
+ env
174
+ else
175
+ ENV.to_h
176
+ end
177
+
166
178
  @config.clamp
167
179
 
168
180
  @config.plugins.fire_starts self
@@ -178,6 +190,7 @@ module Puma
178
190
  graceful_stop
179
191
  when :restart
180
192
  log "* Restarting..."
193
+ ENV.replace(previous_env)
181
194
  @runner.before_restart
182
195
  restart!
183
196
  when :exit
@@ -222,8 +235,8 @@ module Puma
222
235
  else
223
236
  redirects = {:close_others => true}
224
237
  @binder.listeners.each_with_index do |(l, io), i|
225
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
226
- redirects[io.to_i] = io.to_i
238
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
239
+ redirects[io.to_i] = io.to_i
227
240
  end
228
241
 
229
242
  argv = restart_args
@@ -256,6 +269,8 @@ module Puma
256
269
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
257
270
  wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
258
271
  args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
272
+ # Ruby 2.0+ defaults to true which breaks socket activation
273
+ args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
259
274
  Kernel.exec(*args)
260
275
  end
261
276
  end
@@ -284,8 +299,8 @@ module Puma
284
299
  end
285
300
 
286
301
  def title
287
- buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
288
- buffer << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
302
+ buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
303
+ buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
289
304
  buffer
290
305
  end
291
306
 
@@ -335,8 +350,6 @@ module Puma
335
350
 
336
351
  @restart_dir ||= Dir.pwd
337
352
 
338
- require 'rubygems'
339
-
340
353
  # if $0 is a file in the current directory, then restart
341
354
  # it the same, otherwise add -S on there because it was
342
355
  # picked up in PATH.
@@ -347,9 +360,10 @@ module Puma
347
360
  arg0 = [Gem.ruby, "-S", $0]
348
361
  end
349
362
 
350
- # Detect and reinject -Ilib from the command line
363
+ # Detect and reinject -Ilib from the command line, used for testing without bundler
364
+ # cruby has an expanded path, jruby has just "lib"
351
365
  lib = File.expand_path "lib"
352
- arg0[1,0] = ["-I", lib] if $:[0] == lib
366
+ arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
353
367
 
354
368
  if defined? Puma::WILD_ARGS
355
369
  @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
@@ -379,12 +393,28 @@ module Puma
379
393
 
380
394
  begin
381
395
  Signal.trap "SIGTERM" do
382
- stop
396
+ graceful_stop
397
+
398
+ raise SignalException, "SIGTERM"
383
399
  end
384
400
  rescue Exception
385
401
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
386
402
  end
387
403
 
404
+ begin
405
+ Signal.trap "SIGINT" do
406
+ if Puma.jruby?
407
+ @status = :exit
408
+ graceful_stop
409
+ exit
410
+ end
411
+
412
+ stop
413
+ end
414
+ rescue Exception
415
+ log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
416
+ end
417
+
388
418
  begin
389
419
  Signal.trap "SIGHUP" do
390
420
  if @runner.redirected_io?
@@ -396,14 +426,6 @@ module Puma
396
426
  rescue Exception
397
427
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
398
428
  end
399
-
400
- if Puma.jruby?
401
- Signal.trap("INT") do
402
- @status = :exit
403
- graceful_stop
404
- exit
405
- end
406
- end
407
429
  end
408
430
  end
409
431
  end
@@ -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
@@ -11,6 +16,10 @@ module Puma
11
16
  @socket
12
17
  end
13
18
 
19
+ def closed?
20
+ @socket.closed?
21
+ end
22
+
14
23
  def readpartial(size)
15
24
  while true
16
25
  output = @engine.read
@@ -36,12 +45,29 @@ module Puma
36
45
  output
37
46
  end
38
47
 
39
- def read_nonblock(size)
48
+ def read_nonblock(size, *_)
49
+ # *_ is to deal with keyword args that were added
50
+ # at some point (and being used in the wild)
40
51
  while true
41
52
  output = engine_read_all
42
53
  return output if output
43
54
 
44
- data = @socket.read_nonblock(size)
55
+ begin
56
+ data = @socket.read_nonblock(size, exception: false)
57
+ if data == :wait_readable || data == :wait_writable
58
+ if @socket.to_io.respond_to?(data)
59
+ @socket.to_io.__send__(data)
60
+ elsif data == :wait_readable
61
+ IO.select([@socket.to_io])
62
+ else
63
+ IO.select(nil, [@socket.to_io])
64
+ end
65
+ elsif !data
66
+ return nil
67
+ else
68
+ break
69
+ end
70
+ end while true
45
71
 
46
72
  @engine.inject(data)
47
73
  output = engine_read_all
@@ -55,6 +81,8 @@ module Puma
55
81
  end
56
82
 
57
83
  def write(data)
84
+ return 0 if data.empty?
85
+
58
86
  need = data.bytesize
59
87
 
60
88
  while true
@@ -75,13 +103,52 @@ module Puma
75
103
  end
76
104
 
77
105
  alias_method :syswrite, :write
106
+ alias_method :<<, :write
107
+
108
+ # This is a temporary fix to deal with websockets code using
109
+ # write_nonblock. The problem with implementing it properly
110
+ # is that it means we'd have to have the ability to rewind
111
+ # an engine because after we write+extract, the socket
112
+ # write_nonblock call might raise an exception and later
113
+ # code would pass the same data in, but the engine would think
114
+ # it had already written the data in. So for the time being
115
+ # (and since write blocking is quite rare), go ahead and actually
116
+ # block in write_nonblock.
117
+ def write_nonblock(data, *_)
118
+ write data
119
+ end
78
120
 
79
121
  def flush
80
122
  @socket.flush
81
123
  end
82
124
 
125
+ def read_and_drop(timeout = 1)
126
+ return :timeout unless IO.select([@socket], nil, nil, timeout)
127
+ return :eof unless read_nonblock(1024)
128
+ :drop
129
+ rescue Errno::EAGAIN
130
+ # do nothing
131
+ :eagain
132
+ end
133
+
134
+ def should_drop_bytes?
135
+ @engine.init? || !@engine.shutdown
136
+ end
137
+
83
138
  def close
84
- @socket.close
139
+ begin
140
+ # Read any drop any partially initialized sockets and any received bytes during shutdown.
141
+ # Don't let this socket hold this loop forever.
142
+ # If it can't send more packets within 1s, then give up.
143
+ while should_drop_bytes?
144
+ return if [:timeout, :eof].include?(read_and_drop(1))
145
+ end
146
+ rescue IOError, SystemCallError
147
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
148
+ # nothing
149
+ ensure
150
+ @socket.close
151
+ end
85
152
  end
86
153
 
87
154
  def peeraddr
@@ -113,6 +180,7 @@ module Puma
113
180
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
114
181
  attr_reader :keystore
115
182
  attr_accessor :keystore_pass
183
+ attr_accessor :ssl_cipher_list
116
184
 
117
185
  def keystore=(keystore)
118
186
  raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
@@ -128,6 +196,7 @@ module Puma
128
196
  attr_reader :key
129
197
  attr_reader :cert
130
198
  attr_reader :ca
199
+ attr_accessor :ssl_cipher_filter
131
200
 
132
201
  def key=(key)
133
202
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -182,7 +251,7 @@ module Puma
182
251
  end
183
252
 
184
253
  def close
185
- @socket.close
254
+ @socket.close unless @socket.closed? # closed? call is for Windows
186
255
  end
187
256
  end
188
257
  end
@@ -1,42 +1,35 @@
1
1
  module Puma
2
-
3
2
  # Provides an IO-like object that always appears to contain no data.
4
3
  # Used as the value for rack.input when the request has no body.
5
4
  #
6
5
  class NullIO
7
- # Always returns nil
8
- #
9
6
  def gets
10
7
  nil
11
8
  end
12
9
 
13
- # Never yields
14
- #
15
10
  def each
16
11
  end
17
12
 
18
- # Mimics IO#read with no data
13
+ # Mimics IO#read with no data.
19
14
  #
20
- def read(count=nil,buffer=nil)
15
+ def read(count = nil, _buffer = nil)
21
16
  (count && count > 0) ? nil : ""
22
17
  end
23
18
 
24
- # Does nothing
25
- #
26
19
  def rewind
27
20
  end
28
21
 
29
- # Does nothing
30
- #
31
22
  def close
32
23
  end
33
24
 
34
- # Always zero
35
- #
36
25
  def size
37
26
  0
38
27
  end
39
28
 
29
+ def eof?
30
+ true
31
+ end
32
+
40
33
  def sync=(v)
41
34
  end
42
35
 
@@ -8,7 +8,7 @@ Puma::Plugin.create do
8
8
 
9
9
  # If we can't write to the path, then just don't bother with this plugin
10
10
  begin
11
- File.write path, ""
11
+ File.write(path, "") unless File.exist?(path)
12
12
  orig = File.stat(path).mtime
13
13
  rescue SystemCallError
14
14
  return
@@ -32,4 +32,3 @@ Puma::Plugin.create do
32
32
  end
33
33
  end
34
34
  end
35
-
@@ -1,3 +1,6 @@
1
+ module Puma
2
+ end
3
+
1
4
  module Puma::Rack
2
5
  class Options
3
6
  def parse!(args)