puma 4.3.12 → 5.6.6

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1511 -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/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +104 -76
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +13 -6
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +476 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +28 -23
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. 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
@@ -150,6 +159,17 @@ module Puma
150
159
  true
151
160
  end
152
161
 
162
+ # Begin a refork if supported
163
+ def refork
164
+ if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
165
+ @runner.fork_worker!
166
+ true
167
+ else
168
+ log "* refork called but not available."
169
+ false
170
+ end
171
+ end
172
+
153
173
  # Run the server. This blocks until the server is stopped
154
174
  def run
155
175
  previous_env =
@@ -169,29 +189,34 @@ module Puma
169
189
 
170
190
  setup_signals
171
191
  set_process_title
192
+ integrate_with_systemd
172
193
  @runner.run
173
194
 
174
195
  case @status
175
196
  when :halt
176
197
  log "* Stopping immediately!"
198
+ @runner.stop_control
177
199
  when :run, :stop
178
200
  graceful_stop
179
201
  when :restart
180
202
  log "* Restarting..."
181
203
  ENV.replace(previous_env)
182
- @runner.before_restart
204
+ @runner.stop_control
183
205
  restart!
184
206
  when :exit
185
207
  # nothing
186
208
  end
187
- @binder.close_unix_paths
209
+ close_binder_listeners unless @status == :restart
188
210
  end
189
211
 
190
- # Return which tcp port the launcher is using, if it's using TCP
191
- def connected_port
192
- @binder.connected_port
212
+ # Return all tcp ports the launcher may be using, TCP or SSL
213
+ # @!attribute [r] connected_ports
214
+ # @version 5.0.0
215
+ def connected_ports
216
+ @binder.connected_ports
193
217
  end
194
218
 
219
+ # @!attribute [r] restart_args
195
220
  def restart_args
196
221
  cmd = @options[:restart_cmd]
197
222
  if cmd
@@ -202,7 +227,25 @@ module Puma
202
227
  end
203
228
 
204
229
  def close_binder_listeners
230
+ @runner.close_control_listeners
205
231
  @binder.close_listeners
232
+ unless @status == :restart
233
+ log "=== puma shutdown: #{Time.now} ==="
234
+ log "- Goodbye!"
235
+ end
236
+ end
237
+
238
+ # @!attribute [r] thread_status
239
+ # @version 5.0.0
240
+ def thread_status
241
+ Thread.list.each do |thread|
242
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
243
+ name += " #{thread['label']}" if thread['label']
244
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
245
+ backtrace = thread.backtrace || ["<no backtrace available>"]
246
+
247
+ yield name, backtrace
248
+ end
206
249
  end
207
250
 
208
251
  private
@@ -212,11 +255,10 @@ module Puma
212
255
  def write_pid
213
256
  path = @options[:pidfile]
214
257
  return unless path
215
-
216
- File.open(path, 'w') { |f| f.puts Process.pid }
217
- cur = Process.pid
258
+ cur_pid = Process.pid
259
+ File.write path, cur_pid, mode: 'wb:UTF-8'
218
260
  at_exit do
219
- delete_pidfile if cur == Process.pid
261
+ delete_pidfile if cur_pid == Process.pid
220
262
  end
221
263
  end
222
264
 
@@ -225,7 +267,8 @@ module Puma
225
267
  end
226
268
 
227
269
  def restart!
228
- @config.run_hooks :on_restart, self
270
+ @events.fire_on_restart!
271
+ @config.run_hooks :on_restart, self, @events
229
272
 
230
273
  if Puma.jruby?
231
274
  close_binder_listeners
@@ -241,21 +284,20 @@ module Puma
241
284
  else
242
285
  argv = restart_args
243
286
  Dir.chdir(@restart_dir)
287
+ ENV.update(@binder.redirects_for_restart_env)
244
288
  argv += [@binder.redirects_for_restart]
245
289
  Kernel.exec(*argv)
246
290
  end
247
291
  end
248
292
 
249
- def dependencies_and_files_to_require_after_prune
293
+ # @!attribute [r] files_to_require_after_prune
294
+ def files_to_require_after_prune
250
295
  puma = spec_for_gem("puma")
251
296
 
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]
297
+ require_paths_for_gem(puma) + extra_runtime_deps_directories
257
298
  end
258
299
 
300
+ # @!attribute [r] extra_runtime_deps_directories
259
301
  def extra_runtime_deps_directories
260
302
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
303
  if (spec = spec_for_gem(d_name))
@@ -267,6 +309,7 @@ module Puma
267
309
  end.flatten.compact
268
310
  end
269
311
 
312
+ # @!attribute [r] puma_wild_location
270
313
  def puma_wild_location
271
314
  puma = spec_for_gem("puma")
272
315
  dirs = require_paths_for_gem(puma)
@@ -275,6 +318,7 @@ module Puma
275
318
  end
276
319
 
277
320
  def prune_bundler
321
+ return if ENV['PUMA_BUNDLER_PRUNED']
278
322
  return unless defined?(Bundler)
279
323
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
324
  unless puma_wild_location
@@ -282,20 +326,48 @@ module Puma
282
326
  return
283
327
  end
284
328
 
285
- deps, dirs = dependencies_and_files_to_require_after_prune
329
+ dirs = files_to_require_after_prune
286
330
 
287
331
  log '* Pruning Bundler environment'
288
332
  home = ENV['GEM_HOME']
289
- Bundler.with_clean_env do
333
+ bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
334
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
335
+ with_unbundled_env do
290
336
  ENV['GEM_HOME'] = home
337
+ ENV['BUNDLE_GEMFILE'] = bundle_gemfile
291
338
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
292
- args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
339
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
340
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
293
341
  # Ruby 2.0+ defaults to true which breaks socket activation
294
342
  args += [{:close_others => false}]
295
343
  Kernel.exec(*args)
296
344
  end
297
345
  end
298
346
 
347
+ #
348
+ # Puma's systemd integration allows Puma to inform systemd:
349
+ # 1. when it has successfully started
350
+ # 2. when it is starting shutdown
351
+ # 3. periodically for a liveness check with a watchdog thread
352
+ #
353
+
354
+ def integrate_with_systemd
355
+ return unless ENV["NOTIFY_SOCKET"]
356
+
357
+ begin
358
+ require 'puma/systemd'
359
+ rescue LoadError
360
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
361
+ return
362
+ end
363
+
364
+ log "* Enabling systemd notification integration"
365
+
366
+ systemd = Systemd.new(@events)
367
+ systemd.hook_events
368
+ systemd.start_watchdog
369
+ end
370
+
299
371
  def spec_for_gem(gem_name)
300
372
  Bundler.rubygems.loaded_specs(gem_name)
301
373
  end
@@ -318,30 +390,15 @@ module Puma
318
390
  end
319
391
 
320
392
  def graceful_stop
393
+ @events.fire_on_stopped!
321
394
  @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
395
  end
340
396
 
341
397
  def set_process_title
342
398
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
343
399
  end
344
400
 
401
+ # @!attribute [r] title
345
402
  def title
346
403
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
347
404
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -353,6 +410,7 @@ module Puma
353
410
  ENV['RACK_ENV'] = environment
354
411
  end
355
412
 
413
+ # @!attribute [r] environment
356
414
  def environment
357
415
  @environment
358
416
  end
@@ -456,8 +514,13 @@ module Puma
456
514
  end
457
515
 
458
516
  begin
459
- Signal.trap "SIGINFO" do
460
- log_thread_status
517
+ unless Puma.jruby? # INFO in use by JVM already
518
+ Signal.trap "SIGINFO" do
519
+ thread_status do |name, backtrace|
520
+ @events.log name
521
+ @events.log backtrace.map { |bt| " #{bt}" }
522
+ end
523
+ end
461
524
  end
462
525
  rescue Exception
463
526
  # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
@@ -471,5 +534,24 @@ module Puma
471
534
  raise "#{feature} is not supported on your version of RubyGems. " \
472
535
  "You must have RubyGems #{min_version}+ to use this feature."
473
536
  end
537
+
538
+ # @version 5.0.0
539
+ def with_unbundled_env
540
+ bundler_ver = Gem::Version.new(Bundler::VERSION)
541
+ if bundler_ver < Gem::Version.new('2.1.0')
542
+ Bundler.with_clean_env { yield }
543
+ else
544
+ Bundler.with_unbundled_env { yield }
545
+ end
546
+ end
547
+
548
+ def log_config
549
+ log "Configuration:"
550
+
551
+ @config.final_options
552
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
553
+
554
+ log "\n"
555
+ end
474
556
  end
475
557
  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