Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 1.18] Enable Windows workers (#17555) #17814

Merged
merged 7 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/root/intro/arch_overview/intro/threading_model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ to have Envoy forcibly balance connections between worker threads. To support th
Envoy allows for different types of :ref:`connection balancing
<envoy_v3_api_field_config.listener.v3.Listener.connection_balance_config>` to be configured on each :ref:`listener
<arch_overview_listeners>`.

On Windows the kernel is not able to balance the connections properly with the async IO model that Envoy is using.
Until this is fixed by the platform, Envoy will enforce listener connection balancing on Windows. This allows us to
balance connections between different worker threads. This behavior comes with a performance penalty.
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Bug Fixes
*Changes expected to improve the state of the world and are unlikely to have negative effects*

* jwt_authn: unauthorized responses now correctly include a `www-authenticate` header.
* listener: fixed an issue on Windows where connections are not handled by all worker threads.

Removed Config or Runtime
-------------------------
Expand Down
14 changes: 14 additions & 0 deletions source/server/listener_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,19 @@ void ListenerImpl::buildFilterChains() {
void ListenerImpl::buildSocketOptions() {
// TCP specific setup.
if (connection_balancer_ == nullptr) {
#ifdef WIN32
// On Windows we use the exact connection balancer to dispatch connections
// from worker 1 to all workers. This is a perf hit but it is the only way
// to make all the workers do work.
// TODO(davinci26): We can be faster here if we create a balancer implementation
// that dispatches the connection to a random thread.
ENVOY_LOG(warn,
"ExactBalance was forced enabled for TCP listener '{}' because "
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
"Envoy is running on Windows."
"ExactBalance is used to load balance connections between workers on Windows.",
config_.name());
connection_balancer_ = std::make_shared<Network::ExactConnectionBalancerImpl>();
#else
// Not in place listener update.
if (config_.has_connection_balance_config()) {
// Currently exact balance is the only supported type and there are no options.
Expand All @@ -531,6 +544,7 @@ void ListenerImpl::buildSocketOptions() {
} else {
connection_balancer_ = std::make_shared<Network::NopConnectionBalancerImpl>();
}
#endif
}

if (config_.has_tcp_fast_open_queue_length()) {
Expand Down
50 changes: 50 additions & 0 deletions test/integration/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,56 @@ TEST_P(IntegrationTest, PerWorkerStatsAndBalancing) {
check_listener_stats(0, 1);
}

// Make sure all workers pick up connections
#ifdef WIN32
// We can only guarantee this on Windows without the reuse_port changes.
TEST_P(IntegrationTest, AllWorkersAreHandlingLoad) {
concurrency_ = 2;
initialize();

std::string worker0_stat_name, worker1_stat_name;
if (GetParam() == Network::Address::IpVersion::v4) {
worker0_stat_name = "listener.127.0.0.1_0.worker_0.downstream_cx_total";
worker1_stat_name = "listener.127.0.0.1_0.worker_1.downstream_cx_total";
} else {
worker0_stat_name = "listener.[__1]_0.worker_0.downstream_cx_total";
worker1_stat_name = "listener.[__1]_0.worker_1.downstream_cx_total";
}

test_server_->waitForCounterEq(worker0_stat_name, 0);
test_server_->waitForCounterEq(worker1_stat_name, 0);

// We set the counters for the two workers to see how many connections each handles.
uint64_t w0_ctr = 0;
uint64_t w1_ctr = 0;
constexpr int loops = 5;
for (int i = 0; i < loops; i++) {
constexpr int requests_per_loop = 4;
std::array<IntegrationCodecClientPtr, requests_per_loop> connections;
for (int j = 0; j < requests_per_loop; j++) {
connections[j] = makeHttpConnection(lookupPort("http"));
}

auto worker0_ctr = test_server_->counter(worker0_stat_name);
auto worker1_ctr = test_server_->counter(worker1_stat_name);
auto target = w0_ctr + w1_ctr + requests_per_loop;
while (test_server_->counter(worker0_stat_name)->value() +
test_server_->counter(worker1_stat_name)->value() <
target) {
timeSystem().advanceTimeWait(std::chrono::milliseconds(10));
}
w0_ctr = test_server_->counter(worker0_stat_name)->value();
w1_ctr = test_server_->counter(worker1_stat_name)->value();
for (int j = 0; j < requests_per_loop; j++) {
connections[j]->close();
}
}

EXPECT_TRUE(w0_ctr > 1);
EXPECT_TRUE(w1_ctr > 1);
}
#endif

TEST_P(IntegrationTest, RouterDirectResponseWithBody) {
const std::string body = "Response body";
const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body);
Expand Down