puma 5.0.4 → 5.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +322 -48
  3. data/LICENSE +0 -0
  4. data/README.md +95 -24
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +57 -20
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +2 -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 +0 -0
  14. data/docs/jungle/rc.d/README.md +1 -1
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +7 -7
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +85 -66
  24. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +42 -6
  27. data/ext/puma_http11/http11_parser.c +68 -57
  28. data/ext/puma_http11/http11_parser.h +1 -1
  29. data/ext/puma_http11/http11_parser.java.rl +1 -1
  30. data/ext/puma_http11/http11_parser.rl +1 -1
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +226 -88
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +9 -3
  38. data/lib/puma/app/status.rb +4 -7
  39. data/lib/puma/binder.rb +138 -49
  40. data/lib/puma/cli.rb +18 -4
  41. data/lib/puma/client.rb +113 -31
  42. data/lib/puma/cluster/worker.rb +22 -19
  43. data/lib/puma/cluster/worker_handle.rb +13 -2
  44. data/lib/puma/cluster.rb +75 -33
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +21 -2
  47. data/lib/puma/const.rb +17 -8
  48. data/lib/puma/control_cli.rb +76 -71
  49. data/lib/puma/detect.rb +19 -9
  50. data/lib/puma/dsl.rb +225 -31
  51. data/lib/puma/error_logger.rb +12 -5
  52. data/lib/puma/events.rb +18 -3
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/json_serialization.rb +96 -0
  56. data/lib/puma/launcher.rb +56 -7
  57. data/lib/puma/minissl/context_builder.rb +14 -6
  58. data/lib/puma/minissl.rb +72 -40
  59. data/lib/puma/null_io.rb +12 -0
  60. data/lib/puma/plugin/tmp_restart.rb +0 -0
  61. data/lib/puma/plugin.rb +2 -2
  62. data/lib/puma/queue_close.rb +7 -7
  63. data/lib/puma/rack/builder.rb +1 -1
  64. data/lib/puma/rack/urlmap.rb +0 -0
  65. data/lib/puma/rack_default.rb +0 -0
  66. data/lib/puma/reactor.rb +19 -12
  67. data/lib/puma/request.rb +55 -21
  68. data/lib/puma/runner.rb +39 -13
  69. data/lib/puma/server.rb +78 -142
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +45 -9
  72. data/lib/puma/systemd.rb +46 -0
  73. data/lib/puma/thread_pool.rb +11 -8
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +36 -10
  76. data/lib/rack/handler/puma.rb +1 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +15 -9
data/lib/puma/dsl.rb CHANGED
@@ -34,6 +34,42 @@ 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
+ backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
52
+
53
+ if defined?(JRUBY_VERSION)
54
+ ssl_cipher_list = opts[:ssl_cipher_list] ?
55
+ "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
56
+
57
+ keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
58
+
59
+ "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
60
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
61
+ else
62
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ?
63
+ "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
64
+
65
+ v_flags = (ary = opts[:verification_flags]) ?
66
+ "&verification_flags=#{Array(ary).join ','}" : nil
67
+
68
+ "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
69
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
70
+ end
71
+ end
72
+
37
73
  def initialize(options, config)
38
74
  @config = config
39
75
  @options = options
@@ -157,7 +193,7 @@ module Puma
157
193
  end
158
194
 
159
195
  # Bind the server to +url+. "tcp://", "unix://" and "ssl://" are the only
160
- # accepted protocols. Multiple urls can be bound to, calling `bind` does
196
+ # accepted protocols. Multiple urls can be bound to, calling +bind+ does
161
197
  # not overwrite previous bindings.
162
198
  #
163
199
  # The default is "tcp://0.0.0.0:9292".
@@ -167,7 +203,7 @@ module Puma
167
203
  # * Set the socket backlog depth with +backlog+, default is 1024.
168
204
  # * Set up an SSL certificate with +key+ & +cert+.
169
205
  # * Set whether to optimize for low latency instead of throughput with
170
- # +low_latency+, default is to optimize for low latency. This is done
206
+ # +low_latency+, default is to not optimize for low latency. This is done
171
207
  # via +Socket::TCP_NODELAY+.
172
208
  # * Set socket permissions with +umask+.
173
209
  #
@@ -191,13 +227,39 @@ module Puma
191
227
  @options[:binds] = []
192
228
  end
193
229
 
230
+ # Bind to (systemd) activated sockets, regardless of configured binds.
231
+ #
232
+ # Systemd can present sockets as file descriptors that are already opened.
233
+ # By default Puma will use these but only if it was explicitly told to bind
234
+ # to the socket. If not, it will close the activated sockets. This means
235
+ # all configuration is duplicated.
236
+ #
237
+ # Binds can contain additional configuration, but only SSL config is really
238
+ # relevant since the unix and TCP socket options are ignored.
239
+ #
240
+ # This means there is a lot of duplicated configuration for no additional
241
+ # value in most setups. This method tells the launcher to bind to all
242
+ # activated sockets, regardless of existing bind.
243
+ #
244
+ # To clear configured binds, the value only can be passed. This will clear
245
+ # out any binds that may have been configured.
246
+ #
247
+ # @example Use any systemd activated sockets as well as configured binds
248
+ # bind_to_activated_sockets
249
+ #
250
+ # @example Only bind to systemd activated sockets, ignoring other binds
251
+ # bind_to_activated_sockets 'only'
252
+ def bind_to_activated_sockets(bind=true)
253
+ @options[:bind_to_activated_sockets] = bind
254
+ end
255
+
194
256
  # Define the TCP port to bind to. Use +bind+ for more advanced options.
195
257
  #
196
258
  # @example
197
259
  # port 9292
198
260
  def port(port, host=nil)
199
261
  host ||= default_host
200
- bind "tcp://#{host}:#{port}"
262
+ bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
201
263
  end
202
264
 
203
265
  # Define how long persistent connections can be idle before Puma closes them.
@@ -321,6 +383,13 @@ module Puma
321
383
  @options[:rackup] ||= path.to_s
322
384
  end
323
385
 
386
+ # Allows setting `env['rack.url_scheme']`.
387
+ # Only necessary if X-Forwarded-Proto is not being set by your proxy
388
+ # Normal values are 'http' or 'https'.
389
+ def rack_url_scheme(scheme=nil)
390
+ @options[:rack_url_scheme] = scheme
391
+ end
392
+
324
393
  def early_hints(answer=true)
325
394
  @options[:early_hints] = answer
326
395
  end
@@ -345,7 +414,10 @@ module Puma
345
414
  # Configure +min+ to be the minimum number of threads to use to answer
346
415
  # requests and +max+ the maximum.
347
416
  #
348
- # The default is "0, 16".
417
+ # The default is the environment variables +PUMA_MIN_THREADS+ / +PUMA_MAX_THREADS+
418
+ # (or +MIN_THREADS+ / +MAX_THREADS+ if the +PUMA_+ variables aren't set).
419
+ #
420
+ # If these environment variables aren't set, the default is "0, 5" in MRI or "0, 16" for other interpreters.
349
421
  #
350
422
  # @example
351
423
  # threads 0, 16
@@ -366,8 +438,15 @@ module Puma
366
438
  @options[:max_threads] = max
367
439
  end
368
440
 
369
- # Instead of `bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'` you
370
- # can also use the this method.
441
+ # Instead of using +bind+ and manually constructing a URI like:
442
+ #
443
+ # bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
444
+ #
445
+ # you can use the this method.
446
+ #
447
+ # When binding on localhost you don't need to specify +cert+ and +key+,
448
+ # Puma will assume you are using the +localhost+ gem and try to load the
449
+ # appropriate files.
371
450
  #
372
451
  # @example
373
452
  # ssl_bind '127.0.0.1', '9292', {
@@ -375,29 +454,28 @@ module Puma
375
454
  # key: path_to_key,
376
455
  # ssl_cipher_filter: cipher_filter, # optional
377
456
  # verify_mode: verify_mode, # default 'none'
457
+ # verification_flags: flags, # optional, not supported by JRuby
378
458
  # }
379
- # @example For JRuby additional keys are required: keystore & keystore_pass.
459
+ #
460
+ # @example Using self-signed certificate with the +localhost+ gem:
461
+ # ssl_bind '127.0.0.1', '9292'
462
+ #
463
+ # @example Alternatively, you can provide +cert_pem+ and +key_pem+:
464
+ # ssl_bind '127.0.0.1', '9292', {
465
+ # cert_pem: File.read(path_to_cert),
466
+ # key_pem: File.read(path_to_key),
467
+ # }
468
+ #
469
+ # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
380
470
  # 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
471
  # keystore: path_to_keystore,
386
- # keystore_pass: password
472
+ # keystore_pass: password,
473
+ # ssl_cipher_list: cipher_list, # optional
474
+ # verify_mode: verify_mode # default 'none'
387
475
  # }
388
- 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
476
+ def ssl_bind(host, port, opts = {})
477
+ add_pem_values_to_options_store(opts)
478
+ bind self.class.ssl_bind_str(host, port, opts)
401
479
  end
402
480
 
403
481
  # Use +path+ as the file to store the server info state. This is
@@ -422,7 +500,8 @@ module Puma
422
500
  # How many worker processes to run. Typically this is set to
423
501
  # the number of available cores.
424
502
  #
425
- # The default is 0.
503
+ # The default is the value of the environment variable +WEB_CONCURRENCY+ if
504
+ # set, otherwise 0.
426
505
  #
427
506
  # @note Cluster mode only.
428
507
  # @see Puma::Cluster
@@ -430,6 +509,24 @@ module Puma
430
509
  @options[:workers] = count.to_i
431
510
  end
432
511
 
512
+ # Disable warning message when running in cluster mode with a single worker.
513
+ #
514
+ # Cluster mode has some overhead of running an additional 'control' process
515
+ # in order to manage the cluster. If only running a single worker it is
516
+ # likely not worth paying that overhead vs running in single mode with
517
+ # additional threads instead.
518
+ #
519
+ # There are some scenarios where running cluster mode with a single worker
520
+ # may still be warranted and valid under certain deployment scenarios, see
521
+ # https://github.com/puma/puma/issues/2534
522
+ #
523
+ # Moving from workers = 1 to workers = 0 will save 10-30% of memory use.
524
+ #
525
+ # @note Cluster mode only.
526
+ def silence_single_worker_warning
527
+ @options[:silence_single_worker_warning] = true
528
+ end
529
+
433
530
  # Code to run immediately before master process
434
531
  # forks workers (once on boot). These hooks can block if necessary
435
532
  # to wait for background operations unknown to Puma to finish before
@@ -508,7 +605,7 @@ module Puma
508
605
  # end
509
606
  def after_worker_fork(&block)
510
607
  @options[:after_worker_fork] ||= []
511
- @options[:after_worker_fork] = block
608
+ @options[:after_worker_fork] << block
512
609
  end
513
610
 
514
611
  alias_method :after_worker_boot, :after_worker_fork
@@ -561,7 +658,7 @@ module Puma
561
658
  end
562
659
 
563
660
  # Preload the application before starting the workers; this conflicts with
564
- # phased restart feature. This is off by default.
661
+ # phased restart feature. On by default if your app uses more than 1 worker.
565
662
  #
566
663
  # @note Cluster mode only.
567
664
  # @example
@@ -650,6 +747,19 @@ module Puma
650
747
  @options[:tag] = string.to_s
651
748
  end
652
749
 
750
+ # Change the default interval for checking workers.
751
+ #
752
+ # The default value is 5 seconds.
753
+ #
754
+ # @note Cluster mode only.
755
+ # @example
756
+ # worker_check_interval 5
757
+ # @see Puma::Cluster#check_workers
758
+ #
759
+ def worker_check_interval(interval)
760
+ @options[:worker_check_interval] = Integer(interval)
761
+ end
762
+
653
763
  # Verifies that all workers have checked in to the master process within
654
764
  # the given timeout. If not the worker process will be restarted. This is
655
765
  # not a request timeout, it is to protect against a hung or dead process.
@@ -664,7 +774,7 @@ module Puma
664
774
  #
665
775
  def worker_timeout(timeout)
666
776
  timeout = Integer(timeout)
667
- min = Const::WORKER_CHECK_INTERVAL
777
+ min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
668
778
 
669
779
  if timeout <= min
670
780
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -696,6 +806,30 @@ module Puma
696
806
  @options[:worker_shutdown_timeout] = Integer(timeout)
697
807
  end
698
808
 
809
+ # Set the strategy for worker culling.
810
+ #
811
+ # There are two possible values:
812
+ #
813
+ # 1. **:youngest** - the youngest workers (i.e. the workers that were
814
+ # the most recently started) will be culled.
815
+ # 2. **:oldest** - the oldest workers (i.e. the workers that were started
816
+ # the longest time ago) will be culled.
817
+ #
818
+ # @note Cluster mode only.
819
+ # @example
820
+ # worker_culling_strategy :oldest
821
+ # @see Puma::Cluster#cull_workers
822
+ #
823
+ def worker_culling_strategy(strategy)
824
+ stategy = strategy.to_sym
825
+
826
+ if ![:youngest, :oldest].include?(strategy)
827
+ raise "Invalid value for worker_culling_strategy - #{stategy}"
828
+ end
829
+
830
+ @options[:worker_culling_strategy] = strategy
831
+ end
832
+
699
833
  # When set to true (the default), workers accept all requests
700
834
  # and queue them before passing them to the handlers.
701
835
  # When set to false, each worker process accepts exactly as
@@ -741,7 +875,7 @@ module Puma
741
875
  # a kernel syscall is required which for very fast rack handlers
742
876
  # slows down the handling significantly.
743
877
  #
744
- # There are 4 possible values:
878
+ # There are 5 possible values:
745
879
  #
746
880
  # 1. **:socket** (the default) - read the peername from the socket using the
747
881
  # syscall. This is the normal behavior.
@@ -751,7 +885,10 @@ module Puma
751
885
  # `set_remote_address header: "X-Real-IP"`.
752
886
  # Only the first word (as separated by spaces or comma) is used, allowing
753
887
  # headers such as X-Forwarded-For to be used as well.
754
- # 4. **\<Any string\>** - this allows you to hardcode remote address to any value
888
+ # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
889
+ # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
890
+ # protocol attached to it, will fall back to :socket
891
+ # 5. **\<Any string\>** - this allows you to hardcode remote address to any value
755
892
  # you wish. Because Puma never uses this field anyway, it's format is
756
893
  # entirely in your hands.
757
894
  #
@@ -769,6 +906,13 @@ module Puma
769
906
  if hdr = val[:header]
770
907
  @options[:remote_address] = :header
771
908
  @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
909
+ elsif protocol_version = val[:proxy_protocol]
910
+ @options[:remote_address] = :proxy_protocol
911
+ protocol_version = protocol_version.downcase.to_sym
912
+ unless [:v1].include?(protocol_version)
913
+ raise "Invalid value for proxy_protocol - #{protocol_version.inspect}"
914
+ end
915
+ @options[:remote_address_proxy_protocol] = protocol_version
772
916
  else
773
917
  raise "Invalid value for set_remote_address - #{val.inspect}"
774
918
  end
@@ -810,5 +954,55 @@ module Puma
810
954
  def nakayoshi_fork(enabled=true)
811
955
  @options[:nakayoshi_fork] = enabled
812
956
  end
957
+
958
+ # The number of requests to attempt inline before sending a client back to
959
+ # the reactor to be subject to normal ordering.
960
+ #
961
+ def max_fast_inline(num_of_requests)
962
+ @options[:max_fast_inline] = Float(num_of_requests)
963
+ end
964
+
965
+ # Specify the backend for the IO selector.
966
+ #
967
+ # Provided values will be passed directly to +NIO::Selector.new+, with the
968
+ # exception of +:auto+ which will let nio4r choose the backend.
969
+ #
970
+ # Check the documentation of +NIO::Selector.backends+ for the list of valid
971
+ # options. Note that the available options on your system will depend on the
972
+ # operating system. If you want to use the pure Ruby backend (not
973
+ # recommended due to its comparatively low performance), set environment
974
+ # variable +NIO4R_PURE+ to +true+.
975
+ #
976
+ # The default is +:auto+.
977
+ #
978
+ # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
979
+ #
980
+ def io_selector_backend(backend)
981
+ @options[:io_selector_backend] = backend.to_sym
982
+ end
983
+
984
+ def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
985
+ @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
986
+ end
987
+
988
+ private
989
+
990
+ # To avoid adding cert_pem and key_pem as URI params, we store them on the
991
+ # options[:store] from where Puma binder knows how to find and extract them.
992
+ def add_pem_values_to_options_store(opts)
993
+ return if defined?(JRUBY_VERSION)
994
+
995
+ @options[:store] ||= []
996
+
997
+ # Store cert_pem and key_pem to options[:store] if present
998
+ [:cert, :key].each do |v|
999
+ opt_key = :"#{v}_pem"
1000
+ if opts[opt_key]
1001
+ index = @options[:store].length
1002
+ @options[:store] << opts[opt_key]
1003
+ opts[v] = "store:#{index}"
1004
+ end
1005
+ end
1006
+ end
813
1007
  end
814
1008
  end
@@ -15,7 +15,6 @@ module Puma
15
15
 
16
16
  def initialize(ioerr)
17
17
  @ioerr = ioerr
18
- @ioerr.sync = true
19
18
 
20
19
  @debug = ENV.key? 'PUMA_DEBUG'
21
20
  end
@@ -24,7 +23,7 @@ module Puma
24
23
  new $stderr
25
24
  end
26
25
 
27
- # Print occured error details.
26
+ # Print occurred error details.
28
27
  # +options+ hash with additional options:
29
28
  # - +error+ is an exception object
30
29
  # - +req+ the http request
@@ -32,10 +31,10 @@ module Puma
32
31
  # and before all remaining info.
33
32
  #
34
33
  def info(options={})
35
- ioerr.puts title(options)
34
+ log title(options)
36
35
  end
37
36
 
38
- # Print occured error details only if
37
+ # Print occurred error details only if
39
38
  # environment variable PUMA_DEBUG is defined.
40
39
  # +options+ hash with additional options:
41
40
  # - +error+ is an exception object
@@ -54,7 +53,7 @@ module Puma
54
53
  string_block << request_dump(req) if request_parsed?(req)
55
54
  string_block << error.backtrace if error
56
55
 
57
- ioerr.puts string_block.join("\n")
56
+ log string_block.join("\n")
58
57
  end
59
58
 
60
59
  def title(options={})
@@ -93,5 +92,13 @@ module Puma
93
92
  def request_parsed?(req)
94
93
  req && req.env[REQUEST_METHOD]
95
94
  end
95
+
96
+ private
97
+
98
+ def log(str)
99
+ ioerr.puts str
100
+
101
+ ioerr.flush unless ioerr.sync
102
+ end
96
103
  end
97
104
  end
data/lib/puma/events.rb CHANGED
@@ -30,9 +30,6 @@ module Puma
30
30
  @stdout = stdout
31
31
  @stderr = stderr
32
32
 
33
- @stdout.sync = true
34
- @stderr.sync = true
35
-
36
33
  @debug = ENV.key? 'PUMA_DEBUG'
37
34
  @error_logger = ErrorLogger.new(@stderr)
38
35
 
@@ -66,6 +63,8 @@ module Puma
66
63
  #
67
64
  def log(str)
68
65
  @stdout.puts format(str) if @stdout.respond_to? :puts
66
+
67
+ @stdout.flush unless @stdout.sync
69
68
  rescue Errno::EPIPE
70
69
  end
71
70
 
@@ -137,10 +136,26 @@ module Puma
137
136
  register(:on_booted, &block)
138
137
  end
139
138
 
139
+ def on_restart(&block)
140
+ register(:on_restart, &block)
141
+ end
142
+
143
+ def on_stopped(&block)
144
+ register(:on_stopped, &block)
145
+ end
146
+
140
147
  def fire_on_booted!
141
148
  fire(:on_booted)
142
149
  end
143
150
 
151
+ def fire_on_restart!
152
+ fire(:on_restart)
153
+ end
154
+
155
+ def fire_on_stopped!
156
+ fire(:on_stopped)
157
+ end
158
+
144
159
  DEFAULT = new(STDOUT, STDERR)
145
160
 
146
161
  # Returns an Events object which writes its status to 2 StringIO
File without changes
File without changes
@@ -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 JSONSerialization
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