Copilot commented on code in PR #12998:
URL: https://github.com/apache/trafficserver/pull/12998#discussion_r2962510178
##########
tests/gold_tests/tls/ssl_multicert_loader.test.py:
##########
@@ -123,3 +123,45 @@
ts2.Disk.traffic_out.Content = Testers.ExcludesExpression(
'Traffic Server is fully initialized', 'process should fail when invalid
certificate specified')
ts2.Disk.diags_log.Content = Testers.IncludesExpression('EMERGENCY: failed to
load SSL certificate file', 'check diags.log"')
+
+##########################################################################
+# Verify parallel cert loading with configurable concurrency
+
+ts3 = Test.MakeATSProcess("ts3", enable_tls=True)
+server3 = Test.MakeOriginServer("server3")
+server3.addResponse("sessionlog.json", request_header, response_header)
+
+ts3.Disk.records_config.update(
+ {
+ 'proxy.config.ssl.server.cert.path': f'{ts3.Variables.SSLDir}',
+ 'proxy.config.ssl.server.private_key.path': f'{ts3.Variables.SSLDir}',
+ 'proxy.config.ssl.server.multicert.concurrency': 4,
+ })
Review Comment:
The test section claims to verify configurable concurrency, but
`SSLMultiCertConfigLoader::load(..., firstLoad=true)` ignores
`proxy.config.ssl.server.multicert.concurrency` and uses
`hardware_concurrency()` instead. This makes the assertion on the "loading 2
certs with" log line dependent on the CI host having >1 hardware thread (it
will fail on single-core runners) and it doesn't actually validate the reload
behavior that the config controls. Consider restructuring this test to trigger
a `traffic_ctl config reload` and validate the threaded log line during reload
(where the configured concurrency is applied), or otherwise gate/skip when only
1 core is available.
##########
src/iocore/net/SSLUtils.cc:
##########
@@ -1735,10 +1745,69 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
}
swoc::Errata errata(ERRATA_NOTE);
- int item_num = 0;
- for (const auto &item : parse_result.value) {
+ int num_threads = params->configLoadConcurrency;
+ if (num_threads == 0 || firstLoad) {
+ num_threads = std::thread::hardware_concurrency();
+ }
Review Comment:
`std::thread::hardware_concurrency()` can return very large values on big
systems. Since the new record validation caps user input to 256 (`[0-256]`),
consider clamping the computed thread count to the same maximum (both here and
for the `firstLoad` path) so "auto" doesn't spawn hundreds of threads
unexpectedly.
##########
src/iocore/net/SSLUtils.cc:
##########
@@ -1528,11 +1529,20 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup
*lookup, const shared_SSL
SSLMultiCertConfigLoader::CertLoadData data;
if (!this->_prep_ssl_ctx(sslMultCertSettings, data, common_names,
unique_names)) {
- lookup->is_valid = false;
+ {
+ std::lock_guard<std::mutex> lock(_loader_mutex);
+ lookup->is_valid = false;
+ }
return false;
}
std::vector<SSLLoadingContext> ctxs = this->init_server_ssl_ctx(data,
sslMultCertSettings.get());
+
+ // Serialize all mutations to the shared SSLCertLookup.
+ // The expensive work above (_prep_ssl_ctx + init_server_ssl_ctx) runs
+ // without the lock, allowing parallel cert loading across threads.
+ std::lock_guard<std::mutex> lock(_loader_mutex);
+
Review Comment:
The mutex scope in `_store_ssl_ctx` currently covers more than just
`SSLCertLookup` mutations. In particular, the lock is held across the later
`unique_names` processing (which calls `init_server_ssl_ctx()` again), so some
expensive SSL_CTX creation/cert loading work will still run under the lock,
limiting parallelism and contradicting the comment here. Consider narrowing the
lock to only the sections that mutate `lookup` (e.g., lock around
`_store_single_ssl_ctx()` / `register_cert_secrets()`), and do any
`init_server_ssl_ctx()` work outside the critical section.
##########
src/iocore/net/SSLConfig.cc:
##########
@@ -49,6 +49,7 @@
#include <array>
#include <cstring>
#include <cmath>
+#include <thread>
#include <unordered_map>
Review Comment:
`SSLConfigParams::initialize()` now uses `std::max` with
`std::thread::hardware_concurrency()`. Please ensure `<algorithm>` is included
in this translation unit (to avoid relying on transitive headers) since
`std::max` is defined there.
##########
src/iocore/net/SSLUtils.cc:
##########
@@ -1735,10 +1745,69 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
}
swoc::Errata errata(ERRATA_NOTE);
- int item_num = 0;
- for (const auto &item : parse_result.value) {
+ int num_threads = params->configLoadConcurrency;
+ if (num_threads == 0 || firstLoad) {
+ num_threads = std::thread::hardware_concurrency();
+ }
+ if (num_threads < 1) {
+ num_threads = 1;
+ }
+ num_threads = std::min(num_threads,
static_cast<int>(parse_result.value.size()));
+
Review Comment:
This new code uses `std::min` but the file does not include `<algorithm>`,
which makes the build depend on transitive includes. Please add the proper
header so this compiles reliably across toolchains/stdlibs.
##########
src/iocore/net/SSLUtils.cc:
##########
@@ -1735,10 +1745,69 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
}
swoc::Errata errata(ERRATA_NOTE);
- int item_num = 0;
- for (const auto &item : parse_result.value) {
+ int num_threads = params->configLoadConcurrency;
+ if (num_threads == 0 || firstLoad) {
+ num_threads = std::thread::hardware_concurrency();
+ }
+ if (num_threads < 1) {
+ num_threads = 1;
+ }
+ num_threads = std::min(num_threads,
static_cast<int>(parse_result.value.size()));
+
+ if (num_threads > 1 && parse_result.value.size() > 1) {
+ std::size_t bucket_size = parse_result.value.size() / num_threads;
+ std::size_t remainder = parse_result.value.size() % num_threads;
+ auto current = parse_result.value.cbegin();
+
+ std::vector<std::thread> threads;
+ Note("(%s) loading %zu certs with %d threads", this->_debug_tag(),
parse_result.value.size(), num_threads);
+
+ for (int t = 0; t < num_threads; ++t) {
+ std::size_t this_bucket = bucket_size + (static_cast<std::size_t>(t) <
remainder ? 1 : 0);
+ auto end = current + this_bucket;
+ threads.emplace_back(&SSLMultiCertConfigLoader::_load_items, this,
lookup, current, end, std::ref(errata));
+ current = end;
+ }
+
+ for (auto &th : threads) {
+ th.join();
+ }
+
+ Note("(%s) loaded %zu certs in %d threads", this->_debug_tag(),
parse_result.value.size(), num_threads);
+ } else {
+ _load_items(lookup, parse_result.value.cbegin(),
parse_result.value.cend(), errata);
+ Note("(%s) loaded %zu certs (single-threaded)", this->_debug_tag(),
parse_result.value.size());
+ }
+
+ // We *must* have a default context even if it can't possibly work. The
default context is used to
+ // bootstrap the SSL handshake so that we can subsequently do the SNI lookup
to switch to the real
+ // context.
+ if (lookup->ssl_default == nullptr) {
+ shared_SSLMultiCertConfigParams sslMultiCertSettings(new
SSLMultiCertConfigParams);
+ sslMultiCertSettings->addr = ats_strdup("*");
+ if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) {
+ errata.note(ERRATA_ERROR, "failed set default context");
+ }
+ }
+
+ return errata;
+}
+
+void
+SSLMultiCertConfigLoader::_load_items(SSLCertLookup *lookup,
config::SSLMultiCertConfig::const_iterator begin,
+
config::SSLMultiCertConfig::const_iterator end, swoc::Errata &errata)
+{
+ // Each thread needs its own elevated privileges since POSIX capabilities
are per-thread
+ uint32_t elevate_setting = 0;
+ elevate_setting =
RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0);
+ ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE
: 0);
+
+ int item_num = 0;
+ for (auto it = begin; it != end; ++it) {
item_num++;
+ const auto &item = *it;
Review Comment:
`_load_items()` resets `item_num` to 0 for each thread bucket, so the errata
messages that reference "item {}" no longer correspond to the actual (global)
item index in `ssl_multicert.yaml` when loading in parallel (duplicate item
numbers across threads). Consider computing the base index from `begin` (e.g.,
distance from `parse_result.value.cbegin()`) and reporting a stable/global item
number.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]