diff --git a/docs/source/guide/resources.rst b/docs/source/guide/resources.rst new file mode 100644 index 000000000..299104802 --- /dev/null +++ b/docs/source/guide/resources.rst @@ -0,0 +1,128 @@ +Resources +========= + +A *resource* is an object that the framework owns for the whole job and +hands to the algorithms that declare they use it, such as a calibration +service, a geometry, or a library that may only be used by one caller at a +time. +Resources were introduced in Phlex v0.4.0. + +Kinds of resources +------------------ + +Unlimited resources + Any number of calls may use the resource at the same time. + The type must *not* define a ``token_type`` member. + Algorithms receive a ``T const*``. + +Serialized resources + Across *all* algorithms that declare the resource, at most one call + holding it runs at a time. + The type must define a ``token_type`` member, and algorithms receive a + value of that type: + + - a pointer to the resource, if ``T*`` converts to ``token_type`` + (for example ``using token_type = T const*;``); + - a copy of the resource, if ``token_type`` is ``T`` itself, in which + case ``T`` must be copy-constructible; + - otherwise, the result of ``T::token()``, which must return something + convertible to ``token_type`` and be either a ``static`` member + function or a ``const``-qualified member function. + A ``token()`` that is neither is rejected by ``add_serialized_resource``. + + This is the framework's way to protect a non-thread-safe library that + several algorithms share. + A serialized resource limits how many calls run at once; it does not make + the calls run on one particular thread. + +Each resource type may be registered only once per job. + +Registering resources +--------------------- + +Resources are registered by a plugin that uses ``PHLEX_REGISTER_RESOURCES`` +from ``phlex/resource.hpp`` and links to the ``phlex::resource`` CMake +target: + +.. code-block:: cpp + + #include "phlex/configuration.hpp" + #include "phlex/resource.hpp" + + struct calibration { + explicit calibration(int version) : version{version} {} + int version; + }; + + struct legacy_generator { + using token_type = legacy_generator*; + // ... wraps a library that is not thread-safe + }; + + PHLEX_REGISTER_RESOURCES(r, config) + { + r.add_unlimited_resource(config.get("version")); + r.add_serialized_resource(); + } + +Define the resource types in a header that is included both by the plugin +that registers them and by every algorithm module that uses them, as +``test/plugins/resources_for_testing.hpp`` does. +Header-only types like these need no separate shared library. + +The arguments of ``add_unlimited_resource`` and ``add_serialized_resource`` +are forwarded to the resource's constructor. + +Resource plugins are configured in the top-level ``resources`` table and +are loaded before all other plugins. +Only C++ resource plugins are supported: + +.. code-block:: jsonnet + + { + resources: { + services: { cpp: 'my_resources', version: 3 }, + }, + } + +Using resources +--------------- + +An algorithm declares the resources it uses by passing ``resource{}`` +(from ``phlex/core/resource_api.hpp``) to ``input_family(...)`` after all +product selectors. +The algorithm receives one extra parameter per resource, after its product +parameters and in the same order: + +.. code-block:: cpp + + m.transform( + "generate", + [](int seed, calibration const* calib, legacy_generator* gen) -> double { /* ... */ }, + concurrency::unlimited) + .input_family(product_selector{.creator = "seeds", .layer = "event"}, + resource{}, + resource{}); + +A node can run no more calls at once than both its own ``concurrency`` +argument and its serialized resources allow. +Using a resource type that no plugin registered is an error. + +Limitations +----------- + +- Providers cannot use resources + (`issue #945 `_). +- Output modules registered with ``output(...)`` cannot use resources, + because they have no ``input_family(...)``. +- Algorithms written in Python cannot use resources. +- For an unfold, the resource parameters go to the unfold function, not to + the unfolder's constructor + (`issue #946 `_). +- Resources with a pool of several tokens are not supported yet. +- Phlex needs `oneTBB 2023.0.0 or later + `_, + which provides the preview ``flow::resource_limiter`` class; CMake + configuration fails without it. + The ``phlex::core`` target defines ``TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING`` + for its users. diff --git a/phlex/core/resource_api.hpp b/phlex/core/resource_api.hpp index 63630b69b..c8975e110 100644 --- a/phlex/core/resource_api.hpp +++ b/phlex/core/resource_api.hpp @@ -12,6 +12,13 @@ #include namespace phlex { + /// @brief Declares that an algorithm uses the registered resource of type @p T. + /// + /// Pass @c resource{} to @c input_family(...) after all product selectors; each type + /// may appear only once. The algorithm receives one extra parameter per resource, after + /// its product parameters and in the same order: @c T @c const* for a resource registered + /// with @c add_unlimited_resource, and @c T::token_type for one registered with + /// @c add_serialized_resource. template struct resource { static_assert(!std::is_const_v, "resource must name a non-const resource type."); diff --git a/phlex/resource.hpp b/phlex/resource.hpp index 4feebf803..5645f4ca9 100644 --- a/phlex/resource.hpp +++ b/phlex/resource.hpp @@ -18,6 +18,12 @@ namespace phlex::detail { { } + /// @brief Registers a resource of type @p Resource that any number of calls may use at once. + /// + /// @p Resource must not define @c token_type. The resource is constructed from @p args + /// and owned by the framework; algorithms that declare @c resource{} in + /// @c input_family(...) receive a @c Resource @c const* parameter. Each resource type may + /// be registered only once per job. template requires unlimited_resource_registration void add_unlimited_resource(Args&&... args) const @@ -25,6 +31,16 @@ namespace phlex::detail { resources_.template add_unlimited(std::forward(args)...); } + /// @brief Registers a resource of type @p Resource that only one call at a time may use. + /// + /// @p Resource must define @c token_type. The resource is constructed from @p args and + /// owned by the framework. Across all algorithms that declare @c resource{}, + /// at most one call runs at a time; it receives a @c token_type parameter, which is + /// a pointer to the resource if @c Resource* converts to @c token_type; a copy of the + /// resource if @c token_type is @c Resource, which must then be copy-constructible; and + /// otherwise the result of @c Resource::token(), which must return something convertible + /// to @c token_type and be a @c static or @c const-qualified member function. Each + /// resource type may be registered only once per job. template requires serialized_resource_registration void add_serialized_resource(Args&&... args) const