diff --git a/phlex/app/load_module.cpp b/phlex/app/load_module.cpp index dec376bbb..04286af0d 100644 --- a/phlex/app/load_module.cpp +++ b/phlex/app/load_module.cpp @@ -6,11 +6,11 @@ #include "phlex/source.hpp" #include "boost/algorithm/string.hpp" -#include "boost/dll/import.hpp" +#include "boost/dll/shared_library.hpp" #include "boost/json.hpp" #include -#include +#include #include #include @@ -21,17 +21,49 @@ namespace phlex::detail { namespace { constexpr std::string_view pymodule_name{"pymodule"}; - // If factory function goes out of scope, then the library is unloaded...and that's - // bad. + // The shared_library member in each wrapper struct keeps the loaded .so + // alive for the lifetime of the wrapper. If it goes out of scope, the + // library is unloaded and the stored function pointer becomes invalid. + struct module_plugin { + boost::dll::shared_library lib; + internal::module_creator_t* fn{}; + + void operator()(module_graph_proxy proxy, configuration const& config) const + { + fn(std::move(proxy), config); + } + }; + + struct source_plugin { + boost::dll::shared_library lib; + internal::source_creator_t* fn{}; + + void operator()(source_bundle bundle, configuration const& config) const + { + fn(bundle, config); + } + }; + + struct driver_plugin { + boost::dll::shared_library lib; + internal::driver_shim_t* fn{}; + + void operator()(driver_proxy proxy, configuration const& config, driver_bundle* out) const + { + fn(proxy, config, out); + } + }; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - std::vector> create_module; + std::vector create_module; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - std::vector> create_source; + std::vector create_source; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - std::function create_driver; + std::optional create_driver; template - std::function plugin_loader(std::string const& spec, std::string const& symbol_name) + std::pair plugin_loader(std::string const& spec, + std::string const& symbol_name) { // Called during single-threaded graph construction char const* plugin_path_ptr = @@ -39,9 +71,8 @@ namespace phlex::detail { if (!plugin_path_ptr) throw std::runtime_error("PHLEX_PLUGIN_PATH has not been set."); - using namespace boost; std::vector subdirs; - split(subdirs, plugin_path_ptr, is_any_of(":")); + boost::split(subdirs, plugin_path_ptr, boost::is_any_of(":")); // FIXME: Need to test to ensure that first match wins. for (auto const& subdir : subdirs) { @@ -50,9 +81,11 @@ namespace phlex::detail { if (exists(shared_library_path)) { // Load pymodule with rtld_global to make Python symbols available to extension modules // (e.g., NumPy). Load all other plugins with rtld_local (default) to avoid symbol collisions. - auto const load_mode = - (spec == pymodule_name) ? dll::load_mode::rtld_global : dll::load_mode::default_mode; - return dll::import_symbol(shared_library_path, symbol_name, load_mode); + auto const load_mode = (spec == pymodule_name) ? boost::dll::load_mode::rtld_global + : boost::dll::load_mode::default_mode; + boost::dll::shared_library lib{shared_library_path, load_mode}; + auto* fn = &lib.get(symbol_name); + return {std::move(lib), fn}; } } throw std::runtime_error("Could not locate library with specification '"s + spec + @@ -89,8 +122,8 @@ namespace phlex::detail { auto const adjusted_config = internal::adjust_config(label, std::move(raw_config)); auto const& spec = value_to(adjusted_config.at("cpp")); - auto& creator = - create_module.emplace_back(plugin_loader(spec, "create_module")); + auto [lib, fn] = plugin_loader(spec, "create_module"); + auto& creator = create_module.emplace_back(module_plugin{std::move(lib), fn}); configuration const config{adjusted_config}; creator(g.module_proxy(config), config); @@ -101,8 +134,8 @@ namespace phlex::detail { auto const adjusted_config = internal::adjust_config(label, std::move(raw_config)); auto const& spec = value_to(adjusted_config.at("cpp")); - auto& creator = - create_source.emplace_back(plugin_loader(spec, "create_source")); + auto [lib, fn] = plugin_loader(spec, "create_source"); + auto& creator = create_source.emplace_back(source_plugin{std::move(lib), fn}); // FIXME: Should probably use the parameter name (e.g.) 'plugin_label' instead of // 'module_label', but that requires adjusting other parts of the system @@ -121,9 +154,10 @@ namespace phlex::detail { // False positive: clang-analyzer cannot trace ownership through Boost's is_any_of // internal reference counting in classification.hpp. // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks,clang-analyzer-cplusplus.NewDelete) - create_driver = plugin_loader(spec, "create_driver"); + auto [lib, fn] = plugin_loader(spec, "create_driver"); + create_driver.emplace(driver_plugin{std::move(lib), fn}); driver_bundle result; - create_driver(g.driver_proxy(required_sources), config, &result); + (*create_driver)(g.driver_proxy(required_sources), config, &result); g.add_driver(result); } } diff --git a/phlex/core/glue.hpp b/phlex/core/glue.hpp index e127b5e00..b9b098368 100644 --- a/phlex/core/glue.hpp +++ b/phlex/core/glue.hpp @@ -35,7 +35,7 @@ namespace phlex::detail { * * The glue class acts as a registration helper that allows binding user-defined functions and algorithms * to nodes in a TBB flow graph. It provides methods to create different types of processing nodes like - * fold, transform, observe, predicate etc. + * fold, observe, output, predicate, transform, and unfold. * * @tparam T The type of the object that contains the user-defined functions/algorithms to be registered. * This object is stored as a shared pointer and its methods are bound to the created nodes. diff --git a/phlex/core/graph_proxy.hpp b/phlex/core/graph_proxy.hpp index eb746fe33..596c488a1 100644 --- a/phlex/core/graph_proxy.hpp +++ b/phlex/core/graph_proxy.hpp @@ -39,10 +39,7 @@ namespace phlex::detail { tbb::flow::graph& g, node_catalog& nodes, std::vector& errors) - requires(not is_bound_object) - : config_{&config}, graph_{g}, nodes_{nodes}, errors_{errors} - { - } + requires(not is_bound_object); /// @brief Binds a user algorithm object of type @p U to this proxy. /// @@ -51,10 +48,7 @@ namespace phlex::detail { /// be registered as algorithm nodes. template graph_proxy make(Args&&... args) - requires(not is_bound_object) - { - return bind_to(std::forward(args)...); - } + requires(not is_bound_object); /// @brief Registers a fold algorithm node. template @@ -62,38 +56,19 @@ namespace phlex::detail { is_fold_like auto f, concurrency c = concurrency::serial, std::string partition = "job", - InitArgs&&... init_args) - { - return create_glue().fold(std::move(name), - std::move(f), - c, - std::move(partition), - std::forward(init_args)...); - } + InitArgs&&... init_args); /// @brief Registers an observer node. - auto observe(std::string name, is_observer_like auto f, concurrency c = concurrency::serial) - { - return create_glue().observe(std::move(name), std::move(f), c); - } + auto observe(std::string name, is_observer_like auto f, concurrency c = concurrency::serial); /// @brief Registers a predicate node. - auto predicate(std::string name, is_predicate_like auto f, concurrency c = concurrency::serial) - { - return create_glue().predicate(std::move(name), std::move(f), c); - } + auto predicate(std::string name, is_predicate_like auto f, concurrency c = concurrency::serial); /// @brief Registers a provider node. - auto provide(std::string name, is_provider_like auto f, concurrency c = concurrency::serial) - { - return create_glue().provide(std::move(name), std::move(f), c); - } + auto provide(std::string name, is_provider_like auto f, concurrency c = concurrency::serial); /// @brief Registers a transform node. - auto transform(std::string name, is_transform_like auto f, concurrency c = concurrency::serial) - { - return create_glue().transform(std::move(name), std::move(f), c); - } + auto transform(std::string name, is_transform_like auto f, concurrency c = concurrency::serial); /// @brief Registers an unfold node. template @@ -101,53 +76,30 @@ namespace phlex::detail { is_predicate_like auto pred, auto unf, std::string destination_data_layer, - concurrency c = concurrency::serial) - { - return glue{graph_, nodes_, nullptr, errors_, config_}.unfold( - std::move(name), std::move(pred), std::move(unf), c, std::move(destination_data_layer)); - } + concurrency c = concurrency::serial); /// @brief Registers a source (used by the framework to create provider nodes) template Source, typename... Args> void add_source(std::string name, Args&&... args) - requires(not is_bound_object) - { - // The bound object is created when invoking source(...), so we explicitly indicate that - // no bound object should be used in the create_glue(...) call. - return create_glue(false).template add_source(std::move(name), - std::forward(args)...); - } + requires(not is_bound_object); /// @brief Registers an output node. - auto output(std::string name, is_output_like auto f, concurrency c = concurrency::serial) - { - return create_glue().output(std::move(name), std::move(f), c); - } + auto output(std::string name, is_output_like auto f, concurrency c = concurrency::serial); protected: template