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