puma 5.3.2 → 6.0.0

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +284 -11
  3. data/LICENSE +0 -0
  4. data/README.md +61 -16
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +38 -2
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +1 -3
  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 +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +44 -13
  29. data/ext/puma_http11/http11_parser.c +24 -11
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +3 -3
  34. data/ext/puma_http11/mini_ssl.c +122 -23
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  39. data/ext/puma_http11/puma_http11.c +18 -10
  40. data/lib/puma/app/status.rb +9 -6
  41. data/lib/puma/binder.rb +81 -42
  42. data/lib/puma/cli.rb +23 -19
  43. data/lib/puma/client.rb +124 -30
  44. data/lib/puma/cluster/worker.rb +21 -29
  45. data/lib/puma/cluster/worker_handle.rb +8 -1
  46. data/lib/puma/cluster.rb +57 -48
  47. data/lib/puma/commonlogger.rb +0 -0
  48. data/lib/puma/configuration.rb +74 -55
  49. data/lib/puma/const.rb +21 -24
  50. data/lib/puma/control_cli.rb +22 -19
  51. data/lib/puma/detect.rb +10 -2
  52. data/lib/puma/dsl.rb +196 -57
  53. data/lib/puma/error_logger.rb +17 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +29 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +108 -154
  60. data/lib/puma/log_writer.rb +137 -0
  61. data/lib/puma/minissl/context_builder.rb +29 -16
  62. data/lib/puma/minissl.rb +115 -38
  63. data/lib/puma/null_io.rb +5 -0
  64. data/lib/puma/plugin/tmp_restart.rb +1 -1
  65. data/lib/puma/plugin.rb +2 -2
  66. data/lib/puma/rack/builder.rb +5 -5
  67. data/lib/puma/rack/urlmap.rb +0 -0
  68. data/lib/puma/rack_default.rb +1 -1
  69. data/lib/puma/reactor.rb +3 -3
  70. data/lib/puma/request.rb +293 -153
  71. data/lib/puma/runner.rb +63 -28
  72. data/lib/puma/server.rb +83 -88
  73. data/lib/puma/single.rb +10 -10
  74. data/lib/puma/state_file.rb +39 -7
  75. data/lib/puma/systemd.rb +3 -2
  76. data/lib/puma/thread_pool.rb +22 -17
  77. data/lib/puma/util.rb +20 -15
  78. data/lib/puma.rb +12 -9
  79. data/lib/rack/handler/puma.rb +9 -9
  80. data/tools/Dockerfile +1 -1
  81. data/tools/trickletest.rb +0 -0
  82. metadata +13 -9
  83. data/lib/puma/queue_close.rb +0 -26
data/lib/puma/runner.rb CHANGED
@@ -1,20 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/server'
4
- require 'puma/const'
3
+ require_relative 'server'
4
+ require_relative 'const'
5
5
 
6
6
  module Puma
7
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
8
  # serve requests. This class spawns a new instance of `Puma::Server` via
9
9
  # a call to `start_server`.
10
10
  class Runner
11
- def initialize(cli, events)
12
- @launcher = cli
13
- @events = events
14
- @options = cli.options
11
+ def initialize(launcher)
12
+ @launcher = launcher
13
+ @log_writer = launcher.log_writer
14
+ @events = launcher.events
15
+ @config = launcher.config
16
+ @options = launcher.options
15
17
  @app = nil
16
18
  @control = nil
17
19
  @started_at = Time.now
20
+ @wakeup = nil
21
+ end
22
+
23
+ # Returns the hash of configuration options.
24
+ # @return [Puma::UserFileDefaultOptions]
25
+ attr_reader :options
26
+
27
+ def wakeup!
28
+ return unless @wakeup
29
+
30
+ @wakeup.write "!" unless @wakeup.closed?
31
+
32
+ rescue SystemCallError, IOError
33
+ Puma::Util.purge_interrupt_queue
18
34
  end
19
35
 
20
36
  def development?
@@ -26,27 +42,27 @@ module Puma
26
42
  end
27
43
 
28
44
  def log(str)
29
- @events.log str
45
+ @log_writer.log str
30
46
  end
31
47
 
32
48
  # @version 5.0.0
33
49
  def stop_control
34
- @control.stop(true) if @control
50
+ @control&.stop true
35
51
  end
36
52
 
37
53
  def error(str)
38
- @events.error str
54
+ @log_writer.error str
39
55
  end
40
56
 
41
57
  def debug(str)
42
- @events.log "- #{str}" if @options[:debug]
58
+ @log_writer.log "- #{str}" if @options[:debug]
43
59
  end
44
60
 
45
61
  def start_control
46
62
  str = @options[:control_url]
47
63
  return unless str
48
64
 
49
- require 'puma/app/status'
65
+ require_relative 'app/status'
50
66
 
51
67
  if token = @options[:control_auth_token]
52
68
  token = nil if token.empty? || token == 'none'
@@ -54,12 +70,14 @@ module Puma
54
70
 
55
71
  app = Puma::App::Status.new @launcher, token
56
72
 
57
- control = Puma::Server.new app, @launcher.events,
58
- { min_threads: 0, max_threads: 1, queue_requests: false }
73
+ # A Reactor is not created aand nio4r is not loaded when 'queue_requests: false'
74
+ # Use `nil` for events, no hooks in control server
75
+ control = Puma::Server.new app, nil,
76
+ { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
59
77
 
60
- control.binder.parse [str], self, 'Starting control server'
78
+ control.binder.parse [str], nil, 'Starting control server'
61
79
 
62
- control.run thread_name: 'control'
80
+ control.run thread_name: 'ctl'
63
81
  @control = control
64
82
  end
65
83
 
@@ -84,12 +102,13 @@ module Puma
84
102
  def output_header(mode)
85
103
  min_t = @options[:min_threads]
86
104
  max_t = @options[:max_threads]
105
+ environment = @options[:environment]
87
106
 
88
107
  log "Puma starting in #{mode} mode..."
89
108
  log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
90
109
  log "* Min threads: #{min_t}"
91
110
  log "* Max threads: #{max_t}"
92
- log "* Environment: #{ENV['RACK_ENV']}"
111
+ log "* Environment: #{environment}"
93
112
 
94
113
  if mode == "cluster"
95
114
  log "* Master PID: #{Process.pid}"
@@ -108,9 +127,7 @@ module Puma
108
127
  append = @options[:redirect_append]
109
128
 
110
129
  if stdout
111
- unless Dir.exist?(File.dirname(stdout))
112
- raise "Cannot redirect STDOUT to #{stdout}"
113
- end
130
+ ensure_output_directory_exists(stdout, 'STDOUT')
114
131
 
115
132
  STDOUT.reopen stdout, (append ? "a" : "w")
116
133
  STDOUT.puts "=== puma startup: #{Time.now} ==="
@@ -118,9 +135,7 @@ module Puma
118
135
  end
119
136
 
120
137
  if stderr
121
- unless Dir.exist?(File.dirname(stderr))
122
- raise "Cannot redirect STDERR to #{stderr}"
123
- end
138
+ ensure_output_directory_exists(stderr, 'STDERR')
124
139
 
125
140
  STDERR.reopen stderr, (append ? "a" : "w")
126
141
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -134,30 +149,50 @@ module Puma
134
149
  end
135
150
 
136
151
  def load_and_bind
137
- unless @launcher.config.app_configured?
152
+ unless @config.app_configured?
138
153
  error "No application configured, nothing to run"
139
154
  exit 1
140
155
  end
141
156
 
142
157
  begin
143
- @app = @launcher.config.app
158
+ @app = @config.app
144
159
  rescue Exception => e
145
160
  log "! Unable to load application: #{e.class}: #{e.message}"
146
161
  raise e
147
162
  end
148
163
 
149
- @launcher.binder.parse @options[:binds], self
164
+ @launcher.binder.parse @options[:binds]
150
165
  end
151
166
 
152
167
  # @!attribute [r] app
153
168
  def app
154
- @app ||= @launcher.config.app
169
+ @app ||= @config.app
155
170
  end
156
171
 
157
172
  def start_server
158
- server = Puma::Server.new app, @launcher.events, @options
159
- server.inherit_binder @launcher.binder
173
+ server = Puma::Server.new(app, @events, @options)
174
+ server.inherit_binder(@launcher.binder)
160
175
  server
161
176
  end
177
+
178
+ private
179
+ def ensure_output_directory_exists(path, io_name)
180
+ unless Dir.exist?(File.dirname(path))
181
+ raise "Cannot redirect #{io_name} to #{path}"
182
+ end
183
+ end
184
+
185
+ def stats
186
+ {
187
+ versions: {
188
+ puma: Puma::Const::PUMA_VERSION,
189
+ ruby: {
190
+ engine: RUBY_ENGINE,
191
+ version: RUBY_VERSION,
192
+ patchlevel: RUBY_PATCHLEVEL
193
+ }
194
+ }
195
+ }
196
+ end
162
197
  end
163
198
  end
data/lib/puma/server.rb CHANGED
@@ -2,18 +2,20 @@
2
2
 
3
3
  require 'stringio'
4
4
 
5
- require 'puma/thread_pool'
6
- require 'puma/const'
7
- require 'puma/events'
8
- require 'puma/null_io'
9
- require 'puma/reactor'
10
- require 'puma/client'
11
- require 'puma/binder'
12
- require 'puma/util'
13
- require 'puma/io_buffer'
14
- require 'puma/request'
5
+ require_relative 'thread_pool'
6
+ require_relative 'const'
7
+ require_relative 'log_writer'
8
+ require_relative 'events'
9
+ require_relative 'null_io'
10
+ require_relative 'reactor'
11
+ require_relative 'client'
12
+ require_relative 'binder'
13
+ require_relative 'util'
14
+ require_relative 'io_buffer'
15
+ require_relative 'request'
15
16
 
16
17
  require 'socket'
18
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
17
19
  require 'forwardable'
18
20
 
19
21
  module Puma
@@ -29,12 +31,12 @@ module Puma
29
31
  #
30
32
  # Each `Puma::Server` will have one reactor and one thread pool.
31
33
  class Server
32
-
33
34
  include Puma::Const
34
35
  include Request
35
36
  extend Forwardable
36
37
 
37
38
  attr_reader :thread
39
+ attr_reader :log_writer
38
40
  attr_reader :events
39
41
  attr_reader :min_threads, :max_threads # for #stats
40
42
  attr_reader :requests_count # @version 5.0.0
@@ -44,11 +46,6 @@ module Puma
44
46
  :leak_stack_on_error,
45
47
  :persistent_timeout, :reaping_time
46
48
 
47
- # @deprecated v6.0.0
48
- attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
49
- :leak_stack_on_error, :min_threads, :max_threads,
50
- :persistent_timeout, :reaping_time
51
-
52
49
  attr_accessor :app
53
50
  attr_accessor :binder
54
51
 
@@ -59,8 +56,9 @@ module Puma
59
56
 
60
57
  # Create a server for the rack app +app+.
61
58
  #
62
- # +events+ is an object which will be called when certain error events occur
63
- # to be handled. See Puma::Events for the list of current methods to implement.
59
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
60
+ #
61
+ # +events+ is a Puma::Events object used to notify application status events.
64
62
  #
65
63
  # Server#run returns a thread that you can join on to wait for the server
66
64
  # to do its work.
@@ -69,34 +67,40 @@ module Puma
69
67
  # and have default values set via +fetch+. Normally the values are set via
70
68
  # `::Puma::Configuration.puma_default_options`.
71
69
  #
72
- def initialize(app, events=Events.stdio, options={})
70
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
71
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
72
+ # calling code to not require events.rb.
73
+ #
74
+ def initialize(app, events = nil, options = {})
73
75
  @app = app
74
- @events = events
76
+ @events = events || Events.new
75
77
 
76
78
  @check, @notify = nil
77
79
  @status = :stop
78
80
 
79
- @auto_trim_time = 30
80
- @reaping_time = 1
81
-
82
81
  @thread = nil
83
82
  @thread_pool = nil
84
83
 
85
- @options = options
84
+ @options = if options.is_a?(UserFileDefaultOptions)
85
+ options
86
+ else
87
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
88
+ end
86
89
 
87
- @early_hints = options.fetch :early_hints, nil
88
- @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
- @min_threads = options.fetch :min_threads, 0
90
- @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
- @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
- @queue_requests = options.fetch :queue_requests, true
93
- @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
94
- @io_selector_backend = options.fetch :io_selector_backend, :auto
90
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
91
+ @early_hints = @options[:early_hints]
92
+ @first_data_timeout = @options[:first_data_timeout]
93
+ @min_threads = @options[:min_threads]
94
+ @max_threads = @options[:max_threads]
95
+ @persistent_timeout = @options[:persistent_timeout]
96
+ @queue_requests = @options[:queue_requests]
97
+ @max_fast_inline = @options[:max_fast_inline]
98
+ @io_selector_backend = @options[:io_selector_backend]
95
99
 
96
100
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
97
101
  @leak_stack_on_error = @options[:environment] ? temp : true
98
102
 
99
- @binder = Binder.new(events)
103
+ @binder = Binder.new(log_writer)
100
104
 
101
105
  ENV['RACK_ENV'] ||= "development"
102
106
 
@@ -145,7 +149,7 @@ module Puma
145
149
  begin
146
150
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
147
151
  rescue IOError, SystemCallError
148
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
152
+ Puma::Util.purge_interrupt_queue
149
153
  end
150
154
  end
151
155
 
@@ -154,7 +158,7 @@ module Puma
154
158
  begin
155
159
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
156
160
  rescue IOError, SystemCallError
157
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
161
+ Puma::Util.purge_interrupt_queue
158
162
  end
159
163
  end
160
164
  else
@@ -175,7 +179,7 @@ module Puma
175
179
  begin
176
180
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
177
181
  rescue IOError, SystemCallError
178
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
182
+ Puma::Util.purge_interrupt_queue
179
183
  @precheck_closing = false
180
184
  false
181
185
  else
@@ -192,12 +196,12 @@ module Puma
192
196
 
193
197
  # @!attribute [r] backlog
194
198
  def backlog
195
- @thread_pool and @thread_pool.backlog
199
+ @thread_pool&.backlog
196
200
  end
197
201
 
198
202
  # @!attribute [r] running
199
203
  def running
200
- @thread_pool and @thread_pool.spawned
204
+ @thread_pool&.spawned
201
205
  end
202
206
 
203
207
 
@@ -210,7 +214,7 @@ module Puma
210
214
  # value would be 4 until it finishes processing.
211
215
  # @!attribute [r] pool_capacity
212
216
  def pool_capacity
213
- @thread_pool and @thread_pool.pool_capacity
217
+ @thread_pool&.pool_capacity
214
218
  end
215
219
 
216
220
  # Runs the server.
@@ -219,35 +223,23 @@ module Puma
219
223
  # up in the background to handle requests. Otherwise requests
220
224
  # are handled synchronously.
221
225
  #
222
- def run(background=true, thread_name: 'server')
226
+ def run(background=true, thread_name: 'srv')
223
227
  BasicSocket.do_not_reverse_lookup = true
224
228
 
225
229
  @events.fire :state, :booting
226
230
 
227
231
  @status = :run
228
232
 
229
- @thread_pool = ThreadPool.new(
230
- @min_threads,
231
- @max_threads,
232
- ::Puma::IOBuffer,
233
- &method(:process_client)
234
- )
235
-
236
- @thread_pool.out_of_band_hook = @options[:out_of_band]
237
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
233
+ @thread_pool = ThreadPool.new(thread_name, @options) { |a, b| process_client a, b }
238
234
 
239
235
  if @queue_requests
240
- @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
236
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
241
237
  @reactor.run
242
238
  end
243
239
 
244
- if @reaping_time
245
- @thread_pool.auto_reap!(@reaping_time)
246
- end
247
240
 
248
- if @auto_trim_time
249
- @thread_pool.auto_trim!(@auto_trim_time)
250
- end
241
+ @thread_pool.auto_reap! if @options[:reaping_time]
242
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
251
243
 
252
244
  @check, @notify = Puma::Util.pipe unless @notify
253
245
 
@@ -313,14 +305,15 @@ module Puma
313
305
  queue_requests = @queue_requests
314
306
  drain = @options[:drain_on_shutdown] ? 0 : nil
315
307
 
316
- remote_addr_value = nil
317
- remote_addr_header = nil
318
-
319
- case @options[:remote_address]
308
+ addr_send_name, addr_value = case @options[:remote_address]
320
309
  when :value
321
- remote_addr_value = @options[:remote_address_value]
310
+ [:peerip=, @options[:remote_address_value]]
322
311
  when :header
323
- remote_addr_header = @options[:remote_address_header]
312
+ [:remote_addr_header=, @options[:remote_address_header]]
313
+ when :proxy_protocol
314
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
315
+ else
316
+ [nil, nil]
324
317
  end
325
318
 
326
319
  while @status == :run || (drain && shutting_down?)
@@ -340,22 +333,21 @@ module Puma
340
333
  next
341
334
  end
342
335
  drain += 1 if shutting_down?
343
- client = Client.new io, @binder.env(sock)
344
- client.listener = sock
345
- if remote_addr_value
346
- client.peerip = remote_addr_value
347
- elsif remote_addr_header
348
- client.remote_addr_header = remote_addr_header
349
- end
350
- pool << client
336
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
337
+ c.listener = sock
338
+ c.send(addr_send_name, addr_value) if addr_value
339
+ }
351
340
  end
352
341
  end
353
- rescue Object => e
354
- @events.unknown_error e, nil, "Listen loop"
342
+ rescue IOError, Errno::EBADF
343
+ # In the case that any of the sockets are unexpectedly close.
344
+ raise
345
+ rescue StandardError => e
346
+ @log_writer.unknown_error e, nil, "Listen loop"
355
347
  end
356
348
  end
357
349
 
358
- @events.debug "Drained #{drain} additional connections." if drain
350
+ @log_writer.debug "Drained #{drain} additional connections." if drain
359
351
  @events.fire :state, @status
360
352
 
361
353
  if queue_requests
@@ -364,15 +356,15 @@ module Puma
364
356
  end
365
357
  graceful_shutdown if @status == :stop || @status == :restart
366
358
  rescue Exception => e
367
- @events.unknown_error e, nil, "Exception handling servers"
359
+ @log_writer.unknown_error e, nil, "Exception handling servers"
368
360
  ensure
369
- begin
370
- @check.close unless @check.closed?
371
- rescue Errno::EBADF, RuntimeError
372
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
373
- # Errno::EBADF is infrequently raised
361
+ # Errno::EBADF is infrequently raised
362
+ [@check, @notify].each do |io|
363
+ begin
364
+ io.close unless io.closed?
365
+ rescue Errno::EBADF
366
+ end
374
367
  end
375
- @notify.close
376
368
  @notify = nil
377
369
  @check = nil
378
370
  end
@@ -396,7 +388,7 @@ module Puma
396
388
  return true
397
389
  end
398
390
 
399
- return false
391
+ false
400
392
  end
401
393
 
402
394
  # Given a connection on +client+, handle the incoming requests,
@@ -482,10 +474,10 @@ module Puma
482
474
  begin
483
475
  client.close if close_socket
484
476
  rescue IOError, SystemCallError
485
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
477
+ Puma::Util.purge_interrupt_queue
486
478
  # Already closed
487
479
  rescue StandardError => e
488
- @events.unknown_error e, nil, "Client"
480
+ @log_writer.unknown_error e, nil, "Client"
489
481
  end
490
482
  end
491
483
  end
@@ -508,13 +500,16 @@ module Puma
508
500
  lowlevel_error(e, client.env)
509
501
  case e
510
502
  when MiniSSL::SSLError
511
- @events.ssl_error e, client.io
503
+ @log_writer.ssl_error e, client.io
512
504
  when HttpParserError
513
505
  client.write_error(400)
514
- @events.parse_error e, client
506
+ @log_writer.parse_error e, client
507
+ when HttpParserError501
508
+ client.write_error(501)
509
+ @log_writer.parse_error e, client
515
510
  else
516
511
  client.write_error(500)
517
- @events.unknown_error e, nil, "Read"
512
+ @log_writer.unknown_error e, nil, "Read"
518
513
  end
519
514
  end
520
515
 
@@ -574,11 +569,11 @@ module Puma
574
569
  @notify << message
575
570
  rescue IOError, NoMethodError, Errno::EPIPE
576
571
  # The server, in another thread, is shutting down
577
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
572
+ Puma::Util.purge_interrupt_queue
578
573
  rescue RuntimeError => e
579
574
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
580
575
  if e.message.include?('IOError')
581
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
576
+ Puma::Util.purge_interrupt_queue
582
577
  else
583
578
  raise e
584
579
  end
data/lib/puma/single.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/runner'
4
- require 'puma/detect'
5
- require 'puma/plugin'
3
+ require_relative 'runner'
4
+ require_relative 'detect'
5
+ require_relative 'plugin'
6
6
 
7
7
  module Puma
8
8
  # This class is instantiated by the `Puma::Launcher` and used
@@ -17,25 +17,25 @@ module Puma
17
17
  def stats
18
18
  {
19
19
  started_at: @started_at.utc.iso8601
20
- }.merge(@server.stats)
20
+ }.merge(@server.stats).merge(super)
21
21
  end
22
22
 
23
23
  def restart
24
- @server.begin_restart
24
+ @server&.begin_restart
25
25
  end
26
26
 
27
27
  def stop
28
- @server.stop(false) if @server
28
+ @server&.stop false
29
29
  end
30
30
 
31
31
  def halt
32
- @server.halt
32
+ @server&.halt
33
33
  end
34
34
 
35
35
  def stop_blocked
36
36
  log "- Gracefully stopping, waiting for requests to finish"
37
- @control.stop(true) if @control
38
- @server.stop(true) if @server
37
+ @control&.stop true
38
+ @server&.stop true
39
39
  end
40
40
 
41
41
  def run
@@ -55,7 +55,7 @@ module Puma
55
55
  log "Use Ctrl-C to stop"
56
56
  redirect_io
57
57
 
58
- @launcher.events.fire_on_booted!
58
+ @events.fire_on_booted!
59
59
 
60
60
  begin
61
61
  server_thread.join
@@ -1,15 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
-
5
3
  module Puma
4
+
5
+ # Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
6
+ #
7
+ # In previous versions of Puma, YAML was used to read/write the state file.
8
+ # Since Puma is similar to Bundler/RubyGems in that it may load before one's app
9
+ # does, minimizing the dependencies that may be shared with the app is desired.
10
+ #
11
+ # At present, it only works with numeric and string values. It is still a valid
12
+ # yaml file, and the CI tests parse it with Psych.
13
+ #
6
14
  class StateFile
15
+
16
+ ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
17
+
7
18
  def initialize
8
19
  @options = {}
9
20
  end
10
21
 
11
22
  def save(path, permission = nil)
12
- contents =YAML.dump @options
23
+ contents = +"---\n"
24
+ @options.each do |k,v|
25
+ next unless ALLOWED_FIELDS.include? k
26
+ case v
27
+ when Numeric
28
+ contents << "#{k}: #{v}\n"
29
+ when String
30
+ next if v.strip.empty?
31
+ contents << (k == 'running_from' || v.to_s.include?(' ') ?
32
+ "#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
33
+ end
34
+ end
13
35
  if permission
14
36
  File.write path, contents, mode: 'wb:UTF-8'
15
37
  else
@@ -18,12 +40,22 @@ module Puma
18
40
  end
19
41
 
20
42
  def load(path)
21
- @options = YAML.load File.read(path)
43
+ File.read(path).lines.each do |line|
44
+ next if line.start_with? '#'
45
+ k,v = line.split ':', 2
46
+ next unless v && ALLOWED_FIELDS.include?(k)
47
+ v = v.strip
48
+ @options[k] =
49
+ case v
50
+ when '' then nil
51
+ when /\A\d+\z/ then v.to_i
52
+ when /\A\d+\.\d+\z/ then v.to_f
53
+ else v.gsub(/\A"|"\z/, '')
54
+ end
55
+ end
22
56
  end
23
57
 
24
- FIELDS = %w!control_url control_auth_token pid running_from!
25
-
26
- FIELDS.each do |f|
58
+ ALLOWED_FIELDS.each do |f|
27
59
  define_method f do
28
60
  @options[f]
29
61
  end
data/lib/puma/systemd.rb CHANGED
@@ -4,7 +4,8 @@ require 'sd_notify'
4
4
 
5
5
  module Puma
6
6
  class Systemd
7
- def initialize(events)
7
+ def initialize(log_writer, events)
8
+ @log_writer = log_writer
8
9
  @events = events
9
10
  end
10
11
 
@@ -40,7 +41,7 @@ module Puma
40
41
  end
41
42
 
42
43
  def log(str)
43
- @events.log str
44
+ @log_writer.log(str)
44
45
  end
45
46
  end
46
47
  end