Skip to content

Commit 4df9b0c

Browse files
committed
Automatic caching of parsed STRING config values
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 671f6ae commit 4df9b0c

File tree

5 files changed

+208
-18
lines changed

5 files changed

+208
-18
lines changed

include/proxy/http/HttpConfig.h

Lines changed: 70 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 <shared_mutex>
45+
#include <functional>
4346

4447
#include "iocore/eventsystem/IOBuffer.h"
4548
#include "swoc/swoc_ip.h"
@@ -56,6 +59,7 @@
5659
#include "records/RecProcess.h"
5760
#include "tsutil/ts_ip.h"
5861
#include "tsutil/Metrics.h"
62+
#include "ts/apidefs.h"
5963

6064
using ts::Metrics;
6165

@@ -852,6 +856,72 @@ struct HttpConfigParams : public ConfigInfo {
852856
HttpConfigParams &operator=(const HttpConfigParams &) = delete;
853857
};
854858

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,94 @@ 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+
std::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+
parse_host_res_preference(result.conf_value_storage.c_str(), result.host_res_data.order);
755+
result.host_res_data.conf_value = result.conf_value_storage.data();
756+
break;
757+
758+
case TS_CONFIG_HTTP_NEGATIVE_CACHING_LIST:
759+
case TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST:
760+
result.status_code_list.conf_value = result.conf_value_storage.data();
761+
HttpStatusCodeList::Conv.store_string(&result.status_code_list, result.conf_value_storage);
762+
break;
763+
764+
case TS_CONFIG_HTTP_INSERT_FORWARDED: {
765+
swoc::LocalBufferWriter<1024> error;
766+
result.forwarded_bitset = HttpForwarded::optStrToBitset(result.conf_value_storage, error);
767+
if (error.size()) {
768+
Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
769+
}
770+
break;
771+
}
772+
773+
case TS_CONFIG_HTTP_SERVER_SESSION_SHARING_MATCH:
774+
HttpConfig::load_server_session_sharing_match(result.conf_value_storage, result.server_session_sharing_match);
775+
break;
776+
777+
default:
778+
// No special parsing needed for this config.
779+
break;
780+
}
781+
782+
return result;
783+
}
784+
697785
/** Template for creating conversions and initialization for @c std::chrono based configuration variables.
698786
*
699787
* @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)