java-topology/docs/tickets/envoy-0001-previous-hosts-retry-predicate-linear-scan.md
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

77 lines
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# envoy-0001: PreviousHostsRetryPredicate std::find O(n) per retry attempt → O(n²)
**Target:** envoyproxy/envoy
**Severity:** HIGH
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
**File:** `source/extensions/retry/host/previous_hosts/previous_hosts.h`
**Status:** PATCHED
## Description
`PreviousHostsRetryPredicate::shouldSelectAnotherHost()` performs `std::find` on
`attempted_hosts_` (a `std::vector<HostDescription const*>`). This method is
called by the load balancer inside a host-selection retry loop
(`ZoneAwareLoadBalancerBase::chooseHost`) for every candidate host on every
retry attempt. With R retries and H previously-attempted hosts, the total work
is O(R × H) comparisons — O(n²) in the worst case where max_attempts equals
the cluster size.
| Location | Pattern |
|----------|---------|
| `previous_hosts.h:10` | `std::find(attempted_hosts_.begin(), attempted_hosts_.end(), &candidate_host)` |
| `load_balancer_impl.cc:668` | outer loop: `for (size_t i = 0; i < max_attempts; ++i)` calling `shouldSelectAnotherHost` |
| `thread_aware_lb_impl.cc:232` | same pattern in thread-aware variant |
## Root cause
```cpp
// previous_hosts.h — O(n) scan per call
bool shouldSelectAnotherHost(const Upstream::Host& candidate_host) override {
return std::find(attempted_hosts_.begin(), attempted_hosts_.end(),
&candidate_host) != attempted_hosts_.end();
}
void onHostAttempted(Upstream::HostDescriptionConstSharedPtr attempted_host) override {
attempted_hosts_.emplace_back(attempted_host.get());
}
private:
std::vector<Upstream::HostDescription const*> attempted_hosts_;
```
Caller loop in `load_balancer_impl.cc:658`:
```cpp
const size_t max_attempts = context ? context->hostSelectionRetryCount() + 1 : 1;
for (size_t i = 0; i < max_attempts; ++i) {
host = chooseHostOnce(context);
if (!host || !context || !context->shouldSelectAnotherHost(*host)) {
return host; // O(attempted_hosts) per iteration
}
}
```
With a cluster of N=100 endpoints and max_attempts=100, a single load-balancing
decision may require up to 100 × 100 = 10,000 pointer comparisons. In a busy
proxy handling thousands of requests/second, this compounds.
## Fix
Replace `attempted_hosts_` vector with `absl::flat_hash_set<>` or
`std::unordered_set<>`. Pointer identity is the key, so no custom hash is
needed — pointer addresses hash trivially.
```cpp
#include "absl/container/flat_hash_set.h"
bool shouldSelectAnotherHost(const Upstream::Host& candidate_host) override {
return attempted_hosts_.contains(&candidate_host); // O(1)
}
void onHostAttempted(Upstream::HostDescriptionConstSharedPtr attempted_host) override {
attempted_hosts_.insert(attempted_host.get()); // O(1) amortized
}
private:
absl::flat_hash_set<Upstream::HostDescription const*> attempted_hosts_;
```
## Ops/ns numbers (Java benchmark)
See `defects/envoy/unit/EnvoyTest.java`.
At R=100 retries, H=100 hosts: slow ~5050 ops, fast ~100 ops → >50× speedup.