1313#include " source/common/buffer/buffer_impl.h"
1414#include " source/common/common/assert.h"
1515#include " source/common/common/enum_to_int.h"
16+ #include " source/common/common/hex.h"
17+ #include " source/common/common/thread.h"
1618#include " source/common/config/datasource.h"
1719#include " source/common/crypto/crypto_impl.h"
1820#include " source/common/crypto/utility.h"
1921#include " source/common/http/message_impl.h"
2022
23+ #include " absl/container/flat_hash_map.h"
2124#include " absl/strings/escaping.h"
25+ #include " absl/strings/str_cat.h"
2226#include " absl/strings/str_join.h"
2327
2428namespace Envoy {
@@ -212,6 +216,85 @@ Stats::Gauge& lookupLuaVmCountGauge(Stats::Scope& server_scope) {
212216
213217} // namespace
214218
219+ SINGLETON_MANAGER_REGISTRATION (lua_shared_http_filter_code_setups);
220+
221+ namespace {
222+
223+ // Identifies a shareable VM setup. The digest covers the package search paths as well as the
224+ // code, because two configurations that agree on an id and on a script but disagree on where
225+ // `require` looks for modules do not describe equivalent VMs and must not share one. Each part
226+ // is length-prefixed so that no two distinct inputs produce the same string to digest.
227+ std::string sharedVmKey (absl::string_view shared_vm_id, absl::string_view lua_code,
228+ const Filters::Common::Lua::PackagePaths& package_paths) {
229+ const std::string digest_input =
230+ absl::StrCat (lua_code.size (), " :" , lua_code, package_paths.path .size (), " :" ,
231+ package_paths.path , package_paths.cpath .size (), " :" , package_paths.cpath );
232+ // The digest is a fixed-length hex string at the end of the key, so an id that happens to
233+ // contain the separator cannot be mistaken for a different id with a different digest.
234+ return absl::StrCat (
235+ shared_vm_id, " /" ,
236+ Hex::encode (Envoy::Common::Crypto::UtilitySingleton::get ().getSha256Digest (digest_input)));
237+ }
238+
239+ SharedLuaCodeSetupRegistrySharedPtr sharedCodeSetupRegistry (Singleton::Manager& singleton_manager) {
240+ return singleton_manager.getTyped <SharedLuaCodeSetupRegistry>(
241+ SINGLETON_MANAGER_REGISTERED_NAME (lua_shared_http_filter_code_setups),
242+ [] { return std::make_shared<SharedLuaCodeSetupRegistry>(); });
243+ }
244+
245+ // Builds the VM setup for one configured script, reusing an already built one when the
246+ // configuration opted into sharing. `registry` is null when it did not, which keeps the
247+ // original behavior of one dedicated set of VMs per configured script.
248+ PerLuaCodeSetupSharedPtr
249+ createPerLuaCodeSetup (const SharedLuaCodeSetupRegistrySharedPtr& registry,
250+ absl::string_view shared_vm_id, const std::string& lua_code,
251+ const Filters::Common::Lua::PackagePaths& package_paths,
252+ ThreadLocal::SlotAllocator& tls, Stats::Gauge& vm_count_gauge,
253+ uint32_t concurrency, absl::Status& creation_status) {
254+ if (registry == nullptr ) {
255+ return std::make_shared<PerLuaCodeSetup>(lua_code, package_paths, tls, vm_count_gauge,
256+ concurrency, creation_status);
257+ }
258+ return registry->getOrCreate (shared_vm_id, lua_code, package_paths, tls, vm_count_gauge,
259+ concurrency, creation_status);
260+ }
261+
262+ } // namespace
263+
264+ PerLuaCodeSetupSharedPtr SharedLuaCodeSetupRegistry::getOrCreate (
265+ absl::string_view shared_vm_id, const std::string& lua_code,
266+ const Filters::Common::Lua::PackagePaths& package_paths, ThreadLocal::SlotAllocator& tls,
267+ Stats::Gauge& vm_count_gauge, uint32_t concurrency, absl::Status& creation_status) {
268+ // Filter and route configurations are only ever built on the main thread, so the map needs no
269+ // lock. Nothing touches it when a setup is released, which is what makes that safe: a setup
270+ // whose last owner goes away on a worker thread just leaves an expired entry behind.
271+ ASSERT_IS_MAIN_OR_TEST_THREAD ();
272+
273+ const std::string key = sharedVmKey (shared_vm_id, lua_code, package_paths);
274+ const auto it = setups_.find (key);
275+ if (it != setups_.end ()) {
276+ if (PerLuaCodeSetupSharedPtr setup = it->second .lock ()) {
277+ ENVOY_LOG (debug, " reusing shared Lua VM for shared_vm_id '{}'" , shared_vm_id);
278+ return setup;
279+ }
280+ }
281+
282+ PerLuaCodeSetupSharedPtr setup = std::make_shared<PerLuaCodeSetup>(
283+ lua_code, package_paths, tls, vm_count_gauge, concurrency, creation_status);
284+ if (!creation_status.ok ()) {
285+ // A script that does not parse is rejected for this configuration; it must not be handed to
286+ // the next one that asks for the same id.
287+ return nullptr ;
288+ }
289+
290+ // Drop the entries whose last user has gone away, so that a server churning through scripts
291+ // over its lifetime does not accumulate dead keys.
292+ absl::erase_if (setups_, [](const auto & entry) { return entry.second .expired (); });
293+ setups_[key] = setup;
294+ ENVOY_LOG (debug, " created shared Lua VM for shared_vm_id '{}'" , shared_vm_id);
295+ return setup;
296+ }
297+
215298PerLuaCodeSetup::PerLuaCodeSetup (const std::string& lua_code,
216299 const Filters::Common::Lua::PackagePaths& package_paths,
217300 ThreadLocal::SlotAllocator& tls, Stats::Gauge& vm_count_gauge,
@@ -925,7 +1008,8 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua&
9251008 ThreadLocal::SlotAllocator& tls,
9261009 Upstream::ClusterManager& cluster_manager, Api::Api& api,
9271010 Stats::Scope& scope, const std::string& stats_prefix,
928- uint32_t concurrency, absl::Status& creation_status)
1011+ uint32_t concurrency, Singleton::Manager& singleton_manager,
1012+ absl::Status& creation_status)
9291013 : cluster_manager_(cluster_manager),
9301014 clear_route_cache_(
9311015 proto_config.has_clear_route_cache() ? proto_config.clear_route_cache().value() : true),
@@ -938,6 +1022,11 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua&
9381022 Stats::Gauge& vm_count_gauge = lookupLuaVmCountGauge (api.rootScope ());
9391023 const Filters::Common::Lua::PackagePaths package_paths =
9401024 packagePaths (proto_config.package_paths (), proto_config.package_cpaths ());
1025+ // Left null when no id is configured, which is what tells the helper below to build VMs that
1026+ // belong to this configuration alone.
1027+ if (!proto_config.shared_vm_id ().empty ()) {
1028+ shared_code_setup_registry_ = sharedCodeSetupRegistry (singleton_manager);
1029+ }
9411030
9421031 if (proto_config.has_default_source_code ()) {
9431032 if (!proto_config.inline_code ().empty ()) {
@@ -949,21 +1038,23 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua&
9491038
9501039 auto code_or = Config::DataSource::read (proto_config.default_source_code (), true , api);
9511040 SET_AND_RETURN_IF_NOT_OK (code_or.status (), creation_status);
952- default_lua_code_setup_ = std::make_unique<PerLuaCodeSetup>(
953- code_or.value (), package_paths, tls, vm_count_gauge, concurrency, creation_status);
1041+ default_lua_code_setup_ = createPerLuaCodeSetup (
1042+ shared_code_setup_registry_, proto_config.shared_vm_id (), code_or.value (), package_paths,
1043+ tls, vm_count_gauge, concurrency, creation_status);
9541044 RETURN_ONLY_IF_NOT_OK_REF (creation_status);
9551045 } else if (!proto_config.inline_code ().empty ()) {
956- default_lua_code_setup_ =
957- std::make_unique<PerLuaCodeSetup>( proto_config.inline_code (), package_paths, tls ,
958- vm_count_gauge, concurrency, creation_status);
1046+ default_lua_code_setup_ = createPerLuaCodeSetup (
1047+ shared_code_setup_registry_, proto_config.shared_vm_id (), proto_config. inline_code () ,
1048+ package_paths, tls, vm_count_gauge, concurrency, creation_status);
9591049 RETURN_ONLY_IF_NOT_OK_REF (creation_status);
9601050 }
9611051
9621052 for (const auto & source : proto_config.source_codes ()) {
9631053 auto code_or = Config::DataSource::read (source.second , true , api);
9641054 SET_AND_RETURN_IF_NOT_OK (code_or.status (), creation_status);
965- auto per_lua_code_setup_ptr = std::make_unique<PerLuaCodeSetup>(
966- code_or.value (), package_paths, tls, vm_count_gauge, concurrency, creation_status);
1055+ auto per_lua_code_setup_ptr = createPerLuaCodeSetup (
1056+ shared_code_setup_registry_, proto_config.shared_vm_id (), code_or.value (), package_paths,
1057+ tls, vm_count_gauge, concurrency, creation_status);
9671058 RETURN_ONLY_IF_NOT_OK_REF (creation_status);
9681059 per_lua_code_setups_map_[source.first ] = std::move (per_lua_code_setup_ptr);
9691060 }
@@ -982,9 +1073,13 @@ FilterConfigPerRoute::FilterConfigPerRoute(
9821073 auto code_or = Config::DataSource::read (config.source_code (), true , context.api ());
9831074 SET_AND_RETURN_IF_NOT_OK (code_or.status (), creation_status);
9841075 Stats::Gauge& vm_count_gauge = lookupLuaVmCountGauge (context.api ().rootScope ());
985- per_lua_code_setup_ptr_ = std::make_unique<PerLuaCodeSetup>(
986- code_or.value (), packagePaths (config.package_paths (), config.package_cpaths ()),
987- context.threadLocal (), vm_count_gauge, context.options ().concurrency (), creation_status);
1076+ if (!config.shared_vm_id ().empty ()) {
1077+ shared_code_setup_registry_ = sharedCodeSetupRegistry (context.singletonManager ());
1078+ }
1079+ per_lua_code_setup_ptr_ = createPerLuaCodeSetup (
1080+ shared_code_setup_registry_, config.shared_vm_id (), code_or.value (),
1081+ packagePaths (config.package_paths (), config.package_cpaths ()), context.threadLocal (),
1082+ vm_count_gauge, context.options ().concurrency (), creation_status);
9881083 }
9891084}
9901085
0 commit comments