puma 3.4.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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +356 -74
  3. data/README.md +143 -227
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
  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 +291 -447
  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/http11_parser_common.rl +1 -1
  18. data/ext/puma_http11/io_buffer.c +7 -7
  19. data/ext/puma_http11/mini_ssl.c +67 -6
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +76 -94
  21. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  22. data/ext/puma_http11/puma_http11.c +1 -0
  23. data/lib/puma.rb +13 -5
  24. data/lib/puma/app/status.rb +8 -0
  25. data/lib/puma/binder.rb +46 -21
  26. data/lib/puma/cli.rb +49 -33
  27. data/lib/puma/client.rb +149 -4
  28. data/lib/puma/cluster.rb +55 -13
  29. data/lib/puma/commonlogger.rb +19 -20
  30. data/lib/puma/compat.rb +3 -7
  31. data/lib/puma/configuration.rb +136 -131
  32. data/lib/puma/const.rb +19 -37
  33. data/lib/puma/control_cli.rb +38 -35
  34. data/lib/puma/convenient.rb +3 -3
  35. data/lib/puma/detect.rb +3 -1
  36. data/lib/puma/dsl.rb +86 -57
  37. data/lib/puma/events.rb +17 -13
  38. data/lib/puma/io_buffer.rb +1 -1
  39. data/lib/puma/jruby_restart.rb +0 -1
  40. data/lib/puma/launcher.rb +61 -30
  41. data/lib/puma/minissl.rb +85 -4
  42. data/lib/puma/null_io.rb +6 -13
  43. data/lib/puma/plugin.rb +12 -1
  44. data/lib/puma/plugin/tmp_restart.rb +1 -2
  45. data/lib/puma/rack/builder.rb +3 -0
  46. data/lib/puma/rack/urlmap.rb +9 -8
  47. data/lib/puma/reactor.rb +144 -0
  48. data/lib/puma/runner.rb +27 -1
  49. data/lib/puma/server.rb +135 -33
  50. data/lib/puma/single.rb +17 -3
  51. data/lib/puma/tcp_logger.rb +8 -1
  52. data/lib/puma/thread_pool.rb +70 -20
  53. data/lib/puma/util.rb +1 -5
  54. data/lib/rack/handler/puma.rb +58 -17
  55. data/tools/jungle/README.md +12 -2
  56. data/tools/jungle/init.d/README.md +9 -2
  57. data/tools/jungle/init.d/puma +85 -58
  58. data/tools/jungle/init.d/run-puma +16 -1
  59. data/tools/jungle/rc.d/README.md +74 -0
  60. data/tools/jungle/rc.d/puma +61 -0
  61. data/tools/jungle/rc.d/puma.conf +10 -0
  62. data/tools/jungle/upstart/puma.conf +1 -1
  63. data/tools/trickletest.rb +1 -1
  64. metadata +22 -94
  65. data/Gemfile +0 -13
  66. data/Manifest.txt +0 -78
  67. data/Rakefile +0 -158
  68. data/docs/config.md +0 -0
  69. data/lib/puma/rack/backports/uri/common_18.rb +0 -59
  70. data/lib/puma/rack/backports/uri/common_192.rb +0 -55
  71. 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.
@@ -106,24 +104,30 @@ module Puma
106
104
  end
107
105
 
108
106
  # An unknown error has occurred.
109
- # +server+ is the Server object, +env+ the request, +error+ an exception
110
- # object, and +kind+ some additional info.
107
+ # +server+ is the Server object, +error+ an exception object,
108
+ # +kind+ some additional info, and +env+ the request.
111
109
  #
112
- def unknown_error(server, error, kind="Unknown")
110
+ def unknown_error(server, error, kind="Unknown", env=nil)
113
111
  if error.respond_to? :render
114
112
  error.render "#{Time.now}: #{kind} error", @stderr
115
113
  else
116
- @stderr.puts "#{Time.now}: #{kind} error: #{error.inspect}"
117
- @stderr.puts error.backtrace.join("\n")
114
+ if env
115
+ string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
116
+ string_block << error.inspect
117
+ else
118
+ string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
119
+ end
120
+ string_block << error.backtrace
121
+ @stderr.puts string_block.join("\n")
118
122
  end
119
123
  end
120
124
 
121
- def on_booted(&b)
122
- @on_booted << b
125
+ def on_booted(&block)
126
+ register(:on_booted, &block)
123
127
  end
124
128
 
125
129
  def fire_on_booted!
126
- @on_booted.each { |b| b.call }
130
+ fire(:on_booted)
127
131
  end
128
132
 
129
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
 
@@ -316,6 +331,11 @@ module Puma
316
331
  if dir = @options[:directory]
317
332
  @restart_dir = dir
318
333
 
334
+ elsif Puma.windows?
335
+ # I guess the value of PWD is garbage on windows so don't bother
336
+ # using it.
337
+ @restart_dir = Dir.pwd
338
+
319
339
  # Use the same trick as unicorn, namely favor PWD because
320
340
  # it will contain an unresolved symlink, useful for when
321
341
  # the pwd is /data/releases/current.
@@ -330,8 +350,6 @@ module Puma
330
350
 
331
351
  @restart_dir ||= Dir.pwd
332
352
 
333
- require 'rubygems'
334
-
335
353
  # if $0 is a file in the current directory, then restart
336
354
  # it the same, otherwise add -S on there because it was
337
355
  # picked up in PATH.
@@ -342,9 +360,10 @@ module Puma
342
360
  arg0 = [Gem.ruby, "-S", $0]
343
361
  end
344
362
 
345
- # 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"
346
365
  lib = File.expand_path "lib"
347
- arg0[1,0] = ["-I", lib] if $:[0] == lib
366
+ arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
348
367
 
349
368
  if defined? Puma::WILD_ARGS
350
369
  @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
@@ -374,26 +393,38 @@ module Puma
374
393
 
375
394
  begin
376
395
  Signal.trap "SIGTERM" do
377
- stop
396
+ graceful_stop
397
+
398
+ raise SignalException, "SIGTERM"
378
399
  end
379
400
  rescue Exception
380
401
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
381
402
  end
382
403
 
383
404
  begin
384
- Signal.trap "SIGHUP" do
385
- @runner.redirect_io
405
+ Signal.trap "SIGINT" do
406
+ if Puma.jruby?
407
+ @status = :exit
408
+ graceful_stop
409
+ exit
410
+ end
411
+
412
+ stop
386
413
  end
387
414
  rescue Exception
388
- log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
415
+ log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
389
416
  end
390
417
 
391
- if Puma.jruby?
392
- Signal.trap("INT") do
393
- @status = :exit
394
- graceful_stop
395
- exit
418
+ begin
419
+ Signal.trap "SIGHUP" do
420
+ if @runner.redirected_io?
421
+ @runner.redirect_io
422
+ else
423
+ stop
424
+ end
396
425
  end
426
+ rescue Exception
427
+ log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
397
428
  end
398
429
  end
399
430
  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,16 +180,23 @@ 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
119
187
  @keystore = keystore
120
188
  end
189
+
190
+ def check
191
+ raise "Keystore not configured" unless @keystore
192
+ end
193
+
121
194
  else
122
195
  # non-jruby Context properties
123
196
  attr_reader :key
124
197
  attr_reader :cert
125
198
  attr_reader :ca
199
+ attr_accessor :ssl_cipher_filter
126
200
 
127
201
  def key=(key)
128
202
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -138,6 +212,11 @@ module Puma
138
212
  raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
139
213
  @ca = ca
140
214
  end
215
+
216
+ def check
217
+ raise "Key not configured" unless @key
218
+ raise "Cert not configured" unless @cert
219
+ end
141
220
  end
142
221
  end
143
222
 
@@ -156,6 +235,7 @@ module Puma
156
235
  end
157
236
 
158
237
  def accept
238
+ @ctx.check
159
239
  io = @socket.accept
160
240
  engine = Engine.server @ctx
161
241
 
@@ -163,6 +243,7 @@ module Puma
163
243
  end
164
244
 
165
245
  def accept_nonblock
246
+ @ctx.check
166
247
  io = @socket.accept_nonblock
167
248
  engine = Engine.server @ctx
168
249
 
@@ -170,7 +251,7 @@ module Puma
170
251
  end
171
252
 
172
253
  def close
173
- @socket.close
254
+ @socket.close unless @socket.closed? # closed? call is for Windows
174
255
  end
175
256
  end
176
257
  end