gitlab-puma 4.3.1.gitlab.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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