puma 3.11.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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1708 -422
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -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 +9 -0
  14. data/docs/jungle/rc.d/README.md +74 -0
  15. data/docs/jungle/rc.d/puma +61 -0
  16. data/docs/jungle/rc.d/puma.conf +10 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +100 -115
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -35
  44. data/lib/puma/client.rb +369 -232
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +124 -77
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -138
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +34 -5
  57. data/lib/puma/jruby_restart.rb +4 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +197 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +256 -70
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +607 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +336 -703
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +185 -91
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +68 -3
  80. data/lib/rack/handler/puma.rb +17 -14
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -30
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  93. data/tools/jungle/README.md +0 -13
  94. data/tools/jungle/init.d/README.md +0 -59
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/server.rb CHANGED
@@ -1,136 +1,185 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
 
3
- require 'puma/thread_pool'
4
- require 'puma/const'
5
- require 'puma/events'
6
- require 'puma/null_io'
7
- require 'puma/compat'
8
- require 'puma/reactor'
9
- require 'puma/client'
10
- require 'puma/binder'
11
- require 'puma/delegation'
12
- require 'puma/accept_nonblock'
13
- require 'puma/util'
14
-
15
- require 'puma/puma_http11'
16
-
17
- unless Puma.const_defined? "IOBuffer"
18
- require 'puma/io_buffer'
19
- end
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'
20
16
 
21
17
  require 'socket'
18
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
19
+ require 'forwardable'
22
20
 
23
21
  module Puma
24
22
 
25
23
  # The HTTP Server itself. Serves out a single Rack app.
24
+ #
25
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
26
+ # to generate one or more `Puma::Server` instances capable of handling requests.
27
+ # Each Puma process will contain one `Puma::Server` instance.
28
+ #
29
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
30
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
31
+ #
32
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
33
  class Server
27
-
28
34
  include Puma::Const
29
- extend Puma::Delegation
35
+ include Request
36
+ extend Forwardable
30
37
 
31
38
  attr_reader :thread
39
+ attr_reader :log_writer
32
40
  attr_reader :events
41
+ attr_reader :min_threads, :max_threads # for #stats
42
+ attr_reader :requests_count # @version 5.0.0
43
+
44
+ # @todo the following may be deprecated in the future
45
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
46
+ :leak_stack_on_error,
47
+ :persistent_timeout, :reaping_time
48
+
33
49
  attr_accessor :app
50
+ attr_accessor :binder
34
51
 
35
- attr_accessor :min_threads
36
- attr_accessor :max_threads
37
- attr_accessor :persistent_timeout
38
- attr_accessor :auto_trim_time
39
- attr_accessor :reaping_time
40
- attr_accessor :first_data_timeout
52
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
53
+ :add_unix_listener, :connected_ports
54
+
55
+ ThreadLocalKey = :puma_server
41
56
 
42
57
  # Create a server for the rack app +app+.
43
58
  #
44
- # +events+ is an object which will be called when certain error events occur
45
- # 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.
46
62
  #
47
63
  # Server#run returns a thread that you can join on to wait for the server
48
64
  # to do its work.
49
65
  #
50
- def initialize(app, events=Events.stdio, options={})
66
+ # @note Several instance variables exist so they are available for testing,
67
+ # and have default values set via +fetch+. Normally the values are set via
68
+ # `::Puma::Configuration.puma_default_options`.
69
+ #
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 = {})
51
75
  @app = app
52
- @events = events
53
-
54
- @check, @notify = Puma::Util.pipe
76
+ @events = events || Events.new
55
77
 
78
+ @check, @notify = nil
56
79
  @status = :stop
57
80
 
58
- @min_threads = 0
59
- @max_threads = 16
60
- @auto_trim_time = 30
61
- @reaping_time = 1
62
-
63
81
  @thread = nil
64
82
  @thread_pool = nil
65
- @early_hints = nil
66
83
 
67
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
68
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
84
+ @options = if options.is_a?(UserFileDefaultOptions)
85
+ options
86
+ else
87
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
88
+ end
69
89
 
70
- @binder = Binder.new(events)
71
- @own_binder = true
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]
72
99
 
73
- @leak_stack_on_error = true
100
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
101
+ @leak_stack_on_error = @options[:environment] ? temp : true
74
102
 
75
- @options = options
76
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
103
+ @binder = Binder.new(log_writer)
77
104
 
78
105
  ENV['RACK_ENV'] ||= "development"
79
106
 
80
107
  @mode = :http
81
108
 
82
109
  @precheck_closing = true
83
- end
84
110
 
85
- attr_accessor :binder, :leak_stack_on_error, :early_hints
86
-
87
- forward :add_tcp_listener, :@binder
88
- forward :add_ssl_listener, :@binder
89
- forward :add_unix_listener, :@binder
90
- forward :connected_port, :@binder
111
+ @requests_count = 0
112
+ end
91
113
 
92
114
  def inherit_binder(bind)
93
115
  @binder = bind
94
- @own_binder = false
95
116
  end
96
117
 
97
- def tcp_mode!
98
- @mode = :tcp
118
+ class << self
119
+ # @!attribute [r] current
120
+ def current
121
+ Thread.current[ThreadLocalKey]
122
+ end
123
+
124
+ # :nodoc:
125
+ # @version 5.0.0
126
+ def tcp_cork_supported?
127
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
128
+ end
129
+
130
+ # :nodoc:
131
+ # @version 5.0.0
132
+ def closed_socket_supported?
133
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
134
+ end
135
+ private :tcp_cork_supported?
136
+ private :closed_socket_supported?
99
137
  end
100
138
 
101
139
  # On Linux, use TCP_CORK to better control how the TCP stack
102
140
  # packetizes our stream. This improves both latency and throughput.
141
+ # socket parameter may be an MiniSSL::Socket, so use to_io
103
142
  #
104
- if RUBY_PLATFORM =~ /linux/
105
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
106
-
143
+ if tcp_cork_supported?
107
144
  # 6 == Socket::IPPROTO_TCP
108
145
  # 3 == TCP_CORK
109
146
  # 1/0 == turn on/off
110
147
  def cork_socket(socket)
148
+ skt = socket.to_io
111
149
  begin
112
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
150
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
113
151
  rescue IOError, SystemCallError
114
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
152
+ Puma::Util.purge_interrupt_queue
115
153
  end
116
154
  end
117
155
 
118
156
  def uncork_socket(socket)
157
+ skt = socket.to_io
119
158
  begin
120
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
159
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
121
160
  rescue IOError, SystemCallError
122
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
161
+ Puma::Util.purge_interrupt_queue
123
162
  end
124
163
  end
164
+ else
165
+ def cork_socket(socket)
166
+ end
167
+
168
+ def uncork_socket(socket)
169
+ end
170
+ end
171
+
172
+ if closed_socket_supported?
173
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
125
174
 
126
175
  def closed_socket?(socket)
127
- return false unless socket.kind_of? TCPSocket
128
- return false unless @precheck_closing
176
+ skt = socket.to_io
177
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
129
178
 
130
179
  begin
131
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
180
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
132
181
  rescue IOError, SystemCallError
133
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
182
+ Puma::Util.purge_interrupt_queue
134
183
  @precheck_closing = false
135
184
  false
136
185
  else
@@ -140,264 +189,184 @@ module Puma
140
189
  end
141
190
  end
142
191
  else
143
- def cork_socket(socket)
144
- end
145
-
146
- def uncork_socket(socket)
147
- end
148
-
149
192
  def closed_socket?(socket)
150
193
  false
151
194
  end
152
195
  end
153
196
 
197
+ # @!attribute [r] backlog
154
198
  def backlog
155
- @thread_pool and @thread_pool.backlog
199
+ @thread_pool&.backlog
156
200
  end
157
201
 
202
+ # @!attribute [r] running
158
203
  def running
159
- @thread_pool and @thread_pool.spawned
204
+ @thread_pool&.spawned
160
205
  end
161
206
 
162
- # Lopez Mode == raw tcp apps
163
-
164
- def run_lopez_mode(background=true)
165
- @thread_pool = ThreadPool.new(@min_threads,
166
- @max_threads,
167
- Hash) do |client, tl|
168
-
169
- io = client.to_io
170
- addr = io.peeraddr.last
171
-
172
- if addr.empty?
173
- # Set unix socket addrs to localhost
174
- addr = "127.0.0.1:0"
175
- else
176
- addr = "#{addr}:#{io.peeraddr[1]}"
177
- end
178
-
179
- env = { 'thread' => tl, REMOTE_ADDR => addr }
180
-
181
- begin
182
- @app.call env, client.to_io
183
- rescue Object => e
184
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
185
- STDERR.puts e.backtrace
186
- end
187
207
 
188
- client.close unless env['detach']
189
- end
190
-
191
- @events.fire :state, :running
192
-
193
- if background
194
- @thread = Thread.new { handle_servers_lopez_mode }
195
- return @thread
196
- else
197
- handle_servers_lopez_mode
198
- end
208
+ # This number represents the number of requests that
209
+ # the server is capable of taking right now.
210
+ #
211
+ # For example if the number is 5 then it means
212
+ # there are 5 threads sitting idle ready to take
213
+ # a request. If one request comes in, then the
214
+ # value would be 4 until it finishes processing.
215
+ # @!attribute [r] pool_capacity
216
+ def pool_capacity
217
+ @thread_pool&.pool_capacity
199
218
  end
200
219
 
201
- def handle_servers_lopez_mode
202
- begin
203
- check = @check
204
- sockets = [check] + @binder.ios
205
- pool = @thread_pool
206
-
207
- while @status == :run
208
- begin
209
- ios = IO.select sockets
210
- ios.first.each do |sock|
211
- if sock == check
212
- break if handle_check
213
- else
214
- begin
215
- if io = sock.accept_nonblock
216
- client = Client.new io, nil
217
- pool << client
218
- end
219
- rescue SystemCallError
220
- # nothing
221
- rescue Errno::ECONNABORTED
222
- # client closed the socket even before accept
223
- io.close rescue nil
224
- end
225
- end
226
- end
227
- rescue Object => e
228
- @events.unknown_error self, e, "Listen loop"
229
- end
230
- end
231
-
232
- @events.fire :state, @status
233
-
234
- graceful_shutdown if @status == :stop || @status == :restart
235
-
236
- rescue Exception => e
237
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
238
- STDERR.puts e.backtrace
239
- ensure
240
- @check.close
241
- @notify.close
242
-
243
- if @status != :restart and @own_binder
244
- @binder.close
245
- end
246
- end
247
-
248
- @events.fire :state, :done
249
- end
250
220
  # Runs the server.
251
221
  #
252
222
  # If +background+ is true (the default) then a thread is spun
253
223
  # up in the background to handle requests. Otherwise requests
254
224
  # are handled synchronously.
255
225
  #
256
- def run(background=true)
226
+ def run(background=true, thread_name: 'srv')
257
227
  BasicSocket.do_not_reverse_lookup = true
258
228
 
259
229
  @events.fire :state, :booting
260
230
 
261
231
  @status = :run
262
232
 
263
- if @mode == :tcp
264
- return run_lopez_mode(background)
265
- end
266
-
267
- queue_requests = @queue_requests
268
-
269
- @thread_pool = ThreadPool.new(@min_threads,
270
- @max_threads,
271
- IOBuffer) do |client, buffer|
233
+ @thread_pool = ThreadPool.new(thread_name, @options) { |a, b| process_client a, b }
272
234
 
273
- # Advertise this server into the thread
274
- Thread.current[ThreadLocalKey] = self
275
-
276
- process_now = false
277
-
278
- begin
279
- if queue_requests
280
- process_now = client.eagerly_finish
281
- else
282
- client.finish
283
- process_now = true
284
- end
285
- rescue MiniSSL::SSLError => e
286
- ssl_socket = client.io
287
- addr = ssl_socket.peeraddr.last
288
- cert = ssl_socket.peercert
289
-
290
- client.close
291
-
292
- @events.ssl_error self, addr, cert, e
293
- rescue HttpParserError => e
294
- client.write_400
295
- client.close
296
-
297
- @events.parse_error self, client.env, e
298
- rescue ConnectionError
299
- client.close
300
- else
301
- if process_now
302
- process_client client, buffer
303
- else
304
- client.set_timeout @first_data_timeout
305
- @reactor.add client
306
- end
307
- end
235
+ if @queue_requests
236
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
237
+ @reactor.run
308
238
  end
309
239
 
310
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
311
-
312
- if queue_requests
313
- @reactor = Reactor.new self, @thread_pool
314
- @reactor.run_in_thread
315
- end
316
240
 
317
- if @reaping_time
318
- @thread_pool.auto_reap!(@reaping_time)
319
- end
241
+ @thread_pool.auto_reap! if @options[:reaping_time]
242
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
320
243
 
321
- if @auto_trim_time
322
- @thread_pool.auto_trim!(@auto_trim_time)
323
- end
244
+ @check, @notify = Puma::Util.pipe unless @notify
324
245
 
325
246
  @events.fire :state, :running
326
247
 
327
248
  if background
328
- @thread = Thread.new { handle_servers }
249
+ @thread = Thread.new do
250
+ Puma.set_thread_name thread_name
251
+ handle_servers
252
+ end
329
253
  return @thread
330
254
  else
331
255
  handle_servers
332
256
  end
333
257
  end
334
258
 
259
+ # This method is called from the Reactor thread when a queued Client receives data,
260
+ # times out, or when the Reactor is shutting down.
261
+ #
262
+ # It is responsible for ensuring that a request has been completely received
263
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
264
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
265
+ # such as nginx) then the application would be subject to a slow client attack.
266
+ #
267
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
268
+ #
269
+ # The method checks to see if it has the full header and body with
270
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
271
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
272
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
273
+ # The Client is then removed from the reactor (return `true`).
274
+ #
275
+ # If a client object times out, a 408 response is written, its connection is closed,
276
+ # and the object is removed from the reactor (return `true`).
277
+ #
278
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
279
+ # ThreadPool, depending on their current state (#can_close?).
280
+ #
281
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
282
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
283
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
284
+ def reactor_wakeup(client)
285
+ shutdown = !@queue_requests
286
+ if client.try_to_finish || (shutdown && !client.can_close?)
287
+ @thread_pool << client
288
+ elsif shutdown || client.timeout == 0
289
+ client.timeout!
290
+ else
291
+ client.set_timeout(@first_data_timeout)
292
+ false
293
+ end
294
+ rescue StandardError => e
295
+ client_error(e, client)
296
+ client.close
297
+ true
298
+ end
299
+
335
300
  def handle_servers
336
301
  begin
337
302
  check = @check
338
303
  sockets = [check] + @binder.ios
339
304
  pool = @thread_pool
340
305
  queue_requests = @queue_requests
306
+ drain = @options[:drain_on_shutdown] ? 0 : nil
341
307
 
342
- remote_addr_value = nil
343
- remote_addr_header = nil
344
-
345
- case @options[:remote_address]
308
+ addr_send_name, addr_value = case @options[:remote_address]
346
309
  when :value
347
- remote_addr_value = @options[:remote_address_value]
310
+ [:peerip=, @options[:remote_address_value]]
348
311
  when :header
349
- 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]
350
317
  end
351
318
 
352
- while @status == :run
319
+ while @status == :run || (drain && shutting_down?)
353
320
  begin
354
- ios = IO.select sockets
321
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
322
+ break unless ios
355
323
  ios.first.each do |sock|
356
324
  if sock == check
357
325
  break if handle_check
358
326
  else
359
- begin
360
- if io = sock.accept_nonblock
361
- client = Client.new io, @binder.env(sock)
362
- if remote_addr_value
363
- client.peerip = remote_addr_value
364
- elsif remote_addr_header
365
- client.remote_addr_header = remote_addr_header
366
- end
367
-
368
- pool << client
369
- pool.wait_until_not_full
370
- end
371
- rescue SystemCallError
372
- # nothing
373
- rescue Errno::ECONNABORTED
374
- # client closed the socket even before accept
375
- io.close rescue nil
327
+ pool.wait_until_not_full
328
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
329
+
330
+ io = begin
331
+ sock.accept_nonblock
332
+ rescue IO::WaitReadable
333
+ next
376
334
  end
335
+ drain += 1 if shutting_down?
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
+ }
377
340
  end
378
341
  end
379
- rescue Object => e
380
- @events.unknown_error self, e, "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"
381
347
  end
382
348
  end
383
349
 
350
+ @log_writer.debug "Drained #{drain} additional connections." if drain
384
351
  @events.fire :state, @status
385
352
 
386
- graceful_shutdown if @status == :stop || @status == :restart
387
353
  if queue_requests
388
- @reactor.clear!
354
+ @queue_requests = false
389
355
  @reactor.shutdown
390
356
  end
357
+ graceful_shutdown if @status == :stop || @status == :restart
391
358
  rescue Exception => e
392
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
393
- STDERR.puts e.backtrace
359
+ @log_writer.unknown_error e, nil, "Exception handling servers"
394
360
  ensure
395
- @check.close
396
- @notify.close
397
-
398
- if @status != :restart and @own_binder
399
- @binder.close
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
400
367
  end
368
+ @notify = nil
369
+ @check = nil
401
370
  end
402
371
 
403
372
  @events.fire :state, :done
@@ -419,450 +388,149 @@ module Puma
419
388
  return true
420
389
  end
421
390
 
422
- return false
391
+ false
423
392
  end
424
393
 
425
- # Given a connection on +client+, handle the incoming requests.
394
+ # Given a connection on +client+, handle the incoming requests,
395
+ # or queue the connection in the Reactor if no request is available.
426
396
  #
427
- # This method support HTTP Keep-Alive so it may, depending on if the client
397
+ # This method is called from a ThreadPool worker thread.
398
+ #
399
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
428
400
  # indicates that it supports keep alive, wait for another request before
429
401
  # returning.
430
402
  #
403
+ # Return true if one or more requests were processed.
431
404
  def process_client(client, buffer)
405
+ # Advertise this server into the thread
406
+ Thread.current[ThreadLocalKey] = self
407
+
408
+ clean_thread_locals = @options[:clean_thread_locals]
409
+ close_socket = true
410
+
411
+ requests = 0
412
+
432
413
  begin
414
+ if @queue_requests &&
415
+ !client.eagerly_finish
416
+
417
+ client.set_timeout(@first_data_timeout)
418
+ if @reactor.add client
419
+ close_socket = false
420
+ return false
421
+ end
422
+ end
433
423
 
434
- clean_thread_locals = @options[:clean_thread_locals]
435
- close_socket = true
424
+ with_force_shutdown(client) do
425
+ client.finish(@first_data_timeout)
426
+ end
436
427
 
437
428
  while true
438
- case handle_request(client, buffer)
429
+ @requests_count += 1
430
+ case handle_request(client, buffer, requests + 1)
439
431
  when false
440
- return
432
+ break
441
433
  when :async
442
434
  close_socket = false
443
- return
435
+ break
444
436
  when true
445
- return unless @queue_requests
446
437
  buffer.reset
447
438
 
448
439
  ThreadPool.clean_thread_locals if clean_thread_locals
449
440
 
450
- unless client.reset(@status == :run)
451
- close_socket = false
452
- client.set_timeout @persistent_timeout
453
- @reactor.add client
454
- return
455
- end
456
- end
457
- end
458
-
459
- # The client disconnected while we were reading data
460
- rescue ConnectionError
461
- # Swallow them. The ensure tries to close +client+ down
462
-
463
- # SSL handshake error
464
- rescue MiniSSL::SSLError => e
465
- lowlevel_error(e, client.env)
441
+ requests += 1
466
442
 
467
- ssl_socket = client.io
468
- addr = ssl_socket.peeraddr.last
469
- cert = ssl_socket.peercert
443
+ # As an optimization, try to read the next request from the
444
+ # socket for a short time before returning to the reactor.
445
+ fast_check = @status == :run
470
446
 
471
- close_socket = true
447
+ # Always pass the client back to the reactor after a reasonable
448
+ # number of inline requests if there are other requests pending.
449
+ fast_check = false if requests >= @max_fast_inline &&
450
+ @thread_pool.backlog > 0
472
451
 
473
- @events.ssl_error self, addr, cert, e
474
-
475
- # The client doesn't know HTTP well
476
- rescue HttpParserError => e
477
- lowlevel_error(e, client.env)
478
-
479
- client.write_400
480
-
481
- @events.parse_error self, client.env, e
452
+ next_request_ready = with_force_shutdown(client) do
453
+ client.reset(fast_check)
454
+ end
482
455
 
483
- # Server error
456
+ unless next_request_ready
457
+ break unless @queue_requests
458
+ client.set_timeout @persistent_timeout
459
+ if @reactor.add client
460
+ close_socket = false
461
+ break
462
+ end
463
+ end
464
+ end
465
+ end
466
+ true
484
467
  rescue StandardError => e
485
- lowlevel_error(e, client.env)
486
-
487
- client.write_500
488
-
489
- @events.unknown_error self, e, "Read"
490
-
468
+ client_error(e, client)
469
+ # The ensure tries to close +client+ down
470
+ requests > 0
491
471
  ensure
492
472
  buffer.reset
493
473
 
494
474
  begin
495
475
  client.close if close_socket
496
476
  rescue IOError, SystemCallError
497
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
477
+ Puma::Util.purge_interrupt_queue
498
478
  # Already closed
499
479
  rescue StandardError => e
500
- @events.unknown_error self, e, "Client"
501
- end
502
- end
503
- end
504
-
505
- # Given a Hash +env+ for the request read from +client+, add
506
- # and fixup keys to comply with Rack's env guidelines.
507
- #
508
- def normalize_env(env, client)
509
- if host = env[HTTP_HOST]
510
- if colon = host.index(":")
511
- env[SERVER_NAME] = host[0, colon]
512
- env[SERVER_PORT] = host[colon+1, host.bytesize]
513
- else
514
- env[SERVER_NAME] = host
515
- env[SERVER_PORT] = default_server_port(env)
516
- end
517
- else
518
- env[SERVER_NAME] = LOCALHOST
519
- env[SERVER_PORT] = default_server_port(env)
520
- end
521
-
522
- unless env[REQUEST_PATH]
523
- # it might be a dumbass full host request header
524
- uri = URI.parse(env[REQUEST_URI])
525
- env[REQUEST_PATH] = uri.path
526
-
527
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
528
-
529
- # A nil env value will cause a LintError (and fatal errors elsewhere),
530
- # so only set the env value if there actually is a value.
531
- env[QUERY_STRING] = uri.query if uri.query
532
- end
533
-
534
- env[PATH_INFO] = env[REQUEST_PATH]
535
-
536
- # From http://www.ietf.org/rfc/rfc3875 :
537
- # "Script authors should be aware that the REMOTE_ADDR and
538
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
539
- # may not identify the ultimate source of the request.
540
- # They identify the client for the immediate request to the
541
- # server; that client may be a proxy, gateway, or other
542
- # intermediary acting on behalf of the actual source client."
543
- #
544
-
545
- unless env.key?(REMOTE_ADDR)
546
- begin
547
- addr = client.peerip
548
- rescue Errno::ENOTCONN
549
- # Client disconnects can result in an inability to get the
550
- # peeraddr from the socket; default to localhost.
551
- addr = LOCALHOST_IP
552
- end
553
-
554
- # Set unix socket addrs to localhost
555
- addr = LOCALHOST_IP if addr.empty?
556
-
557
- env[REMOTE_ADDR] = addr
558
- end
559
- end
560
-
561
- def default_server_port(env)
562
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
563
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
564
- end
565
-
566
- # Given the request +env+ from +client+ and a partial request body
567
- # in +body+, finish reading the body if there is one and invoke
568
- # the rack app. Then construct the response and write it back to
569
- # +client+
570
- #
571
- # +cl+ is the previously fetched Content-Length header if there
572
- # was one. This is an optimization to keep from having to look
573
- # it up again.
574
- #
575
- def handle_request(req, lines)
576
- env = req.env
577
- client = req.io
578
-
579
- return false if closed_socket?(client)
580
-
581
- normalize_env env, req
582
-
583
- env[PUMA_SOCKET] = client
584
-
585
- if env[HTTPS_KEY] && client.peercert
586
- env[PUMA_PEERCERT] = client.peercert
587
- end
588
-
589
- env[HIJACK_P] = true
590
- env[HIJACK] = req
591
-
592
- body = req.body
593
-
594
- head = env[REQUEST_METHOD] == HEAD
595
-
596
- env[RACK_INPUT] = body
597
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
598
-
599
- if @early_hints
600
- env[EARLY_HINTS] = lambda { |headers|
601
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
602
-
603
- headers.each_pair do |k, vs|
604
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
605
- vs.to_s.split(NEWLINE).each do |v|
606
- fast_write client, "#{k}: #{v}\r\n"
607
- end
608
- else
609
- fast_write client, "#{k}: #{v}\r\n"
610
- end
611
- end
612
-
613
- fast_write client, "\r\n".freeze
614
- }
615
- end
616
-
617
- # A rack extension. If the app writes #call'ables to this
618
- # array, we will invoke them when the request is done.
619
- #
620
- after_reply = env[RACK_AFTER_REPLY] = []
621
-
622
- begin
623
- begin
624
- status, headers, res_body = @app.call(env)
625
-
626
- return :async if req.hijacked
627
-
628
- status = status.to_i
629
-
630
- if status == -1
631
- unless headers.empty? and res_body == []
632
- raise "async response must have empty headers and body"
633
- end
634
-
635
- return :async
636
- end
637
- rescue ThreadPool::ForceShutdown => e
638
- @events.log "Detected force shutdown of a thread, returning 503"
639
- @events.unknown_error self, e, "Rack app"
640
-
641
- status = 503
642
- headers = {}
643
- res_body = ["Request was internally terminated early\n"]
644
-
645
- rescue Exception => e
646
- @events.unknown_error self, e, "Rack app", env
647
-
648
- status, headers, res_body = lowlevel_error(e, env)
649
- end
650
-
651
- content_length = nil
652
- no_body = head
653
-
654
- if res_body.kind_of? Array and res_body.size == 1
655
- content_length = res_body[0].bytesize
656
- end
657
-
658
- cork_socket client
659
-
660
- line_ending = LINE_END
661
- colon = COLON
662
-
663
- http_11 = if env[HTTP_VERSION] == HTTP_11
664
- allow_chunked = true
665
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
666
- include_keepalive_header = false
667
-
668
- # An optimization. The most common response is 200, so we can
669
- # reply with the proper 200 status without having to compute
670
- # the response header.
671
- #
672
- if status == 200
673
- lines << HTTP_11_200
674
- else
675
- lines.append "HTTP/1.1 ", status.to_s, " ",
676
- fetch_status_code(status), line_ending
677
-
678
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
679
- end
680
- true
681
- else
682
- allow_chunked = false
683
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
684
- include_keepalive_header = keep_alive
685
-
686
- # Same optimization as above for HTTP/1.1
687
- #
688
- if status == 200
689
- lines << HTTP_10_200
690
- else
691
- lines.append "HTTP/1.0 ", status.to_s, " ",
692
- fetch_status_code(status), line_ending
693
-
694
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
695
- end
696
- false
697
- end
698
-
699
- response_hijack = nil
700
-
701
- headers.each do |k, vs|
702
- case k.downcase
703
- when CONTENT_LENGTH2
704
- content_length = vs
705
- next
706
- when TRANSFER_ENCODING
707
- allow_chunked = false
708
- content_length = nil
709
- when HIJACK
710
- response_hijack = vs
711
- next
712
- end
713
-
714
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
715
- vs.to_s.split(NEWLINE).each do |v|
716
- lines.append k, colon, v, line_ending
717
- end
718
- else
719
- lines.append k, colon, line_ending
720
- end
480
+ @log_writer.unknown_error e, nil, "Client"
721
481
  end
722
-
723
- if include_keepalive_header
724
- lines << CONNECTION_KEEP_ALIVE
725
- elsif http_11 && !keep_alive
726
- lines << CONNECTION_CLOSE
727
- end
728
-
729
- if no_body
730
- if content_length and status != 204
731
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
732
- end
733
-
734
- lines << line_ending
735
- fast_write client, lines.to_s
736
- return keep_alive
737
- end
738
-
739
- if content_length
740
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
741
- chunked = false
742
- elsif !response_hijack and allow_chunked
743
- lines << TRANSFER_ENCODING_CHUNKED
744
- chunked = true
745
- end
746
-
747
- lines << line_ending
748
-
749
- fast_write client, lines.to_s
750
-
751
- if response_hijack
752
- response_hijack.call client
753
- return :async
754
- end
755
-
756
- begin
757
- res_body.each do |part|
758
- if chunked
759
- next if part.bytesize.zero?
760
- fast_write client, part.bytesize.to_s(16)
761
- fast_write client, line_ending
762
- fast_write client, part
763
- fast_write client, line_ending
764
- else
765
- fast_write client, part
766
- end
767
-
768
- client.flush
769
- end
770
-
771
- if chunked
772
- fast_write client, CLOSE_CHUNKED
773
- client.flush
774
- end
775
- rescue SystemCallError, IOError
776
- raise ConnectionError, "Connection error detected during write"
777
- end
778
-
779
- ensure
780
- uncork_socket client
781
-
782
- body.close
783
- req.tempfile.unlink if req.tempfile
784
- res_body.close if res_body.respond_to? :close
785
-
786
- after_reply.each { |o| o.call }
787
482
  end
788
-
789
- return keep_alive
790
483
  end
791
484
 
792
- def fetch_status_code(status)
793
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
485
+ # Triggers a client timeout if the thread-pool shuts down
486
+ # during execution of the provided block.
487
+ def with_force_shutdown(client, &block)
488
+ @thread_pool.with_force_shutdown(&block)
489
+ rescue ThreadPool::ForceShutdown
490
+ client.timeout!
794
491
  end
795
- private :fetch_status_code
796
-
797
- # Given the request +env+ from +client+ and the partial body +body+
798
- # plus a potential Content-Length value +cl+, finish reading
799
- # the body and return it.
800
- #
801
- # If the body is larger than MAX_BODY, a Tempfile object is used
802
- # for the body, otherwise a StringIO is used.
803
- #
804
- def read_body(env, client, body, cl)
805
- content_length = cl.to_i
806
492
 
807
- remain = content_length - body.bytesize
808
-
809
- return StringIO.new(body) if remain <= 0
810
-
811
- # Use a Tempfile if there is a lot of data left
812
- if remain > MAX_BODY
813
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
814
- stream.binmode
493
+ # :nocov:
494
+
495
+ # Handle various error types thrown by Client I/O operations.
496
+ def client_error(e, client)
497
+ # Swallow, do not log
498
+ return if [ConnectionError, EOFError].include?(e.class)
499
+
500
+ lowlevel_error(e, client.env)
501
+ case e
502
+ when MiniSSL::SSLError
503
+ @log_writer.ssl_error e, client.io
504
+ when HttpParserError
505
+ client.write_error(400)
506
+ @log_writer.parse_error e, client
507
+ when HttpParserError501
508
+ client.write_error(501)
509
+ @log_writer.parse_error e, client
815
510
  else
816
- # The body[0,0] trick is to get an empty string in the same
817
- # encoding as body.
818
- stream = StringIO.new body[0,0]
511
+ client.write_error(500)
512
+ @log_writer.unknown_error e, nil, "Read"
819
513
  end
820
-
821
- stream.write body
822
-
823
- # Read an odd sized chunk so we can read even sized ones
824
- # after this
825
- chunk = client.readpartial(remain % CHUNK_SIZE)
826
-
827
- # No chunk means a closed socket
828
- unless chunk
829
- stream.close
830
- return nil
831
- end
832
-
833
- remain -= stream.write(chunk)
834
-
835
- # Raed the rest of the chunks
836
- while remain > 0
837
- chunk = client.readpartial(CHUNK_SIZE)
838
- unless chunk
839
- stream.close
840
- return nil
841
- end
842
-
843
- remain -= stream.write(chunk)
844
- end
845
-
846
- stream.rewind
847
-
848
- return stream
849
514
  end
850
515
 
851
516
  # A fallback rack response if +@app+ raises as exception.
852
517
  #
853
- def lowlevel_error(e, env)
518
+ def lowlevel_error(e, env, status=500)
854
519
  if handler = @options[:lowlevel_error_handler]
855
520
  if handler.arity == 1
856
521
  return handler.call(e)
857
- else
522
+ elsif handler.arity == 2
858
523
  return handler.call(e, env)
524
+ else
525
+ return handler.call(e, env, status)
859
526
  end
860
527
  end
861
528
 
862
529
  if @leak_stack_on_error
863
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
530
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
531
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
864
532
  else
865
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
533
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
866
534
  end
867
535
  end
868
536
 
@@ -884,31 +552,13 @@ module Puma
884
552
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
885
553
  end
886
554
 
887
- if @options[:drain_on_shutdown]
888
- count = 0
889
-
890
- while true
891
- ios = IO.select @binder.ios, nil, nil, 0
892
- break unless ios
893
-
894
- ios.first.each do |sock|
895
- begin
896
- if io = sock.accept_nonblock
897
- count += 1
898
- client = Client.new io, @binder.env(sock)
899
- @thread_pool << client
900
- end
901
- rescue SystemCallError
902
- end
903
- end
904
- end
905
-
906
- @events.debug "Drained #{count} additional connections."
555
+ if @status != :restart
556
+ @binder.close
907
557
  end
908
558
 
909
559
  if @thread_pool
910
560
  if timeout = @options[:force_shutdown_after]
911
- @thread_pool.shutdown timeout.to_i
561
+ @thread_pool.shutdown timeout.to_f
912
562
  else
913
563
  @thread_pool.shutdown
914
564
  end
@@ -916,18 +566,16 @@ module Puma
916
566
  end
917
567
 
918
568
  def notify_safely(message)
919
- begin
920
- @notify << message
921
- rescue IOError
922
- # The server, in another thread, is shutting down
923
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
924
- rescue RuntimeError => e
925
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
926
- if e.message.include?('IOError')
927
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
928
- else
929
- raise e
930
- end
569
+ @notify << message
570
+ rescue IOError, NoMethodError, Errno::EPIPE
571
+ # The server, in another thread, is shutting down
572
+ Puma::Util.purge_interrupt_queue
573
+ rescue RuntimeError => e
574
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
575
+ if e.message.include?('IOError')
576
+ Puma::Util.purge_interrupt_queue
577
+ else
578
+ raise e
931
579
  end
932
580
  end
933
581
  private :notify_safely
@@ -945,39 +593,24 @@ module Puma
945
593
  @thread.join if @thread && sync
946
594
  end
947
595
 
948
- def begin_restart
596
+ def begin_restart(sync=false)
949
597
  notify_safely(RESTART_COMMAND)
598
+ @thread.join if @thread && sync
950
599
  end
951
600
 
952
- def fast_write(io, str)
953
- n = 0
954
- while true
955
- begin
956
- n = io.syswrite str
957
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
958
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
959
- raise ConnectionError, "Socket timeout writing data"
960
- end
961
-
962
- retry
963
- rescue Errno::EPIPE, SystemCallError, IOError
964
- raise ConnectionError, "Socket timeout writing data"
965
- end
966
-
967
- return if n == str.bytesize
968
- str = str.byteslice(n..-1)
969
- end
601
+ def shutting_down?
602
+ @status == :stop || @status == :restart
970
603
  end
971
- private :fast_write
972
604
 
973
- ThreadLocalKey = :puma_server
605
+ # List of methods invoked by #stats.
606
+ # @version 5.0.0
607
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
974
608
 
975
- def self.current
976
- Thread.current[ThreadLocalKey]
977
- end
978
-
979
- def shutting_down?
980
- @status == :stop || @status == :restart
609
+ # Returns a hash of stats about the running server for reporting purposes.
610
+ # @version 5.0.0
611
+ # @!attribute [r] stats
612
+ def stats
613
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
981
614
  end
982
615
  end
983
616
  end