puma 4.3.12 → 5.6.4

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1461 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +38 -9
  25. data/ext/puma_http11/http11_parser.c +45 -47
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +1 -1
  28. data/ext/puma_http11/http11_parser.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +204 -86
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +47 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +104 -76
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +13 -6
  45. data/lib/puma/control_cli.rb +93 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +364 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +117 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +128 -46
  56. data/lib/puma/null_io.rb +13 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +472 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +290 -763
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +47 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +20 -1
  69. data/lib/puma.rb +46 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  72. metadata +26 -22
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/accept_nonblock.rb +0 -29
  77. data/lib/puma/tcp_logger.rb +0 -41
  78. data/tools/jungle/README.md +0 -19
  79. data/tools/jungle/init.d/README.md +0 -61
  80. data/tools/jungle/init.d/puma +0 -421
  81. data/tools/jungle/init.d/run-puma +0 -18
  82. data/tools/jungle/upstart/README.md +0 -61
  83. data/tools/jungle/upstart/puma-manager.conf +0 -31
  84. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/launcher.rb CHANGED
@@ -15,6 +15,7 @@ module Puma
15
15
  # It is responsible for either launching a cluster of Puma workers or a single
16
16
  # puma server.
17
17
  class Launcher
18
+ # @deprecated 6.0.0
18
19
  KEYS_NOT_TO_PERSIST_IN_STATE = [
19
20
  :logger, :lowlevel_error_handler,
20
21
  :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
@@ -47,8 +48,9 @@ module Puma
47
48
  @original_argv = @argv.dup
48
49
  @config = conf
49
50
 
50
- @binder = Binder.new(@events)
51
- @binder.import_from_env
51
+ @binder = Binder.new(@events, conf)
52
+ @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
53
+ @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
52
54
 
53
55
  @environment = conf.environment
54
56
 
@@ -57,6 +59,13 @@ module Puma
57
59
 
58
60
  @config.load
59
61
 
62
+ if @config.options[:bind_to_activated_sockets]
63
+ @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
+ @config.options[:binds],
65
+ @config.options[:bind_to_activated_sockets] == 'only'
66
+ )
67
+ end
68
+
60
69
  @options = @config.options
61
70
  @config.clamp
62
71
 
@@ -65,14 +74,10 @@ module Puma
65
74
 
66
75
  generate_restart_data
67
76
 
68
- if clustered? && !Process.respond_to?(:fork)
77
+ if clustered? && !Puma.forkable?
69
78
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
70
79
  end
71
80
 
72
- if @options[:daemon] && Puma.windows?
73
- unsupported 'daemon mode not supported on Windows'
74
- end
75
-
76
81
  Dir.chdir(@restart_dir)
77
82
 
78
83
  prune_bundler if prune_bundler?
@@ -90,6 +95,8 @@ module Puma
90
95
  Puma.stats_object = @runner
91
96
 
92
97
  @status = :run
98
+
99
+ log_config if ENV['PUMA_LOG_CONFIG']
93
100
  end
94
101
 
95
102
  attr_reader :binder, :events, :config, :options, :restart_dir
@@ -105,6 +112,7 @@ module Puma
105
112
  write_pid
106
113
 
107
114
  path = @options[:state]
115
+ permission = @options[:state_permission]
108
116
  return unless path
109
117
 
110
118
  require 'puma/state_file'
@@ -113,8 +121,9 @@ module Puma
113
121
  sf.pid = Process.pid
114
122
  sf.control_url = @options[:control_url]
115
123
  sf.control_auth_token = @options[:control_auth_token]
124
+ sf.running_from = File.expand_path('.')
116
125
 
117
- sf.save path
126
+ sf.save path, permission
118
127
  end
119
128
 
120
129
  # Delete the configured pidfile
@@ -169,29 +178,34 @@ module Puma
169
178
 
170
179
  setup_signals
171
180
  set_process_title
181
+ integrate_with_systemd
172
182
  @runner.run
173
183
 
174
184
  case @status
175
185
  when :halt
176
186
  log "* Stopping immediately!"
187
+ @runner.stop_control
177
188
  when :run, :stop
178
189
  graceful_stop
179
190
  when :restart
180
191
  log "* Restarting..."
181
192
  ENV.replace(previous_env)
182
- @runner.before_restart
193
+ @runner.stop_control
183
194
  restart!
184
195
  when :exit
185
196
  # nothing
186
197
  end
187
- @binder.close_unix_paths
198
+ close_binder_listeners unless @status == :restart
188
199
  end
189
200
 
190
- # Return which tcp port the launcher is using, if it's using TCP
191
- def connected_port
192
- @binder.connected_port
201
+ # Return all tcp ports the launcher may be using, TCP or SSL
202
+ # @!attribute [r] connected_ports
203
+ # @version 5.0.0
204
+ def connected_ports
205
+ @binder.connected_ports
193
206
  end
194
207
 
208
+ # @!attribute [r] restart_args
195
209
  def restart_args
196
210
  cmd = @options[:restart_cmd]
197
211
  if cmd
@@ -202,7 +216,25 @@ module Puma
202
216
  end
203
217
 
204
218
  def close_binder_listeners
219
+ @runner.close_control_listeners
205
220
  @binder.close_listeners
221
+ unless @status == :restart
222
+ log "=== puma shutdown: #{Time.now} ==="
223
+ log "- Goodbye!"
224
+ end
225
+ end
226
+
227
+ # @!attribute [r] thread_status
228
+ # @version 5.0.0
229
+ def thread_status
230
+ Thread.list.each do |thread|
231
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
232
+ name += " #{thread['label']}" if thread['label']
233
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
234
+ backtrace = thread.backtrace || ["<no backtrace available>"]
235
+
236
+ yield name, backtrace
237
+ end
206
238
  end
207
239
 
208
240
  private
@@ -212,11 +244,10 @@ module Puma
212
244
  def write_pid
213
245
  path = @options[:pidfile]
214
246
  return unless path
215
-
216
- File.open(path, 'w') { |f| f.puts Process.pid }
217
- cur = Process.pid
247
+ cur_pid = Process.pid
248
+ File.write path, cur_pid, mode: 'wb:UTF-8'
218
249
  at_exit do
219
- delete_pidfile if cur == Process.pid
250
+ delete_pidfile if cur_pid == Process.pid
220
251
  end
221
252
  end
222
253
 
@@ -225,7 +256,8 @@ module Puma
225
256
  end
226
257
 
227
258
  def restart!
228
- @config.run_hooks :on_restart, self
259
+ @events.fire_on_restart!
260
+ @config.run_hooks :on_restart, self, @events
229
261
 
230
262
  if Puma.jruby?
231
263
  close_binder_listeners
@@ -241,21 +273,20 @@ module Puma
241
273
  else
242
274
  argv = restart_args
243
275
  Dir.chdir(@restart_dir)
276
+ ENV.update(@binder.redirects_for_restart_env)
244
277
  argv += [@binder.redirects_for_restart]
245
278
  Kernel.exec(*argv)
246
279
  end
247
280
  end
248
281
 
249
- def dependencies_and_files_to_require_after_prune
282
+ # @!attribute [r] files_to_require_after_prune
283
+ def files_to_require_after_prune
250
284
  puma = spec_for_gem("puma")
251
285
 
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]
286
+ require_paths_for_gem(puma) + extra_runtime_deps_directories
257
287
  end
258
288
 
289
+ # @!attribute [r] extra_runtime_deps_directories
259
290
  def extra_runtime_deps_directories
260
291
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
292
  if (spec = spec_for_gem(d_name))
@@ -267,6 +298,7 @@ module Puma
267
298
  end.flatten.compact
268
299
  end
269
300
 
301
+ # @!attribute [r] puma_wild_location
270
302
  def puma_wild_location
271
303
  puma = spec_for_gem("puma")
272
304
  dirs = require_paths_for_gem(puma)
@@ -275,6 +307,7 @@ module Puma
275
307
  end
276
308
 
277
309
  def prune_bundler
310
+ return if ENV['PUMA_BUNDLER_PRUNED']
278
311
  return unless defined?(Bundler)
279
312
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
313
  unless puma_wild_location
@@ -282,20 +315,48 @@ module Puma
282
315
  return
283
316
  end
284
317
 
285
- deps, dirs = dependencies_and_files_to_require_after_prune
318
+ dirs = files_to_require_after_prune
286
319
 
287
320
  log '* Pruning Bundler environment'
288
321
  home = ENV['GEM_HOME']
289
- Bundler.with_clean_env do
322
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
323
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
324
+ with_unbundled_env do
290
325
  ENV['GEM_HOME'] = home
326
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
291
327
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
292
- args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
328
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
329
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
293
330
  # Ruby 2.0+ defaults to true which breaks socket activation
294
331
  args += [{:close_others => false}]
295
332
  Kernel.exec(*args)
296
333
  end
297
334
  end
298
335
 
336
+ #
337
+ # Puma's systemd integration allows Puma to inform systemd:
338
+ # 1. when it has successfully started
339
+ # 2. when it is starting shutdown
340
+ # 3. periodically for a liveness check with a watchdog thread
341
+ #
342
+
343
+ def integrate_with_systemd
344
+ return unless ENV["NOTIFY_SOCKET"]
345
+
346
+ begin
347
+ require 'puma/systemd'
348
+ rescue LoadError
349
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
350
+ return
351
+ end
352
+
353
+ log "* Enabling systemd notification integration"
354
+
355
+ systemd = Systemd.new(@events)
356
+ systemd.hook_events
357
+ systemd.start_watchdog
358
+ end
359
+
299
360
  def spec_for_gem(gem_name)
300
361
  Bundler.rubygems.loaded_specs(gem_name)
301
362
  end
@@ -318,30 +379,15 @@ module Puma
318
379
  end
319
380
 
320
381
  def graceful_stop
382
+ @events.fire_on_stopped!
321
383
  @runner.stop_blocked
322
- log "=== puma shutdown: #{Time.now} ==="
323
- log "- Goodbye!"
324
- end
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
384
  end
340
385
 
341
386
  def set_process_title
342
387
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
343
388
  end
344
389
 
390
+ # @!attribute [r] title
345
391
  def title
346
392
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
347
393
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -353,6 +399,7 @@ module Puma
353
399
  ENV['RACK_ENV'] = environment
354
400
  end
355
401
 
402
+ # @!attribute [r] environment
356
403
  def environment
357
404
  @environment
358
405
  end
@@ -456,8 +503,13 @@ module Puma
456
503
  end
457
504
 
458
505
  begin
459
- Signal.trap "SIGINFO" do
460
- log_thread_status
506
+ unless Puma.jruby? # INFO in use by JVM already
507
+ Signal.trap "SIGINFO" do
508
+ thread_status do |name, backtrace|
509
+ @events.log name
510
+ @events.log backtrace.map { |bt| " #{bt}" }
511
+ end
512
+ end
461
513
  end
462
514
  rescue Exception
463
515
  # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
@@ -471,5 +523,24 @@ module Puma
471
523
  raise "#{feature} is not supported on your version of RubyGems. " \
472
524
  "You must have RubyGems #{min_version}+ to use this feature."
473
525
  end
526
+
527
+ # @version 5.0.0
528
+ def with_unbundled_env
529
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
530
+ if bundler_ver < Gem::Version.new('2.1.0')
531
+ Bundler.with_clean_env { yield }
532
+ else
533
+ Bundler.with_unbundled_env { yield }
534
+ end
535
+ end
536
+
537
+ def log_config
538
+ log "Configuration:"
539
+
540
+ @config.final_options
541
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
542
+
543
+ log "\n"
544
+ end
474
545
  end
475
546
  end
@@ -2,9 +2,6 @@ module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
4
  def initialize(params, events)
5
- require 'puma/minissl'
6
- MiniSSL.check
7
-
8
5
  @params = params
9
6
  @events = events
10
7
  end
@@ -26,17 +23,19 @@ module Puma
26
23
  ctx.keystore_pass = params['keystore-pass']
27
24
  ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
28
25
  else
29
- unless params['key']
30
- events.error "Please specify the SSL key via 'key='"
26
+ if params['key'].nil? && params['key_pem'].nil?
27
+ events.error "Please specify the SSL key via 'key=' or 'key_pem='"
31
28
  end
32
29
 
33
- ctx.key = params['key']
30
+ ctx.key = params['key'] if params['key']
31
+ ctx.key_pem = params['key_pem'] if params['key_pem']
34
32
 
35
- unless params['cert']
36
- events.error "Please specify the SSL cert via 'cert='"
33
+ if params['cert'].nil? && params['cert_pem'].nil?
34
+ events.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
37
35
  end
38
36
 
39
- ctx.cert = params['cert']
37
+ ctx.cert = params['cert'] if params['cert']
38
+ ctx.cert_pem = params['cert_pem'] if params['cert_pem']
40
39
 
41
40
  if ['peer', 'force_peer'].include?(params['verify_mode'])
42
41
  unless params['ca']
@@ -65,6 +64,12 @@ module Puma
65
64
  end
66
65
  end
67
66
 
67
+ if params['verification_flags']
68
+ ctx.verification_flags = params['verification_flags'].split(',').
69
+ map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
70
+ inject { |sum, flag| sum ? sum | flag : flag }
71
+ end
72
+
68
73
  ctx
69
74
  end
70
75