Skip to content

Commit 8bdb73e

Browse files
authored
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
1 parent 68e4bd3 commit 8bdb73e

File tree

6 files changed

+225
-21
lines changed

6 files changed

+225
-21
lines changed

include/proxy/http/HttpConfig.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@
3737
#include <cstdio>
3838
#include <bitset>
3939
#include <map>
40+
#include <unordered_map>
4041
#include <cctype>
4142
#include <string_view>
4243
#include <chrono>
44+
#include <functional>
45+
#include <variant>
4346

4447
#include "iocore/eventsystem/IOBuffer.h"
4548
#include "swoc/swoc_ip.h"
@@ -54,8 +57,10 @@
5457
#include "iocore/net/ConnectionTracker.h"
5558
#include "iocore/net/SessionSharingAPIEnums.h"
5659
#include "records/RecProcess.h"
60+
#include "tsutil/Bravo.h"
5761
#include "tsutil/ts_ip.h"
5862
#include "tsutil/Metrics.h"
63+
#include "ts/apidefs.h"
5964

6065
using ts::Metrics;
6166

@@ -852,6 +857,74 @@ struct HttpConfigParams : public ConfigInfo {
852857
HttpConfigParams &operator=(const HttpConfigParams &) = delete;
853858
};
854859

860+
/////////////////////////////////////////////////////////////
861+
//
862+
// class ParsedConfigCache
863+
//
864+
/////////////////////////////////////////////////////////////
865+
866+
/** Cache for pre-parsed string config values.
867+
*
868+
* Some overridable STRING configs require parsing (e.g., status code lists,
869+
* host resolution preferences). Parsing can be non-trivial. This cache stores
870+
* parsed results so repeated calls to TSHttpTxnConfigStringSet() with the same
871+
* value don't re-parse.
872+
*
873+
* The static lookup() method handles everything: check cache, parse if needed,
874+
* store in cache, and return the result.
875+
*/
876+
class ParsedConfigCache
877+
{
878+
public:
879+
/** Pre-parsed representations for configs that need special parsing.
880+
*
881+
* Uses std::variant since each cache entry only stores one type of parsed
882+
* result. The conf_value_storage string is always present as it owns the
883+
* string data that the parsed structures may reference.
884+
*/
885+
struct ParsedValue {
886+
std::string conf_value_storage{}; // Owns the string data.
887+
std::variant<std::monostate, HostResData, HttpStatusCodeList, HttpForwarded::OptionBitSet, MgmtByte> parsed{};
888+
};
889+
890+
/** Return the parsed value for the configuration.
891+
*
892+
* On first call for a given (key, value) pair, parses the value and caches it.
893+
* Subsequent calls return the cached result.
894+
*
895+
* @param key The config key being referenced.
896+
* @param value The string value to parse.
897+
* @return Reference to the cached parsed value.
898+
*/
899+
static const ParsedValue &lookup(TSOverridableConfigKey key, std::string_view value);
900+
901+
private:
902+
ParsedConfigCache() = default;
903+
904+
// Enforce singleton pattern.
905+
ParsedConfigCache(const ParsedConfigCache &) = delete;
906+
ParsedConfigCache &operator=(const ParsedConfigCache &) = delete;
907+
ParsedConfigCache(ParsedConfigCache &&) = delete;
908+
ParsedConfigCache &operator=(ParsedConfigCache &&) = delete;
909+
910+
static ParsedConfigCache &instance();
911+
912+
const ParsedValue &lookup_impl(TSOverridableConfigKey key, std::string_view value);
913+
ParsedValue parse(TSOverridableConfigKey key, std::string_view value);
914+
915+
// Custom hash for the cache key.
916+
struct CacheKeyHash {
917+
std::size_t
918+
operator()(const std::pair<TSOverridableConfigKey, std::string> &k) const
919+
{
920+
return std::hash<int>()(static_cast<int>(k.first)) ^ (std::hash<std::string>()(k.second) << 1);
921+
}
922+
};
923+
924+
std::unordered_map<std::pair<TSOverridableConfigKey, std::string>, ParsedValue, CacheKeyHash> _cache;
925+
ts::bravo::shared_mutex _mutex;
926+
};
927+
855928
/////////////////////////////////////////////////////////////
856929
//
857930
// class HttpConfig

include/proxy/http/OverridableConfigDefs.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@
9090
9191
See the X-Macro Dispatch doxygen documentation in InkAPI.cc for more details.
9292
93+
@section caching Automatic Caching for STRING Configs
94+
95+
Some STRING configs require parsing that is more expensive than a simple
96+
string copy (e.g., parsing status code lists or host resolution preferences).
97+
For these configs, TSHttpTxnConfigStringSet() automatically uses
98+
ParsedConfigCache (defined in HttpConfig.h) to cache parsed results.
99+
100+
This means the parsing only happens once per unique (config_key, value) pair.
101+
Subsequent calls with the same value use the cached result directly.
102+
HTTP_NEGATIVE_CACHING_LIST is an example configuration that takes advantage
103+
of this. This optimization is transparent to API users - they just call
104+
TSHttpTxnConfigStringSet() as usual and get the performance benefit
105+
automatically.
106+
93107
@section none_configs Note on CONV=NONE
94108
95109
Some SSL string configs use NONE because they bypass _conf_to_memberp()

src/api/InkAPI.cc

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7309,6 +7309,19 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr
73097309
conv = nullptr;
73107310

73117311
switch (conf) {
7312+
// This uses OVERRIDABLE_CONFIGS to generate cases for each config.
7313+
// For example:
7314+
//
7315+
// case TS_CONFIG_HTTP_CHUNKING_ENABLED:
7316+
// ret = _memberp_to_generic(&overridableHttpConfig->chunking_enabled, conv);
7317+
// break;
7318+
// case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME:
7319+
// conv = &HttpDownServerCacheTimeConv;
7320+
// ret = &overridableHttpConfig->down_server_timeout;
7321+
// break;
7322+
//
7323+
// ... ~130 more cases, one per overridable config ...
7324+
//
73127325
OVERRIDABLE_CONFIGS(_CONF_CASE_DISPATCH)
73137326
case TS_CONFIG_NULL:
73147327
case TS_CONFIG_LAST_ENTRY:
@@ -7472,19 +7485,15 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
74727485
break;
74737486
case TS_CONFIG_HTTP_INSERT_FORWARDED:
74747487
if (value && length > 0) {
7475-
swoc::LocalBufferWriter<1024> error;
7476-
HttpForwarded::OptionBitSet bs = HttpForwarded::optStrToBitset(std::string_view(value, length), error);
7477-
if (!error.size()) {
7478-
s->t_state.my_txn_conf().insert_forwarded = bs;
7479-
} else {
7480-
Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
7481-
}
7488+
auto &parsed = ParsedConfigCache::lookup(conf, std::string_view(value, length));
7489+
s->t_state.my_txn_conf().insert_forwarded = std::get<HttpForwarded::OptionBitSet>(parsed.parsed);
74827490
}
74837491
break;
74847492
case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH:
74857493
if (value && length > 0) {
7486-
HttpConfig::load_server_session_sharing_match(value, s->t_state.my_txn_conf().server_session_sharing_match);
7487-
s->t_state.my_txn_conf().server_session_sharing_match_str = const_cast<char *>(value);
7494+
auto &parsed = ParsedConfigCache::lookup(conf, std::string_view(value, length));
7495+
s->t_state.my_txn_conf().server_session_sharing_match = std::get<MgmtByte>(parsed.parsed);
7496+
s->t_state.my_txn_conf().server_session_sharing_match_str = const_cast<char *>(parsed.conf_value_storage.data());
74887497
}
74897498
break;
74907499
case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY:
@@ -7527,23 +7536,22 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
75277536
break;
75287537
case TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST:
75297538
if (value && length > 0) {
7530-
OverridableHttpConfigParams *target = &s->t_state.my_txn_conf();
7531-
target->negative_caching_list.conf_value = const_cast<char *>(value);
7532-
return _eval_conv(target, conf, value, length);
7539+
auto &parsed = ParsedConfigCache::lookup(conf, std::string_view(value, length));
7540+
s->t_state.my_txn_conf().negative_caching_list = std::get<HttpStatusCodeList>(parsed.parsed);
75337541
}
75347542
break;
75357543
case TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST:
75367544
if (value && length > 0) {
7537-
OverridableHttpConfigParams *target = &s->t_state.my_txn_conf();
7538-
target->negative_revalidating_list.conf_value = const_cast<char *>(value);
7539-
return _eval_conv(target, conf, value, length);
7545+
auto &parsed = ParsedConfigCache::lookup(conf, std::string_view(value, length));
7546+
s->t_state.my_txn_conf().negative_revalidating_list = std::get<HttpStatusCodeList>(parsed.parsed);
75407547
}
75417548
break;
75427549
case TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE:
75437550
if (value && length > 0) {
7544-
s->t_state.my_txn_conf().host_res_data.conf_value = const_cast<char *>(value);
7551+
auto &parsed = ParsedConfigCache::lookup(conf, std::string_view(value, length));
7552+
s->t_state.my_txn_conf().host_res_data = std::get<HostResData>(parsed.parsed);
75457553
}
7546-
[[fallthrough]];
7554+
break;
75477555
default: {
75487556
if (value && length > 0) {
75497557
return _eval_conv(&(s->t_state.my_txn_conf()), conf, value, length);

src/api/InkAPITest.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8781,9 +8781,11 @@ REGRESSION_TEST(SDK_API_OVERRIDABLE_CONFIGS)(RegressionTest *test, int /* atype
87818781
case TS_RECORDDATATYPE_STRING:
87828782
TSHttpTxnConfigStringSet(txnp, key, test_string, -1);
87838783
TSHttpTxnConfigStringGet(txnp, key, &sval_read, &len);
8784-
if (test_string != sval_read) {
8785-
SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_FAIL, "Failed on %s, %s != %s", conf.data(), sval_read,
8786-
test_string);
8784+
// Compare string content, not pointers - the implementation may store
8785+
// a copy of the string (e.g., in ParsedConfigCache for efficiency).
8786+
if (sval_read == nullptr || std::string_view(test_string) != std::string_view(sval_read, len)) {
8787+
SDK_RPRINT(test, "TSHttpTxnConfigStringSet", "TestCase1", TC_FAIL, "Failed on %s, %s != %s", conf.data(),
8788+
sval_read ? sval_read : "(null)", test_string);
87878789
success = false;
87888790
continue;
87898791
}

src/proxy/http/HttpConfig.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,103 @@ const MgmtConverter HttpStatusCodeList::Conv{
694694
}};
695695
// clang-format on
696696

697+
/////////////////////////////////////////////////////////////
698+
//
699+
// ParsedConfigCache implementation
700+
//
701+
/////////////////////////////////////////////////////////////
702+
703+
ParsedConfigCache &
704+
ParsedConfigCache::instance()
705+
{
706+
static ParsedConfigCache inst;
707+
return inst;
708+
}
709+
710+
const ParsedConfigCache::ParsedValue &
711+
ParsedConfigCache::lookup(TSOverridableConfigKey key, std::string_view value)
712+
{
713+
return instance().lookup_impl(key, value);
714+
}
715+
716+
const ParsedConfigCache::ParsedValue &
717+
ParsedConfigCache::lookup_impl(TSOverridableConfigKey key, std::string_view value)
718+
{
719+
auto cache_key = std::make_pair(key, std::string(value));
720+
721+
// Fast path: check cache under read lock.
722+
{
723+
ts::bravo::shared_lock lock(_mutex);
724+
auto it = _cache.find(cache_key);
725+
if (it != _cache.end()) {
726+
return it->second;
727+
}
728+
}
729+
730+
// Slow path: parse and insert under write lock.
731+
std::unique_lock lock(_mutex);
732+
733+
// Double-check after acquiring write lock.
734+
auto it = _cache.find(cache_key);
735+
if (it != _cache.end()) {
736+
return it->second;
737+
}
738+
739+
// Parse and insert.
740+
auto [inserted_it, success] = _cache.emplace(cache_key, parse(key, value));
741+
return inserted_it->second;
742+
}
743+
744+
ParsedConfigCache::ParsedValue
745+
ParsedConfigCache::parse(TSOverridableConfigKey key, std::string_view value)
746+
{
747+
ParsedValue result;
748+
749+
// Store the string value - the parsed structures may reference this.
750+
result.conf_value_storage = std::string(value);
751+
752+
switch (key) {
753+
case TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE: {
754+
HostResData host_res_data{};
755+
parse_host_res_preference(result.conf_value_storage.c_str(), host_res_data.order);
756+
host_res_data.conf_value = result.conf_value_storage.data();
757+
result.parsed = host_res_data;
758+
break;
759+
}
760+
761+
case TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST:
762+
case TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST: {
763+
HttpStatusCodeList status_code_list{};
764+
status_code_list.conf_value = result.conf_value_storage.data();
765+
HttpStatusCodeList::Conv.store_string(&status_code_list, result.conf_value_storage);
766+
result.parsed = status_code_list;
767+
break;
768+
}
769+
770+
case TS_CONFIG_HTTP_INSERT_FORWARDED: {
771+
swoc::LocalBufferWriter<1024> error;
772+
result.parsed = HttpForwarded::optStrToBitset(result.conf_value_storage, error);
773+
if (error.size()) {
774+
Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
775+
}
776+
break;
777+
}
778+
779+
case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH: {
780+
MgmtByte server_session_sharing_match{0};
781+
HttpConfig::load_server_session_sharing_match(result.conf_value_storage, server_session_sharing_match);
782+
result.parsed = server_session_sharing_match;
783+
break;
784+
}
785+
786+
default:
787+
// No special parsing needed for this config.
788+
break;
789+
}
790+
791+
return result;
792+
}
793+
697794
/** Template for creating conversions and initialization for @c std::chrono based configuration variables.
698795
*
699796
* @tparam V The exact type of the configuration variable.

src/shared/overridable_txn_vars.cc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,24 @@ static_assert(sizeof(xmacro_enum_order) / sizeof(xmacro_enum_order[0]) == TS_CON
6666
static_assert(check_xmacro_order(xmacro_enum_order),
6767
"OVERRIDABLE_CONFIGS order must match TSOverridableConfigKey enum order in apidefs.h.in. "
6868
"Ensure entries are in the same order in both files.");
69+
// ============================================================================
70+
// End of compile-time validation.
71+
// ============================================================================
6972

7073
// ============================================================================
71-
// String-to-enum mapping generated from X-macro.
74+
// Configuration string name to enum and type mapping.
7275
// ============================================================================
7376

7477
// clang-format off
7578
const std::unordered_map<std::string_view, std::tuple<const TSOverridableConfigKey, const TSRecordDataType>>
7679
ts::Overridable_Txn_Vars({
80+
81+
/** Use OVERRIDABLE_CONFIGS to populate the map with entries like:
82+
* ...
83+
* "proxy.config.http.chunking_enabled", {TS_CONFIG_HTTP_CHUNKING_ENABLED, TS_RECORDDATATYPE_INT}
84+
* "proxy.config.http.negative_caching_list", {TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST, TS_RECORDDATATYPE_STRING}
85+
* ...
86+
*/
7787
#define X_TXN_VAR(CONFIG_KEY, MEMBER, RECORD_NAME, DATA_TYPE, CONV) \
7888
{RECORD_NAME, {TS_CONFIG_##CONFIG_KEY, TS_RECORDDATATYPE_##DATA_TYPE}},
7989
OVERRIDABLE_CONFIGS(X_TXN_VAR)

0 commit comments

Comments
 (0)