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