4848#endif
4949#include " swoc/swoc_file.h"
5050#include " swoc/Errata.h"
51+
52+ #include < thread>
53+ #include < tuple>
5154#include < openssl/asn1.h>
5255#include < openssl/bio.h>
5356#include < openssl/bn.h>
@@ -1662,11 +1665,20 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL
16621665 SSLMultiCertConfigLoader::CertLoadData data;
16631666
16641667 if (!this ->_prep_ssl_ctx (sslMultCertSettings, data, common_names, unique_names)) {
1665- lookup->is_valid = false ;
1668+ {
1669+ std::lock_guard<std::mutex> lock (_loader_mutex);
1670+ lookup->is_valid = false ;
1671+ }
16661672 return false ;
16671673 }
16681674
16691675 std::vector<SSLLoadingContext> ctxs = this ->init_server_ssl_ctx (data, sslMultCertSettings.get ());
1676+
1677+ // Serialize all mutations to the shared SSLCertLookup.
1678+ // The expensive work above (_prep_ssl_ctx + init_server_ssl_ctx) runs
1679+ // without the lock, allowing parallel cert loading across threads.
1680+ std::lock_guard<std::mutex> lock (_loader_mutex);
1681+
16701682 for (const auto &loadingctx : ctxs) {
16711683 if (!sslMultCertSettings ||
16721684 !this ->_store_single_ssl_ctx (lookup, sslMultCertSettings, shared_SSL_CTX{loadingctx.ctx , SSL_CTX_free}, loadingctx.ctx_type ,
@@ -1916,17 +1928,49 @@ ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams
19161928 return true ;
19171929}
19181930
1919- swoc::Errata
1920- SSLMultiCertConfigLoader::load (SSLCertLookup *lookup)
1931+ void
1932+ SSLMultiCertConfigLoader::_load_lines (SSLCertLookup *lookup, SSLConfigLines::const_iterator begin,
1933+ SSLConfigLines::const_iterator end, swoc::Errata &errata)
19211934{
1922- const SSLConfigParams *params = this ->_params ;
1935+ const matcher_tags sslCertTags = {nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , false };
1936+ const SSLConfigParams *params = this ->_params ;
1937+
1938+ // Each thread needs its own ElevateAccess since POSIX capabilities
1939+ // are per-thread and don't propagate to spawned threads.
1940+ uint32_t elevate_setting = RecGetRecordInt (" proxy.config.ssl.cert.load_elevated" ).value_or (0 );
1941+ ElevateAccess elevate_access (elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0 );
19231942
1924- char *tok_state = nullptr ;
1925- char *line = nullptr ;
1926- unsigned line_num = 0 ;
1927- matcher_line line_info;
1943+ for (auto it = begin; it != end; ++it) {
1944+ auto &[line, line_num] = *it;
19281945
1929- const matcher_tags sslCertTags = {nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , false };
1946+ shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
1947+ matcher_line line_info;
1948+ const char *errPtr;
1949+
1950+ errPtr = parseConfigLine (line, &line_info, &sslCertTags);
1951+ Dbg (dbg_ctl_ssl_load, " currently parsing %s at line %d from config file: %s" , line, line_num, params->configFilePath );
1952+ if (errPtr != nullptr ) {
1953+ Warning (" %s: discarding %s entry at line %d: %s" , __func__, params->configFilePath , line_num, errPtr);
1954+ } else {
1955+ if (ssl_extract_certificate (&line_info, sslMultiCertSettings.get ())) {
1956+ if (sslMultiCertSettings->cert || sslMultiCertSettings->opt != SSLCertContextOption::OPT_TUNNEL) {
1957+ if (!this ->_store_ssl_ctx (lookup, sslMultiCertSettings)) {
1958+ std::lock_guard<std::mutex> lock (_loader_mutex);
1959+ errata.note (ERRATA_ERROR, " Failed to load certificate on line {}" , line_num);
1960+ }
1961+ } else {
1962+ std::lock_guard<std::mutex> lock (_loader_mutex);
1963+ errata.note (ERRATA_WARN, " No ssl_cert_name specified and no tunnel action set on line {}" , line_num);
1964+ }
1965+ }
1966+ }
1967+ }
1968+ }
1969+
1970+ swoc::Errata
1971+ SSLMultiCertConfigLoader::load (SSLCertLookup *lookup, bool firstLoad)
1972+ {
1973+ const SSLConfigParams *params = this ->_params ;
19301974
19311975 Note (" (%s) %s loading ..." , this ->_debug_tag (), ts::filename::SSL_MULTICERT);
19321976
@@ -1935,7 +1979,6 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
19351979 if (ec) {
19361980 switch (ec.value ()) {
19371981 case ENOENT:
1938- // missing config file is an acceptable runtime state
19391982 return swoc::Errata (ERRATA_WARN, " Cannot open SSL certificate configuration \" {}\" - {}" , params->configFilePath , ec);
19401983 default :
19411984 return swoc::Errata (ERRATA_ERROR, " Failed to read SSL certificate configuration from \" {}\" - {}" , params->configFilePath ,
@@ -1949,8 +1992,13 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
19491992 elevate_setting = RecGetRecordInt (" proxy.config.ssl.cert.load_elevated" ).value_or (0 );
19501993 ElevateAccess elevate_access (elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0 );
19511994
1995+ // Collect all non-empty, non-comment lines for processing
1996+ char *tok_state = nullptr ;
1997+ char *line = nullptr ;
1998+ unsigned line_num = 0 ;
1999+ SSLConfigLines config_lines;
2000+
19522001 line = tokLine (content.data (), &tok_state);
1953- swoc::Errata errata (ERRATA_NOTE);
19542002 while (line != nullptr ) {
19552003 line_num++;
19562004
@@ -1960,28 +2008,49 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
19602008 }
19612009
19622010 if (*line != ' \0 ' && *line != ' #' ) {
1963- shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
1964- const char *errPtr;
2011+ config_lines.emplace_back (line, line_num);
2012+ }
2013+ line = tokLine (nullptr , &tok_state);
2014+ }
19652015
1966- errPtr = parseConfigLine (line, &line_info, &sslCertTags);
1967- Dbg (dbg_ctl_ssl_load, " currently parsing %s at line %d from config file: %s" , line, line_num, params->configFilePath );
1968- if (errPtr != nullptr ) {
1969- Warning (" %s: discarding %s entry at line %d: %s" , __func__, params->configFilePath , line_num, errPtr);
1970- } else {
1971- if (ssl_extract_certificate (&line_info, sslMultiCertSettings.get ())) {
1972- // There must be a certificate specified unless the tunnel action is set
1973- if (sslMultiCertSettings->cert || sslMultiCertSettings->opt != SSLCertContextOption::OPT_TUNNEL) {
1974- if (!this ->_store_ssl_ctx (lookup, sslMultiCertSettings)) {
1975- errata.note (ERRATA_ERROR, " Failed to load certificate on line {}" , line_num);
1976- }
1977- } else {
1978- errata.note (ERRATA_WARN, " No ssl_cert_name specified and no tunnel action set on line {}" , line_num);
1979- }
1980- }
1981- }
2016+ swoc::Errata errata (ERRATA_NOTE);
2017+
2018+ if (params->configLoadConcurrency > 1 && config_lines.size () > 1 ) {
2019+ // On first load (no traffic yet), allow more threads for faster startup
2020+ int num_threads = params->configLoadConcurrency ;
2021+ if (firstLoad) {
2022+ num_threads = std::max (static_cast <int >(std::thread::hardware_concurrency ()), num_threads);
19822023 }
19832024
1984- line = tokLine (nullptr , &tok_state);
2025+ // Don't spawn more threads than lines
2026+ num_threads = std::min (num_threads, static_cast <int >(config_lines.size ()));
2027+
2028+ std::size_t bucket_size = config_lines.size () / num_threads;
2029+ std::size_t remainder = config_lines.size () % num_threads;
2030+ SSLConfigLines::const_iterator current = config_lines.cbegin ();
2031+ std::vector<std::thread> threads;
2032+
2033+ Note (" (%s) loading %zu certs with %d threads" , this ->_debug_tag (), config_lines.size (), num_threads);
2034+
2035+ for (int t = 0 ; t < num_threads; ++t) {
2036+ // Distribute remainder lines across the first threads
2037+ std::size_t this_bucket = bucket_size + (static_cast <std::size_t >(t) < remainder ? 1 : 0 );
2038+ SSLConfigLines::const_iterator end = current + this_bucket;
2039+
2040+ threads.emplace_back (&SSLMultiCertConfigLoader::_load_lines, this , lookup, current, end, std::ref (errata));
2041+ current = end;
2042+ }
2043+
2044+ for (auto &th : threads) {
2045+ th.join ();
2046+ }
2047+
2048+ Note (" (%s) loaded %zu certs in %d threads" , this ->_debug_tag (), config_lines.size (), num_threads);
2049+ } else {
2050+ // Single-threaded path (concurrency == 1 or only 1 line)
2051+ this ->_load_lines (lookup, config_lines.cbegin (), config_lines.cend (), errata);
2052+
2053+ Note (" (%s) loaded %zu certs (single-threaded)" , this ->_debug_tag (), config_lines.size ());
19852054 }
19862055
19872056 // We *must* have a default context even if it can't possibly work. The default context is used to
0 commit comments