-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dynamic_modules: adds initial object loading logic (#35550)
Commit Message: dynamic_modules: adds initial object loading logic Additional Description: This is the very first commit of the dynamic loading feature discussed among community members. This is the effort to upstream the playground repository https://github.com/mathetake/envoy-dynamic-modules as an Envoy core extension. Series of commits will follow this little by little. #2053, #24230, #32502 Risk Level: N/A (not compiled into the final build yet) Testing: unit Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Takeshi Yoneda <[email protected]>
- Loading branch information
Showing
10 changed files
with
255 additions
and
2 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
load( | ||
"//bazel:envoy_build_system.bzl", | ||
"envoy_cc_library", | ||
"envoy_extension_package", | ||
) | ||
|
||
licenses(["notice"]) # Apache 2 | ||
|
||
envoy_extension_package() | ||
|
||
envoy_cc_library( | ||
name = "dynamic_modules_lib", | ||
srcs = [ | ||
"dynamic_modules.cc", | ||
], | ||
hdrs = [ | ||
"dynamic_modules.h", | ||
], | ||
deps = [ | ||
"//envoy/common:exception_lib", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include "source/extensions/dynamic_modules/dynamic_modules.h" | ||
|
||
#include <dlfcn.h> | ||
|
||
#include <filesystem> | ||
#include <string> | ||
|
||
#include "envoy/common/exception.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace DynamicModules { | ||
|
||
absl::StatusOr<DynamicModuleSharedPtr> newDynamicModule(const absl::string_view object_file_path, | ||
const bool do_not_close) { | ||
// RTLD_LOCAL is always needed to avoid collisions between multiple modules. | ||
// RTLD_LAZY is required for not only performance but also simply to load the module, otherwise | ||
// dlopen results in Invalid argument. | ||
int mode = RTLD_LOCAL | RTLD_LAZY; | ||
if (do_not_close) { | ||
mode |= RTLD_NODELETE; | ||
} | ||
|
||
const std::filesystem::path file_path_absolute = std::filesystem::absolute(object_file_path); | ||
void* handle = dlopen(file_path_absolute.c_str(), mode); | ||
if (handle == nullptr) { | ||
return absl::InvalidArgumentError( | ||
absl::StrCat("Failed to load dynamic module: ", object_file_path, " : ", dlerror())); | ||
} | ||
return std::make_shared<DynamicModule>(handle); | ||
} | ||
|
||
DynamicModule::~DynamicModule() { dlclose(handle_); } | ||
|
||
void* DynamicModule::getSymbol(const absl::string_view symbol_ref) const { | ||
// TODO(mathetake): maybe we should accept null-terminated const char* instead of string_view to | ||
// avoid unnecessary copy because it is likely that this is only called for a constant string, | ||
// though this is not a performance critical path. | ||
return dlsym(handle_, std::string(symbol_ref).c_str()); | ||
} | ||
|
||
} // namespace DynamicModules | ||
} // namespace Extensions | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#pragma once | ||
|
||
#include <memory> | ||
#include <string> | ||
|
||
#include "absl/status/statusor.h" | ||
#include "absl/strings/string_view.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace DynamicModules { | ||
|
||
/** | ||
* A class for loading and managing dynamic modules. This corresponds to a single dlopen handle. | ||
* When the DynamicModule object is destroyed, the dlopen handle is closed. | ||
* | ||
* This class is supposed to be initialized once in the main thread and can be shared with other | ||
* threads. | ||
*/ | ||
class DynamicModule { | ||
public: | ||
DynamicModule(void* handle) : handle_(handle) {} | ||
~DynamicModule(); | ||
|
||
/** | ||
* Get a function pointer from the dynamic module with a specific type. | ||
* @param T the function pointer type to cast the symbol to. | ||
* @param symbol_ref the symbol to look up. | ||
* @return the symbol if found, otherwise nullptr. | ||
*/ | ||
template <typename T> T getFunctionPointer(const absl::string_view symbol_ref) const { | ||
static_assert(std::is_pointer<T>::value && | ||
std::is_function<typename std::remove_pointer<T>::type>::value, | ||
"T must be a function pointer type"); | ||
return reinterpret_cast<T>(getSymbol(symbol_ref)); | ||
} | ||
|
||
private: | ||
/** | ||
* Get a symbol from the dynamic module. | ||
* @param symbol_ref the symbol to look up. | ||
* @return the symbol if found, otherwise nullptr. | ||
*/ | ||
void* getSymbol(const absl::string_view symbol_ref) const; | ||
|
||
// The raw dlopen handle that can be used to look up symbols. | ||
void* handle_; | ||
}; | ||
|
||
using DynamicModuleSharedPtr = std::shared_ptr<DynamicModule>; | ||
|
||
/** | ||
* Creates a new DynamicModule. | ||
* @param object_file_path the path to the object file to load. | ||
* @param do_not_close if true, the dlopen will be called with RTLD_NODELETE, so the loaded object | ||
* will not be destroyed. This is useful when an object has some global state that should not be | ||
* terminated. For example, c-shared objects compiled by Go doesn't support dlclose | ||
* https://github.com/golang/go/issues/11100. | ||
*/ | ||
absl::StatusOr<DynamicModuleSharedPtr> newDynamicModule(const absl::string_view object_file_path, | ||
const bool do_not_close); | ||
|
||
} // namespace DynamicModules | ||
} // namespace Extensions | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
load( | ||
"//bazel:envoy_build_system.bzl", | ||
"envoy_cc_test", | ||
"envoy_package", | ||
) | ||
|
||
licenses(["notice"]) # Apache 2 | ||
|
||
envoy_package() | ||
|
||
envoy_cc_test( | ||
name = "dynamic_modules_test", | ||
srcs = ["dynamic_modules_test.cc"], | ||
data = [ | ||
"//test/extensions/dynamic_modules/test_data:no_op", | ||
], | ||
deps = [ | ||
"//source/extensions/dynamic_modules:dynamic_modules_lib", | ||
"//test/test_common:environment_lib", | ||
"//test/test_common:utility_lib", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#include <memory> | ||
|
||
#include "envoy/common/exception.h" | ||
|
||
#include "source/extensions/dynamic_modules/dynamic_modules.h" | ||
|
||
#include "test/test_common/environment.h" | ||
#include "test/test_common/utility.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace DynamicModules { | ||
|
||
// This loads a shared object file from the test_data directory. | ||
std::string testSharedObjectPath(std::string name) { | ||
return TestEnvironment::substitute( | ||
"{{ test_rundir }}/test/extensions/dynamic_modules/test_data/") + | ||
"lib" + name + ".so"; | ||
} | ||
|
||
TEST(DynamicModuleTest, InvalidPath) { | ||
absl::StatusOr<DynamicModuleSharedPtr> result = newDynamicModule("invalid_name", false); | ||
EXPECT_FALSE(result.ok()); | ||
EXPECT_EQ(result.status().code(), absl::StatusCode::kInvalidArgument); | ||
} | ||
|
||
TEST(DynamicModuleTest, LoadNoOp) { | ||
using GetSomeVariableFuncType = int (*)(); | ||
absl::StatusOr<DynamicModuleSharedPtr> module = | ||
newDynamicModule(testSharedObjectPath("no_op"), false); | ||
EXPECT_TRUE(module.ok()); | ||
const auto getSomeVariable = | ||
module->get()->getFunctionPointer<GetSomeVariableFuncType>("getSomeVariable"); | ||
EXPECT_EQ(getSomeVariable(), 1); | ||
EXPECT_EQ(getSomeVariable(), 2); | ||
EXPECT_EQ(getSomeVariable(), 3); | ||
|
||
// Release the module, and reload it. | ||
module->reset(); | ||
module = | ||
newDynamicModule(testSharedObjectPath("no_op"), true); // This time, do not close the module. | ||
EXPECT_TRUE(module.ok()); | ||
|
||
// This module must be reloaded and the variable must be reset. | ||
const auto getSomeVariable2 = | ||
(module->get()->getFunctionPointer<GetSomeVariableFuncType>("getSomeVariable")); | ||
EXPECT_NE(getSomeVariable2, nullptr); | ||
EXPECT_EQ(getSomeVariable2(), 1); // Start from 1 again. | ||
EXPECT_EQ(getSomeVariable2(), 2); | ||
EXPECT_EQ(getSomeVariable2(), 3); | ||
|
||
// Release the module, and reload it. | ||
module->reset(); | ||
module = newDynamicModule(testSharedObjectPath("no_op"), false); | ||
EXPECT_TRUE(module.ok()); | ||
|
||
// This module must be the already loaded one, and the variable must be kept. | ||
const auto getSomeVariable3 = | ||
module->get()->getFunctionPointer<GetSomeVariableFuncType>("getSomeVariable"); | ||
EXPECT_NE(getSomeVariable3, nullptr); | ||
EXPECT_EQ(getSomeVariable3(), 4); // Start from 4. | ||
} | ||
|
||
} // namespace DynamicModules | ||
} // namespace Extensions | ||
} // namespace Envoy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
load("//test/extensions/dynamic_modules/test_data:test_data.bzl", "test_program") | ||
|
||
licenses(["notice"]) # Apache 2 | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
test_program(name = "no_op") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
int getSomeVariable() { | ||
static int some_variable = 0; | ||
some_variable++; | ||
return some_variable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
load("@rules_cc//cc:defs.bzl", "cc_library") | ||
|
||
# This declares a cc_library target that is used to build a shared library. | ||
# name + ".c" is the source file that is compiled to create the shared library. | ||
def test_program(name): | ||
cc_library( | ||
name = name, | ||
srcs = [name + ".c"], | ||
linkopts = [ | ||
"-shared", | ||
"-fPIC", | ||
], | ||
linkstatic = False, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters