Skip to content
9 changes: 9 additions & 0 deletions source/common/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "async_client_utility_lib",
srcs = ["async_client_utility.cc"],
hdrs = ["async_client_utility.h"],
deps = [
"//include/envoy/http:async_client_interface",
],
)

envoy_cc_library(
name = "codec_client_lib",
srcs = ["codec_client.cc"],
Expand Down
36 changes: 36 additions & 0 deletions source/common/http/async_client_utility.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "common/http/async_client_utility.h"

namespace Envoy {
namespace Http {

AsyncClientRequestTracker::~AsyncClientRequestTracker() {
for (auto* active_request : active_requests_) {
active_request->cancel();
}
}

AsyncClientRequestTracker& AsyncClientRequestTracker::operator+=(AsyncClient::Request* request) {
// Let client code to avoid conditionals.
if (request) {
ASSERT(active_requests_.find(request) == active_requests_.end());
active_requests_.insert(request);
}
return *this;
}

AsyncClientRequestTracker&
AsyncClientRequestTracker::operator-=(const AsyncClient::Request* request) {
// Let client code to avoid conditionals.
if (request) {
auto it = active_requests_.find(const_cast<AsyncClient::Request*>(request));
// Support a use case where request callbacks might get called prior to a request handle
// is returned from AsyncClient::send().
if (it != active_requests_.end()) {
active_requests_.erase(it);
}
}
return *this;
}

} // namespace Http
} // namespace Envoy
32 changes: 32 additions & 0 deletions source/common/http/async_client_utility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "envoy/http/async_client.h"

namespace Envoy {
namespace Http {

/**
* Keeps track of active async HTTP requests to be able to cancel them on destruction.
*/
class AsyncClientRequestTracker {
public:
/**
* Cancels all known active async HTTP requests.
*/
~AsyncClientRequestTracker();
/**
* Includes a given async HTTP request into a set of known active requests.
*/
AsyncClientRequestTracker& operator+=(AsyncClient::Request* request);
/**
* Excludes a given async HTTP request from a set of known active requests.
*/
AsyncClientRequestTracker& operator-=(const AsyncClient::Request* request);
Comment thread
yskopets marked this conversation as resolved.
Outdated

private:
// Track active async HTTP requests to be able to cancel them on destruction.
std::unordered_set<AsyncClient::Request*> active_requests_;
Comment thread
yskopets marked this conversation as resolved.
Outdated
};

} // namespace Http
} // namespace Envoy
9 changes: 9 additions & 0 deletions test/common/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ envoy_cc_test(
],
)

envoy_cc_test(
name = "async_client_utility_test",
srcs = ["async_client_utility_test.cc"],
deps = [
"//source/common/http:async_client_utility_lib",
"//test/mocks/http:http_mocks",
],
)

envoy_cc_test(
name = "codec_client_test",
srcs = ["codec_client_test.cc"],
Expand Down
49 changes: 49 additions & 0 deletions test/common/http/async_client_utility_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "common/http/async_client_utility.h"

#include "test/mocks/http/mocks.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

using testing::NiceMock;
using testing::StrictMock;

namespace Envoy {
namespace Http {
namespace {

class AsyncClientRequestTrackerTest : public testing::Test {
public:
std::unique_ptr<AsyncClientRequestTracker> active_requests_{
std::make_unique<AsyncClientRequestTracker>()};

NiceMock<MockAsyncClient> async_client_;
StrictMock<MockAsyncClientRequest> request1_{&async_client_};
StrictMock<MockAsyncClientRequest> request2_{&async_client_};
StrictMock<MockAsyncClientRequest> request3_{&async_client_};
};

TEST_F(AsyncClientRequestTrackerTest, OnDestructDoNothingIfThereAreNoActiveRequests) {
// Trigger destruction.
active_requests_.reset();
}

TEST_F(AsyncClientRequestTrackerTest, OnDestructCancelActiveRequests) {
// Include active requests.
*active_requests_ += &request1_;
*active_requests_ += &request2_;
*active_requests_ += &request3_;
// Exclude active requests.
*active_requests_ -= &request2_;

// Must cancel active requests on destruction.
EXPECT_CALL(request1_, cancel());
EXPECT_CALL(request3_, cancel());

// Trigger destruction.
active_requests_.reset();
}

} // namespace
} // namespace Http
} // namespace Envoy