gitlab-puma 4.3.1.gitlab.2

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