actionpack 4.2.11.1 → 5.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +328 -458
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +45 -49
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +47 -31
- data/lib/abstract_controller/collector.rb +8 -11
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +25 -25
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
- data/lib/abstract_controller/rendering.rb +42 -41
- data/lib/abstract_controller/translation.rb +10 -7
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +12 -5
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/base.rb +27 -19
- data/lib/action_controller/caching.rb +14 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +10 -15
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +118 -44
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +3 -3
- data/lib/action_controller/metal/data_streaming.rb +27 -46
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
- data/lib/action_controller/metal/exceptions.rb +8 -14
- data/lib/action_controller/metal/flash.rb +4 -3
- data/lib/action_controller/metal/force_ssl.rb +23 -21
- data/lib/action_controller/metal/head.rb +21 -19
- data/lib/action_controller/metal/helpers.rb +24 -14
- data/lib/action_controller/metal/http_authentication.rb +65 -58
- data/lib/action_controller/metal/implicit_render.rb +62 -8
- data/lib/action_controller/metal/instrumentation.rb +19 -21
- data/lib/action_controller/metal/live.rb +90 -106
- data/lib/action_controller/metal/mime_responds.rb +33 -46
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +61 -53
- data/lib/action_controller/metal/redirecting.rb +49 -28
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +72 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +284 -97
- data/lib/action_controller/metal/rescue.rb +9 -16
- data/lib/action_controller/metal/streaming.rb +12 -10
- data/lib/action_controller/metal/strong_parameters.rb +583 -164
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/metal.rb +98 -83
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +282 -413
- data/lib/action_controller.rb +29 -21
- data/lib/action_dispatch/http/cache.rb +93 -47
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +26 -20
- data/lib/action_dispatch/http/filter_redirect.rb +10 -11
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +56 -41
- data/lib/action_dispatch/http/mime_type.rb +134 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameter_filter.rb +25 -11
- data/lib/action_dispatch/http/parameters.rb +98 -39
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +200 -118
- data/lib/action_dispatch/http/response.rb +225 -110
- data/lib/action_dispatch/http/upload.rb +12 -6
- data/lib/action_dispatch/http/url.rb +110 -28
- data/lib/action_dispatch/journey/formatter.rb +55 -32
- data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
- data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
- data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
- data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
- data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
- data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
- data/lib/action_dispatch/journey/nodes/node.rb +18 -6
- data/lib/action_dispatch/journey/parser.rb +23 -22
- data/lib/action_dispatch/journey/parser.y +3 -2
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +50 -44
- data/lib/action_dispatch/journey/route.rb +106 -28
- data/lib/action_dispatch/journey/router/utils.rb +20 -11
- data/lib/action_dispatch/journey/router.rb +35 -23
- data/lib/action_dispatch/journey/routes.rb +18 -16
- data/lib/action_dispatch/journey/scanner.rb +18 -15
- data/lib/action_dispatch/journey/visitors.rb +99 -52
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/middleware/callbacks.rb +1 -2
- data/lib/action_dispatch/middleware/cookies.rb +304 -193
- data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
- data/lib/action_dispatch/middleware/request_id.rb +17 -9
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
- data/lib/action_dispatch/middleware/ssl.rb +114 -36
- data/lib/action_dispatch/middleware/stack.rb +31 -44
- data/lib/action_dispatch/middleware/static.rb +57 -50
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
- data/lib/action_dispatch/railtie.rb +19 -11
- data/lib/action_dispatch/request/session.rb +106 -59
- data/lib/action_dispatch/request/utils.rb +67 -24
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +58 -67
- data/lib/action_dispatch/routing/mapper.rb +733 -447
- data/lib/action_dispatch/routing/polymorphic_routes.rb +166 -140
- data/lib/action_dispatch/routing/redirection.rb +36 -26
- data/lib/action_dispatch/routing/route_set.rb +321 -291
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +65 -25
- data/lib/action_dispatch/routing.rb +17 -18
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions/response.rb +45 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/integration.rb +348 -209
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +28 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +35 -7
- data/lib/action_dispatch.rb +27 -19
- data/lib/action_pack/gem_version.rb +5 -3
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +4 -2
- metadata +56 -38
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,110 +1,205 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/http/request"
|
4
|
+
require "action_dispatch/middleware/exception_wrapper"
|
5
|
+
require "action_dispatch/routing/inspector"
|
6
|
+
require "action_view"
|
7
|
+
require "action_view/base"
|
8
|
+
|
9
|
+
require "pp"
|
4
10
|
|
5
11
|
module ActionDispatch
|
6
12
|
# This middleware is responsible for logging exceptions and
|
7
13
|
# showing a debugging page in case the request is local.
|
8
14
|
class DebugExceptions
|
9
|
-
RESCUES_TEMPLATE_PATH = File.expand_path(
|
15
|
+
RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
|
16
|
+
|
17
|
+
class DebugView < ActionView::Base
|
18
|
+
def debug_params(params)
|
19
|
+
clean_params = params.clone
|
20
|
+
clean_params.delete("action")
|
21
|
+
clean_params.delete("controller")
|
22
|
+
|
23
|
+
if clean_params.empty?
|
24
|
+
"None"
|
25
|
+
else
|
26
|
+
PP.pp(clean_params, "".dup, 200)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug_headers(headers)
|
31
|
+
if headers.present?
|
32
|
+
headers.inspect.gsub(",", ",\n")
|
33
|
+
else
|
34
|
+
"None"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def debug_hash(object)
|
39
|
+
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
def render(*)
|
43
|
+
logger = ActionView::Base.logger
|
44
|
+
|
45
|
+
if logger && logger.respond_to?(:silence)
|
46
|
+
logger.silence { super }
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
10
52
|
|
11
|
-
def initialize(app, routes_app = nil)
|
12
|
-
@app
|
13
|
-
@routes_app
|
53
|
+
def initialize(app, routes_app = nil, response_format = :default)
|
54
|
+
@app = app
|
55
|
+
@routes_app = routes_app
|
56
|
+
@response_format = response_format
|
14
57
|
end
|
15
58
|
|
16
59
|
def call(env)
|
60
|
+
request = ActionDispatch::Request.new env
|
17
61
|
_, headers, body = response = @app.call(env)
|
18
62
|
|
19
|
-
if headers[
|
63
|
+
if headers["X-Cascade"] == "pass"
|
20
64
|
body.close if body.respond_to?(:close)
|
21
65
|
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
|
22
66
|
end
|
23
67
|
|
24
68
|
response
|
25
69
|
rescue Exception => exception
|
26
|
-
raise exception
|
27
|
-
render_exception(
|
70
|
+
raise exception unless request.show_exceptions?
|
71
|
+
render_exception(request, exception)
|
28
72
|
end
|
29
73
|
|
30
74
|
private
|
31
75
|
|
32
|
-
|
33
|
-
|
34
|
-
|
76
|
+
def render_exception(request, exception)
|
77
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
78
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
79
|
+
log_error(request, wrapper)
|
80
|
+
|
81
|
+
if request.get_header("action_dispatch.show_detailed_exceptions")
|
82
|
+
content_type = request.formats.first
|
83
|
+
|
84
|
+
if api_request?(content_type)
|
85
|
+
render_for_api_request(content_type, wrapper)
|
86
|
+
else
|
87
|
+
render_for_browser_request(request, wrapper)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise exception
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def render_for_browser_request(request, wrapper)
|
95
|
+
template = create_template(request, wrapper)
|
96
|
+
file = "rescues/#{wrapper.rescue_template}"
|
97
|
+
|
98
|
+
if request.xhr?
|
99
|
+
body = template.render(template: file, layout: false, formats: [:text])
|
100
|
+
format = "text/plain"
|
101
|
+
else
|
102
|
+
body = template.render(template: file, layout: "rescues/layout")
|
103
|
+
format = "text/html"
|
104
|
+
end
|
105
|
+
render(wrapper.status_code, body, format)
|
106
|
+
end
|
107
|
+
|
108
|
+
def render_for_api_request(content_type, wrapper)
|
109
|
+
body = {
|
110
|
+
status: wrapper.status_code,
|
111
|
+
error: Rack::Utils::HTTP_STATUS_CODES.fetch(
|
112
|
+
wrapper.status_code,
|
113
|
+
Rack::Utils::HTTP_STATUS_CODES[500]
|
114
|
+
),
|
115
|
+
exception: wrapper.exception.inspect,
|
116
|
+
traces: wrapper.traces
|
117
|
+
}
|
118
|
+
|
119
|
+
to_format = "to_#{content_type.to_sym}"
|
120
|
+
|
121
|
+
if content_type && body.respond_to?(to_format)
|
122
|
+
formatted_body = body.public_send(to_format)
|
123
|
+
format = content_type
|
124
|
+
else
|
125
|
+
formatted_body = body.to_json
|
126
|
+
format = Mime[:json]
|
127
|
+
end
|
128
|
+
|
129
|
+
render(wrapper.status_code, formatted_body, format)
|
130
|
+
end
|
35
131
|
|
36
|
-
|
37
|
-
request = Request.new(env)
|
132
|
+
def create_template(request, wrapper)
|
38
133
|
traces = wrapper.traces
|
39
134
|
|
40
|
-
trace_to_show =
|
41
|
-
if traces[trace_to_show].empty? && wrapper.rescue_template !=
|
42
|
-
trace_to_show =
|
135
|
+
trace_to_show = "Application Trace"
|
136
|
+
if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
|
137
|
+
trace_to_show = "Full Trace"
|
43
138
|
end
|
44
139
|
|
45
140
|
if source_to_show = traces[trace_to_show].first
|
46
141
|
source_to_show_id = source_to_show[:id]
|
47
142
|
end
|
48
143
|
|
49
|
-
|
144
|
+
DebugView.new([RESCUES_TEMPLATE_PATH],
|
50
145
|
request: request,
|
51
146
|
exception: wrapper.exception,
|
52
147
|
traces: traces,
|
53
148
|
show_source_idx: source_to_show_id,
|
54
149
|
trace_to_show: trace_to_show,
|
55
|
-
routes_inspector: routes_inspector(exception),
|
150
|
+
routes_inspector: routes_inspector(wrapper.exception),
|
56
151
|
source_extracts: wrapper.source_extracts,
|
57
152
|
line_number: wrapper.line_number,
|
58
153
|
file: wrapper.file
|
59
154
|
)
|
60
|
-
|
155
|
+
end
|
61
156
|
|
62
|
-
|
63
|
-
|
64
|
-
format = "text/plain"
|
65
|
-
else
|
66
|
-
body = template.render(template: file, layout: 'rescues/layout')
|
67
|
-
format = "text/html"
|
68
|
-
end
|
69
|
-
render(wrapper.status_code, body, format)
|
70
|
-
else
|
71
|
-
raise exception
|
157
|
+
def render(status, body, format)
|
158
|
+
[status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
|
72
159
|
end
|
73
|
-
end
|
74
160
|
|
75
|
-
|
76
|
-
|
77
|
-
|
161
|
+
def log_error(request, wrapper)
|
162
|
+
logger = logger(request)
|
163
|
+
return unless logger
|
78
164
|
|
79
|
-
|
80
|
-
logger = logger(env)
|
81
|
-
return unless logger
|
165
|
+
exception = wrapper.exception
|
82
166
|
|
83
|
-
|
167
|
+
trace = wrapper.application_trace
|
168
|
+
trace = wrapper.framework_trace if trace.empty?
|
84
169
|
|
85
|
-
|
86
|
-
|
170
|
+
ActiveSupport::Deprecation.silence do
|
171
|
+
logger.fatal " "
|
172
|
+
logger.fatal "#{exception.class} (#{exception.message}):"
|
173
|
+
log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
|
174
|
+
logger.fatal " "
|
175
|
+
log_array logger, trace
|
176
|
+
end
|
177
|
+
end
|
87
178
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
179
|
+
def log_array(logger, array)
|
180
|
+
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
181
|
+
logger.fatal array.join("\n#{logger.formatter.tags_text}")
|
182
|
+
else
|
183
|
+
logger.fatal array.join("\n")
|
184
|
+
end
|
93
185
|
end
|
94
|
-
end
|
95
186
|
|
96
|
-
|
97
|
-
|
98
|
-
|
187
|
+
def logger(request)
|
188
|
+
request.logger || ActionView::Base.logger || stderr_logger
|
189
|
+
end
|
99
190
|
|
100
|
-
|
101
|
-
|
102
|
-
|
191
|
+
def stderr_logger
|
192
|
+
@stderr_logger ||= ActiveSupport::Logger.new($stderr)
|
193
|
+
end
|
103
194
|
|
104
|
-
|
105
|
-
|
106
|
-
|
195
|
+
def routes_inspector(exception)
|
196
|
+
if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
|
197
|
+
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def api_request?(content_type)
|
202
|
+
@response_format == :api && !content_type.html?
|
107
203
|
end
|
108
|
-
end
|
109
204
|
end
|
110
205
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
# This middleware can be used to diagnose deadlocks in the autoload interlock.
|
5
|
+
#
|
6
|
+
# To use it, insert it near the top of the middleware stack, using
|
7
|
+
# <tt>config/application.rb</tt>:
|
8
|
+
#
|
9
|
+
# config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
|
10
|
+
#
|
11
|
+
# After restarting the application and re-triggering the deadlock condition,
|
12
|
+
# <tt>/rails/locks</tt> will show a summary of all threads currently known to
|
13
|
+
# the interlock, which lock level they are holding or awaiting, and their
|
14
|
+
# current backtrace.
|
15
|
+
#
|
16
|
+
# Generally a deadlock will be caused by the interlock conflicting with some
|
17
|
+
# other external lock or blocking I/O call. These cannot be automatically
|
18
|
+
# identified, but should be visible in the displayed backtraces.
|
19
|
+
#
|
20
|
+
# NOTE: The formatting and content of this middleware's output is intended for
|
21
|
+
# human consumption, and should be expected to change between releases.
|
22
|
+
#
|
23
|
+
# This middleware exposes operational details of the server, with no access
|
24
|
+
# control. It should only be enabled when in use, and removed thereafter.
|
25
|
+
class DebugLocks
|
26
|
+
def initialize(app, path = "/rails/locks")
|
27
|
+
@app = app
|
28
|
+
@path = path
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
req = ActionDispatch::Request.new env
|
33
|
+
|
34
|
+
if req.get?
|
35
|
+
path = req.path_info.chomp("/".freeze)
|
36
|
+
if path == @path
|
37
|
+
return render_details(req)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@app.call(env)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def render_details(req)
|
46
|
+
threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
|
47
|
+
# The Interlock itself comes to a complete halt as long as this block
|
48
|
+
# is executing. That gives us a more consistent picture of everything,
|
49
|
+
# but creates a pretty strong Observer Effect.
|
50
|
+
#
|
51
|
+
# Most directly, that means we need to do as little as possible in
|
52
|
+
# this block. More widely, it means this middleware should remain a
|
53
|
+
# strictly diagnostic tool (to be used when something has gone wrong),
|
54
|
+
# and not for any sort of general monitoring.
|
55
|
+
|
56
|
+
raw_threads.each.with_index do |(thread, info), idx|
|
57
|
+
info[:index] = idx
|
58
|
+
info[:backtrace] = thread.backtrace
|
59
|
+
end
|
60
|
+
|
61
|
+
raw_threads
|
62
|
+
end
|
63
|
+
|
64
|
+
str = threads.map do |thread, info|
|
65
|
+
if info[:exclusive]
|
66
|
+
lock_state = "Exclusive".dup
|
67
|
+
elsif info[:sharing] > 0
|
68
|
+
lock_state = "Sharing".dup
|
69
|
+
lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
|
70
|
+
else
|
71
|
+
lock_state = "No lock".dup
|
72
|
+
end
|
73
|
+
|
74
|
+
if info[:waiting]
|
75
|
+
lock_state << " (yielded share)"
|
76
|
+
end
|
77
|
+
|
78
|
+
msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
|
79
|
+
|
80
|
+
if info[:sleeper]
|
81
|
+
msg << " Waiting in #{info[:sleeper]}"
|
82
|
+
msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil?
|
83
|
+
msg << "\n"
|
84
|
+
|
85
|
+
if info[:compatible]
|
86
|
+
compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect }
|
87
|
+
msg << " may be pre-empted for: #{compat.join(', ')}\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
|
91
|
+
msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any?
|
92
|
+
end
|
93
|
+
|
94
|
+
blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
|
95
|
+
msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any?
|
96
|
+
|
97
|
+
msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
|
98
|
+
end.join("\n\n---\n\n\n")
|
99
|
+
|
100
|
+
[200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
|
101
|
+
end
|
102
|
+
|
103
|
+
def blocked_by?(victim, blocker, all_threads)
|
104
|
+
return false if victim.equal?(blocker)
|
105
|
+
|
106
|
+
case victim[:sleeper]
|
107
|
+
when :start_sharing
|
108
|
+
blocker[:exclusive] ||
|
109
|
+
(!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false))
|
110
|
+
when :start_exclusive
|
111
|
+
blocker[:sharing] > 0 ||
|
112
|
+
blocker[:exclusive] ||
|
113
|
+
(blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose]))
|
114
|
+
when :yield_shares
|
115
|
+
blocker[:exclusive]
|
116
|
+
when :stop_exclusive
|
117
|
+
blocker[:exclusive] ||
|
118
|
+
victim[:compatible] &&
|
119
|
+
victim[:compatible].include?(blocker[:purpose]) &&
|
120
|
+
all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,40 +1,41 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/attribute_accessors"
|
4
|
+
require "rack/utils"
|
3
5
|
|
4
6
|
module ActionDispatch
|
5
7
|
class ExceptionWrapper
|
6
|
-
cattr_accessor :rescue_responses
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
8
|
+
cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
|
9
|
+
"ActionController::RoutingError" => :not_found,
|
10
|
+
"AbstractController::ActionNotFound" => :not_found,
|
11
|
+
"ActionController::MethodNotAllowed" => :method_not_allowed,
|
12
|
+
"ActionController::UnknownHttpMethod" => :method_not_allowed,
|
13
|
+
"ActionController::NotImplemented" => :not_implemented,
|
14
|
+
"ActionController::UnknownFormat" => :not_acceptable,
|
15
|
+
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
|
16
|
+
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
|
17
|
+
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
18
|
+
"ActionController::BadRequest" => :bad_request,
|
19
|
+
"ActionController::ParameterMissing" => :bad_request,
|
20
|
+
"Rack::QueryParser::ParameterTypeError" => :bad_request,
|
21
|
+
"Rack::QueryParser::InvalidParameterError" => :bad_request
|
20
22
|
)
|
21
23
|
|
22
|
-
cattr_accessor :rescue_templates
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
'ActionView::Template::Error' => 'template_error'
|
24
|
+
cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
|
25
|
+
"ActionView::MissingTemplate" => "missing_template",
|
26
|
+
"ActionController::RoutingError" => "routing_error",
|
27
|
+
"AbstractController::ActionNotFound" => "unknown_action",
|
28
|
+
"ActiveRecord::StatementInvalid" => "invalid_statement",
|
29
|
+
"ActionView::Template::Error" => "template_error"
|
29
30
|
)
|
30
31
|
|
31
|
-
attr_reader :
|
32
|
+
attr_reader :backtrace_cleaner, :exception, :line_number, :file
|
32
33
|
|
33
|
-
def initialize(
|
34
|
-
@
|
34
|
+
def initialize(backtrace_cleaner, exception)
|
35
|
+
@backtrace_cleaner = backtrace_cleaner
|
35
36
|
@exception = original_exception(exception)
|
36
37
|
|
37
|
-
expand_backtrace if exception.is_a?(SyntaxError) || exception.
|
38
|
+
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
|
38
39
|
end
|
39
40
|
|
40
41
|
def rescue_template
|
@@ -58,7 +59,7 @@ module ActionDispatch
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def traces
|
61
|
-
|
62
|
+
application_trace_with_ids = []
|
62
63
|
framework_trace_with_ids = []
|
63
64
|
full_trace_with_ids = []
|
64
65
|
|
@@ -66,7 +67,7 @@ module ActionDispatch
|
|
66
67
|
trace_with_id = { id: idx, trace: trace }
|
67
68
|
|
68
69
|
if application_trace.include?(trace)
|
69
|
-
|
70
|
+
application_trace_with_ids << trace_with_id
|
70
71
|
else
|
71
72
|
framework_trace_with_ids << trace_with_id
|
72
73
|
end
|
@@ -75,7 +76,7 @@ module ActionDispatch
|
|
75
76
|
end
|
76
77
|
|
77
78
|
{
|
78
|
-
"Application Trace" =>
|
79
|
+
"Application Trace" => application_trace_with_ids,
|
79
80
|
"Framework Trace" => framework_trace_with_ids,
|
80
81
|
"Full Trace" => full_trace_with_ids
|
81
82
|
}
|
@@ -87,8 +88,7 @@ module ActionDispatch
|
|
87
88
|
|
88
89
|
def source_extracts
|
89
90
|
backtrace.map do |trace|
|
90
|
-
file,
|
91
|
-
line_number = line.to_i
|
91
|
+
file, line_number = extract_file_and_line_number(trace)
|
92
92
|
|
93
93
|
{
|
94
94
|
code: source_fragment(file, line_number),
|
@@ -99,50 +99,49 @@ module ActionDispatch
|
|
99
99
|
|
100
100
|
private
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
def original_exception(exception)
|
107
|
-
if registered_original_exception?(exception)
|
108
|
-
exception.original_exception
|
109
|
-
else
|
110
|
-
exception
|
102
|
+
def backtrace
|
103
|
+
Array(@exception.backtrace)
|
111
104
|
end
|
112
|
-
end
|
113
105
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
backtrace_cleaner.clean(backtrace, *args)
|
121
|
-
else
|
122
|
-
backtrace
|
106
|
+
def original_exception(exception)
|
107
|
+
if @@rescue_responses.has_key?(exception.cause.class.name)
|
108
|
+
exception.cause
|
109
|
+
else
|
110
|
+
exception
|
111
|
+
end
|
123
112
|
end
|
124
|
-
end
|
125
113
|
|
126
|
-
|
127
|
-
|
128
|
-
|
114
|
+
def clean_backtrace(*args)
|
115
|
+
if backtrace_cleaner
|
116
|
+
backtrace_cleaner.clean(backtrace, *args)
|
117
|
+
else
|
118
|
+
backtrace
|
119
|
+
end
|
120
|
+
end
|
129
121
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
122
|
+
def source_fragment(path, line)
|
123
|
+
return unless Rails.respond_to?(:root) && Rails.root
|
124
|
+
full_path = Rails.root.join(path)
|
125
|
+
if File.exist?(full_path)
|
126
|
+
File.open(full_path, "r") do |file|
|
127
|
+
start = [line - 3, 0].max
|
128
|
+
lines = file.each_line.drop(start).take(6)
|
129
|
+
Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
|
130
|
+
end
|
138
131
|
end
|
139
132
|
end
|
140
|
-
end
|
141
133
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
134
|
+
def extract_file_and_line_number(trace)
|
135
|
+
# Split by the first colon followed by some digits, which works for both
|
136
|
+
# Windows and Unix path styles.
|
137
|
+
file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
|
138
|
+
[file, line.to_i]
|
139
|
+
end
|
140
|
+
|
141
|
+
def expand_backtrace
|
142
|
+
@exception.backtrace.unshift(
|
143
|
+
@exception.to_s.split("\n")
|
144
|
+
).flatten!
|
145
|
+
end
|
147
146
|
end
|
148
147
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/body_proxy"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
class Executor
|
7
|
+
def initialize(app, executor)
|
8
|
+
@app, @executor = app, executor
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
state = @executor.run!
|
13
|
+
begin
|
14
|
+
response = @app.call(env)
|
15
|
+
returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
16
|
+
ensure
|
17
|
+
state.complete! unless returned
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|