puma 5.2.2 → 6.3.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +483 -4
  3. data/README.md +101 -20
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +50 -16
  6. data/docs/compile_options.md +38 -2
  7. data/docs/deployment.md +53 -67
  8. data/docs/fork_worker.md +1 -3
  9. data/docs/jungle/rc.d/README.md +1 -1
  10. data/docs/kubernetes.md +1 -1
  11. data/docs/nginx.md +1 -1
  12. data/docs/plugins.md +15 -15
  13. data/docs/rails_dev_mode.md +2 -3
  14. data/docs/restart.md +7 -7
  15. data/docs/signals.md +11 -10
  16. data/docs/stats.md +8 -8
  17. data/docs/systemd.md +65 -69
  18. data/docs/testing_benchmarks_local_files.md +150 -0
  19. data/docs/testing_test_rackup_ci_files.md +36 -0
  20. data/ext/puma_http11/extconf.rb +44 -13
  21. data/ext/puma_http11/http11_parser.c +24 -11
  22. data/ext/puma_http11/http11_parser.h +2 -2
  23. data/ext/puma_http11/http11_parser.java.rl +2 -2
  24. data/ext/puma_http11/http11_parser.rl +2 -2
  25. data/ext/puma_http11/http11_parser_common.rl +3 -3
  26. data/ext/puma_http11/mini_ssl.c +150 -23
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  29. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  30. data/ext/puma_http11/puma_http11.c +18 -10
  31. data/lib/puma/app/status.rb +10 -7
  32. data/lib/puma/binder.rb +112 -62
  33. data/lib/puma/cli.rb +24 -20
  34. data/lib/puma/client.rb +162 -36
  35. data/lib/puma/cluster/worker.rb +31 -27
  36. data/lib/puma/cluster/worker_handle.rb +12 -1
  37. data/lib/puma/cluster.rb +102 -61
  38. data/lib/puma/commonlogger.rb +21 -14
  39. data/lib/puma/configuration.rb +78 -54
  40. data/lib/puma/const.rb +135 -97
  41. data/lib/puma/control_cli.rb +25 -20
  42. data/lib/puma/detect.rb +12 -2
  43. data/lib/puma/dsl.rb +308 -58
  44. data/lib/puma/error_logger.rb +20 -11
  45. data/lib/puma/events.rb +6 -126
  46. data/lib/puma/io_buffer.rb +39 -4
  47. data/lib/puma/jruby_restart.rb +2 -1
  48. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  49. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  50. data/lib/puma/launcher.rb +114 -173
  51. data/lib/puma/log_writer.rb +147 -0
  52. data/lib/puma/minissl/context_builder.rb +30 -16
  53. data/lib/puma/minissl.rb +132 -38
  54. data/lib/puma/null_io.rb +5 -0
  55. data/lib/puma/plugin/systemd.rb +90 -0
  56. data/lib/puma/plugin/tmp_restart.rb +1 -1
  57. data/lib/puma/plugin.rb +2 -2
  58. data/lib/puma/rack/builder.rb +7 -7
  59. data/lib/puma/rack_default.rb +19 -4
  60. data/lib/puma/reactor.rb +19 -10
  61. data/lib/puma/request.rb +373 -153
  62. data/lib/puma/runner.rb +74 -28
  63. data/lib/puma/sd_notify.rb +149 -0
  64. data/lib/puma/server.rb +127 -136
  65. data/lib/puma/single.rb +13 -11
  66. data/lib/puma/state_file.rb +39 -7
  67. data/lib/puma/thread_pool.rb +33 -26
  68. data/lib/puma/util.rb +20 -15
  69. data/lib/puma.rb +28 -11
  70. data/lib/rack/handler/puma.rb +113 -86
  71. data/tools/Dockerfile +1 -1
  72. metadata +15 -10
  73. data/lib/puma/queue_close.rb +0 -26
  74. data/lib/puma/systemd.rb +0 -46
data/lib/puma/server.rb CHANGED
@@ -2,18 +2,19 @@
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 'request'
15
15
 
16
16
  require 'socket'
17
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
17
18
  require 'forwardable'
18
19
 
19
20
  module Puma
@@ -29,12 +30,12 @@ module Puma
29
30
  #
30
31
  # Each `Puma::Server` will have one reactor and one thread pool.
31
32
  class Server
32
-
33
33
  include Puma::Const
34
34
  include Request
35
35
  extend Forwardable
36
36
 
37
37
  attr_reader :thread
38
+ attr_reader :log_writer
38
39
  attr_reader :events
39
40
  attr_reader :min_threads, :max_threads # for #stats
40
41
  attr_reader :requests_count # @version 5.0.0
@@ -44,23 +45,19 @@ module Puma
44
45
  :leak_stack_on_error,
45
46
  :persistent_timeout, :reaping_time
46
47
 
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
48
  attr_accessor :app
53
49
  attr_accessor :binder
54
50
 
55
51
  def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
56
52
  :add_unix_listener, :connected_ports
57
53
 
58
- ThreadLocalKey = :puma_server
54
+ THREAD_LOCAL_KEY = :puma_server
59
55
 
60
56
  # Create a server for the rack app +app+.
61
57
  #
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.
58
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
59
+ #
60
+ # +events+ is a Puma::Events object used to notify application status events.
64
61
  #
65
62
  # Server#run returns a thread that you can join on to wait for the server
66
63
  # to do its work.
@@ -69,34 +66,53 @@ module Puma
69
66
  # and have default values set via +fetch+. Normally the values are set via
70
67
  # `::Puma::Configuration.puma_default_options`.
71
68
  #
72
- def initialize(app, events=Events.stdio, options={})
69
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
70
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
71
+ # calling code to not require events.rb.
72
+ #
73
+ def initialize(app, events = nil, options = {})
73
74
  @app = app
74
- @events = events
75
+ @events = events || Events.new
75
76
 
76
77
  @check, @notify = nil
77
78
  @status = :stop
78
79
 
79
- @auto_trim_time = 30
80
- @reaping_time = 1
81
-
82
80
  @thread = nil
83
81
  @thread_pool = nil
84
82
 
85
- @options = options
83
+ @options = if options.is_a?(UserFileDefaultOptions)
84
+ options
85
+ else
86
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
87
+ end
86
88
 
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
89
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
90
+ @early_hints = @options[:early_hints]
91
+ @first_data_timeout = @options[:first_data_timeout]
92
+ @min_threads = @options[:min_threads]
93
+ @max_threads = @options[:max_threads]
94
+ @persistent_timeout = @options[:persistent_timeout]
95
+ @queue_requests = @options[:queue_requests]
96
+ @max_fast_inline = @options[:max_fast_inline]
97
+ @io_selector_backend = @options[:io_selector_backend]
98
+ @http_content_length_limit = @options[:http_content_length_limit]
99
+
100
+ # make this a hash, since we prefer `key?` over `include?`
101
+ @supported_http_methods =
102
+ if @options[:supported_http_methods] == :any
103
+ :any
104
+ else
105
+ if (ary = @options[:supported_http_methods])
106
+ ary
107
+ else
108
+ SUPPORTED_HTTP_METHODS
109
+ end.sort.product([nil]).to_h.freeze
110
+ end
95
111
 
96
112
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
97
113
  @leak_stack_on_error = @options[:environment] ? temp : true
98
114
 
99
- @binder = Binder.new(events)
115
+ @binder = Binder.new(log_writer)
100
116
 
101
117
  ENV['RACK_ENV'] ||= "development"
102
118
 
@@ -114,7 +130,7 @@ module Puma
114
130
  class << self
115
131
  # @!attribute [r] current
116
132
  def current
117
- Thread.current[ThreadLocalKey]
133
+ Thread.current[THREAD_LOCAL_KEY]
118
134
  end
119
135
 
120
136
  # :nodoc:
@@ -137,8 +153,6 @@ module Puma
137
153
  # socket parameter may be an MiniSSL::Socket, so use to_io
138
154
  #
139
155
  if tcp_cork_supported?
140
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
141
-
142
156
  # 6 == Socket::IPPROTO_TCP
143
157
  # 3 == TCP_CORK
144
158
  # 1/0 == turn on/off
@@ -147,7 +161,7 @@ module Puma
147
161
  begin
148
162
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
149
163
  rescue IOError, SystemCallError
150
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
164
+ Puma::Util.purge_interrupt_queue
151
165
  end
152
166
  end
153
167
 
@@ -156,7 +170,7 @@ module Puma
156
170
  begin
157
171
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
158
172
  rescue IOError, SystemCallError
159
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
173
+ Puma::Util.purge_interrupt_queue
160
174
  end
161
175
  end
162
176
  else
@@ -168,14 +182,16 @@ module Puma
168
182
  end
169
183
 
170
184
  if closed_socket_supported?
185
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
186
+
171
187
  def closed_socket?(socket)
172
- return false unless socket.kind_of? TCPSocket
173
- return false unless @precheck_closing
188
+ skt = socket.to_io
189
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
174
190
 
175
191
  begin
176
- tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
192
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
177
193
  rescue IOError, SystemCallError
178
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
194
+ Puma::Util.purge_interrupt_queue
179
195
  @precheck_closing = false
180
196
  false
181
197
  else
@@ -192,12 +208,12 @@ module Puma
192
208
 
193
209
  # @!attribute [r] backlog
194
210
  def backlog
195
- @thread_pool and @thread_pool.backlog
211
+ @thread_pool&.backlog
196
212
  end
197
213
 
198
214
  # @!attribute [r] running
199
215
  def running
200
- @thread_pool and @thread_pool.spawned
216
+ @thread_pool&.spawned
201
217
  end
202
218
 
203
219
 
@@ -210,7 +226,7 @@ module Puma
210
226
  # value would be 4 until it finishes processing.
211
227
  # @!attribute [r] pool_capacity
212
228
  def pool_capacity
213
- @thread_pool and @thread_pool.pool_capacity
229
+ @thread_pool&.pool_capacity
214
230
  end
215
231
 
216
232
  # Runs the server.
@@ -219,35 +235,23 @@ module Puma
219
235
  # up in the background to handle requests. Otherwise requests
220
236
  # are handled synchronously.
221
237
  #
222
- def run(background=true, thread_name: 'server')
238
+ def run(background=true, thread_name: 'srv')
223
239
  BasicSocket.do_not_reverse_lookup = true
224
240
 
225
241
  @events.fire :state, :booting
226
242
 
227
243
  @status = :run
228
244
 
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]
245
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
238
246
 
239
247
  if @queue_requests
240
- @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
248
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
241
249
  @reactor.run
242
250
  end
243
251
 
244
- if @reaping_time
245
- @thread_pool.auto_reap!(@reaping_time)
246
- end
247
252
 
248
- if @auto_trim_time
249
- @thread_pool.auto_trim!(@auto_trim_time)
250
- end
253
+ @thread_pool.auto_reap! if @options[:reaping_time]
254
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
251
255
 
252
256
  @check, @notify = Puma::Util.pipe unless @notify
253
257
 
@@ -295,6 +299,9 @@ module Puma
295
299
  @thread_pool << client
296
300
  elsif shutdown || client.timeout == 0
297
301
  client.timeout!
302
+ else
303
+ client.set_timeout(@first_data_timeout)
304
+ false
298
305
  end
299
306
  rescue StandardError => e
300
307
  client_error(e, client)
@@ -308,47 +315,52 @@ module Puma
308
315
  sockets = [check] + @binder.ios
309
316
  pool = @thread_pool
310
317
  queue_requests = @queue_requests
318
+ drain = @options[:drain_on_shutdown] ? 0 : nil
311
319
 
312
- remote_addr_value = nil
313
- remote_addr_header = nil
314
-
315
- case @options[:remote_address]
320
+ addr_send_name, addr_value = case @options[:remote_address]
316
321
  when :value
317
- remote_addr_value = @options[:remote_address_value]
322
+ [:peerip=, @options[:remote_address_value]]
318
323
  when :header
319
- remote_addr_header = @options[:remote_address_header]
324
+ [:remote_addr_header=, @options[:remote_address_header]]
325
+ when :proxy_protocol
326
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
327
+ else
328
+ [nil, nil]
320
329
  end
321
330
 
322
- while @status == :run
331
+ while @status == :run || (drain && shutting_down?)
323
332
  begin
324
- ios = IO.select sockets
333
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
+ break unless ios
325
335
  ios.first.each do |sock|
326
336
  if sock == check
327
337
  break if handle_check
328
338
  else
329
339
  pool.wait_until_not_full
330
- pool.wait_for_less_busy_worker(
331
- @options[:wait_for_less_busy_worker].to_f)
340
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
332
341
 
333
342
  io = begin
334
343
  sock.accept_nonblock
335
344
  rescue IO::WaitReadable
336
345
  next
337
346
  end
338
- client = Client.new io, @binder.env(sock)
339
- if remote_addr_value
340
- client.peerip = remote_addr_value
341
- elsif remote_addr_header
342
- client.remote_addr_header = remote_addr_header
343
- end
344
- pool << client
347
+ drain += 1 if shutting_down?
348
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
349
+ c.listener = sock
350
+ c.http_content_length_limit = @http_content_length_limit
351
+ c.send(addr_send_name, addr_value) if addr_value
352
+ }
345
353
  end
346
354
  end
347
- rescue Object => e
348
- @events.unknown_error e, nil, "Listen loop"
355
+ rescue IOError, Errno::EBADF
356
+ # In the case that any of the sockets are unexpectedly close.
357
+ raise
358
+ rescue StandardError => e
359
+ @log_writer.unknown_error e, nil, "Listen loop"
349
360
  end
350
361
  end
351
362
 
363
+ @log_writer.debug "Drained #{drain} additional connections." if drain
352
364
  @events.fire :state, @status
353
365
 
354
366
  if queue_requests
@@ -357,15 +369,15 @@ module Puma
357
369
  end
358
370
  graceful_shutdown if @status == :stop || @status == :restart
359
371
  rescue Exception => e
360
- @events.unknown_error e, nil, "Exception handling servers"
372
+ @log_writer.unknown_error e, nil, "Exception handling servers"
361
373
  ensure
362
- begin
363
- @check.close unless @check.closed?
364
- rescue Errno::EBADF, RuntimeError
365
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
366
- # Errno::EBADF is infrequently raised
374
+ # Errno::EBADF is infrequently raised
375
+ [@check, @notify].each do |io|
376
+ begin
377
+ io.close unless io.closed?
378
+ rescue Errno::EBADF
379
+ end
367
380
  end
368
- @notify.close
369
381
  @notify = nil
370
382
  @check = nil
371
383
  end
@@ -389,7 +401,7 @@ module Puma
389
401
  return true
390
402
  end
391
403
 
392
- return false
404
+ false
393
405
  end
394
406
 
395
407
  # Given a connection on +client+, handle the incoming requests,
@@ -402,9 +414,9 @@ module Puma
402
414
  # returning.
403
415
  #
404
416
  # Return true if one or more requests were processed.
405
- def process_client(client, buffer)
417
+ def process_client(client)
406
418
  # Advertise this server into the thread
407
- Thread.current[ThreadLocalKey] = self
419
+ Thread.current[THREAD_LOCAL_KEY] = self
408
420
 
409
421
  clean_thread_locals = @options[:clean_thread_locals]
410
422
  close_socket = true
@@ -428,31 +440,28 @@ module Puma
428
440
 
429
441
  while true
430
442
  @requests_count += 1
431
- case handle_request(client, buffer)
443
+ case handle_request(client, requests + 1)
432
444
  when false
433
445
  break
434
446
  when :async
435
447
  close_socket = false
436
448
  break
437
449
  when true
438
- buffer.reset
439
-
440
450
  ThreadPool.clean_thread_locals if clean_thread_locals
441
451
 
442
452
  requests += 1
443
453
 
444
- check_for_more_data = @status == :run
454
+ # As an optimization, try to read the next request from the
455
+ # socket for a short time before returning to the reactor.
456
+ fast_check = @status == :run
445
457
 
446
- if requests >= @max_fast_inline
447
- # This will mean that reset will only try to use the data it already
448
- # has buffered and won't try to read more data. What this means is that
449
- # every client, independent of their request speed, gets treated like a slow
450
- # one once every max_fast_inline requests.
451
- check_for_more_data = false
452
- end
458
+ # Always pass the client back to the reactor after a reasonable
459
+ # number of inline requests if there are other requests pending.
460
+ fast_check = false if requests >= @max_fast_inline &&
461
+ @thread_pool.backlog > 0
453
462
 
454
463
  next_request_ready = with_force_shutdown(client) do
455
- client.reset(check_for_more_data)
464
+ client.reset(fast_check)
456
465
  end
457
466
 
458
467
  unless next_request_ready
@@ -471,15 +480,15 @@ module Puma
471
480
  # The ensure tries to close +client+ down
472
481
  requests > 0
473
482
  ensure
474
- buffer.reset
483
+ client.io_buffer.reset
475
484
 
476
485
  begin
477
486
  client.close if close_socket
478
487
  rescue IOError, SystemCallError
479
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
480
489
  # Already closed
481
490
  rescue StandardError => e
482
- @events.unknown_error e, nil, "Client"
491
+ @log_writer.unknown_error e, nil, "Client"
483
492
  end
484
493
  end
485
494
  end
@@ -502,13 +511,16 @@ module Puma
502
511
  lowlevel_error(e, client.env)
503
512
  case e
504
513
  when MiniSSL::SSLError
505
- @events.ssl_error e, client.io
514
+ @log_writer.ssl_error e, client.io
506
515
  when HttpParserError
507
516
  client.write_error(400)
508
- @events.parse_error e, client
517
+ @log_writer.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @log_writer.parse_error e, client
509
521
  else
510
522
  client.write_error(500)
511
- @events.unknown_error e, nil, "Read"
523
+ @log_writer.unknown_error e, nil, "Read"
512
524
  end
513
525
  end
514
526
 
@@ -526,7 +538,8 @@ module Puma
526
538
  end
527
539
 
528
540
  if @leak_stack_on_error
529
- [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
541
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
530
543
  else
531
544
  [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
532
545
  end
@@ -550,28 +563,6 @@ module Puma
550
563
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
551
564
  end
552
565
 
553
- if @options[:drain_on_shutdown]
554
- count = 0
555
-
556
- while true
557
- ios = IO.select @binder.ios, nil, nil, 0
558
- break unless ios
559
-
560
- ios.first.each do |sock|
561
- begin
562
- if io = sock.accept_nonblock
563
- count += 1
564
- client = Client.new io, @binder.env(sock)
565
- @thread_pool << client
566
- end
567
- rescue SystemCallError
568
- end
569
- end
570
- end
571
-
572
- @events.debug "Drained #{count} additional connections."
573
- end
574
-
575
566
  if @status != :restart
576
567
  @binder.close
577
568
  end
@@ -587,13 +578,13 @@ module Puma
587
578
 
588
579
  def notify_safely(message)
589
580
  @notify << message
590
- rescue IOError, NoMethodError, Errno::EPIPE
581
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
591
582
  # The server, in another thread, is shutting down
592
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
583
+ Puma::Util.purge_interrupt_queue
593
584
  rescue RuntimeError => e
594
585
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
595
586
  if e.message.include?('IOError')
596
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
587
+ Puma::Util.purge_interrupt_queue
597
588
  else
598
589
  raise e
599
590
  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
@@ -16,26 +16,26 @@ module Puma
16
16
  # @!attribute [r] stats
17
17
  def stats
18
18
  {
19
- started_at: @started_at.utc.iso8601
20
- }.merge(@server.stats)
19
+ started_at: utc_iso8601(@started_at)
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,9 @@ 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
+
60
+ debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
59
61
 
60
62
  begin
61
63
  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