puma 5.0.0 → 5.0.1

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.

@@ -10,23 +10,26 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
- require_relative 'puma/puma_http11'
14
- require_relative 'puma/detect'
13
+ require 'puma/puma_http11'
14
+ require 'puma/detect'
15
15
 
16
16
  module Puma
17
17
  autoload :Const, 'puma/const'
18
18
  autoload :Server, 'puma/server'
19
19
  autoload :Launcher, 'puma/launcher'
20
20
 
21
+ # @!attribute [rw] stats_object=
21
22
  def self.stats_object=(val)
22
23
  @get_stats = val
23
24
  end
24
25
 
26
+ # @!attribute [rw] stats_object
25
27
  def self.stats
26
28
  require 'json'
27
29
  @get_stats.stats.to_json
28
30
  end
29
31
 
32
+ # @!attribute [r] stats_hash
30
33
  # @version 5.0.0
31
34
  def self.stats_hash
32
35
  @get_stats.stats
@@ -66,6 +66,7 @@ module Puma
66
66
  @ios.each { |i| i.close }
67
67
  end
68
68
 
69
+ # @!attribute [r] connected_ports
69
70
  # @version 5.0.0
70
71
  def connected_ports
71
72
  ios.map { |io| io.addr[1] }.uniq
@@ -391,6 +392,7 @@ module Puma
391
392
 
392
393
  private
393
394
 
395
+ # @!attribute [r] loopback_addresses
394
396
  def loopback_addresses
395
397
  Socket.ip_address_list.select do |addrinfo|
396
398
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
@@ -85,6 +85,7 @@ module Puma
85
85
 
86
86
  def_delegators :@io, :closed?
87
87
 
88
+ # @!attribute [r] inspect
88
89
  def inspect
89
90
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
90
91
  end
@@ -96,6 +97,7 @@ module Puma
96
97
  env[HIJACK_IO] ||= @io
97
98
  end
98
99
 
100
+ # @!attribute [r] in_data_phase
99
101
  def in_data_phase
100
102
  !@read_header
101
103
  end
@@ -155,7 +157,9 @@ module Puma
155
157
  data = @io.read_nonblock(CHUNK_SIZE)
156
158
  rescue IO::WaitReadable
157
159
  return false
158
- rescue SystemCallError, IOError, EOFError
160
+ rescue EOFError
161
+ # Swallow error, don't log
162
+ rescue SystemCallError, IOError
159
163
  raise ConnectionError, "Connection error detected during read"
160
164
  end
161
165
 
@@ -194,6 +194,7 @@ module Puma
194
194
  end
195
195
  end
196
196
 
197
+ # @!attribute [r] next_worker_index
197
198
  def next_worker_index
198
199
  all_positions = 0...@options[:workers]
199
200
  occupied_positions = @workers.map { |w| w.index }
@@ -396,6 +397,7 @@ module Puma
396
397
 
397
398
  # Inside of a child process, this will return all zeroes, as @workers is only populated in
398
399
  # the master process.
400
+ # @!attribute [r] stats
399
401
  def stats
400
402
  old_worker_count = @workers.count { |w| w.phase != @phase }
401
403
  worker_status = @workers.map do |w|
@@ -100,7 +100,7 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.0.0".freeze
103
+ PUMA_VERSION = VERSION = "5.0.1".freeze
104
104
  CODE_NAME = "Spoony Bard".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
@@ -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
@@ -188,11 +188,13 @@ module Puma
188
188
  end
189
189
 
190
190
  # Return all tcp ports the launcher may be using, TCP or SSL
191
+ # @!attribute [r] connected_ports
191
192
  # @version 5.0.0
192
193
  def connected_ports
193
194
  @binder.connected_ports
194
195
  end
195
196
 
197
+ # @!attribute [r] restart_args
196
198
  def restart_args
197
199
  cmd = @options[:restart_cmd]
198
200
  if cmd
@@ -207,6 +209,7 @@ module Puma
207
209
  @binder.close_listeners
208
210
  end
209
211
 
212
+ # @!attribute [r] thread_status
210
213
  # @version 5.0.0
211
214
  def thread_status
212
215
  Thread.list.each do |thread|
@@ -261,6 +264,7 @@ module Puma
261
264
  end
262
265
  end
263
266
 
267
+ # @!attribute [r] dependencies_and_files_to_require_after_prune
264
268
  def dependencies_and_files_to_require_after_prune
265
269
  puma = spec_for_gem("puma")
266
270
 
@@ -271,6 +275,7 @@ module Puma
271
275
  [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
272
276
  end
273
277
 
278
+ # @!attribute [r] extra_runtime_deps_directories
274
279
  def extra_runtime_deps_directories
275
280
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
276
281
  if (spec = spec_for_gem(d_name))
@@ -282,6 +287,7 @@ module Puma
282
287
  end.flatten.compact
283
288
  end
284
289
 
290
+ # @!attribute [r] puma_wild_location
285
291
  def puma_wild_location
286
292
  puma = spec_for_gem("puma")
287
293
  dirs = require_paths_for_gem(puma)
@@ -345,6 +351,7 @@ module Puma
345
351
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
346
352
  end
347
353
 
354
+ # @!attribute [r] title
348
355
  def title
349
356
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
350
357
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -356,6 +363,7 @@ module Puma
356
363
  ENV['RACK_ENV'] = environment
357
364
  end
358
365
 
366
+ # @!attribute [r] environment
359
367
  def environment
360
368
  @environment
361
369
  end
@@ -24,6 +24,7 @@ module Puma
24
24
  @peercert = nil
25
25
  end
26
26
 
27
+ # @!attribute [r] to_io
27
28
  def to_io
28
29
  @socket
29
30
  end
@@ -38,6 +39,7 @@ module Puma
38
39
  #
39
40
  # Used for dropping tcp connections to ssl.
40
41
  # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
42
+ # @!attribute [r] ssl_version_state
41
43
  # @version 5.0.0
42
44
  #
43
45
  def ssl_version_state
@@ -188,10 +190,12 @@ module Puma
188
190
  end
189
191
  end
190
192
 
193
+ # @!attribute [r] peeraddr
191
194
  def peeraddr
192
195
  @socket.peeraddr
193
196
  end
194
197
 
198
+ # @!attribute [r] peercert
195
199
  def peercert
196
200
  return @peercert if @peercert
197
201
 
@@ -264,12 +268,14 @@ module Puma
264
268
  end
265
269
 
266
270
  # disables TLSv1
271
+ # @!attribute [w] no_tlsv1=
267
272
  def no_tlsv1=(tlsv1)
268
273
  raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
269
274
  @no_tlsv1 = tlsv1
270
275
  end
271
276
 
272
277
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
278
+ # @!attribute [w] no_tlsv1_1=
273
279
  def no_tlsv1_1=(tlsv1_1)
274
280
  raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
275
281
  @no_tlsv1_1 = tlsv1_1
@@ -287,6 +293,7 @@ module Puma
287
293
  @ctx = ctx
288
294
  end
289
295
 
296
+ # @!attribute [r] to_io
290
297
  def to_io
291
298
  @socket
292
299
  end
@@ -307,6 +314,7 @@ module Puma
307
314
  Socket.new io, engine
308
315
  end
309
316
 
317
+ # @!attribute [r] addr
310
318
  # @version 5.0.0
311
319
  def addr
312
320
  @socket.addr
@@ -237,23 +237,12 @@ module Puma
237
237
 
238
238
  # SSL handshake failure
239
239
  rescue MiniSSL::SSLError => e
240
- @server.lowlevel_error(e, c.env)
241
-
242
- ssl_socket = c.io
243
- begin
244
- addr = ssl_socket.peeraddr.last
245
- # EINVAL can happen when browser closes socket w/security exception
246
- rescue IOError, Errno::EINVAL
247
- addr = "<unknown>"
248
- end
249
-
250
- cert = ssl_socket.peercert
240
+ @server.lowlevel_error e, c.env
241
+ @events.ssl_error e, c.io
251
242
 
252
243
  c.close
253
244
  clear_monitor mon
254
245
 
255
- @events.ssl_error e, addr, cert
256
-
257
246
  # The client doesn't know HTTP well
258
247
  rescue HttpParserError => e
259
248
  @server.lowlevel_error(e, c.env)
@@ -54,9 +54,8 @@ module Puma
54
54
 
55
55
  app = Puma::App::Status.new @launcher, token
56
56
 
57
- control = Puma::Server.new app, @launcher.events
58
- control.min_threads = 0
59
- control.max_threads = 1
57
+ control = Puma::Server.new app, @launcher.events,
58
+ { min_threads: 0, max_threads: 1 }
60
59
 
61
60
  control.binder.parse [str], self, 'Starting control server'
62
61
 
@@ -69,6 +68,7 @@ module Puma
69
68
  @control.binder.close_listeners if @control
70
69
  end
71
70
 
71
+ # @!attribute [r] ruby_engine
72
72
  def ruby_engine
73
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
74
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -137,27 +137,14 @@ module Puma
137
137
  @launcher.binder.parse @options[:binds], self
138
138
  end
139
139
 
140
+ # @!attribute [r] app
140
141
  def app
141
142
  @app ||= @launcher.config.app
142
143
  end
143
144
 
144
145
  def start_server
145
- min_t = @options[:min_threads]
146
- max_t = @options[:max_threads]
147
-
148
146
  server = Puma::Server.new app, @launcher.events, @options
149
- server.min_threads = min_t
150
- server.max_threads = max_t
151
147
  server.inherit_binder @launcher.binder
152
-
153
- if @options[:early_hints]
154
- server.early_hints = true
155
- end
156
-
157
- unless development? || test?
158
- server.leak_stack_on_error = false
159
- end
160
-
161
148
  server
162
149
  end
163
150
  end
@@ -34,15 +34,21 @@ module Puma
34
34
 
35
35
  attr_reader :thread
36
36
  attr_reader :events
37
- attr_reader :requests_count # @version 5.0.0
37
+ attr_reader :min_threads, :max_threads # for #stats
38
+ attr_reader :requests_count # @version 5.0.0
39
+
40
+ # the following may be deprecated in the future
41
+ attr_reader :auto_trim_time
42
+ attr_reader :first_data_timeout
43
+ attr_reader :persistent_timeout
44
+ attr_reader :reaping_time
45
+
38
46
  attr_accessor :app
47
+ attr_accessor :binder
39
48
 
40
- attr_accessor :min_threads
41
- attr_accessor :max_threads
42
- attr_accessor :persistent_timeout
43
- attr_accessor :auto_trim_time
44
- attr_accessor :reaping_time
45
- attr_accessor :first_data_timeout
49
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
50
+
51
+ ThreadLocalKey = :puma_server
46
52
 
47
53
  # Create a server for the rack app +app+.
48
54
  #
@@ -52,6 +58,10 @@ module Puma
52
58
  # Server#run returns a thread that you can join on to wait for the server
53
59
  # to do its work.
54
60
  #
61
+ # @note Several instance variables exist so they are available for testing,
62
+ # and have default values set via +fetch+. Normally the values are set via
63
+ # `::Puma::Configuration.puma_default_options`.
64
+ #
55
65
  def initialize(app, events=Events.stdio, options={})
56
66
  @app = app
57
67
  @events = events
@@ -59,24 +69,24 @@ module Puma
59
69
  @check, @notify = nil
60
70
  @status = :stop
61
71
 
62
- @min_threads = 0
63
- @max_threads = 16
64
72
  @auto_trim_time = 30
65
73
  @reaping_time = 1
66
74
 
67
75
  @thread = nil
68
76
  @thread_pool = nil
69
- @early_hints = nil
70
77
 
71
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
72
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
78
+ @options = options
73
79
 
74
- @binder = Binder.new(events)
80
+ @early_hints = options.fetch :early_hints, nil
81
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
82
+ @min_threads = options.fetch :min_threads, 0
83
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
84
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
85
+ @queue_requests = options.fetch :queue_requests, true
75
86
 
76
- @leak_stack_on_error = true
87
+ @leak_stack_on_error = !!(@options[:environment] =~ /\A(development|test)\z/)
77
88
 
78
- @options = options
79
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
89
+ @binder = Binder.new(events)
80
90
 
81
91
  ENV['RACK_ENV'] ||= "development"
82
92
 
@@ -85,26 +95,37 @@ module Puma
85
95
  @precheck_closing = true
86
96
 
87
97
  @requests_count = 0
88
- end
89
98
 
90
- attr_accessor :binder, :leak_stack_on_error, :early_hints
91
-
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
99
+ @shutdown_mutex = Mutex.new
100
+ end
93
101
 
94
102
  def inherit_binder(bind)
95
103
  @binder = bind
96
104
  end
97
105
 
98
106
  class << self
107
+ # @!attribute [r] current
108
+ def current
109
+ Thread.current[ThreadLocalKey]
110
+ end
111
+
99
112
  # :nodoc:
100
113
  # @version 5.0.0
101
114
  def tcp_cork_supported?
102
115
  RbConfig::CONFIG['host_os'] =~ /linux/ &&
103
116
  Socket.const_defined?(:IPPROTO_TCP) &&
104
- Socket.const_defined?(:TCP_CORK) &&
117
+ Socket.const_defined?(:TCP_CORK)
118
+ end
119
+
120
+ # :nodoc:
121
+ # @version 5.0.0
122
+ def closed_socket_supported?
123
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
124
+ Socket.const_defined?(:IPPROTO_TCP) &&
105
125
  Socket.const_defined?(:TCP_INFO)
106
126
  end
107
127
  private :tcp_cork_supported?
128
+ private :closed_socket_supported?
108
129
  end
109
130
 
110
131
  # On Linux, use TCP_CORK to better control how the TCP stack
@@ -131,7 +152,15 @@ module Puma
131
152
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
132
153
  end
133
154
  end
155
+ else
156
+ def cork_socket(socket)
157
+ end
158
+
159
+ def uncork_socket(socket)
160
+ end
161
+ end
134
162
 
163
+ if closed_socket_supported?
135
164
  def closed_socket?(socket)
136
165
  return false unless socket.kind_of? TCPSocket
137
166
  return false unless @precheck_closing
@@ -149,21 +178,17 @@ module Puma
149
178
  end
150
179
  end
151
180
  else
152
- def cork_socket(socket)
153
- end
154
-
155
- def uncork_socket(socket)
156
- end
157
-
158
181
  def closed_socket?(socket)
159
182
  false
160
183
  end
161
184
  end
162
185
 
186
+ # @!attribute [r] backlog
163
187
  def backlog
164
188
  @thread_pool and @thread_pool.backlog
165
189
  end
166
190
 
191
+ # @!attribute [r] running
167
192
  def running
168
193
  @thread_pool and @thread_pool.spawned
169
194
  end
@@ -176,6 +201,7 @@ module Puma
176
201
  # there are 5 threads sitting idle ready to take
177
202
  # a request. If one request comes in, then the
178
203
  # value would be 4 until it finishes processing.
204
+ # @!attribute [r] pool_capacity
179
205
  def pool_capacity
180
206
  @thread_pool and @thread_pool.pool_capacity
181
207
  end
@@ -206,33 +232,36 @@ module Puma
206
232
  if @queue_requests
207
233
  process_now = client.eagerly_finish
208
234
  else
209
- client.finish(@first_data_timeout)
235
+ @thread_pool.with_force_shutdown do
236
+ client.finish(@first_data_timeout)
237
+ end
210
238
  process_now = true
211
239
  end
212
240
  rescue MiniSSL::SSLError => e
213
- ssl_socket = client.io
214
- addr = ssl_socket.peeraddr.last
215
- cert = ssl_socket.peercert
216
-
241
+ @events.ssl_error e, client.io
217
242
  client.close
218
243
 
219
- @events.ssl_error e, addr, cert
220
244
  rescue HttpParserError => e
221
245
  client.write_error(400)
222
246
  client.close
223
247
 
224
248
  @events.parse_error e, client
225
- rescue ConnectionError, EOFError => e
249
+ rescue EOFError => e
250
+ client.close
251
+
252
+ # Swallow, do not log
253
+ rescue ConnectionError, ThreadPool::ForceShutdown => e
226
254
  client.close
227
255
 
228
256
  @events.connection_error e, client
229
257
  else
230
- if process_now
231
- process_client client, buffer
232
- else
233
- client.set_timeout @first_data_timeout
234
- @reactor.add client
235
- end
258
+ process_now ||= @shutdown_mutex.synchronize do
259
+ next true unless @queue_requests
260
+ client.set_timeout @first_data_timeout
261
+ @reactor.add client
262
+ false
263
+ end
264
+ process_client client, buffer if process_now
236
265
  end
237
266
 
238
267
  process_now
@@ -318,7 +347,9 @@ module Puma
318
347
  @events.fire :state, @status
319
348
 
320
349
  if queue_requests
321
- @queue_requests = false
350
+ @shutdown_mutex.synchronize do
351
+ @queue_requests = false
352
+ end
322
353
  @reactor.clear!
323
354
  @reactor.shutdown
324
355
  end
@@ -397,32 +428,32 @@ module Puma
397
428
  check_for_more_data = false
398
429
  end
399
430
 
400
- unless client.reset(check_for_more_data)
401
- return unless @queue_requests
402
- close_socket = false
403
- client.set_timeout @persistent_timeout
404
- @reactor.add client
405
- return
431
+ next_request_ready = @thread_pool.with_force_shutdown do
432
+ client.reset(check_for_more_data)
433
+ end
434
+
435
+ unless next_request_ready
436
+ @shutdown_mutex.synchronize do
437
+ return unless @queue_requests
438
+ close_socket = false
439
+ client.set_timeout @persistent_timeout
440
+ @reactor.add client
441
+ return
442
+ end
406
443
  end
407
444
  end
408
445
  end
409
446
 
410
447
  # The client disconnected while we were reading data
411
- rescue ConnectionError
448
+ rescue ConnectionError, ThreadPool::ForceShutdown
412
449
  # Swallow them. The ensure tries to close +client+ down
413
450
 
414
451
  # SSL handshake error
415
452
  rescue MiniSSL::SSLError => e
416
- lowlevel_error(e, client.env)
417
-
418
- ssl_socket = client.io
419
- addr = ssl_socket.peeraddr.last
420
- cert = ssl_socket.peercert
421
-
453
+ lowlevel_error e, client.env
454
+ @events.ssl_error e, client.io
422
455
  close_socket = true
423
456
 
424
- @events.ssl_error e, addr, cert
425
-
426
457
  # The client doesn't know HTTP well
427
458
  rescue HttpParserError => e
428
459
  lowlevel_error(e, client.env)
@@ -617,7 +648,9 @@ module Puma
617
648
 
618
649
  begin
619
650
  begin
620
- status, headers, res_body = @app.call(env)
651
+ status, headers, res_body = @thread_pool.with_force_shutdown do
652
+ @app.call(env)
653
+ end
621
654
 
622
655
  return :async if req.hijacked
623
656
 
@@ -915,7 +948,7 @@ module Puma
915
948
 
916
949
  if @thread_pool
917
950
  if timeout = @options[:force_shutdown_after]
918
- @thread_pool.shutdown timeout.to_i
951
+ @thread_pool.shutdown timeout.to_f
919
952
  else
920
953
  @thread_pool.shutdown
921
954
  end
@@ -979,12 +1012,6 @@ module Puma
979
1012
  end
980
1013
  private :fast_write
981
1014
 
982
- ThreadLocalKey = :puma_server
983
-
984
- def self.current
985
- Thread.current[ThreadLocalKey]
986
- end
987
-
988
1015
  def shutting_down?
989
1016
  @status == :stop || @status == :restart
990
1017
  end
@@ -1000,6 +1027,7 @@ module Puma
1000
1027
 
1001
1028
  # Returns a hash of stats about the running server for reporting purposes.
1002
1029
  # @version 5.0.0
1030
+ # @!attribute [r] stats
1003
1031
  def stats
1004
1032
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1005
1033
  end