Skip to content

Commit 5a946d6

Browse files
Ensure requests_active is decremented on error.
1 parent 96b7241 commit 5a946d6

File tree

5 files changed

+48
-5
lines changed

5 files changed

+48
-5
lines changed

lib/falcon/body/request_finished.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ module Body
1414
# then decrements the metric. Use this so requests_active stays elevated until
1515
# the request is fully finished (including response_finished callbacks).
1616
class RequestFinished < Protocol::HTTP::Body::Wrapper
17-
# Wrap a response body with a metric. If the body is nil or empty, decrements immediately.
17+
# Wrap a response body with a metric.
18+
#
19+
# Expects the caller to have incremented `requests_active` beforehand. If the body is
20+
# not wrapped (nil or empty message/body, or an error while assigning the wrapper), the
21+
# `ensure` block decrements once. When wrapped, the metric is decremented in {#close}.
1822
#
1923
# @parameter message [Protocol::HTTP::Response] The response whose body to wrap.
2024
# @parameter metric [Async::Utilization::Metric] The metric to decrement when the body is closed.
@@ -29,6 +33,8 @@ def self.wrap(message, metric)
2933
message
3034
end
3135

36+
# Initialize the wrapper.
37+
#
3238
# @parameter body [Protocol::HTTP::Body::Readable] The body to wrap.
3339
# @parameter metric [Async::Utilization::Metric] The metric to decrement on close.
3440
def initialize(body, metric)

lib/falcon/server.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,22 @@ def accept(...)
6969
# Body::RequestFinished wrapper runs the decrement after the body closes,
7070
# so response_finished callbacks are counted as active.
7171
def call(...)
72+
decrement = false
73+
7274
@requests_total_metric.increment
7375
@requests_active_metric.increment
76+
decrement = true
77+
78+
# Roll back `requests_active` unless the full `super` + wrap path completes. Covers
79+
# `super` raising before `wrap` runs, errors inside `wrap`, and other abnormal exits.
80+
response = Body::RequestFinished.wrap(super, @requests_active_metric)
81+
decrement = false
7482

75-
return Body::RequestFinished.wrap(super, @requests_active_metric)
83+
return response
84+
ensure
85+
if decrement
86+
@requests_active_metric.decrement
87+
end
7688
end
7789

7890
# Generates a human-readable string representing the current statistics.

releases.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Decrement `requests_active` in `Falcon::Server#call` when `super` or `Falcon::Body::RequestFinished.wrap` raises, so utilization metrics are not leaked on error paths.
6+
37
## v0.55.2
48

5-
- Remove unnecessary require for `async/service/supervisor/supervised`."
9+
- Remove unnecessary require for `async/service/supervisor/supervised`.
610

711
## v0.55.1
812

test/falcon/body/request_finished.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@
6969
end
7070

7171
with "nil message" do
72-
it "decrements immediately" do
72+
it "decrements immediately and returns nil" do
7373
metric.increment
74-
subject.wrap(nil, metric)
74+
expect(subject.wrap(nil, metric)).to be == nil
7575
expect(metric.value).to be == 0
7676
end
7777
end

test/falcon/server.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,26 @@ def make_server(endpoint)
148148
expect(utilization_registry.metric(:requests_active).value).to be == 0
149149
expect(utilization_registry.metric(:requests_total).value).to be == 1
150150
end
151+
152+
it "restores requests_active when delegate raises" do
153+
boom = Object.new
154+
def boom.call(_request)
155+
raise RuntimeError, "boom"
156+
end
157+
def boom.close
158+
end
159+
160+
endpoint = Async::HTTP::Endpoint.parse("http://127.0.0.1:0", reuse_port: true)
161+
server = ::Falcon::Server.new(
162+
::Protocol::HTTP::Middleware.new(boom),
163+
endpoint,
164+
utilization_registry: utilization_registry
165+
)
166+
167+
request = ::Protocol::HTTP::Request["GET", "/", {}]
168+
expect{server.call(request)}.to raise_exception(RuntimeError, message: be =~ /boom/)
169+
expect(utilization_registry.metric(:requests_active).value).to be == 0
170+
expect(utilization_registry.metric(:requests_total).value).to be == 1
171+
end
151172
end
152173
end

0 commit comments

Comments
 (0)