Skip to content

Commit be1513b

Browse files
authored
fix: release CurlRequest handles on error (#7244)
1 parent f486e0f commit be1513b

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

google/cloud/storage/internal/curl_request.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ extern "C" std::size_t CurlRequestOnReadData(char* ptr, std::size_t size,
6464
return v->OnRead(ptr, size, nitems);
6565
}
6666

67+
CurlRequest::~CurlRequest() {
68+
if (factory_) factory_->CleanupHandle(std::move(handle_));
69+
}
70+
6771
StatusOr<HttpResponse> CurlRequest::MakeRequest(std::string const& payload) {
6872
handle_.SetOption(CURLOPT_UPLOAD, 0L);
6973
if (!payload.empty()) {
@@ -90,6 +94,14 @@ StatusOr<HttpResponse> CurlRequest::MakeUploadRequest(
9094
return MakeRequestImpl();
9195
}
9296

97+
Status CurlRequest::OnError(Status status) {
98+
// When there is a transfer error the handle is suspect. It could be pointing
99+
// to an invalid host, a host that is slow and trickling data, or otherwise in
100+
// a bad state. Release the handle, but do not return it to the pool.
101+
auto handle = std::move(handle_);
102+
return status;
103+
}
104+
93105
StatusOr<HttpResponse> CurlRequest::MakeRequestImpl() {
94106
// We get better performance using a slightly larger buffer (128KiB) than the
95107
// default buffer size set by libcurl (16KiB)
@@ -111,7 +123,7 @@ StatusOr<HttpResponse> CurlRequest::MakeRequestImpl() {
111123
handle_.SetOption(CURLOPT_HEADERFUNCTION, &CurlRequestOnHeaderData);
112124
handle_.SetOption(CURLOPT_HEADERDATA, this);
113125
auto status = handle_.EasyPerform();
114-
if (!status.ok()) return status;
126+
if (!status.ok()) return OnError(std::move(status));
115127

116128
if (logging_enabled_) handle_.FlushDebug(__func__);
117129
auto code = handle_.GetResponseCode();

google/cloud/storage/internal/curl_request.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ extern "C" size_t CurlRequestOnHeaderData(char* contents, size_t size,
3535
class CurlRequest {
3636
public:
3737
CurlRequest() = default;
38-
~CurlRequest() {
39-
if (factory_) factory_->CleanupHandle(std::move(handle_));
40-
}
38+
~CurlRequest();
4139

4240
CurlRequest(CurlRequest&&) = default;
4341
CurlRequest& operator=(CurlRequest&&) = default;
@@ -55,6 +53,9 @@ class CurlRequest {
5553
StatusOr<HttpResponse> MakeUploadRequest(ConstBufferSequence payload);
5654

5755
private:
56+
/// Handle a libcurl error during the request.
57+
Status OnError(Status status);
58+
5859
StatusOr<HttpResponse> MakeRequestImpl();
5960

6061
friend class CurlRequestBuilder;

google/cloud/storage/tests/curl_download_request_integration_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ TEST(CurlDownloadRequestTest, HandlesReleasedOnError) {
226226

227227
CurlRequestBuilder request("https://localhost:1/get", factory);
228228
auto download = std::move(request).BuildDownloadRequest();
229-
// Perform a series of very small `.Read()` calls. This will force the
229+
// This `.Read()` call fails as the endpoint is invalid.
230230
char buffer[4096];
231231
auto read = download->Read(buffer, sizeof(buffer));
232232
ASSERT_THAT(read, Not(IsOk()));

google/cloud/storage/tests/curl_request_integration_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,34 @@ TEST(CurlRequestTest, Logging) {
565565
EXPECT_THAT(log_messages, HasSubstr("curl(Recv Data)"));
566566
}
567567

568+
TEST(CurlRequestTest, HandlesReleasedOnError) {
569+
auto constexpr kTestPoolSize = 8;
570+
auto factory =
571+
std::make_shared<PooledCurlHandleFactory>(kTestPoolSize, Options{});
572+
ASSERT_EQ(0, factory->CurrentHandleCount());
573+
ASSERT_EQ(0, factory->CurrentMultiHandleCount());
574+
575+
CurlRequestBuilder builder("https://localhost:1/get", factory);
576+
auto response = std::move(builder).BuildRequest().MakeRequest(std::string{});
577+
ASSERT_THAT(response, Not(IsOk()));
578+
// Assuming there was an error the CURL* handle should not be returned to the
579+
// pool.
580+
EXPECT_EQ(0, factory->CurrentHandleCount());
581+
}
582+
583+
TEST(CurlRequestTest, HandlesReusedOnSuccess) {
584+
auto constexpr kTestPoolSize = 8;
585+
auto factory =
586+
std::make_shared<PooledCurlHandleFactory>(kTestPoolSize, Options{});
587+
ASSERT_EQ(0, factory->CurrentHandleCount());
588+
ASSERT_EQ(0, factory->CurrentMultiHandleCount());
589+
590+
CurlRequestBuilder builder(HttpBinEndpoint() + "/get", factory);
591+
auto response = std::move(builder).BuildRequest().MakeRequest(std::string{});
592+
ASSERT_THAT(response, IsOk());
593+
EXPECT_EQ(1, factory->CurrentHandleCount());
594+
}
595+
568596
} // namespace
569597
} // namespace internal
570598
} // namespace STORAGE_CLIENT_NS

0 commit comments

Comments
 (0)