puma 2.0.0.b5 → 5.0.0.beta1

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1598 -0
  3. data/LICENSE +23 -20
  4. data/README.md +222 -62
  5. data/bin/puma-wild +31 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/architecture.md +37 -0
  8. data/docs/deployment.md +113 -0
  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 +13 -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/jungle/upstart/README.md +61 -0
  18. data/docs/jungle/upstart/puma-manager.conf +31 -0
  19. data/docs/jungle/upstart/puma.conf +69 -0
  20. data/docs/nginx.md +5 -10
  21. data/docs/plugins.md +38 -0
  22. data/docs/restart.md +41 -0
  23. data/docs/signals.md +97 -0
  24. data/docs/systemd.md +228 -0
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/extconf.rb +23 -2
  27. data/ext/puma_http11/http11_parser.c +301 -482
  28. data/ext/puma_http11/http11_parser.h +13 -11
  29. data/ext/puma_http11/http11_parser.java.rl +26 -42
  30. data/ext/puma_http11/http11_parser.rl +22 -21
  31. data/ext/puma_http11/http11_parser_common.rl +5 -5
  32. data/ext/puma_http11/mini_ssl.c +377 -18
  33. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -107
  34. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +137 -170
  35. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +265 -191
  36. data/ext/puma_http11/puma_http11.c +57 -81
  37. data/lib/puma.rb +25 -4
  38. data/lib/puma/accept_nonblock.rb +7 -1
  39. data/lib/puma/app/status.rb +61 -24
  40. data/lib/puma/binder.rb +212 -78
  41. data/lib/puma/cli.rb +149 -644
  42. data/lib/puma/client.rb +316 -65
  43. data/lib/puma/cluster.rb +659 -0
  44. data/lib/puma/commonlogger.rb +108 -0
  45. data/lib/puma/configuration.rb +279 -180
  46. data/lib/puma/const.rb +126 -39
  47. data/lib/puma/control_cli.rb +183 -96
  48. data/lib/puma/detect.rb +20 -1
  49. data/lib/puma/dsl.rb +776 -0
  50. data/lib/puma/events.rb +91 -23
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +9 -5
  53. data/lib/puma/launcher.rb +487 -0
  54. data/lib/puma/minissl.rb +239 -93
  55. data/lib/puma/minissl/context_builder.rb +76 -0
  56. data/lib/puma/null_io.rb +22 -12
  57. data/lib/puma/plugin.rb +111 -0
  58. data/lib/puma/plugin/tmp_restart.rb +36 -0
  59. data/lib/puma/rack/builder.rb +297 -0
  60. data/lib/puma/rack/urlmap.rb +93 -0
  61. data/lib/puma/rack_default.rb +9 -0
  62. data/lib/puma/reactor.rb +290 -43
  63. data/lib/puma/runner.rb +163 -0
  64. data/lib/puma/server.rb +493 -126
  65. data/lib/puma/single.rb +66 -0
  66. data/lib/puma/state_file.rb +34 -0
  67. data/lib/puma/thread_pool.rb +228 -47
  68. data/lib/puma/util.rb +115 -0
  69. data/lib/rack/handler/puma.rb +78 -31
  70. data/tools/Dockerfile +16 -0
  71. data/tools/trickletest.rb +44 -0
  72. metadata +60 -155
  73. data/COPYING +0 -55
  74. data/Gemfile +0 -8
  75. data/History.txt +0 -196
  76. data/Manifest.txt +0 -56
  77. data/Rakefile +0 -121
  78. data/TODO +0 -5
  79. data/docs/config.md +0 -0
  80. data/ext/puma_http11/io_buffer.c +0 -154
  81. data/lib/puma/capistrano.rb +0 -26
  82. data/lib/puma/compat.rb +0 -11
  83. data/lib/puma/daemon_ext.rb +0 -20
  84. data/lib/puma/delegation.rb +0 -11
  85. data/lib/puma/java_io_buffer.rb +0 -45
  86. data/lib/puma/rack_patch.rb +0 -25
  87. data/puma.gemspec +0 -45
  88. data/test/test_app_status.rb +0 -88
  89. data/test/test_cli.rb +0 -171
  90. data/test/test_config.rb +0 -16
  91. data/test/test_http10.rb +0 -27
  92. data/test/test_http11.rb +0 -126
  93. data/test/test_integration.rb +0 -150
  94. data/test/test_iobuffer.rb +0 -38
  95. data/test/test_minissl.rb +0 -22
  96. data/test/test_null_io.rb +0 -31
  97. data/test/test_persistent.rb +0 -238
  98. data/test/test_puma_server.rb +0 -128
  99. data/test/test_rack_handler.rb +0 -10
  100. data/test/test_rack_server.rb +0 -141
  101. data/test/test_thread_pool.rb +0 -146
  102. data/test/test_unix_socket.rb +0 -39
  103. data/test/test_ws.rb +0 -89
  104. data/tools/jungle/README.md +0 -54
  105. data/tools/jungle/puma +0 -332
  106. data/tools/jungle/run-puma +0 -3
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/server'
4
+ require 'puma/const'
5
+ require 'puma/minissl/context_builder'
6
+
7
+ module Puma
8
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
9
+ # serve requests. This class spawns a new instance of `Puma::Server` via
10
+ # a call to `start_server`.
11
+ class Runner
12
+ def initialize(cli, events)
13
+ @launcher = cli
14
+ @events = events
15
+ @options = cli.options
16
+ @app = nil
17
+ @control = nil
18
+ @started_at = Time.now
19
+ end
20
+
21
+ def development?
22
+ @options[:environment] == "development"
23
+ end
24
+
25
+ def test?
26
+ @options[:environment] == "test"
27
+ end
28
+
29
+ def log(str)
30
+ @events.log str
31
+ end
32
+
33
+ def before_restart
34
+ @control.stop(true) if @control
35
+ end
36
+
37
+ def error(str)
38
+ @events.error str
39
+ end
40
+
41
+ def debug(str)
42
+ @events.log "- #{str}" if @options[:debug]
43
+ end
44
+
45
+ def start_control
46
+ str = @options[:control_url]
47
+ return unless str
48
+
49
+ require 'puma/app/status'
50
+
51
+ if token = @options[:control_auth_token]
52
+ token = nil if token.empty? || token == 'none'
53
+ end
54
+
55
+ app = Puma::App::Status.new @launcher, token
56
+
57
+ control = Puma::Server.new app, @launcher.events
58
+ control.min_threads = 0
59
+ control.max_threads = 1
60
+
61
+ control.binder.parse [str], self, 'Starting control server'
62
+
63
+ control.run
64
+ @control = control
65
+ end
66
+
67
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
71
+ def ruby_engine
72
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
73
+ "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
74
+ else
75
+ if defined?(RUBY_ENGINE_VERSION)
76
+ "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
77
+ else
78
+ "#{RUBY_ENGINE} #{RUBY_VERSION}"
79
+ end
80
+ end
81
+ end
82
+
83
+ def output_header(mode)
84
+ min_t = @options[:min_threads]
85
+ max_t = @options[:max_threads]
86
+
87
+ log "Puma starting in #{mode} mode..."
88
+ log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
89
+ log "* Min threads: #{min_t}, max threads: #{max_t}"
90
+ log "* Environment: #{ENV['RACK_ENV']}"
91
+ end
92
+
93
+ def redirected_io?
94
+ @options[:redirect_stdout] || @options[:redirect_stderr]
95
+ end
96
+
97
+ def redirect_io
98
+ stdout = @options[:redirect_stdout]
99
+ stderr = @options[:redirect_stderr]
100
+ append = @options[:redirect_append]
101
+
102
+ if stdout
103
+ unless Dir.exist?(File.dirname(stdout))
104
+ raise "Cannot redirect STDOUT to #{stdout}"
105
+ end
106
+
107
+ STDOUT.reopen stdout, (append ? "a" : "w")
108
+ STDOUT.sync = true
109
+ STDOUT.puts "=== puma startup: #{Time.now} ==="
110
+ end
111
+
112
+ if stderr
113
+ unless Dir.exist?(File.dirname(stderr))
114
+ raise "Cannot redirect STDERR to #{stderr}"
115
+ end
116
+
117
+ STDERR.reopen stderr, (append ? "a" : "w")
118
+ STDERR.sync = true
119
+ STDERR.puts "=== puma startup: #{Time.now} ==="
120
+ end
121
+ end
122
+
123
+ def load_and_bind
124
+ unless @launcher.config.app_configured?
125
+ error "No application configured, nothing to run"
126
+ exit 1
127
+ end
128
+
129
+ begin
130
+ @app = @launcher.config.app
131
+ rescue Exception => e
132
+ log "! Unable to load application: #{e.class}: #{e.message}"
133
+ raise e
134
+ end
135
+
136
+ @launcher.binder.parse @options[:binds], self
137
+ end
138
+
139
+ def app
140
+ @app ||= @launcher.config.app
141
+ end
142
+
143
+ def start_server
144
+ min_t = @options[:min_threads]
145
+ max_t = @options[:max_threads]
146
+
147
+ server = Puma::Server.new app, @launcher.events, @options
148
+ server.min_threads = min_t
149
+ server.max_threads = max_t
150
+ server.inherit_binder @launcher.binder
151
+
152
+ if @options[:early_hints]
153
+ server.early_hints = true
154
+ end
155
+
156
+ unless development? || test?
157
+ server.leak_stack_on_error = false
158
+ end
159
+
160
+ server
161
+ end
162
+ end
163
+ end
@@ -1,42 +1,51 @@
1
- require 'rack'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'stringio'
3
4
 
4
5
  require 'puma/thread_pool'
5
6
  require 'puma/const'
6
7
  require 'puma/events'
7
8
  require 'puma/null_io'
8
- require 'puma/compat'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/delegation'
13
12
  require 'puma/accept_nonblock'
14
13
  require 'puma/util'
14
+ require 'puma/io_buffer'
15
15
 
16
16
  require 'puma/puma_http11'
17
17
 
18
- unless Puma.const_defined? "IOBuffer"
19
- require 'puma/io_buffer'
20
- end
21
-
22
18
  require 'socket'
19
+ require 'forwardable'
23
20
 
24
21
  module Puma
25
22
 
26
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.
27
33
  class Server
28
34
 
29
35
  include Puma::Const
30
- extend Puma::Delegation
36
+ extend Forwardable
31
37
 
32
38
  attr_reader :thread
33
39
  attr_reader :events
40
+ attr_reader :requests_count
34
41
  attr_accessor :app
35
42
 
36
43
  attr_accessor :min_threads
37
44
  attr_accessor :max_threads
38
45
  attr_accessor :persistent_timeout
39
46
  attr_accessor :auto_trim_time
47
+ attr_accessor :reaping_time
48
+ attr_accessor :first_data_timeout
40
49
 
41
50
  # Create a server for the rack app +app+.
42
51
  #
@@ -44,50 +53,91 @@ module Puma
44
53
  # to be handled. See Puma::Events for the list of current methods to implement.
45
54
  #
46
55
  # Server#run returns a thread that you can join on to wait for the server
47
- # to do it's work.
56
+ # to do its work.
48
57
  #
49
- def initialize(app, events=Events::DEFAULT)
58
+ def initialize(app, events=Events.stdio, options={})
50
59
  @app = app
51
60
  @events = events
52
61
 
53
- @check, @notify = Puma::Util.pipe
54
-
62
+ @check, @notify = nil
55
63
  @status = :stop
56
64
 
57
65
  @min_threads = 0
58
66
  @max_threads = 16
59
- @auto_trim_time = 1
67
+ @auto_trim_time = 30
68
+ @reaping_time = 1
60
69
 
61
70
  @thread = nil
62
71
  @thread_pool = nil
72
+ @early_hints = nil
63
73
 
64
- @persistent_timeout = PERSISTENT_TIMEOUT
74
+ @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
75
+ @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
65
76
 
66
77
  @binder = Binder.new(events)
67
- @first_data_timeout = FIRST_DATA_TIMEOUT
78
+
79
+ @leak_stack_on_error = true
80
+
81
+ @options = options
82
+ @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
68
83
 
69
84
  ENV['RACK_ENV'] ||= "development"
85
+
86
+ @mode = :http
87
+
88
+ @precheck_closing = true
89
+
90
+ @requests_count = 0
70
91
  end
71
92
 
72
- attr_accessor :binder
93
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
73
94
 
74
- forward :add_tcp_listener, :@binder
75
- forward :add_ssl_listener, :@binder
76
- forward :add_unix_listener, :@binder
95
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
96
+
97
+ def inherit_binder(bind)
98
+ @binder = bind
99
+ end
77
100
 
78
101
  # On Linux, use TCP_CORK to better control how the TCP stack
79
102
  # packetizes our stream. This improves both latency and throughput.
80
103
  #
81
104
  if RUBY_PLATFORM =~ /linux/
105
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
106
+
82
107
  # 6 == Socket::IPPROTO_TCP
83
108
  # 3 == TCP_CORK
84
109
  # 1/0 == turn on/off
85
110
  def cork_socket(socket)
86
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
111
+ begin
112
+ socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
113
+ rescue IOError, SystemCallError
114
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
115
+ end
87
116
  end
88
117
 
89
118
  def uncork_socket(socket)
90
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
119
+ begin
120
+ socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
121
+ rescue IOError, SystemCallError
122
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
123
+ end
124
+ end
125
+
126
+ def closed_socket?(socket)
127
+ return false unless socket.kind_of? TCPSocket
128
+ return false unless @precheck_closing
129
+
130
+ begin
131
+ tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
132
+ rescue IOError, SystemCallError
133
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
134
+ @precheck_closing = false
135
+ false
136
+ else
137
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
138
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
139
+ (state >= 6 && state <= 9) || state == 11
140
+ end
91
141
  end
92
142
  else
93
143
  def cork_socket(socket)
@@ -95,6 +145,10 @@ module Puma
95
145
 
96
146
  def uncork_socket(socket)
97
147
  end
148
+
149
+ def closed_socket?(socket)
150
+ false
151
+ end
98
152
  end
99
153
 
100
154
  def backlog
@@ -105,6 +159,18 @@ module Puma
105
159
  @thread_pool and @thread_pool.spawned
106
160
  end
107
161
 
162
+
163
+ # This number represents the number of requests that
164
+ # the server is capable of taking right now.
165
+ #
166
+ # For example if the number is 5 then it means
167
+ # there are 5 threads sitting idle ready to take
168
+ # a request. If one request comes in, then the
169
+ # value would be 4 until it finishes processing.
170
+ def pool_capacity
171
+ @thread_pool and @thread_pool.pool_capacity
172
+ end
173
+
108
174
  # Runs the server.
109
175
  #
110
176
  # If +background+ is true (the default) then a thread is spun
@@ -114,21 +180,40 @@ module Puma
114
180
  def run(background=true)
115
181
  BasicSocket.do_not_reverse_lookup = true
116
182
 
183
+ @events.fire :state, :booting
184
+
117
185
  @status = :run
118
186
 
119
187
  @thread_pool = ThreadPool.new(@min_threads,
120
188
  @max_threads,
121
- IOBuffer) do |client, buffer|
189
+ ::Puma::IOBuffer) do |client, buffer|
190
+
191
+ # Advertise this server into the thread
192
+ Thread.current[ThreadLocalKey] = self
193
+
122
194
  process_now = false
123
195
 
124
196
  begin
125
- process_now = client.eagerly_finish
197
+ if @queue_requests
198
+ process_now = client.eagerly_finish
199
+ else
200
+ client.finish(@first_data_timeout)
201
+ process_now = true
202
+ end
203
+ rescue MiniSSL::SSLError => e
204
+ ssl_socket = client.io
205
+ addr = ssl_socket.peeraddr.last
206
+ cert = ssl_socket.peercert
207
+
208
+ client.close
209
+
210
+ @events.ssl_error self, addr, cert, e
126
211
  rescue HttpParserError => e
127
- client.write_400
212
+ client.write_error(400)
128
213
  client.close
129
214
 
130
215
  @events.parse_error self, client.env, e
131
- rescue IOError
216
+ rescue ConnectionError, EOFError
132
217
  client.close
133
218
  else
134
219
  if process_now
@@ -138,18 +223,33 @@ module Puma
138
223
  @reactor.add client
139
224
  end
140
225
  end
226
+
227
+ process_now
141
228
  end
142
229
 
143
- @reactor = Reactor.new self, @thread_pool
230
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
231
+ @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
144
232
 
145
- @reactor.run_in_thread
233
+ if @queue_requests
234
+ @reactor = Reactor.new self, @thread_pool
235
+ @reactor.run_in_thread
236
+ end
237
+
238
+ if @reaping_time
239
+ @thread_pool.auto_reap!(@reaping_time)
240
+ end
146
241
 
147
242
  if @auto_trim_time
148
243
  @thread_pool.auto_trim!(@auto_trim_time)
149
244
  end
150
245
 
246
+ @events.fire :state, :running
247
+
151
248
  if background
152
- @thread = Thread.new { handle_servers }
249
+ @thread = Thread.new do
250
+ Puma.set_thread_name "server"
251
+ handle_servers
252
+ end
153
253
  return @thread
154
254
  else
155
255
  handle_servers
@@ -157,10 +257,22 @@ module Puma
157
257
  end
158
258
 
159
259
  def handle_servers
260
+ @check, @notify = Puma::Util.pipe unless @notify
160
261
  begin
161
262
  check = @check
162
263
  sockets = [check] + @binder.ios
163
264
  pool = @thread_pool
265
+ queue_requests = @queue_requests
266
+
267
+ remote_addr_value = nil
268
+ remote_addr_header = nil
269
+
270
+ case @options[:remote_address]
271
+ when :value
272
+ remote_addr_value = @options[:remote_address_value]
273
+ when :header
274
+ remote_addr_header = @options[:remote_address_header]
275
+ end
164
276
 
165
277
  while @status == :run
166
278
  begin
@@ -170,39 +282,61 @@ module Puma
170
282
  break if handle_check
171
283
  else
172
284
  begin
285
+ pool.wait_until_not_full
286
+ pool.wait_for_less_busy_worker(
287
+ @options[:wait_for_less_busy_worker].to_f)
288
+
173
289
  if io = sock.accept_nonblock
174
- c = Client.new io, @binder.env(sock)
175
- pool << c
290
+ client = Client.new io, @binder.env(sock)
291
+ if remote_addr_value
292
+ client.peerip = remote_addr_value
293
+ elsif remote_addr_header
294
+ client.remote_addr_header = remote_addr_header
295
+ end
296
+
297
+ pool << client
176
298
  end
177
299
  rescue SystemCallError
300
+ # nothing
301
+ rescue Errno::ECONNABORTED
302
+ # client closed the socket even before accept
303
+ begin
304
+ io.close
305
+ rescue
306
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
307
+ end
178
308
  end
179
309
  end
180
310
  end
181
- rescue Errno::ECONNABORTED
182
- # client closed the socket even before accept
183
- client.close rescue nil
184
311
  rescue Object => e
185
312
  @events.unknown_error self, e, "Listen loop"
186
313
  end
187
314
  end
188
315
 
189
- graceful_shutdown if @status == :stop || @status == :restart
190
- @reactor.clear! if @status == :restart
316
+ @events.fire :state, @status
191
317
 
192
- @reactor.shutdown
318
+ if queue_requests
319
+ @queue_requests = false
320
+ @reactor.clear!
321
+ @reactor.shutdown
322
+ end
323
+ graceful_shutdown if @status == :stop || @status == :restart
324
+ rescue Exception => e
325
+ STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
326
+ STDERR.puts e.backtrace
193
327
  ensure
194
- @check.close
328
+ @check.close unless @check.closed? # Ruby 2.2 issue
195
329
  @notify.close
196
-
197
- unless @status == :restart
198
- @binder.close
199
- end
330
+ @notify = nil
331
+ @check = nil
200
332
  end
333
+
334
+ @events.fire :state, :done
201
335
  end
202
336
 
203
337
  # :nodoc:
204
338
  def handle_check
205
- cmd = @check.read(1)
339
+ cmd = @check.read(1)
206
340
 
207
341
  case cmd
208
342
  when STOP_COMMAND
@@ -227,8 +361,12 @@ module Puma
227
361
  #
228
362
  def process_client(client, buffer)
229
363
  begin
364
+
365
+ clean_thread_locals = @options[:clean_thread_locals]
230
366
  close_socket = true
231
367
 
368
+ requests = 0
369
+
232
370
  while true
233
371
  case handle_request(client, buffer)
234
372
  when false
@@ -239,7 +377,22 @@ module Puma
239
377
  when true
240
378
  buffer.reset
241
379
 
242
- unless client.reset(@status == :run)
380
+ ThreadPool.clean_thread_locals if clean_thread_locals
381
+
382
+ requests += 1
383
+
384
+ check_for_more_data = @status == :run
385
+
386
+ if requests >= MAX_FAST_INLINE
387
+ # This will mean that reset will only try to use the data it already
388
+ # has buffered and won't try to read more data. What this means is that
389
+ # every client, independent of their request speed, gets treated like a slow
390
+ # one once every MAX_FAST_INLINE requests.
391
+ check_for_more_data = false
392
+ end
393
+
394
+ unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
243
396
  close_socket = false
244
397
  client.set_timeout @persistent_timeout
245
398
  @reactor.add client
@@ -249,18 +402,34 @@ module Puma
249
402
  end
250
403
 
251
404
  # The client disconnected while we were reading data
252
- rescue IOError, SystemCallError => e
405
+ rescue ConnectionError
253
406
  # Swallow them. The ensure tries to close +client+ down
254
407
 
408
+ # SSL handshake error
409
+ rescue MiniSSL::SSLError => e
410
+ lowlevel_error(e, client.env)
411
+
412
+ ssl_socket = client.io
413
+ addr = ssl_socket.peeraddr.last
414
+ cert = ssl_socket.peercert
415
+
416
+ close_socket = true
417
+
418
+ @events.ssl_error self, addr, cert, e
419
+
255
420
  # The client doesn't know HTTP well
256
421
  rescue HttpParserError => e
257
- client.write_400
422
+ lowlevel_error(e, client.env)
423
+
424
+ client.write_error(400)
258
425
 
259
426
  @events.parse_error self, client.env, e
260
427
 
261
428
  # Server error
262
429
  rescue StandardError => e
263
- client.write_500
430
+ lowlevel_error(e, client.env)
431
+
432
+ client.write_error(500)
264
433
 
265
434
  @events.unknown_error self, e, "Read"
266
435
 
@@ -270,6 +439,7 @@ module Puma
270
439
  begin
271
440
  client.close if close_socket
272
441
  rescue IOError, SystemCallError
442
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
273
443
  # Already closed
274
444
  rescue StandardError => e
275
445
  @events.unknown_error self, e, "Client"
@@ -287,11 +457,11 @@ module Puma
287
457
  env[SERVER_PORT] = host[colon+1, host.bytesize]
288
458
  else
289
459
  env[SERVER_NAME] = host
290
- env[SERVER_PORT] = PORT_80
460
+ env[SERVER_PORT] = default_server_port(env)
291
461
  end
292
462
  else
293
463
  env[SERVER_NAME] = LOCALHOST
294
- env[SERVER_PORT] = PORT_80
464
+ env[SERVER_PORT] = default_server_port(env)
295
465
  end
296
466
 
297
467
  unless env[REQUEST_PATH]
@@ -300,6 +470,10 @@ module Puma
300
470
  env[REQUEST_PATH] = uri.path
301
471
 
302
472
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
473
+
474
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
475
+ # so only set the env value if there actually is a value.
476
+ env[QUERY_STRING] = uri.query if uri.query
303
477
  end
304
478
 
305
479
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -312,33 +486,92 @@ module Puma
312
486
  # server; that client may be a proxy, gateway, or other
313
487
  # intermediary acting on behalf of the actual source client."
314
488
  #
315
- env[REMOTE_ADDR] = client.peeraddr.last
489
+
490
+ unless env.key?(REMOTE_ADDR)
491
+ begin
492
+ addr = client.peerip
493
+ rescue Errno::ENOTCONN
494
+ # Client disconnects can result in an inability to get the
495
+ # peeraddr from the socket; default to localhost.
496
+ addr = LOCALHOST_IP
497
+ end
498
+
499
+ # Set unix socket addrs to localhost
500
+ addr = LOCALHOST_IP if addr.empty?
501
+
502
+ env[REMOTE_ADDR] = addr
503
+ end
316
504
  end
317
505
 
318
- # Given the request +env+ from +client+ and a partial request body
319
- # in +body+, finish reading the body if there is one and invoke
320
- # the rack app. Then construct the response and write it back to
321
- # +client+
506
+ def default_server_port(env)
507
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
508
+ PORT_443
509
+ else
510
+ PORT_80
511
+ end
512
+ end
513
+
514
+ # Takes the request +req+, invokes the Rack application to construct
515
+ # the response and writes it back to +req.io+.
516
+ #
517
+ # The second parameter +lines+ is a IO-like object unique to this thread.
518
+ # This is normally an instance of Puma::IOBuffer.
519
+ #
520
+ # It'll return +false+ when the connection is closed, this doesn't mean
521
+ # that the response wasn't successful.
322
522
  #
323
- # +cl+ is the previously fetched Content-Length header if there
324
- # was one. This is an optimization to keep from having to look
325
- # it up again.
523
+ # It'll return +:async+ if the connection remains open but will be handled
524
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
326
525
  #
526
+ # Finally, it'll return +true+ on keep-alive connections.
327
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
328
530
  env = req.env
329
531
  client = req.io
330
532
 
331
- normalize_env env, client
533
+ return false if closed_socket?(client)
534
+
535
+ normalize_env env, req
332
536
 
333
537
  env[PUMA_SOCKET] = client
334
538
 
539
+ if env[HTTPS_KEY] && client.peercert
540
+ env[PUMA_PEERCERT] = client.peercert
541
+ end
542
+
335
543
  env[HIJACK_P] = true
336
544
  env[HIJACK] = req
337
545
 
338
546
  body = req.body
339
547
 
548
+ head = env[REQUEST_METHOD] == HEAD
549
+
340
550
  env[RACK_INPUT] = body
341
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
551
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
552
+
553
+ if @early_hints
554
+ env[EARLY_HINTS] = lambda { |headers|
555
+ begin
556
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
557
+
558
+ headers.each_pair do |k, vs|
559
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
560
+ vs.to_s.split(NEWLINE).each do |v|
561
+ next if possible_header_injection?(v)
562
+ fast_write client, "#{k}: #{v}\r\n"
563
+ end
564
+ else
565
+ fast_write client, "#{k}: #{vs}\r\n"
566
+ end
567
+ end
568
+
569
+ fast_write client, "\r\n".freeze
570
+ rescue ConnectionError
571
+ # noop, if we lost the socket we just won't send the early hints
572
+ end
573
+ }
574
+ end
342
575
 
343
576
  # A rack extension. If the app writes #call'ables to this
344
577
  # array, we will invoke them when the request is done.
@@ -360,14 +593,19 @@ module Puma
360
593
 
361
594
  return :async
362
595
  end
363
- rescue => e
364
- @events.unknown_error self, e, "Rack app"
596
+ rescue ThreadPool::ForceShutdown => e
597
+ @events.unknown_error self, e, "Rack app", env
598
+ @events.log "Detected force shutdown of a thread"
599
+
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
601
+ rescue Exception => e
602
+ @events.unknown_error self, e, "Rack app", env
365
603
 
366
- status, headers, res_body = lowlevel_error(e)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
367
605
  end
368
606
 
369
607
  content_length = nil
370
- no_body = false
608
+ no_body = head
371
609
 
372
610
  if res_body.kind_of? Array and res_body.size == 1
373
611
  content_length = res_body[0].bytesize
@@ -378,10 +616,10 @@ module Puma
378
616
  line_ending = LINE_END
379
617
  colon = COLON
380
618
 
381
- if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
382
621
  allow_chunked = true
383
- keep_alive = env[HTTP_CONNECTION] != CLOSE
384
- include_keepalive_header = false
622
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
385
623
 
386
624
  # An optimization. The most common response is 200, so we can
387
625
  # reply with the proper 200 status without having to compute
@@ -391,14 +629,13 @@ module Puma
391
629
  lines << HTTP_11_200
392
630
  else
393
631
  lines.append "HTTP/1.1 ", status.to_s, " ",
394
- HTTP_STATUS_CODES[status], line_ending
632
+ fetch_status_code(status), line_ending
395
633
 
396
- no_body = status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
634
+ no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
397
635
  end
398
636
  else
399
637
  allow_chunked = false
400
- keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
401
- include_keepalive_header = keep_alive
638
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
402
639
 
403
640
  # Same optimization as above for HTTP/1.1
404
641
  #
@@ -406,54 +643,69 @@ module Puma
406
643
  lines << HTTP_10_200
407
644
  else
408
645
  lines.append "HTTP/1.0 ", status.to_s, " ",
409
- HTTP_STATUS_CODES[status], line_ending
646
+ fetch_status_code(status), line_ending
410
647
 
411
- no_body = status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
648
+ no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
412
649
  end
413
650
  end
414
651
 
652
+ # regardless of what the client wants, we always close the connection
653
+ # if running without request queueing
654
+ keep_alive &&= @queue_requests
655
+
415
656
  response_hijack = nil
416
657
 
417
658
  headers.each do |k, vs|
418
- case k
659
+ case k.downcase
419
660
  when CONTENT_LENGTH2
661
+ next if possible_header_injection?(vs)
420
662
  content_length = vs
421
663
  next
422
664
  when TRANSFER_ENCODING
423
665
  allow_chunked = false
424
666
  content_length = nil
425
- when CONTENT_TYPE
426
- next if no_body
427
667
  when HIJACK
428
668
  response_hijack = vs
429
669
  next
430
670
  end
431
671
 
432
- vs.split(NEWLINE).each do |v|
433
- lines.append k, colon, v, line_ending
672
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
673
+ vs.to_s.split(NEWLINE).each do |v|
674
+ next if possible_header_injection?(v)
675
+ lines.append k, colon, v, line_ending
676
+ end
677
+ else
678
+ lines.append k, colon, line_ending
434
679
  end
435
680
  end
436
681
 
682
+ # HTTP/1.1 & 1.0 assume different defaults:
683
+ # - HTTP 1.0 assumes the connection will be closed if not specified
684
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
685
+ # Only set the header if we're doing something which is not the default
686
+ # for this protocol version
687
+ if http_11
688
+ lines << CONNECTION_CLOSE if !keep_alive
689
+ else
690
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
691
+ end
692
+
437
693
  if no_body
694
+ if content_length and status != 204
695
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
696
+ end
697
+
438
698
  lines << line_ending
439
699
  fast_write client, lines.to_s
440
700
  return keep_alive
441
701
  end
442
702
 
443
- if include_keepalive_header
444
- lines << CONNECTION_KEEP_ALIVE
445
- elsif !keep_alive
446
- lines << CONNECTION_CLOSE
447
- end
448
-
449
- unless response_hijack
450
- if content_length
451
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
452
- chunked = false
453
- elsif allow_chunked
454
- lines << TRANSFER_ENCODING_CHUNKED
455
- chunked = true
456
- end
703
+ if content_length
704
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
705
+ chunked = false
706
+ elsif !response_hijack and allow_chunked
707
+ lines << TRANSFER_ENCODING_CHUNKED
708
+ chunked = true
457
709
  end
458
710
 
459
711
  lines << line_ending
@@ -465,28 +717,34 @@ module Puma
465
717
  return :async
466
718
  end
467
719
 
468
- res_body.each do |part|
469
- if chunked
470
- client.syswrite part.bytesize.to_s(16)
471
- client.syswrite line_ending
472
- fast_write client, part
473
- client.syswrite line_ending
474
- else
475
- fast_write client, part
476
- end
720
+ begin
721
+ res_body.each do |part|
722
+ next if part.bytesize.zero?
723
+ if chunked
724
+ fast_write client, part.bytesize.to_s(16)
725
+ fast_write client, line_ending
726
+ fast_write client, part
727
+ fast_write client, line_ending
728
+ else
729
+ fast_write client, part
730
+ end
477
731
 
478
- client.flush
479
- end
732
+ client.flush
733
+ end
480
734
 
481
- if chunked
482
- client.syswrite CLOSE_CHUNKED
483
- client.flush
735
+ if chunked
736
+ fast_write client, CLOSE_CHUNKED
737
+ client.flush
738
+ end
739
+ rescue SystemCallError, IOError
740
+ raise ConnectionError, "Connection error detected during write"
484
741
  end
485
742
 
486
743
  ensure
487
744
  uncork_socket client
488
745
 
489
746
  body.close
747
+ req.tempfile.unlink if req.tempfile
490
748
  res_body.close if res_body.respond_to? :close
491
749
 
492
750
  after_reply.each { |o| o.call }
@@ -495,7 +753,12 @@ module Puma
495
753
  return keep_alive
496
754
  end
497
755
 
498
- # Given the requset +env+ from +client+ and the partial body +body+
756
+ def fetch_status_code(status)
757
+ HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
758
+ end
759
+ private :fetch_status_code
760
+
761
+ # Given the request +env+ from +client+ and the partial body +body+
499
762
  # plus a potential Content-Length value +cl+, finish reading
500
763
  # the body and return it.
501
764
  #
@@ -551,51 +814,155 @@ module Puma
551
814
 
552
815
  # A fallback rack response if +@app+ raises as exception.
553
816
  #
554
- def lowlevel_error(e)
555
- [500, {}, ["Puma caught this error: #{e}\n#{e.backtrace.join("\n")}"]]
817
+ def lowlevel_error(e, env, status=500)
818
+ if handler = @options[:lowlevel_error_handler]
819
+ if handler.arity == 1
820
+ return handler.call(e)
821
+ elsif handler.arity == 2
822
+ return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
825
+ end
826
+ end
827
+
828
+ if @leak_stack_on_error
829
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
830
+ else
831
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
832
+ end
556
833
  end
557
834
 
558
835
  # Wait for all outstanding requests to finish.
559
836
  #
560
837
  def graceful_shutdown
561
- @thread_pool.shutdown if @thread_pool
838
+ if @options[:shutdown_debug]
839
+ threads = Thread.list
840
+ total = threads.size
841
+
842
+ pid = Process.pid
843
+
844
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
845
+
846
+ threads.each_with_index do |t,i|
847
+ $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
848
+ $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
849
+ end
850
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
851
+ end
852
+
853
+ if @options[:drain_on_shutdown]
854
+ count = 0
855
+
856
+ while true
857
+ ios = IO.select @binder.ios, nil, nil, 0
858
+ break unless ios
859
+
860
+ ios.first.each do |sock|
861
+ begin
862
+ if io = sock.accept_nonblock
863
+ count += 1
864
+ client = Client.new io, @binder.env(sock)
865
+ @thread_pool << client
866
+ end
867
+ rescue SystemCallError
868
+ end
869
+ end
870
+ end
871
+
872
+ @events.debug "Drained #{count} additional connections."
873
+ end
874
+
875
+ if @status != :restart
876
+ @binder.close
877
+ end
878
+
879
+ if @thread_pool
880
+ if timeout = @options[:force_shutdown_after]
881
+ @thread_pool.shutdown timeout.to_i
882
+ else
883
+ @thread_pool.shutdown
884
+ end
885
+ end
562
886
  end
563
887
 
888
+ def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
890
+ begin
891
+ @notify << message
892
+ rescue IOError
893
+ # The server, in another thread, is shutting down
894
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
895
+ rescue RuntimeError => e
896
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
897
+ if e.message.include?('IOError')
898
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
899
+ else
900
+ raise e
901
+ end
902
+ end
903
+ end
904
+ private :notify_safely
905
+
564
906
  # Stops the acceptor thread and then causes the worker threads to finish
565
907
  # off the request queue before finally exiting.
566
- #
567
- def stop(sync=false)
568
- @notify << STOP_COMMAND
569
908
 
909
+ def stop(sync=false)
910
+ notify_safely(STOP_COMMAND)
570
911
  @thread.join if @thread && sync
571
912
  end
572
913
 
573
914
  def halt(sync=false)
574
- @notify << HALT_COMMAND
575
-
915
+ notify_safely(HALT_COMMAND)
576
916
  @thread.join if @thread && sync
577
917
  end
578
918
 
579
- def begin_restart
580
- @notify << RESTART_COMMAND
919
+ def begin_restart(sync=false)
920
+ notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
581
922
  end
582
923
 
583
924
  def fast_write(io, str)
584
- n = io.syswrite str
585
-
586
- # Fast path.
587
- return if n == str.bytesize
588
-
589
- pos = n
590
- left = str.bytesize - n
925
+ n = 0
926
+ while true
927
+ begin
928
+ n = io.syswrite str
929
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
930
+ if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
931
+ raise ConnectionError, "Socket timeout writing data"
932
+ end
591
933
 
592
- until left == 0
593
- n = io.syswrite str.byteslice(pos..-1)
934
+ retry
935
+ rescue Errno::EPIPE, SystemCallError, IOError
936
+ raise ConnectionError, "Socket timeout writing data"
937
+ end
594
938
 
595
- pos += n
596
- left -= n
939
+ return if n == str.bytesize
940
+ str = str.byteslice(n..-1)
597
941
  end
598
942
  end
599
943
  private :fast_write
944
+
945
+ ThreadLocalKey = :puma_server
946
+
947
+ def self.current
948
+ Thread.current[ThreadLocalKey]
949
+ end
950
+
951
+ def shutting_down?
952
+ @status == :stop || @status == :restart
953
+ end
954
+
955
+ def possible_header_injection?(header_value)
956
+ HTTP_INJECTION_REGEX =~ header_value.to_s
957
+ end
958
+ private :possible_header_injection?
959
+
960
+ # List of methods invoked by #stats.
961
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
+
963
+ # Returns a hash of stats about the running server for reporting purposes.
964
+ def stats
965
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
+ end
600
967
  end
601
968
  end