puma 4.3.12 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1526 -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 +5 -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 +146 -84
  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 +22 -7
  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 +489 -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 +29 -24
  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