puma 3.8.2 → 4.3.12

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 (91) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +305 -0
  3. data/LICENSE +0 -0
  4. data/README.md +162 -224
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +37 -0
  7. data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/nginx.md +0 -0
  12. data/docs/plugins.md +38 -0
  13. data/docs/restart.md +41 -0
  14. data/docs/signals.md +56 -3
  15. data/docs/systemd.md +130 -37
  16. data/docs/tcp_mode.md +96 -0
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  18. data/ext/puma_http11/ext_help.h +0 -0
  19. data/ext/puma_http11/extconf.rb +21 -0
  20. data/ext/puma_http11/http11_parser.c +134 -144
  21. data/ext/puma_http11/http11_parser.h +0 -0
  22. data/ext/puma_http11/http11_parser.java.rl +21 -37
  23. data/ext/puma_http11/http11_parser.rl +12 -10
  24. data/ext/puma_http11/http11_parser_common.rl +4 -4
  25. data/ext/puma_http11/io_buffer.c +0 -0
  26. data/ext/puma_http11/mini_ssl.c +165 -34
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +85 -101
  29. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  30. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  31. data/ext/puma_http11/puma_http11.c +3 -0
  32. data/lib/puma/accept_nonblock.rb +7 -1
  33. data/lib/puma/app/status.rb +42 -26
  34. data/lib/puma/binder.rb +57 -74
  35. data/lib/puma/cli.rb +26 -7
  36. data/lib/puma/client.rb +307 -191
  37. data/lib/puma/cluster.rb +78 -34
  38. data/lib/puma/commonlogger.rb +2 -0
  39. data/lib/puma/configuration.rb +24 -16
  40. data/lib/puma/const.rb +41 -20
  41. data/lib/puma/control_cli.rb +46 -19
  42. data/lib/puma/detect.rb +2 -0
  43. data/lib/puma/dsl.rb +329 -68
  44. data/lib/puma/events.rb +6 -2
  45. data/lib/puma/io_buffer.rb +3 -6
  46. data/lib/puma/jruby_restart.rb +2 -1
  47. data/lib/puma/launcher.rb +125 -61
  48. data/lib/puma/minissl/context_builder.rb +76 -0
  49. data/lib/puma/minissl.rb +85 -28
  50. data/lib/puma/null_io.rb +2 -0
  51. data/lib/puma/plugin/tmp_restart.rb +2 -1
  52. data/lib/puma/plugin.rb +7 -2
  53. data/lib/puma/rack/builder.rb +4 -1
  54. data/lib/puma/rack/urlmap.rb +2 -0
  55. data/lib/puma/rack_default.rb +2 -0
  56. data/lib/puma/reactor.rb +224 -34
  57. data/lib/puma/runner.rb +27 -6
  58. data/lib/puma/server.rb +212 -68
  59. data/lib/puma/single.rb +16 -5
  60. data/lib/puma/state_file.rb +2 -0
  61. data/lib/puma/tcp_logger.rb +2 -0
  62. data/lib/puma/thread_pool.rb +67 -36
  63. data/lib/puma/util.rb +2 -6
  64. data/lib/puma.rb +16 -0
  65. data/lib/rack/handler/puma.rb +16 -5
  66. data/tools/docker/Dockerfile +16 -0
  67. data/tools/jungle/README.md +12 -2
  68. data/tools/jungle/init.d/README.md +2 -0
  69. data/tools/jungle/init.d/puma +8 -8
  70. data/tools/jungle/init.d/run-puma +1 -1
  71. data/tools/jungle/rc.d/README.md +74 -0
  72. data/tools/jungle/rc.d/puma +61 -0
  73. data/tools/jungle/rc.d/puma.conf +10 -0
  74. data/tools/jungle/upstart/README.md +0 -0
  75. data/tools/jungle/upstart/puma-manager.conf +0 -0
  76. data/tools/jungle/upstart/puma.conf +0 -0
  77. data/tools/trickletest.rb +1 -2
  78. metadata +32 -93
  79. data/.github/issue_template.md +0 -20
  80. data/Gemfile +0 -12
  81. data/Manifest.txt +0 -78
  82. data/Rakefile +0 -158
  83. data/Release.md +0 -9
  84. data/gemfiles/2.1-Gemfile +0 -12
  85. data/lib/puma/compat.rb +0 -14
  86. data/lib/puma/convenient.rb +0 -23
  87. data/lib/puma/daemon_ext.rb +0 -31
  88. data/lib/puma/delegation.rb +0 -11
  89. data/lib/puma/java_io_buffer.rb +0 -45
  90. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  91. data/puma.gemspec +0 -52
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
 
3
5
  module Puma
@@ -80,4 +82,3 @@ module Puma
80
82
  end
81
83
  end
82
84
  end
83
-
data/lib/puma/launcher.rb CHANGED
@@ -1,11 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/events'
2
4
  require 'puma/detect'
3
-
4
5
  require 'puma/cluster'
5
6
  require 'puma/single'
6
-
7
7
  require 'puma/const'
8
-
9
8
  require 'puma/binder'
10
9
 
11
10
  module Puma
@@ -40,7 +39,7 @@ module Puma
40
39
  # [200, {}, ["hello world"]]
41
40
  # end
42
41
  # end
43
- # Puma::Launcher.new(conf, argv: Puma::Events.stdio).run
42
+ # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
44
43
  def initialize(conf, launcher_args={})
45
44
  @runner = nil
46
45
  @events = launcher_args[:events] || Events::DEFAULT
@@ -61,10 +60,13 @@ module Puma
61
60
  @options = @config.options
62
61
  @config.clamp
63
62
 
63
+ @events.formatter = Events::PidFormatter.new if clustered?
64
+ @events.formatter = options[:log_formatter] if @options[:log_formatter]
65
+
64
66
  generate_restart_data
65
67
 
66
- if clustered? && (Puma.jruby? || Puma.windows?)
67
- unsupported 'worker mode not supported on JRuby or Windows'
68
+ if clustered? && !Process.respond_to?(:fork)
69
+ unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
68
70
  end
69
71
 
70
72
  if @options[:daemon] && Puma.windows?
@@ -79,13 +81,13 @@ module Puma
79
81
  set_rack_environment
80
82
 
81
83
  if clustered?
82
- @events.formatter = Events::PidFormatter.new
83
84
  @options[:logger] = @events
84
85
 
85
86
  @runner = Cluster.new(self, @events)
86
87
  else
87
88
  @runner = Single.new(self, @events)
88
89
  end
90
+ Puma.stats_object = @runner
89
91
 
90
92
  @status = :run
91
93
  end
@@ -121,19 +123,6 @@ module Puma
121
123
  File.unlink(path) if path && File.exist?(path)
122
124
  end
123
125
 
124
- # If configured, write the pid of the current process out
125
- # to a file.
126
- def write_pid
127
- path = @options[:pidfile]
128
- return unless path
129
-
130
- File.open(path, 'w') { |f| f.puts Process.pid }
131
- cur = Process.pid
132
- at_exit do
133
- delete_pidfile if cur == Process.pid
134
- end
135
- end
136
-
137
126
  # Begin async shutdown of the server
138
127
  def halt
139
128
  @status = :halt
@@ -163,6 +152,17 @@ module Puma
163
152
 
164
153
  # Run the server. This blocks until the server is stopped
165
154
  def run
155
+ previous_env =
156
+ if defined?(Bundler)
157
+ env = Bundler::ORIGINAL_ENV.dup
158
+ # add -rbundler/setup so we load from Gemfile when restarting
159
+ bundle = "-rbundler/setup"
160
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
161
+ env
162
+ else
163
+ ENV.to_h
164
+ end
165
+
166
166
  @config.clamp
167
167
 
168
168
  @config.plugins.fire_starts self
@@ -178,11 +178,13 @@ module Puma
178
178
  graceful_stop
179
179
  when :restart
180
180
  log "* Restarting..."
181
+ ENV.replace(previous_env)
181
182
  @runner.before_restart
182
183
  restart!
183
184
  when :exit
184
185
  # nothing
185
186
  end
187
+ @binder.close_unix_paths
186
188
  end
187
189
 
188
190
  # Return which tcp port the launcher is using, if it's using TCP
@@ -199,8 +201,25 @@ module Puma
199
201
  end
200
202
  end
201
203
 
204
+ def close_binder_listeners
205
+ @binder.close_listeners
206
+ end
207
+
202
208
  private
203
209
 
210
+ # If configured, write the pid of the current process out
211
+ # to a file.
212
+ def write_pid
213
+ path = @options[:pidfile]
214
+ return unless path
215
+
216
+ File.open(path, 'w') { |f| f.puts Process.pid }
217
+ cur = Process.pid
218
+ at_exit do
219
+ delete_pidfile if cur == Process.pid
220
+ end
221
+ end
222
+
204
223
  def reload_worker_directory
205
224
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
206
225
  end
@@ -220,48 +239,71 @@ module Puma
220
239
  Dir.chdir(@restart_dir)
221
240
  Kernel.exec(*argv)
222
241
  else
223
- redirects = {:close_others => true}
224
- @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
227
- end
228
-
229
242
  argv = restart_args
230
243
  Dir.chdir(@restart_dir)
231
- argv += [redirects] if RUBY_VERSION >= '1.9'
244
+ argv += [@binder.redirects_for_restart]
232
245
  Kernel.exec(*argv)
233
246
  end
234
247
  end
235
248
 
236
- def prune_bundler
237
- return unless defined?(Bundler)
238
- puma = Bundler.rubygems.loaded_specs("puma")
239
- dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
249
+ def dependencies_and_files_to_require_after_prune
250
+ puma = spec_for_gem("puma")
251
+
252
+ deps = puma.runtime_dependencies.map do |d|
253
+ "#{d.name}:#{spec_for_gem(d.name).version}"
254
+ end
255
+
256
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
257
+ end
258
+
259
+ def extra_runtime_deps_directories
260
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
+ if (spec = spec_for_gem(d_name))
262
+ require_paths_for_gem(spec)
263
+ else
264
+ log "* Could not load extra dependency: #{d_name}"
265
+ nil
266
+ end
267
+ end.flatten.compact
268
+ end
269
+
270
+ def puma_wild_location
271
+ puma = spec_for_gem("puma")
272
+ dirs = require_paths_for_gem(puma)
240
273
  puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
274
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
275
+ end
241
276
 
242
- unless puma_lib_dir
277
+ def prune_bundler
278
+ return unless defined?(Bundler)
279
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
+ unless puma_wild_location
243
281
  log "! Unable to prune Bundler environment, continuing"
244
282
  return
245
283
  end
246
284
 
247
- deps = puma.runtime_dependencies.map do |d|
248
- spec = Bundler.rubygems.loaded_specs(d.name)
249
- "#{d.name}:#{spec.version.to_s}"
250
- end
285
+ deps, dirs = dependencies_and_files_to_require_after_prune
251
286
 
252
287
  log '* Pruning Bundler environment'
253
288
  home = ENV['GEM_HOME']
254
289
  Bundler.with_clean_env do
255
290
  ENV['GEM_HOME'] = home
256
291
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
257
- wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
258
- args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
292
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
259
293
  # Ruby 2.0+ defaults to true which breaks socket activation
260
- args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
294
+ args += [{:close_others => false}]
261
295
  Kernel.exec(*args)
262
296
  end
263
297
  end
264
298
 
299
+ def spec_for_gem(gem_name)
300
+ Bundler.rubygems.loaded_specs(gem_name)
301
+ end
302
+
303
+ def require_paths_for_gem(gem_spec)
304
+ gem_spec.full_require_paths
305
+ end
306
+
265
307
  def log(str)
266
308
  @events.log str
267
309
  end
@@ -281,13 +323,28 @@ module Puma
281
323
  log "- Goodbye!"
282
324
  end
283
325
 
326
+ def log_thread_status
327
+ Thread.list.each do |thread|
328
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
331
+ log logstr
332
+
333
+ if thread.backtrace
334
+ log thread.backtrace.join("\n")
335
+ else
336
+ log "<no backtrace available>"
337
+ end
338
+ end
339
+ end
340
+
284
341
  def set_process_title
285
342
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
286
343
  end
287
344
 
288
345
  def title
289
- buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
290
- buffer << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
346
+ buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
347
+ buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
291
348
  buffer
292
349
  end
293
350
 
@@ -304,16 +361,6 @@ module Puma
304
361
  @options[:prune_bundler] && clustered? && !@options[:preload_app]
305
362
  end
306
363
 
307
- def close_binder_listeners
308
- @binder.listeners.each do |l, io|
309
- io.close
310
- uri = URI.parse(l)
311
- next unless uri.scheme == 'unix'
312
- File.unlink("#{uri.host}#{uri.path}")
313
- end
314
- end
315
-
316
-
317
364
  def generate_restart_data
318
365
  if dir = @options[:directory]
319
366
  @restart_dir = dir
@@ -337,8 +384,6 @@ module Puma
337
384
 
338
385
  @restart_dir ||= Dir.pwd
339
386
 
340
- require 'rubygems'
341
-
342
387
  # if $0 is a file in the current directory, then restart
343
388
  # it the same, otherwise add -S on there because it was
344
389
  # picked up in PATH.
@@ -349,9 +394,10 @@ module Puma
349
394
  arg0 = [Gem.ruby, "-S", $0]
350
395
  end
351
396
 
352
- # Detect and reinject -Ilib from the command line
397
+ # Detect and reinject -Ilib from the command line, used for testing without bundler
398
+ # cruby has an expanded path, jruby has just "lib"
353
399
  lib = File.expand_path "lib"
354
- arg0[1,0] = ["-I", lib] if $:[0] == lib
400
+ arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
355
401
 
356
402
  if defined? Puma::WILD_ARGS
357
403
  @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
@@ -381,12 +427,22 @@ module Puma
381
427
 
382
428
  begin
383
429
  Signal.trap "SIGTERM" do
384
- stop
430
+ graceful_stop
431
+
432
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
385
433
  end
386
434
  rescue Exception
387
435
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
388
436
  end
389
437
 
438
+ begin
439
+ Signal.trap "SIGINT" do
440
+ stop
441
+ end
442
+ rescue Exception
443
+ log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
444
+ end
445
+
390
446
  begin
391
447
  Signal.trap "SIGHUP" do
392
448
  if @runner.redirected_io?
@@ -399,13 +455,21 @@ module Puma
399
455
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
400
456
  end
401
457
 
402
- if Puma.jruby?
403
- Signal.trap("INT") do
404
- @status = :exit
405
- graceful_stop
406
- exit
458
+ begin
459
+ Signal.trap "SIGINFO" do
460
+ log_thread_status
407
461
  end
462
+ rescue Exception
463
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
464
+ # to see this constantly on Linux.
408
465
  end
409
466
  end
467
+
468
+ def require_rubygems_min_version!(min_version, feature)
469
+ return if min_version <= Gem::Version.new(Gem::VERSION)
470
+
471
+ raise "#{feature} is not supported on your version of RubyGems. " \
472
+ "You must have RubyGems #{min_version}+ to use this feature."
473
+ end
410
474
  end
411
475
  end
@@ -0,0 +1,76 @@
1
+ module Puma
2
+ module MiniSSL
3
+ class ContextBuilder
4
+ def initialize(params, events)
5
+ require 'puma/minissl'
6
+ MiniSSL.check
7
+
8
+ @params = params
9
+ @events = events
10
+ end
11
+
12
+ def context
13
+ ctx = MiniSSL::Context.new
14
+
15
+ if defined?(JRUBY_VERSION)
16
+ unless params['keystore']
17
+ events.error "Please specify the Java keystore via 'keystore='"
18
+ end
19
+
20
+ ctx.keystore = params['keystore']
21
+
22
+ unless params['keystore-pass']
23
+ events.error "Please specify the Java keystore password via 'keystore-pass='"
24
+ end
25
+
26
+ ctx.keystore_pass = params['keystore-pass']
27
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
28
+ else
29
+ unless params['key']
30
+ events.error "Please specify the SSL key via 'key='"
31
+ end
32
+
33
+ ctx.key = params['key']
34
+
35
+ unless params['cert']
36
+ events.error "Please specify the SSL cert via 'cert='"
37
+ end
38
+
39
+ ctx.cert = params['cert']
40
+
41
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
42
+ unless params['ca']
43
+ events.error "Please specify the SSL ca via 'ca='"
44
+ end
45
+ end
46
+
47
+ ctx.ca = params['ca'] if params['ca']
48
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
49
+ end
50
+
51
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
52
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
53
+
54
+ if params['verify_mode']
55
+ ctx.verify_mode = case params['verify_mode']
56
+ when "peer"
57
+ MiniSSL::VERIFY_PEER
58
+ when "force_peer"
59
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
60
+ when "none"
61
+ MiniSSL::VERIFY_NONE
62
+ else
63
+ events.error "Please specify a valid verify_mode="
64
+ MiniSSL::VERIFY_NONE
65
+ end
66
+ end
67
+
68
+ ctx
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :params, :events
74
+ end
75
+ end
76
+ end
data/lib/puma/minissl.rb CHANGED
@@ -1,3 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'io/wait'
5
+ rescue LoadError
6
+ end
7
+
1
8
  module Puma
2
9
  module MiniSSL
3
10
  class Socket
@@ -11,6 +18,10 @@ module Puma
11
18
  @socket
12
19
  end
13
20
 
21
+ def closed?
22
+ @socket.closed?
23
+ end
24
+
14
25
  def readpartial(size)
15
26
  while true
16
27
  output = @engine.read
@@ -36,12 +47,28 @@ module Puma
36
47
  output
37
48
  end
38
49
 
39
- def read_nonblock(size)
50
+ def read_nonblock(size, *_)
51
+ # *_ is to deal with keyword args that were added
52
+ # at some point (and being used in the wild)
40
53
  while true
41
54
  output = engine_read_all
42
55
  return output if output
43
56
 
44
- data = @socket.read_nonblock(size)
57
+ data = @socket.read_nonblock(size, exception: false)
58
+ if data == :wait_readable || data == :wait_writable
59
+ # It would make more sense to let @socket.read_nonblock raise
60
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
61
+ # I don't have a Windows machine to debug this so I can't explain
62
+ # exactly whats happening in that OS. Please let me know if you
63
+ # find out!
64
+ #
65
+ # In the meantime, we can emulate the correct behavior by
66
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
67
+ # ourselves.
68
+ raise IO::EAGAINWaitReadable
69
+ elsif data.nil?
70
+ return nil
71
+ end
45
72
 
46
73
  @engine.inject(data)
47
74
  output = engine_read_all
@@ -55,6 +82,8 @@ module Puma
55
82
  end
56
83
 
57
84
  def write(data)
85
+ return 0 if data.empty?
86
+
58
87
  need = data.bytesize
59
88
 
60
89
  while true
@@ -77,39 +106,46 @@ module Puma
77
106
  alias_method :syswrite, :write
78
107
  alias_method :<<, :write
79
108
 
109
+ # This is a temporary fix to deal with websockets code using
110
+ # write_nonblock. The problem with implementing it properly
111
+ # is that it means we'd have to have the ability to rewind
112
+ # an engine because after we write+extract, the socket
113
+ # write_nonblock call might raise an exception and later
114
+ # code would pass the same data in, but the engine would think
115
+ # it had already written the data in. So for the time being
116
+ # (and since write blocking is quite rare), go ahead and actually
117
+ # block in write_nonblock.
118
+ def write_nonblock(data, *_)
119
+ write data
120
+ end
121
+
80
122
  def flush
81
123
  @socket.flush
82
124
  end
83
125
 
84
- def close
85
- begin
86
- # Try to setup (so that we can then close them) any
87
- # partially initialized sockets.
88
- while @engine.init?
89
- # Don't let this socket hold this loop forever.
90
- # If it can't send more packets within 1s, then
91
- # give up.
92
- return unless IO.select([@socket], nil, nil, 1)
93
- begin
94
- read_nonblock(1024)
95
- rescue Errno::EAGAIN
96
- end
97
- end
98
-
99
- done = @engine.shutdown
100
-
101
- while true
102
- enc = @engine.extract
103
- @socket.write enc
104
-
105
- notify = @socket.sysread(1024)
126
+ def read_and_drop(timeout = 1)
127
+ return :timeout unless IO.select([@socket], nil, nil, timeout)
128
+ return :eof unless read_nonblock(1024)
129
+ :drop
130
+ rescue Errno::EAGAIN
131
+ # do nothing
132
+ :eagain
133
+ end
106
134
 
107
- @engine.inject notify
108
- done = @engine.shutdown
135
+ def should_drop_bytes?
136
+ @engine.init? || !@engine.shutdown
137
+ end
109
138
 
110
- break if done
139
+ def close
140
+ begin
141
+ # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
+ # Don't let this socket hold this loop forever.
143
+ # If it can't send more packets within 1s, then give up.
144
+ while should_drop_bytes?
145
+ return if [:timeout, :eof].include?(read_and_drop(1))
111
146
  end
112
147
  rescue IOError, SystemCallError
148
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
113
149
  # nothing
114
150
  ensure
115
151
  @socket.close
@@ -140,11 +176,18 @@ module Puma
140
176
 
141
177
  class Context
142
178
  attr_accessor :verify_mode
179
+ attr_reader :no_tlsv1, :no_tlsv1_1
180
+
181
+ def initialize
182
+ @no_tlsv1 = false
183
+ @no_tlsv1_1 = false
184
+ end
143
185
 
144
186
  if defined?(JRUBY_VERSION)
145
187
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
146
188
  attr_reader :keystore
147
189
  attr_accessor :keystore_pass
190
+ attr_accessor :ssl_cipher_list
148
191
 
149
192
  def keystore=(keystore)
150
193
  raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
@@ -160,6 +203,7 @@ module Puma
160
203
  attr_reader :key
161
204
  attr_reader :cert
162
205
  attr_reader :ca
206
+ attr_accessor :ssl_cipher_filter
163
207
 
164
208
  def key=(key)
165
209
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -181,6 +225,19 @@ module Puma
181
225
  raise "Cert not configured" unless @cert
182
226
  end
183
227
  end
228
+
229
+ # disables TLSv1
230
+ def no_tlsv1=(tlsv1)
231
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
232
+ @no_tlsv1 = tlsv1
233
+ end
234
+
235
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
+ def no_tlsv1_1=(tlsv1_1)
237
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
238
+ @no_tlsv1_1 = tlsv1_1
239
+ end
240
+
184
241
  end
185
242
 
186
243
  VERIFY_NONE = 0
@@ -214,7 +271,7 @@ module Puma
214
271
  end
215
272
 
216
273
  def close
217
- @socket.close
274
+ @socket.close unless @socket.closed? # closed? call is for Windows
218
275
  end
219
276
  end
220
277
  end
data/lib/puma/null_io.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Provides an IO-like object that always appears to contain no data.
3
5
  # Used as the value for rack.input when the request has no body.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
@@ -32,4 +34,3 @@ Puma::Plugin.create do
32
34
  end
33
35
  end
34
36
  end
35
-
data/lib/puma/plugin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class UnknownPlugin < RuntimeError; end
3
5
 
@@ -60,8 +62,11 @@ module Puma
60
62
  end
61
63
 
62
64
  def fire_background
63
- @background.each do |b|
64
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
65
70
  end
66
71
  end
67
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -110,7 +112,8 @@ module Puma::Rack
110
112
 
111
113
  has_options = false
112
114
  server.valid_options.each do |name, description|
113
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
115
+ next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
116
+
114
117
  info << " -O %-21s %s" % [name, description]
115
118
  has_options = true
116
119
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma::Rack
2
4
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
5
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/handler/puma'
2
4
 
3
5
  module Rack::Handler