puma 5.0.0-java → 5.1.0-java

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1190 -574
  3. data/README.md +28 -20
  4. data/bin/puma-wild +3 -9
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +5 -6
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/README.md +0 -4
  9. data/docs/jungle/rc.d/puma +2 -2
  10. data/docs/nginx.md +1 -1
  11. data/docs/restart.md +46 -23
  12. data/docs/systemd.md +25 -3
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/extconf.rb +4 -5
  15. data/ext/puma_http11/http11_parser.c +64 -64
  16. data/ext/puma_http11/mini_ssl.c +39 -37
  17. data/ext/puma_http11/puma_http11.c +25 -12
  18. data/lib/puma.rb +7 -4
  19. data/lib/puma/app/status.rb +44 -46
  20. data/lib/puma/binder.rb +48 -1
  21. data/lib/puma/cli.rb +4 -0
  22. data/lib/puma/client.rb +31 -80
  23. data/lib/puma/cluster.rb +39 -202
  24. data/lib/puma/cluster/worker.rb +176 -0
  25. data/lib/puma/cluster/worker_handle.rb +86 -0
  26. data/lib/puma/configuration.rb +20 -8
  27. data/lib/puma/const.rb +11 -3
  28. data/lib/puma/control_cli.rb +71 -70
  29. data/lib/puma/dsl.rb +67 -19
  30. data/lib/puma/error_logger.rb +2 -2
  31. data/lib/puma/events.rb +21 -3
  32. data/lib/puma/json.rb +96 -0
  33. data/lib/puma/launcher.rb +61 -12
  34. data/lib/puma/minissl.rb +8 -0
  35. data/lib/puma/puma_http11.jar +0 -0
  36. data/lib/puma/queue_close.rb +26 -0
  37. data/lib/puma/reactor.rb +79 -373
  38. data/lib/puma/request.rb +451 -0
  39. data/lib/puma/runner.rb +15 -21
  40. data/lib/puma/server.rb +193 -508
  41. data/lib/puma/single.rb +3 -2
  42. data/lib/puma/state_file.rb +5 -3
  43. data/lib/puma/systemd.rb +46 -0
  44. data/lib/puma/thread_pool.rb +22 -2
  45. data/lib/puma/util.rb +12 -0
  46. metadata +9 -6
  47. data/docs/jungle/upstart/README.md +0 -61
  48. data/docs/jungle/upstart/puma-manager.conf +0 -31
  49. data/docs/jungle/upstart/puma.conf +0 -69
  50. data/lib/puma/accept_nonblock.rb +0 -29
@@ -34,6 +34,34 @@ module Puma
34
34
  class DSL
35
35
  include ConfigDefault
36
36
 
37
+ # convenience method so logic can be used in CI
38
+ # @see ssl_bind
39
+ #
40
+ def self.ssl_bind_str(host, port, opts)
41
+ verify = opts.fetch(:verify_mode, 'none').to_s
42
+
43
+ tls_str =
44
+ if opts[:no_tlsv1_1] then '&no_tlsv1_1=true'
45
+ elsif opts[:no_tlsv1] then '&no_tlsv1=true'
46
+ else ''
47
+ end
48
+
49
+ ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
50
+
51
+ if defined?(JRUBY_VERSION)
52
+ ssl_cipher_list = opts[:ssl_cipher_list] ?
53
+ "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
54
+ keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
55
+ "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
56
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
57
+ else
58
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ?
59
+ "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
60
+ "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
61
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}"
62
+ end
63
+ end
64
+
37
65
  def initialize(options, config)
38
66
  @config = config
39
67
  @options = options
@@ -191,6 +219,32 @@ module Puma
191
219
  @options[:binds] = []
192
220
  end
193
221
 
222
+ # Bind to (systemd) activated sockets, regardless of configured binds.
223
+ #
224
+ # Systemd can present sockets as file descriptors that are already opened.
225
+ # By default Puma will use these but only if it was explicitly told to bind
226
+ # to the socket. If not, it will close the activated sockets. This means
227
+ # all configuration is duplicated.
228
+ #
229
+ # Binds can contain additional configuration, but only SSL config is really
230
+ # relevant since the unix and TCP socket options are ignored.
231
+ #
232
+ # This means there is a lot of duplicated configuration for no additional
233
+ # value in most setups. This method tells the launcher to bind to all
234
+ # activated sockets, regardless of existing bind.
235
+ #
236
+ # To clear configured binds, the value only can be passed. This will clear
237
+ # out any binds that may have been configured.
238
+ #
239
+ # @example Use any systemd activated sockets as well as configured binds
240
+ # bind_to_activated_sockets
241
+ #
242
+ # @example Only bind to systemd activated sockets, ignoring other binds
243
+ # bind_to_activated_sockets 'only'
244
+ def bind_to_activated_sockets(bind=true)
245
+ @options[:bind_to_activated_sockets] = bind
246
+ end
247
+
194
248
  # Define the TCP port to bind to. Use +bind+ for more advanced options.
195
249
  #
196
250
  # @example
@@ -376,28 +430,15 @@ module Puma
376
430
  # ssl_cipher_filter: cipher_filter, # optional
377
431
  # verify_mode: verify_mode, # default 'none'
378
432
  # }
379
- # @example For JRuby additional keys are required: keystore & keystore_pass.
433
+ # @example For JRuby, two keys are required: keystore & keystore_pass.
380
434
  # ssl_bind '127.0.0.1', '9292', {
381
- # cert: path_to_cert,
382
- # key: path_to_key,
383
- # ssl_cipher_filter: cipher_filter, # optional
384
- # verify_mode: verify_mode, # default 'none'
385
435
  # keystore: path_to_keystore,
386
- # keystore_pass: password
436
+ # keystore_pass: password,
437
+ # ssl_cipher_list: cipher_list, # optional
438
+ # verify_mode: verify_mode # default 'none'
387
439
  # }
388
440
  def ssl_bind(host, port, opts)
389
- verify = opts.fetch(:verify_mode, 'none').to_s
390
- no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
391
- no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
392
- ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
393
-
394
- if defined?(JRUBY_VERSION)
395
- keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
396
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
397
- else
398
- ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
399
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
400
- end
441
+ bind self.class.ssl_bind_str(host, port, opts)
401
442
  end
402
443
 
403
444
  # Use +path+ as the file to store the server info state. This is
@@ -561,7 +602,7 @@ module Puma
561
602
  end
562
603
 
563
604
  # Preload the application before starting the workers; this conflicts with
564
- # phased restart feature. This is off by default.
605
+ # phased restart feature. On by default if your app uses more than 1 worker.
565
606
  #
566
607
  # @note Cluster mode only.
567
608
  # @example
@@ -810,5 +851,12 @@ module Puma
810
851
  def nakayoshi_fork(enabled=true)
811
852
  @options[:nakayoshi_fork] = enabled
812
853
  end
854
+
855
+ # The number of requests to attempt inline before sending a client back to
856
+ # the reactor to be subject to normal ordering.
857
+ #
858
+ def max_fast_inline(num_of_requests)
859
+ @options[:max_fast_inline] = Float(num_of_requests)
860
+ end
813
861
  end
814
862
  end
@@ -51,8 +51,8 @@ module Puma
51
51
 
52
52
  string_block = []
53
53
  string_block << title(options)
54
- string_block << request_dump(req) if req
55
- string_block << error_backtrace(options) if error
54
+ string_block << request_dump(req) if request_parsed?(req)
55
+ string_block << error.backtrace if error
56
56
 
57
57
  ioerr.puts string_block.join("\n")
58
58
  end
@@ -106,10 +106,12 @@ module Puma
106
106
  end
107
107
 
108
108
  # An SSL error has occurred.
109
- # +error+ an exception object, +peeraddr+ peer address,
110
- # and +peercert+ any peer certificate (if present).
109
+ # @param error <Puma::MiniSSL::SSLError>
110
+ # @param ssl_socket <Puma::MiniSSL::Socket>
111
111
  #
112
- def ssl_error(error, peeraddr, peercert)
112
+ def ssl_error(error, ssl_socket)
113
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
114
+ peercert = ssl_socket.peercert
113
115
  subject = peercert ? peercert.subject : nil
114
116
  @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
115
117
  end
@@ -135,10 +137,26 @@ module Puma
135
137
  register(:on_booted, &block)
136
138
  end
137
139
 
140
+ def on_restart(&block)
141
+ register(:on_restart, &block)
142
+ end
143
+
144
+ def on_stopped(&block)
145
+ register(:on_stopped, &block)
146
+ end
147
+
138
148
  def fire_on_booted!
139
149
  fire(:on_booted)
140
150
  end
141
151
 
152
+ def fire_on_restart!
153
+ fire(:on_restart)
154
+ end
155
+
156
+ def fire_on_stopped!
157
+ fire(:on_stopped)
158
+ end
159
+
142
160
  DEFAULT = new(STDOUT, STDERR)
143
161
 
144
162
  # Returns an Events object which writes its status to 2 StringIO
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'stringio'
3
+
4
+ module Puma
5
+
6
+ # Puma deliberately avoids the use of the json gem and instead performs JSON
7
+ # serialization without any external dependencies. In a puma cluster, loading
8
+ # any gem into the puma master process means that operators cannot use a
9
+ # phased restart to upgrade their application if the new version of that
10
+ # application uses a different version of that gem. The json gem in
11
+ # particular is additionally problematic because it leverages native
12
+ # extensions. If the puma master process relies on a gem with native
13
+ # extensions and operators remove gems from disk related to old releases,
14
+ # subsequent phased restarts can fail.
15
+ #
16
+ # The implementation of JSON serialization in this module is not designed to
17
+ # be particularly full-featured or fast. It just has to handle the few places
18
+ # where Puma relies on JSON serialization internally.
19
+
20
+ module JSON
21
+ QUOTE = /"/
22
+ BACKSLASH = /\\/
23
+ CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
24
+ CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
25
+
26
+ class SerializationError < StandardError; end
27
+
28
+ class << self
29
+ def generate(value)
30
+ StringIO.open do |io|
31
+ serialize_value io, value
32
+ io.string
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_value(output, value)
39
+ case value
40
+ when Hash
41
+ output << '{'
42
+ value.each_with_index do |(k, v), index|
43
+ output << ',' if index != 0
44
+ serialize_object_key output, k
45
+ output << ':'
46
+ serialize_value output, v
47
+ end
48
+ output << '}'
49
+ when Array
50
+ output << '['
51
+ value.each_with_index do |member, index|
52
+ output << ',' if index != 0
53
+ serialize_value output, member
54
+ end
55
+ output << ']'
56
+ when Integer, Float
57
+ output << value.to_s
58
+ when String
59
+ serialize_string output, value
60
+ when true
61
+ output << 'true'
62
+ when false
63
+ output << 'false'
64
+ when nil
65
+ output << 'null'
66
+ else
67
+ raise SerializationError, "Unexpected value of type #{value.class}"
68
+ end
69
+ end
70
+
71
+ def serialize_string(output, value)
72
+ output << '"'
73
+ output << value.gsub(CHAR_TO_ESCAPE) do |character|
74
+ case character
75
+ when BACKSLASH
76
+ '\\\\'
77
+ when QUOTE
78
+ '\\"'
79
+ when CONTROL_CHAR_TO_ESCAPE
80
+ '\u%.4X' % character.ord
81
+ end
82
+ end
83
+ output << '"'
84
+ end
85
+
86
+ def serialize_object_key(output, value)
87
+ case value
88
+ when Symbol, String
89
+ serialize_string output, value.to_s
90
+ else
91
+ raise SerializationError, "Could not serialize object of type #{value.class} as object key"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -58,6 +58,13 @@ module Puma
58
58
 
59
59
  @config.load
60
60
 
61
+ if @config.options[:bind_to_activated_sockets]
62
+ @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
63
+ @config.options[:binds],
64
+ @config.options[:bind_to_activated_sockets] == 'only'
65
+ )
66
+ end
67
+
61
68
  @options = @config.options
62
69
  @config.clamp
63
70
 
@@ -87,6 +94,8 @@ module Puma
87
94
  Puma.stats_object = @runner
88
95
 
89
96
  @status = :run
97
+
98
+ log_config if ENV['PUMA_LOG_CONFIG']
90
99
  end
91
100
 
92
101
  attr_reader :binder, :events, :config, :options, :restart_dir
@@ -130,6 +139,7 @@ module Puma
130
139
 
131
140
  # Begin async shutdown of the server gracefully
132
141
  def stop
142
+ @events.fire_on_stopped!
133
143
  @status = :stop
134
144
  @runner.stop
135
145
  end
@@ -168,6 +178,7 @@ module Puma
168
178
 
169
179
  setup_signals
170
180
  set_process_title
181
+ integrate_with_systemd
171
182
  @runner.run
172
183
 
173
184
  case @status
@@ -188,11 +199,13 @@ module Puma
188
199
  end
189
200
 
190
201
  # Return all tcp ports the launcher may be using, TCP or SSL
202
+ # @!attribute [r] connected_ports
191
203
  # @version 5.0.0
192
204
  def connected_ports
193
205
  @binder.connected_ports
194
206
  end
195
207
 
208
+ # @!attribute [r] restart_args
196
209
  def restart_args
197
210
  cmd = @options[:restart_cmd]
198
211
  if cmd
@@ -207,6 +220,7 @@ module Puma
207
220
  @binder.close_listeners
208
221
  end
209
222
 
223
+ # @!attribute [r] thread_status
210
224
  # @version 5.0.0
211
225
  def thread_status
212
226
  Thread.list.each do |thread|
@@ -226,11 +240,10 @@ module Puma
226
240
  def write_pid
227
241
  path = @options[:pidfile]
228
242
  return unless path
229
-
230
- File.open(path, 'w') { |f| f.puts Process.pid }
231
- cur = Process.pid
243
+ cur_pid = Process.pid
244
+ File.write path, cur_pid, mode: 'wb:UTF-8'
232
245
  at_exit do
233
- delete_pidfile if cur == Process.pid
246
+ delete_pidfile if cur_pid == Process.pid
234
247
  end
235
248
  end
236
249
 
@@ -239,6 +252,7 @@ module Puma
239
252
  end
240
253
 
241
254
  def restart!
255
+ @events.fire_on_restart!
242
256
  @config.run_hooks :on_restart, self, @events
243
257
 
244
258
  if Puma.jruby?
@@ -261,16 +275,14 @@ module Puma
261
275
  end
262
276
  end
263
277
 
264
- def dependencies_and_files_to_require_after_prune
278
+ # @!attribute [r] files_to_require_after_prune
279
+ def files_to_require_after_prune
265
280
  puma = spec_for_gem("puma")
266
281
 
267
- deps = puma.runtime_dependencies.map do |d|
268
- "#{d.name}:#{spec_for_gem(d.name).version}"
269
- end
270
-
271
- [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
282
+ require_paths_for_gem(puma) + extra_runtime_deps_directories
272
283
  end
273
284
 
285
+ # @!attribute [r] extra_runtime_deps_directories
274
286
  def extra_runtime_deps_directories
275
287
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
276
288
  if (spec = spec_for_gem(d_name))
@@ -282,6 +294,7 @@ module Puma
282
294
  end.flatten.compact
283
295
  end
284
296
 
297
+ # @!attribute [r] puma_wild_location
285
298
  def puma_wild_location
286
299
  puma = spec_for_gem("puma")
287
300
  dirs = require_paths_for_gem(puma)
@@ -298,7 +311,7 @@ module Puma
298
311
  return
299
312
  end
300
313
 
301
- deps, dirs = dependencies_and_files_to_require_after_prune
314
+ dirs = files_to_require_after_prune
302
315
 
303
316
  log '* Pruning Bundler environment'
304
317
  home = ENV['GEM_HOME']
@@ -307,13 +320,37 @@ module Puma
307
320
  ENV['GEM_HOME'] = home
308
321
  ENV['BUNDLE_GEMFILE'] = bundle_gemfile
309
322
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
310
- args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
323
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
311
324
  # Ruby 2.0+ defaults to true which breaks socket activation
312
325
  args += [{:close_others => false}]
313
326
  Kernel.exec(*args)
314
327
  end
315
328
  end
316
329
 
330
+ #
331
+ # Puma's systemd integration allows Puma to inform systemd:
332
+ # 1. when it has successfully started
333
+ # 2. when it is starting shutdown
334
+ # 3. periodically for a liveness check with a watchdog thread
335
+ #
336
+
337
+ def integrate_with_systemd
338
+ return unless ENV["NOTIFY_SOCKET"]
339
+
340
+ begin
341
+ require 'puma/systemd'
342
+ rescue LoadError
343
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
344
+ return
345
+ end
346
+
347
+ log "* Enabling systemd notification integration"
348
+
349
+ systemd = Systemd.new(@events)
350
+ systemd.hook_events
351
+ systemd.start_watchdog
352
+ end
353
+
317
354
  def spec_for_gem(gem_name)
318
355
  Bundler.rubygems.loaded_specs(gem_name)
319
356
  end
@@ -336,6 +373,7 @@ module Puma
336
373
  end
337
374
 
338
375
  def graceful_stop
376
+ @events.fire_on_stopped!
339
377
  @runner.stop_blocked
340
378
  log "=== puma shutdown: #{Time.now} ==="
341
379
  log "- Goodbye!"
@@ -345,6 +383,7 @@ module Puma
345
383
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
346
384
  end
347
385
 
386
+ # @!attribute [r] title
348
387
  def title
349
388
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
350
389
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -356,6 +395,7 @@ module Puma
356
395
  ENV['RACK_ENV'] = environment
357
396
  end
358
397
 
398
+ # @!attribute [r] environment
359
399
  def environment
360
400
  @environment
361
401
  end
@@ -489,5 +529,14 @@ module Puma
489
529
  Bundler.with_unbundled_env { yield }
490
530
  end
491
531
  end
532
+
533
+ def log_config
534
+ log "Configuration:"
535
+
536
+ @config.final_options
537
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
538
+
539
+ log "\n"
540
+ end
492
541
  end
493
542
  end