From 98f54f84627d91324ab60fcfd8a373ca66c58826 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 24 May 2026 09:49:23 +0900 Subject: [PATCH] Fix requests_active leak when response close raises --- lib/falcon/body/request_finished.rb | 7 ++++--- releases.md | 4 ++++ test/falcon/body/request_finished.rb | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/falcon/body/request_finished.rb b/lib/falcon/body/request_finished.rb index 9a6e1c6..8e589d9 100644 --- a/lib/falcon/body/request_finished.rb +++ b/lib/falcon/body/request_finished.rb @@ -11,8 +11,9 @@ module Body # Wraps a response body and decrements a metric after the body is closed. # # Runs close on the underlying body first (which invokes rack.response_finished), - # then decrements the metric. Use this so requests_active stays elevated until - # the request is fully finished (including response_finished callbacks). + # then decrements the metric, even if closing raises. Use this so requests_active + # stays elevated until the request is fully finished (including response_finished + # callbacks), without leaking if close reports an error. class RequestFinished < Protocol::HTTP::Body::Wrapper # Wrap a response body with a metric. # @@ -56,7 +57,7 @@ def rewind # @parameter error [Exception, nil] Optional error that caused the close. def close(error = nil) super - + ensure @metric&.decrement @metric = nil end diff --git a/releases.md b/releases.md index 68281fc..02bf651 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Ensure `requests_active` is decremented if closing the response body raises. + ## v0.55.3 - Decrement `requests_active` in `Falcon::Server#call` when `super` or `Falcon::Body::RequestFinished.wrap` raises, so utilization metrics are not leaked on error paths. diff --git a/test/falcon/body/request_finished.rb b/test/falcon/body/request_finished.rb index ea6d2d2..645f3cb 100644 --- a/test/falcon/body/request_finished.rb +++ b/test/falcon/body/request_finished.rb @@ -32,6 +32,25 @@ expect(metric.value).to be == 0 end + it "decrements metric when body close raises" do + body = Class.new(Protocol::HTTP::Body::Buffered) do + def close(error = nil) + super + raise RuntimeError, "close failed" + end + end.new(["Hello World"]) + response = Protocol::HTTP::Response[200, {"content-type" => "text/plain"}, body] + + metric.increment + subject.wrap(response, metric) + + expect do + response.body.close + end.to raise_exception(RuntimeError, message: be =~ /close failed/) + + expect(metric.value).to be == 0 + end + it "decrements only once on multiple close calls" do metric.increment subject.wrap(response, metric)