This is an automated email from the ASF dual-hosted git repository.
bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 8bdb73ec29 Automatic caching of parsed STRING config values (#12735)
8bdb73ec29 is described below
commit 8bdb73ec298ce360e1a6dd74572a9897d445c58d
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Dec 9 10:00:11 2025 -0600
Automatic caching of parsed STRING config values (#12735)
This introduces ParsedConfigCache in HttpConfig to automatically cache
parsed results for STRING configs that require expensive parsing. When
TSHttpTxnConfigStringSet() is called for configs like
negative_caching_list, insert_forwarded, server_session_sharing.match,
negative_revalidating_list, or host_res_data (ip_resolve), the parsing
now happens once per unique value and is cached for subsequent calls.
This optimization is transparent to plugins - they call
TSHttpTxnConfigStringSet() as usual and automatically benefit from the
caching.
Fixes: #12292
---
include/proxy/http/HttpConfig.h | 73 ++++++++++++++++++++++
include/proxy/http/OverridableConfigDefs.h | 14 +++++
src/api/InkAPI.cc | 42 +++++++------
src/api/InkAPITest.cc | 8 ++-
src/proxy/http/HttpConfig.cc | 97 ++++++++++++++++++++++++++++++
src/shared/overridable_txn_vars.cc | 12 +++-
6 files changed, 225 insertions(+), 21 deletions(-)
diff --git a/include/proxy/http/HttpConfig.h b/include/proxy/http/HttpConfig.h
index bfdef446c6..85cb7ea433 100644
--- a/include/proxy/http/HttpConfig.h
+++ b/include/proxy/http/HttpConfig.h
@@ -37,9 +37,12 @@
#include <cstdio>
#include <bitset>
#include <map>
+#include <unordered_map>
#include <cctype>
#include <string_view>
#include <chrono>
+#include <functional>
+#include <variant>
#include "iocore/eventsystem/IOBuffer.h"
#include "swoc/swoc_ip.h"
@@ -54,8 +57,10 @@
#include "iocore/net/ConnectionTracker.h"
#include "iocore/net/SessionSharingAPIEnums.h"
#include "records/RecProcess.h"
+#include "tsutil/Bravo.h"
#include "tsutil/ts_ip.h"
#include "tsutil/Metrics.h"
+#include "ts/apidefs.h"
using ts::Metrics;
@@ -852,6 +857,74 @@ public:
HttpConfigParams &operator=(const HttpConfigParams &) = delete;
};
+/////////////////////////////////////////////////////////////
+//
+// class ParsedConfigCache
+//
+/////////////////////////////////////////////////////////////
+
+/** Cache for pre-parsed string config values.
+ *
+ * Some overridable STRING configs require parsing (e.g., status code lists,
+ * host resolution preferences). Parsing can be non-trivial. This cache stores
+ * parsed results so repeated calls to TSHttpTxnConfigStringSet() with the same
+ * value don't re-parse.
+ *
+ * The static lookup() method handles everything: check cache, parse if needed,
+ * store in cache, and return the result.
+ */
+class ParsedConfigCache
+{
+public:
+ /** Pre-parsed representations for configs that need special parsing.
+ *
+ * Uses std::variant since each cache entry only stores one type of parsed
+ * result. The conf_value_storage string is always present as it owns the
+ * string data that the parsed structures may reference.
+ */
+ struct ParsedValue {
+ std::string conf_value_storage{}; // Owns the string data.
+ std::variant<std::monostate, HostResData, HttpStatusCodeList,
HttpForwarded::OptionBitSet, MgmtByte> parsed{};
+ };
+
+ /** Return the parsed value for the configuration.
+ *
+ * On first call for a given (key, value) pair, parses the value and caches
it.
+ * Subsequent calls return the cached result.
+ *
+ * @param key The config key being referenced.
+ * @param value The string value to parse.
+ * @return Reference to the cached parsed value.
+ */
+ static const ParsedValue &lookup(TSOverridableConfigKey key,
std::string_view value);
+
+private:
+ ParsedConfigCache() = default;
+
+ // Enforce singleton pattern.
+ ParsedConfigCache(const ParsedConfigCache &) = delete;
+ ParsedConfigCache &operator=(const ParsedConfigCache &) = delete;
+ ParsedConfigCache(ParsedConfigCache &&) = delete;
+ ParsedConfigCache &operator=(ParsedConfigCache &&) = delete;
+
+ static ParsedConfigCache &instance();
+
+ const ParsedValue &lookup_impl(TSOverridableConfigKey key, std::string_view
value);
+ ParsedValue parse(TSOverridableConfigKey key, std::string_view value);
+
+ // Custom hash for the cache key.
+ struct CacheKeyHash {
+ std::size_t
+ operator()(const std::pair<TSOverridableConfigKey, std::string> &k) const
+ {
+ return std::hash<int>()(static_cast<int>(k.first)) ^
(std::hash<std::string>()(k.second) << 1);
+ }
+ };
+
+ std::unordered_map<std::pair<TSOverridableConfigKey, std::string>,
ParsedValue, CacheKeyHash> _cache;
+ ts::bravo::shared_mutex
_mutex;
+};
+
/////////////////////////////////////////////////////////////
//
// class HttpConfig
diff --git a/include/proxy/http/OverridableConfigDefs.h
b/include/proxy/http/OverridableConfigDefs.h
index 0d9cedfa7a..52440b40b1 100644
--- a/include/proxy/http/OverridableConfigDefs.h
+++ b/include/proxy/http/OverridableConfigDefs.h
@@ -90,6 +90,20 @@
See the X-Macro Dispatch doxygen documentation in InkAPI.cc for more details.
+ @section caching Automatic Caching for STRING Configs
+
+ Some STRING configs require parsing that is more expensive than a simple
+ string copy (e.g., parsing status code lists or host resolution preferences).
+ For these configs, TSHttpTxnConfigStringSet() automatically uses
+ ParsedConfigCache (defined in HttpConfig.h) to cache parsed results.
+
+ This means the parsing only happens once per unique (config_key, value) pair.
+ Subsequent calls with the same value use the cached result directly.
+ HTTP_NEGATIVE_CACHING_LIST is an example configuration that takes advantage
+ of this. This optimization is transparent to API users - they just call
+ TSHttpTxnConfigStringSet() as usual and get the performance benefit
+ automatically.
+
@section none_configs Note on CONV=NONE
Some SSL string configs use NONE because they bypass _conf_to_memberp()
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 9dc23d829a..9b6e2e0a7e 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -7309,6 +7309,19 @@ _conf_to_memberp(TSOverridableConfigKey conf,
OverridableHttpConfigParams *overr
conv = nullptr;
switch (conf) {
+ // This uses OVERRIDABLE_CONFIGS to generate cases for each config.
+ // For example:
+ //
+ // case TS_CONFIG_HTTP_CHUNKING_ENABLED:
+ // ret = _memberp_to_generic(&overridableHttpConfig->chunking_enabled,
conv);
+ // break;
+ // case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME:
+ // conv = &HttpDownServerCacheTimeConv;
+ // ret = &overridableHttpConfig->down_server_timeout;
+ // break;
+ //
+ // ... ~130 more cases, one per overridable config ...
+ //
OVERRIDABLE_CONFIGS(_CONF_CASE_DISPATCH)
case TS_CONFIG_NULL:
case TS_CONFIG_LAST_ENTRY:
@@ -7472,19 +7485,15 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp,
TSOverridableConfigKey conf, const char
break;
case TS_CONFIG_HTTP_INSERT_FORWARDED:
if (value && length > 0) {
- swoc::LocalBufferWriter<1024> error;
- HttpForwarded::OptionBitSet bs =
HttpForwarded::optStrToBitset(std::string_view(value, length), error);
- if (!error.size()) {
- s->t_state.my_txn_conf().insert_forwarded = bs;
- } else {
- Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
- }
+ auto &parsed =
ParsedConfigCache::lookup(conf, std::string_view(value, length));
+ s->t_state.my_txn_conf().insert_forwarded =
std::get<HttpForwarded::OptionBitSet>(parsed.parsed);
}
break;
case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH:
if (value && length > 0) {
- HttpConfig::load_server_session_sharing_match(value,
s->t_state.my_txn_conf().server_session_sharing_match);
- s->t_state.my_txn_conf().server_session_sharing_match_str =
const_cast<char *>(value);
+ auto &parsed =
ParsedConfigCache::lookup(conf, std::string_view(value, length));
+ s->t_state.my_txn_conf().server_session_sharing_match =
std::get<MgmtByte>(parsed.parsed);
+ s->t_state.my_txn_conf().server_session_sharing_match_str =
const_cast<char *>(parsed.conf_value_storage.data());
}
break;
case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY:
@@ -7527,23 +7536,22 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp,
TSOverridableConfigKey conf, const char
break;
case TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST:
if (value && length > 0) {
- OverridableHttpConfigParams *target = &s->t_state.my_txn_conf();
- target->negative_caching_list.conf_value = const_cast<char *>(value);
- return _eval_conv(target, conf, value, length);
+ auto &parsed =
ParsedConfigCache::lookup(conf, std::string_view(value, length));
+ s->t_state.my_txn_conf().negative_caching_list =
std::get<HttpStatusCodeList>(parsed.parsed);
}
break;
case TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST:
if (value && length > 0) {
- OverridableHttpConfigParams *target =
&s->t_state.my_txn_conf();
- target->negative_revalidating_list.conf_value = const_cast<char
*>(value);
- return _eval_conv(target, conf, value, length);
+ auto &parsed =
ParsedConfigCache::lookup(conf, std::string_view(value, length));
+ s->t_state.my_txn_conf().negative_revalidating_list =
std::get<HttpStatusCodeList>(parsed.parsed);
}
break;
case TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE:
if (value && length > 0) {
- s->t_state.my_txn_conf().host_res_data.conf_value = const_cast<char
*>(value);
+ auto &parsed = ParsedConfigCache::lookup(conf,
std::string_view(value, length));
+ s->t_state.my_txn_conf().host_res_data =
std::get<HostResData>(parsed.parsed);
}
- [[fallthrough]];
+ break;
default: {
if (value && length > 0) {
return _eval_conv(&(s->t_state.my_txn_conf()), conf, value, length);
diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc
index 0bb7a36887..7d2fcc27cd 100644
--- a/src/api/InkAPITest.cc
+++ b/src/api/InkAPITest.cc
@@ -8781,9 +8781,11 @@
REGRESSION_TEST(SDK_API_OVERRIDABLE_CONFIGS)(RegressionTest *test, int /* atype
case TS_RECORDDATATYPE_STRING:
TSHttpTxnConfigStringSet(txnp, key, test_string, -1);
TSHttpTxnConfigStringGet(txnp, key, &sval_read, &len);
- if (test_string != sval_read) {
- SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_FAIL,
"Failed on %s, %s != %s", conf.data(), sval_read,
- test_string);
+ // Compare string content, not pointers - the implementation may store
+ // a copy of the string (e.g., in ParsedConfigCache for efficiency).
+ if (sval_read == nullptr || std::string_view(test_string) !=
std::string_view(sval_read, len)) {
+ SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_FAIL,
"Failed on %s, %s != %s", conf.data(),
+ sval_read ? sval_read : "(null)", test_string);
success = false;
continue;
}
diff --git a/src/proxy/http/HttpConfig.cc b/src/proxy/http/HttpConfig.cc
index baac34ddf9..9276abdaef 100644
--- a/src/proxy/http/HttpConfig.cc
+++ b/src/proxy/http/HttpConfig.cc
@@ -694,6 +694,103 @@ const MgmtConverter HttpStatusCodeList::Conv{
}};
// clang-format on
+/////////////////////////////////////////////////////////////
+//
+// ParsedConfigCache implementation
+//
+/////////////////////////////////////////////////////////////
+
+ParsedConfigCache &
+ParsedConfigCache::instance()
+{
+ static ParsedConfigCache inst;
+ return inst;
+}
+
+const ParsedConfigCache::ParsedValue &
+ParsedConfigCache::lookup(TSOverridableConfigKey key, std::string_view value)
+{
+ return instance().lookup_impl(key, value);
+}
+
+const ParsedConfigCache::ParsedValue &
+ParsedConfigCache::lookup_impl(TSOverridableConfigKey key, std::string_view
value)
+{
+ auto cache_key = std::make_pair(key, std::string(value));
+
+ // Fast path: check cache under read lock.
+ {
+ ts::bravo::shared_lock lock(_mutex);
+ auto it = _cache.find(cache_key);
+ if (it != _cache.end()) {
+ return it->second;
+ }
+ }
+
+ // Slow path: parse and insert under write lock.
+ std::unique_lock lock(_mutex);
+
+ // Double-check after acquiring write lock.
+ auto it = _cache.find(cache_key);
+ if (it != _cache.end()) {
+ return it->second;
+ }
+
+ // Parse and insert.
+ auto [inserted_it, success] = _cache.emplace(cache_key, parse(key, value));
+ return inserted_it->second;
+}
+
+ParsedConfigCache::ParsedValue
+ParsedConfigCache::parse(TSOverridableConfigKey key, std::string_view value)
+{
+ ParsedValue result;
+
+ // Store the string value - the parsed structures may reference this.
+ result.conf_value_storage = std::string(value);
+
+ switch (key) {
+ case TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE: {
+ HostResData host_res_data{};
+ parse_host_res_preference(result.conf_value_storage.c_str(),
host_res_data.order);
+ host_res_data.conf_value = result.conf_value_storage.data();
+ result.parsed = host_res_data;
+ break;
+ }
+
+ case TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST:
+ case TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST: {
+ HttpStatusCodeList status_code_list{};
+ status_code_list.conf_value = result.conf_value_storage.data();
+ HttpStatusCodeList::Conv.store_string(&status_code_list,
result.conf_value_storage);
+ result.parsed = status_code_list;
+ break;
+ }
+
+ case TS_CONFIG_HTTP_INSERT_FORWARDED: {
+ swoc::LocalBufferWriter<1024> error;
+ result.parsed = HttpForwarded::optStrToBitset(result.conf_value_storage,
error);
+ if (error.size()) {
+ Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
+ }
+ break;
+ }
+
+ case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH: {
+ MgmtByte server_session_sharing_match{0};
+ HttpConfig::load_server_session_sharing_match(result.conf_value_storage,
server_session_sharing_match);
+ result.parsed = server_session_sharing_match;
+ break;
+ }
+
+ default:
+ // No special parsing needed for this config.
+ break;
+ }
+
+ return result;
+}
+
/** Template for creating conversions and initialization for @c std::chrono
based configuration variables.
*
* @tparam V The exact type of the configuration variable.
diff --git a/src/shared/overridable_txn_vars.cc
b/src/shared/overridable_txn_vars.cc
index da25503b1d..ba1b873e6e 100644
--- a/src/shared/overridable_txn_vars.cc
+++ b/src/shared/overridable_txn_vars.cc
@@ -66,14 +66,24 @@ static_assert(sizeof(xmacro_enum_order) /
sizeof(xmacro_enum_order[0]) == TS_CON
static_assert(check_xmacro_order(xmacro_enum_order),
"OVERRIDABLE_CONFIGS order must match TSOverridableConfigKey
enum order in apidefs.h.in. "
"Ensure entries are in the same order in both files.");
+// ============================================================================
+// End of compile-time validation.
+// ============================================================================
// ============================================================================
-// String-to-enum mapping generated from X-macro.
+// Configuration string name to enum and type mapping.
// ============================================================================
// clang-format off
const std::unordered_map<std::string_view, std::tuple<const
TSOverridableConfigKey, const TSRecordDataType>>
ts::Overridable_Txn_Vars({
+
+/** Use OVERRIDABLE_CONFIGS to populate the map with entries like:
+ * ...
+ * "proxy.config.http.chunking_enabled", {TS_CONFIG_HTTP_CHUNKING_ENABLED,
TS_RECORDDATATYPE_INT}
+ * "proxy.config.http.negative_caching_list",
{TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST, TS_RECORDDATATYPE_STRING}
+ * ...
+ */
#define X_TXN_VAR(CONFIG_KEY, MEMBER, RECORD_NAME, DATA_TYPE, CONV) \
{RECORD_NAME, {TS_CONFIG_##CONFIG_KEY, TS_RECORDDATATYPE_##DATA_TYPE}},
OVERRIDABLE_CONFIGS(X_TXN_VAR)