From f754c78c7bb275ab1e009e0e380ef8e58247d875 Mon Sep 17 00:00:00 2001 From: David Gardner Date: Wed, 3 Jul 2024 10:18:33 -0700 Subject: [PATCH 01/18] Creating branch for v24.10 From 246ac979edc4159fcd1833fc11920247b5b45829 Mon Sep 17 00:00:00 2001 From: David Gardner Date: Wed, 3 Jul 2024 10:20:49 -0700 Subject: [PATCH 02/18] Updating versions for v24.10.00 --- .gitmodules | 2 +- CMakeLists.txt | 2 +- docs/quickstart/CMakeLists.txt | 2 +- docs/quickstart/environment_cpp.yml | 2 +- external/utilities | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 547102253..fc54a6f5a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "morpheus_utils"] path = external/utilities url = https://github.com/nv-morpheus/utilities.git - branch = branch-24.06 + branch = branch-24.10 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e9931166..36109ad2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ morpheus_utils_initialize_package_manager( morpheus_utils_initialize_cuda_arch(mrc) project(mrc - VERSION 24.06.00 + VERSION 24.10.00 LANGUAGES C CXX ) diff --git a/docs/quickstart/CMakeLists.txt b/docs/quickstart/CMakeLists.txt index 26f832047..ef55ee70d 100644 --- a/docs/quickstart/CMakeLists.txt +++ b/docs/quickstart/CMakeLists.txt @@ -28,7 +28,7 @@ list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../external/utili include(morpheus_utils/load) project(mrc-quickstart - VERSION 24.06.00 + VERSION 24.10.00 LANGUAGES C CXX ) diff --git a/docs/quickstart/environment_cpp.yml b/docs/quickstart/environment_cpp.yml index ac2ea44f9..a8c0eb2c2 100644 --- a/docs/quickstart/environment_cpp.yml +++ b/docs/quickstart/environment_cpp.yml @@ -30,7 +30,7 @@ dependencies: - pkg-config=0.29 - python=3.10 - scikit-build>=0.12 - - mrc=24.06 + - mrc=24.10 - sysroot_linux-64=2.17 - pip: - cython diff --git a/external/utilities b/external/utilities index 54be32e6d..3e5d28d5d 160000 --- a/external/utilities +++ b/external/utilities @@ -1 +1 @@ -Subproject commit 54be32e6d3e1c7dea65ede5d721ef4496a225aec +Subproject commit 3e5d28d5d8a3c6aaa42b30608175f684a465478c From bceb7ef531814428b738ada83522eaea203ba180 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:24:21 -0700 Subject: [PATCH 03/18] Ensure proper initialization of `CMAKE_INSTALL_PREFIX` if needed (#485) * Invoke `morpheus_utils_initialize_install_prefix` function after calling `project` * Related to nv-morpheus/morpheus#1776 Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/485 --- CMakeLists.txt | 2 ++ external/utilities | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36109ad2e..668c00603 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,8 @@ project(mrc LANGUAGES C CXX ) +morpheus_utils_initialize_install_prefix(MRC_USE_CONDA) + rapids_cmake_write_version_file(${CMAKE_BINARY_DIR}/autogenerated/include/mrc/version.hpp) # Delay enabling CUDA until after we have determined our CXX compiler diff --git a/external/utilities b/external/utilities index 3e5d28d5d..fb2c9503f 160000 --- a/external/utilities +++ b/external/utilities @@ -1 +1 @@ -Subproject commit 3e5d28d5d8a3c6aaa42b30608175f684a465478c +Subproject commit fb2c9503fbfdd08503013f712b8bc1e4d9869933 From ca8a73feb25f64546b92f18154d90129d84bc1c6 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:42:25 -0700 Subject: [PATCH 04/18] Stop a python source once the subscriber is no longer subscribed (#493) * When a Python generator source yields a value, and the subscriber is no longer subscribed, stop the source. * Fix out of date docstring comment. This is a partial fix for nv-morpheus/Morpheus#1838 Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Anuradha Karuppiah (https://github.com/AnuradhaKaruppiah) URL: https://github.com/nv-morpheus/MRC/pull/493 --- cpp/mrc/src/internal/service.hpp | 4 ++-- python/mrc/_pymrc/src/segment.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cpp/mrc/src/internal/service.hpp b/cpp/mrc/src/internal/service.hpp index d24e059c5..47d5b7fab 100644 --- a/cpp/mrc/src/internal/service.hpp +++ b/cpp/mrc/src/internal/service.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ enum class ServiceState }; /** - * @brief Converts a `AsyncServiceState` enum to a string + * @brief Converts a `ServiceState` enum to a string * * @param f * @return std::string diff --git a/python/mrc/_pymrc/src/segment.cpp b/python/mrc/_pymrc/src/segment.cpp index f5b931cf0..8a6f6fb33 100644 --- a/python/mrc/_pymrc/src/segment.cpp +++ b/python/mrc/_pymrc/src/segment.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -233,6 +233,11 @@ std::shared_ptr build_source(mrc::segment::IBuil { subscriber.on_next(std::move(next_val)); } + else + { + DVLOG(10) << ctx.info() << " Source unsubscribed. Stopping"; + break; + } } } catch (const std::exception& e) From 8489b455e3c646ee232ceeaff7a86fdbe7b073c1 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:31:15 -0700 Subject: [PATCH 05/18] Define a Python source which receives a reference to a subscriber (#496) * Allows a Python generator source to check if the subscriber is still subscribed. * Define a class `SubscriberFuncWrapper` for Python sources rather than just a lambda. The reason is that python objects captured by the lambda need to be destroyed while the gil is held, which causes a problem if the lambda is destroyed unexpectedly. * Update `conftest.py` to set the loglevel to `DEBUG` if the `GLOG_v` environment variable is defined. Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/496 --- python/mrc/_pymrc/include/pymrc/segment.hpp | 9 +- python/mrc/_pymrc/src/segment.cpp | 62 +++++++++++++ python/mrc/core/segment.cpp | 8 +- python/tests/conftest.py | 6 +- python/tests/test_executor.py | 98 ++++++++++++++++++++- 5 files changed, 175 insertions(+), 8 deletions(-) diff --git a/python/mrc/_pymrc/include/pymrc/segment.hpp b/python/mrc/_pymrc/include/pymrc/segment.hpp index 2da23cace..2ceae6f25 100644 --- a/python/mrc/_pymrc/include/pymrc/segment.hpp +++ b/python/mrc/_pymrc/include/pymrc/segment.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -143,10 +143,9 @@ class BuilderProxy const std::string& name, pybind11::function gen_factory); - static std::shared_ptr make_source( - mrc::segment::IBuilder& self, - const std::string& name, - const std::function& f); + static std::shared_ptr make_source_subscriber(mrc::segment::IBuilder& self, + const std::string& name, + pybind11::function gen_factory); static std::shared_ptr make_source_component(mrc::segment::IBuilder& self, const std::string& name, diff --git a/python/mrc/_pymrc/src/segment.cpp b/python/mrc/_pymrc/src/segment.cpp index 8a6f6fb33..6afb01967 100644 --- a/python/mrc/_pymrc/src/segment.cpp +++ b/python/mrc/_pymrc/src/segment.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -257,6 +258,60 @@ std::shared_ptr build_source(mrc::segment::IBuil return self.construct_object>(name, wrapper); } +class SubscriberFuncWrapper : public mrc::pymrc::PythonSource +{ + public: + using base_t = mrc::pymrc::PythonSource; + using typename base_t::source_type_t; + using typename base_t::subscriber_fn_t; + + SubscriberFuncWrapper(py::function gen_factory) : PythonSource(build()), m_gen_factory{std::move(gen_factory)} {} + + private: + subscriber_fn_t build() + { + return [this](rxcpp::subscriber subscriber) { + auto& ctx = runnable::Context::get_runtime_context(); + + try + { + DVLOG(10) << ctx.info() << " Starting source"; + py::gil_scoped_acquire gil; + py::object py_sub = py::cast(subscriber); + auto py_iter = m_gen_factory.operator()(std::move(py_sub)); + PyIteratorWrapper iter_wrapper{std::move(py_iter)}; + + for (auto next_val : iter_wrapper) + { + // Only send if its subscribed. Very important to ensure the object has been moved! + if (subscriber.is_subscribed()) + { + py::gil_scoped_release no_gil; + subscriber.on_next(std::move(next_val)); + } + else + { + DVLOG(10) << ctx.info() << " Source unsubscribed. Stopping"; + break; + } + } + + } catch (const std::exception& e) + { + LOG(ERROR) << ctx.info() << "Error occurred in source. Error msg: " << e.what(); + + subscriber.on_error(std::current_exception()); + return; + } + subscriber.on_completed(); + + DVLOG(10) << ctx.info() << " Source complete"; + }; + } + + PyFuncWrapper m_gen_factory{}; +}; + std::shared_ptr build_source_component(mrc::segment::IBuilder& self, const std::string& name, PyIteratorWrapper iter_wrapper) @@ -308,6 +363,13 @@ std::shared_ptr BuilderProxy::make_source(mrc::s return build_source(self, name, PyIteratorWrapper(std::move(gen_factory))); } +std::shared_ptr BuilderProxy::make_source_subscriber(mrc::segment::IBuilder& self, + const std::string& name, + py::function gen_factory) +{ + return self.construct_object(name, std::move(gen_factory)); +} + std::shared_ptr BuilderProxy::make_source_component(mrc::segment::IBuilder& self, const std::string& name, pybind11::iterator source_iterator) diff --git a/python/mrc/core/segment.cpp b/python/mrc/core/segment.cpp index addba6813..2224fb2e4 100644 --- a/python/mrc/core/segment.cpp +++ b/python/mrc/core/segment.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -134,6 +134,12 @@ PYBIND11_MODULE(segment, py_mod) const std::string&, py::function)>(&BuilderProxy::make_source)); + Builder.def("make_source_subscriber", + static_cast (*)(mrc::segment::IBuilder&, + const std::string&, + py::function)>( + &BuilderProxy::make_source_subscriber)); + Builder.def("make_source_component", static_cast (*)(mrc::segment::IBuilder&, const std::string&, diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 842261f10..7052fe176 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import typing import pytest @@ -50,6 +51,9 @@ def configure_tests_logging(is_debugger_attached: bool): if (is_debugger_attached): log_level = logging.INFO + if (os.environ.get('GLOG_v') is not None): + log_level = logging.DEBUG + mrc_logging.init_logging("mrc_testing", py_level=log_level) diff --git a/python/tests/test_executor.py b/python/tests/test_executor.py index 8e5b7ad47..eb0e3596f 100644 --- a/python/tests/test_executor.py +++ b/python/tests/test_executor.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ # limitations under the License. import asyncio +import os +import time import typing import pytest @@ -30,6 +32,53 @@ def pairwise(t): node_fn_type = typing.Callable[[mrc.Builder], mrc.SegmentObject] +@pytest.fixture +def source(): + + def build(builder: mrc.Builder): + + def gen_data(): + yield 1 + yield 2 + yield 3 + + return builder.make_source("source", gen_data) + + return build + + +@pytest.fixture +def endless_source(): + + def build(builder: mrc.Builder): + + def gen_data(): + i = 0 + while True: + yield i + i += 1 + time.sleep(0.1) + + return builder.make_source("endless_source", gen_data()) + + return build + + +@pytest.fixture +def blocking_source(): + + def build(builder: mrc.Builder): + + def gen_data(subscriber: mrc.Subscriber): + yield 1 + while subscriber.is_subscribed(): + time.sleep(0.1) + + return builder.make_source_subscriber("blocking_source", gen_data) + + return build + + @pytest.fixture def source_pyexception(): @@ -64,6 +113,21 @@ def gen_data_and_raise(): return build +@pytest.fixture +def node_exception(): + + def build(builder: mrc.Builder): + + def on_next(data): + time.sleep(1) + print("Received value: {}".format(data), flush=True) + raise RuntimeError("unittest") + + return builder.make_node("node", mrc.core.operators.map(on_next)) + + return build + + @pytest.fixture def sink(): @@ -112,6 +176,8 @@ def build_executor(): def inner(pipe: mrc.Pipeline): options = mrc.Options() + options.topology.user_cpuset = f"0-{os.cpu_count() - 1}" + options.engine_factories.default_engine_type = mrc.core.options.EngineType.Thread executor = mrc.Executor(options) executor.register_pipeline(pipe) @@ -183,5 +249,35 @@ async def run_pipeline(): asyncio.run(run_pipeline()) +@pytest.mark.parametrize("souce_name", ["source", "endless_source", "blocking_source"]) +def test_pyexception_in_node(source: node_fn_type, + endless_source: node_fn_type, + blocking_source: node_fn_type, + node_exception: node_fn_type, + build_pipeline: build_pipeline_type, + build_executor: build_executor_type, + souce_name: str): + """ + Test to reproduce Morpheus issue #1838 where an exception raised in a node doesn't always shutdown the executor + when the source is intended to run indefinitely. + """ + + if souce_name == "endless_source": + source_fn = endless_source + elif souce_name == "blocking_source": + source_fn = blocking_source + else: + source_fn = source + + pipe = build_pipeline(source_fn, node_exception) + + executor: mrc.Executor = None + + executor = build_executor(pipe) + + with pytest.raises(RuntimeError): + executor.join() + + if (__name__ in ("__main__", )): test_pyexception_in_source() From ccbcd76c53962f31ef68241522c9a628bf3bb0d8 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:41:49 -0700 Subject: [PATCH 06/18] Change `LOG(WARNING)` to `VLOG(1)` when no GPUs are detected (#497) * Since CPU-only mode will become a supported feature we want to avoid unnecessary warnings. Relates to nv-morpheus/Morpheus#1851 Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/497 --- cpp/mrc/src/internal/system/device_info.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/mrc/src/internal/system/device_info.cpp b/cpp/mrc/src/internal/system/device_info.cpp index b9f3461f2..2ead6abee 100644 --- a/cpp/mrc/src/internal/system/device_info.cpp +++ b/cpp/mrc/src/internal/system/device_info.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2018-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2018-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -150,7 +150,7 @@ struct NvmlState m_nvml_handle = std::make_unique(); } catch (std::runtime_error e) { - LOG(WARNING) << "NVML: " << e.what() << ". Setting DeviceCount to 0, CUDA will not be initialized"; + VLOG(1) << "NVML: " << e.what() << ". Setting DeviceCount to 0, CUDA will not be initialized"; return; } From 48d17a17bbfa176a4cc650a7782a38224605d7f3 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:10:21 -0700 Subject: [PATCH 07/18] Pass a `mrc.Subscription` object to sources rather than a `mrc.Subscriber` (#499) * Remove the `make_source_subscriber` method in favor of inspecting the Python function signature. * Since the `make_source_subscriber` method was never part of a release I think this can still be considered a non-breaking change. Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/499 --- python/mrc/_pymrc/include/pymrc/segment.hpp | 4 -- .../mrc/_pymrc/include/pymrc/subscriber.hpp | 8 +++- python/mrc/_pymrc/src/segment.cpp | 38 ++++++++++++++----- python/mrc/_pymrc/src/subscriber.cpp | 8 +++- python/mrc/core/segment.cpp | 6 --- python/mrc/core/subscriber.cpp | 3 +- python/tests/test_executor.py | 6 +-- python/tests/test_node.py | 36 +++++++++++++++++- 8 files changed, 83 insertions(+), 26 deletions(-) diff --git a/python/mrc/_pymrc/include/pymrc/segment.hpp b/python/mrc/_pymrc/include/pymrc/segment.hpp index 2ceae6f25..94bce476e 100644 --- a/python/mrc/_pymrc/include/pymrc/segment.hpp +++ b/python/mrc/_pymrc/include/pymrc/segment.hpp @@ -143,10 +143,6 @@ class BuilderProxy const std::string& name, pybind11::function gen_factory); - static std::shared_ptr make_source_subscriber(mrc::segment::IBuilder& self, - const std::string& name, - pybind11::function gen_factory); - static std::shared_ptr make_source_component(mrc::segment::IBuilder& self, const std::string& name, pybind11::iterator source_iterator); diff --git a/python/mrc/_pymrc/include/pymrc/subscriber.hpp b/python/mrc/_pymrc/include/pymrc/subscriber.hpp index 5a079906f..6cc793dd5 100644 --- a/python/mrc/_pymrc/include/pymrc/subscriber.hpp +++ b/python/mrc/_pymrc/include/pymrc/subscriber.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +47,12 @@ class SubscriberProxy static bool is_subscribed(PyObjectSubscriber* self); }; +class SubscriptionProxy +{ + public: + static bool is_subscribed(PySubscription* self); +}; + class ObservableProxy { public: diff --git a/python/mrc/_pymrc/src/segment.cpp b/python/mrc/_pymrc/src/segment.cpp index 6afb01967..ec78dc927 100644 --- a/python/mrc/_pymrc/src/segment.cpp +++ b/python/mrc/_pymrc/src/segment.cpp @@ -277,8 +277,9 @@ class SubscriberFuncWrapper : public mrc::pymrc::PythonSource { DVLOG(10) << ctx.info() << " Starting source"; py::gil_scoped_acquire gil; - py::object py_sub = py::cast(subscriber); - auto py_iter = m_gen_factory.operator()(std::move(py_sub)); + PySubscription subscription = subscriber.get_subscription(); + py::object py_sub = py::cast(subscription); + auto py_iter = m_gen_factory.operator()(std::move(py_sub)); PyIteratorWrapper iter_wrapper{std::move(py_iter)}; for (auto next_val : iter_wrapper) @@ -360,14 +361,33 @@ std::shared_ptr BuilderProxy::make_source(mrc::s const std::string& name, py::function gen_factory) { - return build_source(self, name, PyIteratorWrapper(std::move(gen_factory))); -} + // Determine if the gen_factory is expecting to receive a subscription object + auto inspect_mod = py::module::import("inspect"); + auto signature = inspect_mod.attr("signature")(gen_factory); + auto params = signature.attr("parameters"); + auto num_params = py::len(params); + bool expects_subscription = false; + + if (num_params > 0) + { + // We know there is at least one parameter. Check if the first parameter is a subscription object + // Note, when we receive a function that has been bound with `functools.partial(fn, arg1=some_value)`, the + // parameter is still visible in the signature of the partial object. + auto mrc_mod = py::module::import("mrc"); + auto param_values = params.attr("values")(); + auto first_param = py::iter(param_values); + auto type_hint = py::object((*first_param).attr("annotation")); + expects_subscription = (type_hint.is(mrc_mod.attr("Subscription")) || + type_hint.equal(py::str("mrc.Subscription")) || + type_hint.equal(py::str("Subscription"))); + } -std::shared_ptr BuilderProxy::make_source_subscriber(mrc::segment::IBuilder& self, - const std::string& name, - py::function gen_factory) -{ - return self.construct_object(name, std::move(gen_factory)); + if (expects_subscription) + { + return self.construct_object(name, std::move(gen_factory)); + } + + return build_source(self, name, PyIteratorWrapper(std::move(gen_factory))); } std::shared_ptr BuilderProxy::make_source_component(mrc::segment::IBuilder& self, diff --git a/python/mrc/_pymrc/src/subscriber.cpp b/python/mrc/_pymrc/src/subscriber.cpp index c00aaa187..6d94efff9 100644 --- a/python/mrc/_pymrc/src/subscriber.cpp +++ b/python/mrc/_pymrc/src/subscriber.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -115,6 +115,12 @@ bool SubscriberProxy::is_subscribed(PyObjectSubscriber* self) return self->is_subscribed(); } +bool SubscriptionProxy::is_subscribed(PySubscription* self) +{ + // No GIL here + return self->is_subscribed(); +} + PySubscription ObservableProxy::subscribe(PyObjectObservable* self, PyObjectObserver& observer) { // Call the internal subscribe function diff --git a/python/mrc/core/segment.cpp b/python/mrc/core/segment.cpp index 2224fb2e4..6c1898d33 100644 --- a/python/mrc/core/segment.cpp +++ b/python/mrc/core/segment.cpp @@ -134,12 +134,6 @@ PYBIND11_MODULE(segment, py_mod) const std::string&, py::function)>(&BuilderProxy::make_source)); - Builder.def("make_source_subscriber", - static_cast (*)(mrc::segment::IBuilder&, - const std::string&, - py::function)>( - &BuilderProxy::make_source_subscriber)); - Builder.def("make_source_component", static_cast (*)(mrc::segment::IBuilder&, const std::string&, diff --git a/python/mrc/core/subscriber.cpp b/python/mrc/core/subscriber.cpp index d435c4edf..8d6de717a 100644 --- a/python/mrc/core/subscriber.cpp +++ b/python/mrc/core/subscriber.cpp @@ -50,7 +50,8 @@ PYBIND11_MODULE(subscriber, py_mod) // Common must be first in every module pymrc::import(py_mod, "mrc.core.common"); - py::class_(py_mod, "Subscription"); + py::class_(py_mod, "Subscription") + .def("is_subscribed", &SubscriptionProxy::is_subscribed, py::call_guard()); py::class_(py_mod, "Observer") .def("on_next", diff --git a/python/tests/test_executor.py b/python/tests/test_executor.py index eb0e3596f..46381d285 100644 --- a/python/tests/test_executor.py +++ b/python/tests/test_executor.py @@ -69,12 +69,12 @@ def blocking_source(): def build(builder: mrc.Builder): - def gen_data(subscriber: mrc.Subscriber): + def gen_data(subscription: mrc.Subscription): yield 1 - while subscriber.is_subscribed(): + while subscription.is_subscribed(): time.sleep(0.1) - return builder.make_source_subscriber("blocking_source", gen_data) + return builder.make_source("blocking_source", gen_data) return build diff --git a/python/tests/test_node.py b/python/tests/test_node.py index a520e9c65..a59e11eef 100644 --- a/python/tests/test_node.py +++ b/python/tests/test_node.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -489,5 +489,39 @@ def on_completed(): assert on_completed_count == 1 +def test_source_with_bound_value(): + """ + This test ensures that the bound values isn't confused with a subscription object + """ + on_next_value = None + + def segment_init(seg: mrc.Builder): + + def source_gen(a): + yield a + + bound_gen = functools.partial(source_gen, a=1) + source = seg.make_source("my_src", bound_gen) + + def on_next(x: int): + nonlocal on_next_value + on_next_value = x + + sink = seg.make_sink("sink", on_next) + seg.make_edge(source, sink) + + pipeline = mrc.Pipeline() + pipeline.make_segment("my_seg", segment_init) + + options = mrc.Options() + executor = mrc.Executor(options) + executor.register_pipeline(pipeline) + + executor.start() + executor.join() + + assert on_next_value == 1 + + if (__name__ == "__main__"): test_launch_options_properties() From cef7f0bfd96c9934c505c47b38657ffd676bd888 Mon Sep 17 00:00:00 2001 From: Christopher Harris Date: Fri, 4 Oct 2024 08:21:32 -0500 Subject: [PATCH 08/18] Update to RAPIDS 24.10 (#494) Closes https://github.com/nv-morpheus/MRC/issues/478 Requires https://github.com/nv-morpheus/utilities/pull/75 Authors: - Christopher Harris (https://github.com/cwharris) - Michael Demoret (https://github.com/mdemoret-nv) Approvers: - Anuradha Karuppiah (https://github.com/AnuradhaKaruppiah) - David Gardner (https://github.com/dagardner-nv) URL: https://github.com/nv-morpheus/MRC/pull/494 --- .../opt/mrc/bin/post-attach-command.sh | 4 +-- .github/workflows/pr.yaml | 8 ++--- CMakeLists.txt | 2 +- CONTRIBUTING.md | 2 +- Dockerfile | 6 ++-- README.md | 2 +- .../recipes/libmrc/conda_build_config.yaml | 8 ++--- ci/conda/recipes/libmrc/meta.yaml | 18 +++++------ ci/conda/recipes/run_conda_build.sh | 2 +- ci/scripts/github/common.sh | 2 +- ci/scripts/run_ci_local.sh | 2 +- cmake/dependencies.cmake | 32 ++++--------------- ..._64.yaml => all_cuda-125_arch-x86_64.yaml} | 21 ++++++------ ...6_64.yaml => ci_cuda-125_arch-x86_64.yaml} | 21 ++++++------ cpp/mrc/CMakeLists.txt | 19 ++++++----- cpp/mrc/include/mrc/core/utils.hpp | 3 +- cpp/mrc/src/internal/utils/contains.hpp | 14 ++++---- dependencies.yaml | 27 ++++++++-------- docs/quickstart/CMakeLists.txt | 2 +- docs/quickstart/environment_cpp.yml | 4 +-- external/utilities | 2 +- python/mrc/_pymrc/src/logging.cpp | 5 +-- 22 files changed, 94 insertions(+), 112 deletions(-) rename conda/environments/{all_cuda-121_arch-x86_64.yaml => all_cuda-125_arch-x86_64.yaml} (78%) rename conda/environments/{ci_cuda-121_arch-x86_64.yaml => ci_cuda-125_arch-x86_64.yaml} (74%) diff --git a/.devcontainer/opt/mrc/bin/post-attach-command.sh b/.devcontainer/opt/mrc/bin/post-attach-command.sh index e86c3a259..4af1fe68e 100755 --- a/.devcontainer/opt/mrc/bin/post-attach-command.sh +++ b/.devcontainer/opt/mrc/bin/post-attach-command.sh @@ -28,6 +28,6 @@ sed -ri "s/conda activate base/conda activate $ENV_NAME/g" ~/.bashrc; if conda_env_find "${ENV_NAME}" ; \ -then mamba env update --name ${ENV_NAME} -f ${MRC_ROOT}/conda/environments/all_cuda-121_arch-x86_64.yaml --prune; \ -else mamba env create --name ${ENV_NAME} -f ${MRC_ROOT}/conda/environments/all_cuda-121_arch-x86_64.yaml; \ +then mamba env update --name ${ENV_NAME} -f ${MRC_ROOT}/conda/environments/all_cuda-125_arch-x86_64.yaml --prune; \ +else mamba env create --name ${ENV_NAME} -f ${MRC_ROOT}/conda/environments/all_cuda-125_arch-x86_64.yaml; \ fi diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index dce3cfeb6..6f36c3754 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -49,7 +49,7 @@ jobs: - prepare - ci_pipe secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.10 prepare: name: Prepare runs-on: ubuntu-latest @@ -72,7 +72,7 @@ jobs: needs: [prepare] if: ${{ !fromJSON(needs.prepare.outputs.has_skip_ci_label) && fromJSON(needs.prepare.outputs.is_pr )}} secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.02 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.10 with: enable_check_generated_files: false @@ -89,9 +89,9 @@ jobs: # Update conda package only for non PR branches. Use 'main' for main branch and 'dev' for all other branches conda_upload_label: ${{ !fromJSON(needs.prepare.outputs.is_pr) && (fromJSON(needs.prepare.outputs.is_main_branch) && 'main' || 'dev') || '' }} # Build container - container: nvcr.io/ea-nvidia-morpheus/morpheus:mrc-ci-build-240214 + container: nvcr.io/ea-nvidia-morpheus/morpheus:mrc-ci-build-241002 # Test container - test_container: nvcr.io/ea-nvidia-morpheus/morpheus:mrc-ci-test-240214 + test_container: nvcr.io/ea-nvidia-morpheus/morpheus:mrc-ci-test-241002 # Info about the PR. Empty for non PR branches. Useful for extracting PR number, title, etc. pr_info: ${{ needs.prepare.outputs.pr_info }} secrets: diff --git a/CMakeLists.txt b/CMakeLists.txt index 668c00603..c4ff438bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ option(MRC_USE_CONDA "Enables finding dependencies via conda. All dependencies m environment" ON) option(MRC_USE_IWYU "Enable running include-what-you-use as part of the build process" OFF) -set(MRC_RAPIDS_VERSION "24.02" CACHE STRING "Which version of RAPIDS to build for. Sets default versions for RAPIDS CMake and RMM.") +set(MRC_RAPIDS_VERSION "24.10" CACHE STRING "Which version of RAPIDS to build for. Sets default versions for RAPIDS CMake and RMM.") set(MRC_CACHE_DIR "${CMAKE_SOURCE_DIR}/.cache" CACHE PATH "Directory to contain all CPM and CCache data") mark_as_advanced(MRC_CACHE_DIR) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f64ff959..bd755b289 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ cd $MRC_ROOT #### Create MRC Conda environment ```bash # note: `mamba` may be used in place of `conda` for better performance. -conda env create -n mrc --file $MRC_ROOT/conda/environments/all_cuda-121_arch-x86_64.yaml +conda env create -n mrc --file $MRC_ROOT/conda/environments/all_cuda-125_arch-x86_64.yaml conda activate mrc ``` #### Build MRC diff --git a/Dockerfile b/Dockerfile index 989d5a1c1..f2df5d0fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG FROM_IMAGE="rapidsai/ci-conda" -ARG CUDA_VER=12.1.1 +ARG CUDA_VER=12.5.1 ARG LINUX_DISTRO=ubuntu ARG LINUX_VER=22.04 ARG PYTHON_VER=3.10 @@ -45,13 +45,13 @@ RUN useradd --uid $USER_UID --gid $USER_GID -m $USERNAME && \ echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME && \ chmod 0440 /etc/sudoers.d/$USERNAME -COPY ./conda/environments/all_cuda-121_arch-x86_64.yaml /opt/mrc/conda/environments/all_cuda-121_arch-x86_64.yaml +COPY ./conda/environments/all_cuda-125_arch-x86_64.yaml /opt/mrc/conda/environments/all_cuda-125_arch-x86_64.yaml RUN --mount=type=cache,target=/opt/conda/pkgs,sharing=locked \ echo "create env: ${PROJ_NAME}" && \ sudo -g conda -u $USERNAME \ CONDA_ALWAYS_YES=true \ - /opt/conda/bin/mamba env create -q -n ${PROJ_NAME} --file /opt/mrc/conda/environments/all_cuda-121_arch-x86_64.yaml && \ + /opt/conda/bin/mamba env create -q -n ${PROJ_NAME} --file /opt/mrc/conda/environments/all_cuda-125_arch-x86_64.yaml && \ chmod -R a+rwX /opt/conda && \ rm -rf /tmp/conda diff --git a/README.md b/README.md index c79ef1086..3baca24c3 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ cd $MRC_ROOT #### Create MRC Conda Environment ```bash # note: `mamba` may be used in place of `conda` for better performance. -conda env create -n mrc-dev --file $MRC_ROOT/conda/environments/all_cuda-121_arch-x86_64.yaml +conda env create -n mrc-dev --file $MRC_ROOT/conda/environments/all_cuda-125_arch-x86_64.yaml conda activate mrc-dev ``` diff --git a/ci/conda/recipes/libmrc/conda_build_config.yaml b/ci/conda/recipes/libmrc/conda_build_config.yaml index f16cfda2d..0ab4a5dd9 100644 --- a/ci/conda/recipes/libmrc/conda_build_config.yaml +++ b/ci/conda/recipes/libmrc/conda_build_config.yaml @@ -14,20 +14,20 @@ # limitations under the License. c_compiler_version: - - 11.2 + - 12.1 cxx_compiler_version: - - 11.2 + - 12.1 cuda_compiler: - cuda-nvcc cuda_compiler_version: - - 12.1 + - 12.5 python: - 3.10 # Setup the dependencies to build with multiple versions of RAPIDS rapids_version: # Keep around compatibility with current version -2 - - 24.02 + - 24.10 diff --git a/ci/conda/recipes/libmrc/meta.yaml b/ci/conda/recipes/libmrc/meta.yaml index 30916f85c..2664ba474 100644 --- a/ci/conda/recipes/libmrc/meta.yaml +++ b/ci/conda/recipes/libmrc/meta.yaml @@ -15,7 +15,7 @@ {% set version = environ.get('GIT_VERSION', '0.0.0.dev').lstrip('v') + environ.get('VERSION_SUFFIX', '') %} {% set py_version = environ.get('CONDA_PY', '3.10') %} -{% set cuda_version = '.'.join(environ.get('CUDA', '12.1').split('.')[:2]) %} +{% set cuda_version = '.'.join(environ.get('CUDA', '12.5').split('.')[:2]) %} package: name: libmrc-split @@ -42,9 +42,9 @@ requirements: - cmake =3.27 - libtool - ninja =1.11 - - numactl-libs-cos7-x86_64 + - numactl =2.0.18 - pkg-config =0.29 - - sysroot_linux-64 >=2.17 + - sysroot_linux-64 >=2.28 host: # Libraries necessary to build. Keep sorted! - boost-cpp =1.84 @@ -53,8 +53,8 @@ requirements: - cuda-nvrtc-dev {{ cuda_version }}.* - cuda-version {{ cuda_version }}.* - doxygen 1.10.0 - - glog =0.6 - - libgrpc =1.59 + - glog>=0.7.1,<0.8 + - libgrpc =1.62.2 - gtest =1.14 - libhwloc =2.9.2 - librmm {{ rapids_version }} @@ -80,14 +80,14 @@ outputs: - {{ compiler("cuda") }} - {{ compiler("cxx") }} - cmake =3.27 - - numactl-libs-cos7-x86_64 - - sysroot_linux-64 =2.17 + - numactl =2.0.18 + - sysroot_linux-64 >=2.28 host: # Any libraries with weak run_exports need to go here to be added to the run. Keep sorted! - boost-cpp =1.84 - cuda-version # Needed to allow pin_compatible to work - - glog =0.6 - - libgrpc =1.59 + - glog>=0.7.1,<0.8 + - libgrpc =1.62.2 - libhwloc =2.9.2 - librmm {{ rapids_version }} - nlohmann_json =3.11 diff --git a/ci/conda/recipes/run_conda_build.sh b/ci/conda/recipes/run_conda_build.sh index 9f60d4340..263a93388 100755 --- a/ci/conda/recipes/run_conda_build.sh +++ b/ci/conda/recipes/run_conda_build.sh @@ -95,7 +95,7 @@ fi # Choose default variants if hasArg quick; then # For quick build, just do most recent version of rapids - CONDA_ARGS_ARRAY+=("--variants" "{rapids_version: 24.02}") + CONDA_ARGS_ARRAY+=("--variants" "{rapids_version: 24.10}") fi # And default channels (should match dependencies.yaml) diff --git a/ci/scripts/github/common.sh b/ci/scripts/github/common.sh index 542d9b4e9..6b5ba72bd 100644 --- a/ci/scripts/github/common.sh +++ b/ci/scripts/github/common.sh @@ -35,7 +35,7 @@ id export NUM_PROC=${PARALLEL_LEVEL:-$(nproc)} export BUILD_CC=${BUILD_CC:-"gcc"} -export CONDA_ENV_YML="${MRC_ROOT}/conda/environments/all_cuda-121_arch-x86_64.yaml" +export CONDA_ENV_YML="${MRC_ROOT}/conda/environments/all_cuda-125_arch-x86_64.yaml" export CMAKE_BUILD_ALL_FEATURES="-DCMAKE_MESSAGE_CONTEXT_SHOW=ON -DMRC_BUILD_BENCHMARKS=ON -DMRC_BUILD_EXAMPLES=ON -DMRC_BUILD_PYTHON=ON -DMRC_BUILD_TESTS=ON -DMRC_USE_CONDA=ON -DMRC_PYTHON_BUILD_STUBS=ON" export CMAKE_BUILD_WITH_CODECOV="-DCMAKE_BUILD_TYPE=Debug -DMRC_ENABLE_CODECOV=ON -DMRC_PYTHON_PERFORM_INSTALL:BOOL=ON -DMRC_PYTHON_INPLACE_BUILD:BOOL=ON" diff --git a/ci/scripts/run_ci_local.sh b/ci/scripts/run_ci_local.sh index e2da9f6cd..41299c3fb 100755 --- a/ci/scripts/run_ci_local.sh +++ b/ci/scripts/run_ci_local.sh @@ -58,7 +58,7 @@ GIT_BRANCH=$(git branch --show-current) GIT_COMMIT=$(git log -n 1 --pretty=format:%H) BASE_LOCAL_CI_TMP=${BASE_LOCAL_CI_TMP:-${MRC_ROOT}/.tmp/local_ci_tmp} -CONTAINER_VER=${CONTAINER_VER:-240214} +CONTAINER_VER=${CONTAINER_VER:-241002} CUDA_VER=${CUDA_VER:-12.1} DOCKER_EXTRA_ARGS=${DOCKER_EXTRA_ARGS:-""} diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index f1e15d946..477bb374e 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -48,18 +48,8 @@ morpheus_utils_configure_cccl() # ================= morpheus_utils_configure_rmm() -# gflags -# ====== -rapids_find_package(gflags REQUIRED - GLOBAL_TARGETS gflags - BUILD_EXPORT_SET ${PROJECT_NAME}-exports - INSTALL_EXPORT_SET ${PROJECT_NAME}-exports -) - # glog # ==== -# - link against shared -# - todo: compile with -DWITH_GFLAGS=OFF and remove gflags dependency morpheus_utils_configure_glog() # nvidia cub @@ -73,9 +63,9 @@ find_path(CUB_INCLUDE_DIRS "cub/cub.cuh" # ========= rapids_find_package(gRPC REQUIRED GLOBAL_TARGETS - gRPC::address_sorting gRPC::gpr gRPC::grpc gRPC::grpc_unsecure gRPC::grpc++ gRPC::grpc++_alts gRPC::grpc++_error_details gRPC::grpc++_reflection - gRPC::grpc++_unsecure gRPC::grpc_plugin_support gRPC::grpcpp_channelz gRPC::upb gRPC::grpc_cpp_plugin gRPC::grpc_csharp_plugin gRPC::grpc_node_plugin - gRPC::grpc_objective_c_plugin gRPC::grpc_php_plugin gRPC::grpc_python_plugin gRPC::grpc_ruby_plugin + gRPC::address_sorting gRPC::gpr gRPC::grpc gRPC::grpc_unsecure gRPC::grpc++ gRPC::grpc++_alts gRPC::grpc++_error_details gRPC::grpc++_reflection + gRPC::grpc++_unsecure gRPC::grpc_plugin_support gRPC::grpcpp_channelz gRPC::upb gRPC::grpc_cpp_plugin gRPC::grpc_csharp_plugin gRPC::grpc_node_plugin + gRPC::grpc_objective_c_plugin gRPC::grpc_php_plugin gRPC::grpc_python_plugin gRPC::grpc_ruby_plugin BUILD_EXPORT_SET ${PROJECT_NAME}-exports INSTALL_EXPORT_SET ${PROJECT_NAME}-exports ) @@ -101,26 +91,18 @@ morpheus_utils_configure_prometheus_cpp() if(MRC_BUILD_BENCHMARKS) # google benchmark # ================ - rapids_find_package(benchmark REQUIRED - GLOBAL_TARGETS benchmark::benchmark + include(${rapids-cmake-dir}/cpm/gbench.cmake) + rapids_cpm_gbench( BUILD_EXPORT_SET ${PROJECT_NAME}-exports - - # No install set - FIND_ARGS - CONFIG ) endif() if(MRC_BUILD_TESTS) # google test # =========== - rapids_find_package(GTest REQUIRED - GLOBAL_TARGETS GTest::gtest GTest::gmock GTest::gtest_main GTest::gmock_main + include(${rapids-cmake-dir}/cpm/gtest.cmake) + rapids_cpm_gtest( BUILD_EXPORT_SET ${PROJECT_NAME}-exports - - # No install set - FIND_ARGS - CONFIG ) endif() diff --git a/conda/environments/all_cuda-121_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml similarity index 78% rename from conda/environments/all_cuda-121_arch-x86_64.yaml rename to conda/environments/all_cuda-125_arch-x86_64.yaml index 7e6e17b84..1e672e8b0 100644 --- a/conda/environments/all_cuda-121_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -16,31 +16,30 @@ dependencies: - clangxx=16 - cmake=3.27 - codecov=2.1 -- cuda-cudart-dev=12.1 +- cuda-cudart-dev=12.5 - cuda-nvcc -- cuda-nvml-dev=12.1 -- cuda-nvrtc-dev=12.1 -- cuda-tools=12.1 -- cuda-version=12.1 +- cuda-nvml-dev=12.5 +- cuda-nvrtc-dev=12.5 +- cuda-version=12.5 - cxx-compiler - doxygen=1.9.2 - flake8 - gcovr=5.2 - gdb -- glog=0.6 +- glog>=0.7.1,<0.8 - gtest=1.14 -- gxx=11.2 +- gxx=12.1 - include-what-you-use=0.20 - libclang-cpp=16 - libclang=16 -- libgrpc=1.59 +- libgrpc=1.62.2 - libhwloc=2.9.2 -- librmm=24.02 +- librmm=24.10 - libxml2=2.11.6 - llvmdev=16 - ninja=1.11 - nlohmann_json=3.11 -- numactl-libs-cos7-x86_64 +- numactl=2.0.18 - numpy=1.24 - pkg-config=0.29 - pre-commit @@ -53,4 +52,4 @@ dependencies: - scikit-build=0.17 - ucx=1.15 - yapf -name: all_cuda-121_arch-x86_64 +name: all_cuda-125_arch-x86_64 diff --git a/conda/environments/ci_cuda-121_arch-x86_64.yaml b/conda/environments/ci_cuda-125_arch-x86_64.yaml similarity index 74% rename from conda/environments/ci_cuda-121_arch-x86_64.yaml rename to conda/environments/ci_cuda-125_arch-x86_64.yaml index 6d5ccef0a..78cf2d601 100644 --- a/conda/environments/ci_cuda-121_arch-x86_64.yaml +++ b/conda/environments/ci_cuda-125_arch-x86_64.yaml @@ -11,26 +11,25 @@ dependencies: - ccache - cmake=3.27 - codecov=2.1 -- cuda-cudart-dev=12.1 +- cuda-cudart-dev=12.5 - cuda-nvcc -- cuda-nvml-dev=12.1 -- cuda-nvrtc-dev=12.1 -- cuda-tools=12.1 -- cuda-version=12.1 +- cuda-nvml-dev=12.5 +- cuda-nvrtc-dev=12.5 +- cuda-version=12.5 - cxx-compiler - doxygen=1.9.2 - gcovr=5.2 -- glog=0.6 +- glog>=0.7.1,<0.8 - gtest=1.14 -- gxx=11.2 +- gxx=12.1 - include-what-you-use=0.20 -- libgrpc=1.59 +- libgrpc=1.62.2 - libhwloc=2.9.2 -- librmm=24.02 +- librmm=24.10 - libxml2=2.11.6 - ninja=1.11 - nlohmann_json=3.11 -- numactl-libs-cos7-x86_64 +- numactl=2.0.18 - pkg-config=0.29 - pre-commit - pybind11-stubgen=0.10 @@ -41,4 +40,4 @@ dependencies: - python=3.10 - scikit-build=0.17 - ucx=1.15 -name: ci_cuda-121_arch-x86_64 +name: ci_cuda-125_arch-x86_64 diff --git a/cpp/mrc/CMakeLists.txt b/cpp/mrc/CMakeLists.txt index fbebef5cf..88ac29a70 100644 --- a/cpp/mrc/CMakeLists.txt +++ b/cpp/mrc/CMakeLists.txt @@ -163,19 +163,18 @@ add_library(${PROJECT_NAME}::libmrc ALIAS libmrc) target_link_libraries(libmrc PUBLIC - mrc_protos - mrc_architect_protos - rmm::rmm - CUDA::cudart - rxcpp::rxcpp - glog::glog - libcudacxx::libcudacxx - Boost::fiber Boost::context + Boost::fiber + CUDA::cudart glog::glog - gRPC::grpc++ - gRPC::grpc gRPC::gpr + gRPC::grpc + gRPC::grpc++ + libcudacxx::libcudacxx + mrc_architect_protos + mrc_protos + rmm::rmm + rxcpp::rxcpp PRIVATE hwloc::hwloc prometheus-cpp::core # private in MR !199 diff --git a/cpp/mrc/include/mrc/core/utils.hpp b/cpp/mrc/include/mrc/core/utils.hpp index 72d9089a7..78410149a 100644 --- a/cpp/mrc/include/mrc/core/utils.hpp +++ b/cpp/mrc/include/mrc/core/utils.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ #include #include +#include #include #include #include diff --git a/cpp/mrc/src/internal/utils/contains.hpp b/cpp/mrc/src/internal/utils/contains.hpp index 61d613692..690b8e9b2 100644 --- a/cpp/mrc/src/internal/utils/contains.hpp +++ b/cpp/mrc/src/internal/utils/contains.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,13 +29,15 @@ bool contains(const ContainerT& container, const KeyT& key) } template -class KeyIterator : public std::iterator +class KeyIterator { public: + using iterator_category_t = std::bidirectional_iterator_tag; + using value_type = C::key_type; + using difference_type = C::difference_type; + using pointer_t = C::pointer; + using reference_t = C::reference; + KeyIterator() = default; explicit KeyIterator(typename C::const_iterator it) : m_iter(it) {} diff --git a/dependencies.yaml b/dependencies.yaml index 92c468507..22e206c14 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -3,7 +3,7 @@ files: all: output: conda matrix: - cuda: ["12.1"] + cuda: ["12.5"] arch: [x86_64] includes: - build @@ -21,7 +21,7 @@ files: ci: output: conda matrix: - cuda: ["12.1"] + cuda: ["12.5"] arch: [x86_64] includes: - build @@ -55,16 +55,16 @@ dependencies: - cmake=3.27 - cuda-nvcc - cxx-compiler - - glog=0.6 + - glog>=0.7.1,<0.8 - gtest=1.14 - - gxx=11.2 - - libgrpc=1.59 + - gxx=12.1 + - libgrpc=1.62.2 - libhwloc=2.9.2 - - librmm=24.02 - - libxml2=2.11.6 # 2.12 has a bug preventing round-trip serialization in hwloc + - librmm=24.10 + - libxml2=2.11.6 - ninja=1.11 - nlohmann_json=3.11 - - numactl-libs-cos7-x86_64 + - numactl=2.0.18 - pkg-config=0.29 - pybind11-stubgen=0.10 - scikit-build=0.17 @@ -144,10 +144,9 @@ dependencies: - output_types: [conda] matrices: - matrix: - cuda: "12.1" + cuda: "12.5" packages: - - cuda-cudart-dev=12.1 - - cuda-nvml-dev=12.1 - - cuda-nvrtc-dev=12.1 - - cuda-tools=12.1 - - cuda-version=12.1 + - cuda-cudart-dev=12.5 + - cuda-nvml-dev=12.5 + - cuda-nvrtc-dev=12.5 + - cuda-version=12.5 diff --git a/docs/quickstart/CMakeLists.txt b/docs/quickstart/CMakeLists.txt index ef55ee70d..b201248c6 100644 --- a/docs/quickstart/CMakeLists.txt +++ b/docs/quickstart/CMakeLists.txt @@ -42,7 +42,7 @@ set(OPTION_PREFIX "MRC") morpheus_utils_python_configure() -rapids_find_package(mrc REQUIRED) +morpheus_utils_configure_mrc() rapids_find_package(CUDAToolkit REQUIRED) # To make it easier for CI to find output files, set the default executable suffix to .x if not set diff --git a/docs/quickstart/environment_cpp.yml b/docs/quickstart/environment_cpp.yml index a8c0eb2c2..860d54a4c 100644 --- a/docs/quickstart/environment_cpp.yml +++ b/docs/quickstart/environment_cpp.yml @@ -24,14 +24,14 @@ dependencies: - isort - libtool - ninja=1.10 - - numactl-libs-cos7-x86_64 + - numactl=2.0.18 - numpy>=1.21 - nvcc_linux-64=11.8 - pkg-config=0.29 - python=3.10 - scikit-build>=0.12 - mrc=24.10 - - sysroot_linux-64=2.17 + - sysroot_linux-64>=2.28 - pip: - cython - flake8 diff --git a/external/utilities b/external/utilities index fb2c9503f..6e10e2c9a 160000 --- a/external/utilities +++ b/external/utilities @@ -1 +1 @@ -Subproject commit fb2c9503fbfdd08503013f712b8bc1e4d9869933 +Subproject commit 6e10e2c9a686041fdeb3d9e100874c6fa55f0856 diff --git a/python/mrc/_pymrc/src/logging.cpp b/python/mrc/_pymrc/src/logging.cpp index 1150340e8..73455caa5 100644 --- a/python/mrc/_pymrc/src/logging.cpp +++ b/python/mrc/_pymrc/src/logging.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,7 +88,8 @@ void log(const std::string& msg, int py_level, const std::string& filename, int LOG(WARNING) << "Log called prior to calling init_logging, initialized with defaults"; } - google::LogMessage(filename.c_str(), line, static_cast(py_level_to_mrc(py_level))).stream() << msg; + google::LogMessage(filename.c_str(), line, static_cast(py_level_to_mrc(py_level))).stream() + << msg; } } // namespace mrc::pymrc From 43f200ee39f2cb86829c69cffce8d25630884406 Mon Sep 17 00:00:00 2001 From: Michael Demoret <42954918+mdemoret-nv@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:18:26 -0400 Subject: [PATCH 09/18] Adding environment variable to allow skip NUMA node check (#505) If the NUMA node check fails for a GPU, the entire pipeline cannot start. This allows setting `MRC_SKIP_NUMA_CHECK=1` to bypass the assertion if the check fails. Closes #504 Authors: - Michael Demoret (https://github.com/mdemoret-nv) - David Gardner (https://github.com/dagardner-nv) Approvers: - Devin Robison (https://github.com/drobison00) - David Gardner (https://github.com/dagardner-nv) URL: https://github.com/nv-morpheus/MRC/pull/505 --- cpp/mrc/src/internal/system/partitions.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cpp/mrc/src/internal/system/partitions.cpp b/cpp/mrc/src/internal/system/partitions.cpp index 01fbeacca..cc27bffb4 100644 --- a/cpp/mrc/src/internal/system/partitions.cpp +++ b/cpp/mrc/src/internal/system/partitions.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,8 +32,8 @@ #include #include -#include #include +#include // for getenv, size_t #include #include #include @@ -80,7 +80,18 @@ Partitions::Partitions(const Topology& topology, const Options& options) CHECK_NE(rc, -1); if (node_set.weight() != 0) { - CHECK_EQ(node_set.weight(), 1); + if (node_set.weight() != 1) + { + if (std::getenv("MRC_IGNORE_NUMA_CHECK") != nullptr) + { + LOG(WARNING) << "warning: gpu_id: " << gpu_id << " is not associated with exactly 1 numa node"; + } + else + { + LOG(FATAL) << "fatal: gpu_id: " << gpu_id << " is not associated with exactly 1 numa node"; + } + } + gpus_per_numa_node.insert(node_set, gpu_id); } } From 4f23470b9eda6ebb2ee1b93c9efdaf5f0f374df5 Mon Sep 17 00:00:00 2001 From: David Gardner <96306125+dagardner-nv@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:59:01 -0700 Subject: [PATCH 10/18] Fix Incorrect docstring in `mrc.core.coro` (#503) * Include MRC libs with quotes not <> * Add MRC version string to coro module (all the other Python modules have this) * Fix spelling mistake in a comment in `python/mrc/core/executor.cpp` Closes [#492](https://github.com/nv-morpheus/MRC/issues/492) Authors: - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/503 --- python/mrc/_pymrc/include/pymrc/coro.hpp | 3 +-- python/mrc/core/coro.cpp | 12 +++++++----- python/mrc/core/executor.cpp | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/python/mrc/_pymrc/include/pymrc/coro.hpp b/python/mrc/_pymrc/include/pymrc/coro.hpp index ad8224a58..d368f308d 100644 --- a/python/mrc/_pymrc/include/pymrc/coro.hpp +++ b/python/mrc/_pymrc/include/pymrc/coro.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/python/mrc/core/coro.cpp b/python/mrc/core/coro.cpp index d647a7b11..c699355d2 100644 --- a/python/mrc/core/coro.cpp +++ b/python/mrc/core/coro.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,10 @@ */ #include "pymrc/coro.hpp" +#include "mrc/coroutines/task.hpp" +#include "mrc/version.hpp" + #include -#include #include #include #include @@ -36,7 +38,7 @@ PYBIND11_MODULE(coro, _module) { _module.doc() = R"pbdoc( ----------------------- - .. currentmodule:: morpheus.llm + .. currentmodule:: coro .. autosummary:: :toctree: _generate @@ -61,7 +63,7 @@ PYBIND11_MODULE(coro, _module) co_return strings[0]; }); - // _module.attr("__version__") = - // MRC_CONCAT_STR(morpheus_VERSION_MAJOR << "." << morpheus_VERSION_MINOR << "." << morpheus_VERSION_PATCH); + _module.attr("__version__") = MRC_CONCAT_STR(mrc_VERSION_MAJOR << "." << mrc_VERSION_MINOR << "." + << mrc_VERSION_PATCH); } } // namespace mrc::pymrc::coro diff --git a/python/mrc/core/executor.cpp b/python/mrc/core/executor.cpp index 8169733a2..1a616df98 100644 --- a/python/mrc/core/executor.cpp +++ b/python/mrc/core/executor.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,7 @@ #include #include // for move -// IWYU thinks we need vectir for py::class_> +// IWYU thinks we need vector for py::class_> // IWYU pragma: no_include namespace mrc::pymrc { From 4acef2b13a33a12c5dcde85aa15bfc02fdd99143 Mon Sep 17 00:00:00 2001 From: Christopher Harris Date: Mon, 21 Oct 2024 18:15:12 -0500 Subject: [PATCH 11/18] Fix get-pr-info gha reference (#500) `rapidsai/shared-action-workflows/get-pr-info` was deleted in favor of `nv-gha-runners/get-pr-info` https://github.com/rapidsai/shared-actions/pull/11 Authors: - Christopher Harris (https://github.com/cwharris) - David Gardner (https://github.com/dagardner-nv) Approvers: - Michael Demoret (https://github.com/mdemoret-nv) URL: https://github.com/nv-morpheus/MRC/pull/500 --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 6f36c3754..f63edd92c 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -58,7 +58,7 @@ jobs: steps: - name: Get PR Info id: get-pr-info - uses: rapidsai/shared-action-workflows/get-pr-info@branch-23.08 + uses: nv-gha-runners/get-pr-info@main if: ${{ startsWith(github.ref_name, 'pull-request/') }} outputs: is_pr: ${{ startsWith(github.ref_name, 'pull-request/') }} From 61862b56176b1b65f985fc3291eae284513e4dc4 Mon Sep 17 00:00:00 2001 From: Jordan Jacobelli Date: Tue, 22 Oct 2024 21:41:53 +0200 Subject: [PATCH 12/18] devcontainer: replace VAULT_HOST with AWS_ROLE_ARN (#506) This PR is replacing the `VAULT_HOST` variable with `AWS_ROLE_ARN`. This is required to use the new token service to get AWS credentials Authors: - Jordan Jacobelli (https://github.com/jjacobelli) Approvers: - David Gardner (https://github.com/dagardner-nv) URL: https://github.com/nv-morpheus/MRC/pull/506 --- .devcontainer/conda/Dockerfile | 2 +- .devcontainer/conda/devcontainer.json | 2 +- .devcontainer/opt/mrc/conda/Dockerfile | 2 +- .devcontainer/opt/mrc/conda/devcontainer.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/conda/Dockerfile b/.devcontainer/conda/Dockerfile index 62c801dd2..af03369a8 100644 --- a/.devcontainer/conda/Dockerfile +++ b/.devcontainer/conda/Dockerfile @@ -13,6 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM rapidsai/devcontainers:23.04-cuda12.1-mambaforge-ubuntu22.04 AS base +FROM rapidsai/devcontainers:24.12-cuda12.1-mambaforge-ubuntu22.04 AS base ENV PATH="${PATH}:/workspaces/mrc/.devcontainer/bin" diff --git a/.devcontainer/conda/devcontainer.json b/.devcontainer/conda/devcontainer.json index 219f6748b..8f5937318 100644 --- a/.devcontainer/conda/devcontainer.json +++ b/.devcontainer/conda/devcontainer.json @@ -35,7 +35,7 @@ "MRC_ROOT": "${containerWorkspaceFolder}", "DEFAULT_CONDA_ENV": "mrc", "MAMBA_NO_BANNER": "1", - "VAULT_HOST": "https://vault.ops.k8s.rapids.ai" + "AWS_ROLE_ARN": "arn:aws:iam::279114543810:role/nv-gha-token-sccache-devs" }, "initializeCommand": [ "${localWorkspaceFolder}/.devcontainer/conda/initialize-command.sh" ], "remoteUser": "coder", diff --git a/.devcontainer/opt/mrc/conda/Dockerfile b/.devcontainer/opt/mrc/conda/Dockerfile index 62c801dd2..af03369a8 100644 --- a/.devcontainer/opt/mrc/conda/Dockerfile +++ b/.devcontainer/opt/mrc/conda/Dockerfile @@ -13,6 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM rapidsai/devcontainers:23.04-cuda12.1-mambaforge-ubuntu22.04 AS base +FROM rapidsai/devcontainers:24.12-cuda12.1-mambaforge-ubuntu22.04 AS base ENV PATH="${PATH}:/workspaces/mrc/.devcontainer/bin" diff --git a/.devcontainer/opt/mrc/conda/devcontainer.json b/.devcontainer/opt/mrc/conda/devcontainer.json index 6046d60a1..3a9f80795 100644 --- a/.devcontainer/opt/mrc/conda/devcontainer.json +++ b/.devcontainer/opt/mrc/conda/devcontainer.json @@ -35,7 +35,7 @@ "MRC_ROOT": "${containerWorkspaceFolder}", "DEFAULT_CONDA_ENV": "mrc", "MAMBA_NO_BANNER": "1", - "VAULT_HOST": "https://vault.ops.k8s.rapids.ai" + "AWS_ROLE_ARN": "arn:aws:iam::279114543810:role/nv-gha-token-sccache-devs" }, "initializeCommand": [ "${localWorkspaceFolder}/.devcontainer/initialize-command.sh" ], "remoteUser": "coder", From a4b2ca18200fd9928bf07597ea5e7c102bdb8837 Mon Sep 17 00:00:00 2001 From: Michael Demoret <42954918+mdemoret-nv@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:58:41 -0400 Subject: [PATCH 13/18] Add Router nodes to support single input, multi output routing. (#502) Overview of new classes added in this PR: - `DynamicRouterBase` - Sink - `WritableProvider`, `ReadableAcceptor`, `SinkChannelOwner` - Source - `MultiWritableAcceptor`, `MultiReadableProvider`, `MultiSourceChannelOwner` - Runnable with ability to change number of outputs at runtime - Cannot be added to a Segment - Good backpressure support. Can still process requests if downstream blocks - `LambdaDynamicRouter` - Provide key function via lambda - `DynamicRouterComponentBase` - Sink - `ForwardingWritableProvider` - Source - `MultiWritableAcceptor`, `MultiReadableProvider`, `MultiSourceChannelOwner` - Non-Runnable with ability to change number of outputs at runtime - Limited backpressure support. Will block if downstream blocks - `LambdaDynamicRouterComponent` - Provide key function via lambda - `StaticRouterRunnableBase` - Sink - `WritableProvider`, `ReadableAcceptor`, `SinkChannelOwner` - Source - `MultiWritableAcceptor`, `MultiReadableProvider`, `MultiSourceChannelOwner` - Runnable with fixed number of outputs - Can be added to a Segment since it supports child outputs - Good backpressure support. Can still process requests if downstream blocks - `LambdaStaticRouterRunnable` - Provide key function via lambda - `StaticRouterComponentBase` - Sink - `ForwardingWritableProvider` - Source - `MultiWritableAcceptor`, `MultiReadableProvider`, `MultiSourceChannelOwner` - Non-Runnable with fixed number of outputs - Can be added to a Segment since it supports child outputs - Limited backpressure support. Will block if downstream blocks - `LambdaStaticRouterComponent` - Provide key function via lambda Closes #491 Authors: - Michael Demoret (https://github.com/mdemoret-nv) - David Gardner (https://github.com/dagardner-nv) Approvers: - David Gardner (https://github.com/dagardner-nv) URL: https://github.com/nv-morpheus/MRC/pull/502 --- CMakeLists.txt | 43 +- ci/iwyu/mappings.imp | 1 + ci/scripts/cpp_checks.sh | 3 +- cpp/mrc/CMakeLists.txt | 88 ++-- cpp/mrc/include/mrc/api.hpp | 77 --- cpp/mrc/include/mrc/channel/status.hpp | 25 +- cpp/mrc/include/mrc/core/fiber_pool.hpp | 6 +- cpp/mrc/include/mrc/core/task_queue.hpp | 10 +- cpp/mrc/include/mrc/edge/deferred_edge.hpp | 4 +- cpp/mrc/include/mrc/edge/edge.hpp | 15 +- cpp/mrc/include/mrc/edge/edge_builder.hpp | 87 +++- cpp/mrc/include/mrc/edge/edge_channel.hpp | 21 +- cpp/mrc/include/mrc/edge/edge_connector.hpp | 4 +- cpp/mrc/include/mrc/edge/edge_holder.hpp | 14 +- cpp/mrc/include/mrc/edge/edge_readable.hpp | 37 +- cpp/mrc/include/mrc/edge/edge_writable.hpp | 31 +- cpp/mrc/include/mrc/exceptions/checks.hpp | 34 ++ .../mrc/exceptions/exception_catcher.hpp | 6 +- .../modules/mirror_tap/mirror_tap.hpp | 6 +- .../modules/mirror_tap/mirror_tap_stream.hpp | 4 +- .../stream_buffer/stream_buffer_module.hpp | 6 +- cpp/mrc/include/mrc/manifold/egress.hpp | 4 +- .../include/mrc/modules/sample_modules.hpp | 6 +- .../include/mrc/modules/segment_modules.hpp | 23 +- cpp/mrc/include/mrc/node/generic_source.hpp | 4 +- cpp/mrc/include/mrc/node/node_parent.hpp | 47 ++ .../mrc/node/operators/combine_latest.hpp | 250 ++++----- .../mrc/node/operators/conditional.hpp | 17 +- cpp/mrc/include/mrc/node/operators/router.hpp | 453 ++++++++++++++-- .../mrc/node/operators/with_latest_from.hpp | 309 +++++++++++ cpp/mrc/include/mrc/node/operators/zip.hpp | 353 +++++++++++++ .../include/mrc/node/sink_channel_owner.hpp | 6 +- cpp/mrc/include/mrc/node/sink_properties.hpp | 114 +++- .../include/mrc/node/source_channel_owner.hpp | 53 +- .../include/mrc/node/source_properties.hpp | 90 +++- cpp/mrc/include/mrc/segment/builder.hpp | 55 +- cpp/mrc/include/mrc/segment/object.hpp | 491 ++++++++++++++---- cpp/mrc/include/mrc/segment/runnable.hpp | 10 +- cpp/mrc/include/mrc/type_traits.hpp | 11 +- cpp/mrc/include/mrc/utils/tuple_utils.hpp | 103 ++++ cpp/mrc/include/mrc/utils/type_utils.hpp | 21 +- .../internal/pubsub/subscriber_service.cpp | 5 +- .../internal/segment/builder_definition.cpp | 163 ++++-- .../internal/segment/builder_definition.hpp | 14 +- cpp/mrc/src/internal/system/fiber_manager.hpp | 12 +- cpp/mrc/src/internal/system/thread_pool.hpp | 6 +- cpp/mrc/src/public/edge/edge_builder.cpp | 16 +- cpp/mrc/src/public/exceptions/checks.cpp | 37 ++ cpp/mrc/src/public/modules/sample_modules.cpp | 20 +- .../src/public/modules/segment_modules.cpp | 49 +- cpp/mrc/src/tests/test_control_plane.cpp | 26 +- cpp/mrc/src/tests/test_network.cpp | 6 +- cpp/mrc/tests/CMakeLists.txt | 4 + cpp/mrc/tests/modules/dynamic_module.cpp | 5 +- cpp/mrc/tests/modules/test_modules.hpp | 6 +- .../node/operators/test_combine_latest.cpp | 173 ++++++ cpp/mrc/tests/node/operators/test_router.cpp | 395 ++++++++++++++ .../node/operators/test_with_latest_from.cpp | 235 +++++++++ cpp/mrc/tests/node/operators/test_zip.cpp | 172 ++++++ cpp/mrc/tests/node/test_nodes.hpp | 487 +++++++++++++++++ cpp/mrc/tests/test_edges.cpp | 483 +++++------------ cpp/mrc/tests/test_segment.cpp | 307 ++++++++++- mrc.code-workspace | 36 +- python/mrc/_pymrc/CMakeLists.txt | 57 +- .../mrc/_pymrc/include/pymrc/edge_adapter.hpp | 28 +- .../pymrc/utilities/function_wrappers.hpp | 25 +- .../src/utilities/function_wrappers.cpp | 6 +- python/mrc/core/node.cpp | 269 ++++++++++ python/mrc/core/segment.cpp | 5 +- python/mrc/tests/test_edges.cpp | 19 +- python/tests/test_edges.py | 151 ++++++ python/tests/test_pipeline.py | 15 +- 72 files changed, 5127 insertions(+), 1047 deletions(-) delete mode 100644 cpp/mrc/include/mrc/api.hpp create mode 100644 cpp/mrc/include/mrc/exceptions/checks.hpp create mode 100644 cpp/mrc/include/mrc/node/node_parent.hpp create mode 100644 cpp/mrc/include/mrc/node/operators/with_latest_from.hpp create mode 100644 cpp/mrc/include/mrc/node/operators/zip.hpp create mode 100644 cpp/mrc/include/mrc/utils/tuple_utils.hpp create mode 100644 cpp/mrc/src/public/exceptions/checks.cpp create mode 100644 cpp/mrc/tests/node/operators/test_combine_latest.cpp create mode 100644 cpp/mrc/tests/node/operators/test_router.cpp create mode 100644 cpp/mrc/tests/node/operators/test_with_latest_from.cpp create mode 100644 cpp/mrc/tests/node/operators/test_zip.cpp create mode 100644 cpp/mrc/tests/node/test_nodes.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c4ff438bf..fcbebb09b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,8 +85,6 @@ project(mrc morpheus_utils_initialize_install_prefix(MRC_USE_CONDA) -rapids_cmake_write_version_file(${CMAKE_BINARY_DIR}/autogenerated/include/mrc/version.hpp) - # Delay enabling CUDA until after we have determined our CXX compiler if(NOT DEFINED CMAKE_CUDA_HOST_COMPILER) message(STATUS "Setting CUDA host compiler to match CXX compiler: ${CMAKE_CXX_COMPILER}") @@ -180,6 +178,47 @@ if(MRC_BUILD_DOCS) add_subdirectory(docs) endif() +# ################################################################################################## +# - install export --------------------------------------------------------------------------------- + +set(doc_string + [=[ +Provide targets for mrc. +]=]) + +set(code_string "") + +set(rapids_project_version_compat SameMinorVersion) + +# Need to explicitly set VERSION ${PROJECT_VERSION} here since rapids_cmake gets +# confused with the `RAPIDS_VERSION` variable we use +rapids_export(INSTALL ${PROJECT_NAME} + EXPORT_SET ${PROJECT_NAME}-exports + GLOBAL_TARGETS libmrc pymrc + COMPONENTS python + COMPONENTS_EXPORT_SET ${PROJECT_NAME}-python-exports + VERSION ${PROJECT_VERSION} + NAMESPACE mrc:: + DOCUMENTATION doc_string + FINAL_CODE_BLOCK code_string +) + +# ################################################################################################## +# - build export ----------------------------------------------------------------------------------- +rapids_export(BUILD ${PROJECT_NAME} + EXPORT_SET ${PROJECT_NAME}-exports + GLOBAL_TARGETS libmrc pymrc + COMPONENTS python + COMPONENTS_EXPORT_SET ${PROJECT_NAME}-python-exports + VERSION ${PROJECT_VERSION} + LANGUAGES C CXX CUDA + NAMESPACE mrc:: + DOCUMENTATION doc_string + FINAL_CODE_BLOCK code_string +) + +# ################################################################################################## +# - debug info ------------------------------------------------------------------------------------- if (MRC_ENABLE_DEBUG_INFO) morpheus_utils_print_all_targets() diff --git a/ci/iwyu/mappings.imp b/ci/iwyu/mappings.imp index 627e20127..ff6a9f562 100644 --- a/ci/iwyu/mappings.imp +++ b/ci/iwyu/mappings.imp @@ -3,6 +3,7 @@ ## Include mappings # stdlib +{ "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, { "include": [ "", private, "", "public" ] }, diff --git a/ci/scripts/cpp_checks.sh b/ci/scripts/cpp_checks.sh index c9127cc36..95cf68cf9 100755 --- a/ci/scripts/cpp_checks.sh +++ b/ci/scripts/cpp_checks.sh @@ -80,9 +80,8 @@ if [[ -n "${MRC_MODIFIED_FILES}" ]]; then # Include What You Use if [[ "${SKIP_IWYU}" == "" ]]; then - # Remove .h, .hpp, and .cu files from the modified list shopt -s extglob - IWYU_MODIFIED_FILES=( "${MRC_MODIFIED_FILES[@]/*.@(h|hpp|cu)/}" ) + IWYU_MODIFIED_FILES=( "${MRC_MODIFIED_FILES[@]}" ) if [[ -n "${IWYU_MODIFIED_FILES}" ]]; then # Get the list of compiled files relative to this directory diff --git a/cpp/mrc/CMakeLists.txt b/cpp/mrc/CMakeLists.txt index 88ac29a70..10d57f9e3 100644 --- a/cpp/mrc/CMakeLists.txt +++ b/cpp/mrc/CMakeLists.txt @@ -16,6 +16,11 @@ # ################################################################################################## # - libmrc ----------------------------------------------------------------------------------------- +include(GenerateExportHeader) + +# Generate the version header file +rapids_cmake_write_version_file(${CMAKE_CURRENT_BINARY_DIR}/autogenerated/include/mrc/version.hpp) + # Keep all source files sorted!!! add_library(libmrc src/internal/codable/codable_storage.cpp @@ -125,6 +130,7 @@ add_library(libmrc src/public/cuda/sync.cpp src/public/edge/edge_adapter_registry.cpp src/public/edge/edge_builder.cpp + src/public/exceptions/checks.cpp src/public/exceptions/exception_catcher.cpp src/public/manifold/manifold.cpp src/public/memory/buffer_view.cpp @@ -204,6 +210,47 @@ target_compile_features(libmrc PUBLIC cxx_std_20) set_target_properties(libmrc PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +# Generates an include file for specifying external linkage since everything is hidden by default +generate_export_header(libmrc + NO_EXPORT_MACRO_NAME + MRC_LOCAL + EXPORT_FILE_NAME + "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/include/mrc/export.h" +) + +# ################################################################################################## +# - source information ----------------------------------------------------------------------------- + +# Ideally, we dont use glob here. But there is no good way to guarantee you dont miss anything like *.cpp +file(GLOB_RECURSE libmrc_public_headers + LIST_DIRECTORIES FALSE + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/include/mrc/*" +) + +# Add headers to target sources file_set so they can be installed +# https://discourse.cmake.org/t/installing-headers-the-modern-way-regurgitated-and-revisited/3238/3 +target_sources(libmrc + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" + FILES + ${libmrc_public_headers} +) + +# Add generated headers to fileset +target_sources(libmrc + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS + "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/include" + FILES + "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/include/mrc/version.hpp" + "${CMAKE_CURRENT_BINARY_DIR}/autogenerated/include/mrc/export.h" +) + # ################################################################################################## # - install targets -------------------------------------------------------------------------------- rapids_cmake_install_lib_dir(lib_dir) @@ -215,12 +262,7 @@ install( DESTINATION ${lib_dir} EXPORT ${PROJECT_NAME}-exports COMPONENT Core -) - -install( - DIRECTORY include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - COMPONENT Core + FILE_SET public_headers ) # ################################################################################################## @@ -234,37 +276,3 @@ endif() if(MRC_BUILD_BENCHMARKS) add_subdirectory(benchmarks) endif() - -# ################################################################################################## -# - install export --------------------------------------------------------------------------------- -set(doc_string - [=[ -Provide targets for mrc. -]=]) - -set(code_string "") - -set(rapids_project_version_compat SameMinorVersion) - -# Need to explicitly set VERSION ${PROJECT_VERSION} here since rapids_cmake gets -# confused with the `RAPIDS_VERSION` variable we use -rapids_export(INSTALL ${PROJECT_NAME} - EXPORT_SET ${PROJECT_NAME}-exports - GLOBAL_TARGETS libmrc - VERSION ${PROJECT_VERSION} - NAMESPACE mrc:: - DOCUMENTATION doc_string - FINAL_CODE_BLOCK code_string -) - -# ################################################################################################## -# - build export ---------------------------------------------------------------------------------- -rapids_export(BUILD ${PROJECT_NAME} - EXPORT_SET ${PROJECT_NAME}-exports - GLOBAL_TARGETS libmrc - VERSION ${PROJECT_VERSION} - LANGUAGES C CXX CUDA - NAMESPACE mrc:: - DOCUMENTATION doc_string - FINAL_CODE_BLOCK code_string -) diff --git a/cpp/mrc/include/mrc/api.hpp b/cpp/mrc/include/mrc/api.hpp deleted file mode 100644 index ede97c7e9..000000000 --- a/cpp/mrc/include/mrc/api.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -// Generic helper definitions for shared library support from -// https://gcc.gnu.org/wiki/Visibility - -// For every non-templated non-static function definition in your library (both headers and source files), decide if it -// is publicly used or internally used: If it is publicly used, mark with MRC_API like this: extern MRC_API PublicFunc() - -// If it is only internally used, mark with MRC_LOCAL like this: extern MRC_LOCAL PublicFunc() Remember, static -// functions need no demarcation, nor does anything in an anonymous namespace, nor does anything which is templated. - -// For every non-templated class definition in your library (both headers and source files), decide if it is publicly -// used or internally used: If it is publicly used, mark with MRC_API like this: class MRC_API PublicClass - -// If it is only internally used, mark with MRC_LOCAL like this: class MRC_LOCAL PublicClass - -// Individual member functions of an exported class that are not part of the interface, in particular ones which are -// private, and are not used by friend code, should be marked individually with MRC_LOCAL. - -// In your build system (Makefile etc), you will probably wish to add the -fvisibility=hidden and -// -fvisibility-inlines-hidden options to the command line arguments of every GCC invocation. Remember to test your -// library thoroughly afterwards, including that all exceptions correctly traverse shared object boundaries. - -#if defined _WIN32 || defined __CYGWIN__ - #define MRC_HELPER_DLL_IMPORT __declspec(dllimport) - #define MRC_HELPER_DLL_EXPORT __declspec(dllexport) - #define MRC_HELPER_DLL_LOCAL -#else - #if __GNUC__ >= 4 - #define MRC_HELPER_DLL_IMPORT __attribute__((visibility("default"))) - #define MRC_HELPER_DLL_EXPORT __attribute__((visibility("default"))) - #define MRC_HELPER_DLL_LOCAL __attribute__((visibility("hidden"))) - #else - #define MRC_HELPER_DLL_IMPORT - #define MRC_HELPER_DLL_EXPORT - #define MRC_HELPER_DLL_LOCAL - #endif -#endif - -// Now we use the generic helper definitions above to define MRC_API and MRC_LOCAL. -// MRC_API is used for the public API symbols. It either DLL imports or DLL exports (or does nothing for static build) -// MRC_LOCAL is used for non-api symbols. - -#define MRC_DLL // we alway build the .so/.dll -#ifdef libmrc_EXPORTS - #define MRC_DLL_EXPORTS -#endif - -#ifdef MRC_DLL // defined if MRC is compiled as a DLL - #ifdef MRC_DLL_EXPORTS // defined if we are building the MRC DLL (instead of using it) - #define MRC_API MRC_HELPER_DLL_EXPORT - #else - #define MRC_API MRC_HELPER_DLL_IMPORT - #endif // MRC_DLL_EXPORTS - #define MRC_LOCAL MRC_HELPER_DLL_LOCAL -#else // MRC_DLL is not defined: this means MRC is a static lib. - #define MRC_API - #define MRC_LOCAL -static_assert(false, "always build the .so/.dll") -#endif // MRC_DLL diff --git a/cpp/mrc/include/mrc/channel/status.hpp b/cpp/mrc/include/mrc/channel/status.hpp index 91f6b2800..cf307b9b1 100644 --- a/cpp/mrc/include/mrc/channel/status.hpp +++ b/cpp/mrc/include/mrc/channel/status.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,8 @@ #pragma once +#include + namespace mrc::channel { enum class Status @@ -29,4 +31,25 @@ enum class Status error }; +static inline std::ostream& operator<<(std::ostream& os, const Status& s) +{ + switch (s) + { + case Status::success: + return os << "success"; + case Status::empty: + return os << "empty"; + case Status::full: + return os << "full"; + case Status::closed: + return os << "closed"; + case Status::timeout: + return os << "timeout"; + case Status::error: + return os << "error"; + default: + throw std::logic_error("Unsupported channel::Status enum. Was a new value added recently?"); + } } + +} // namespace mrc::channel diff --git a/cpp/mrc/include/mrc/core/fiber_pool.hpp b/cpp/mrc/include/mrc/core/fiber_pool.hpp index 09838d473..94ff9b8ad 100644 --- a/cpp/mrc/include/mrc/core/fiber_pool.hpp +++ b/cpp/mrc/include/mrc/core/fiber_pool.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,14 +43,14 @@ class FiberPool [[nodiscard]] virtual std::size_t thread_count() const = 0; template - auto enqueue(std::uint32_t index, F&& f, ArgsT&&... args) -> Future::type> + auto enqueue(std::uint32_t index, F&& f, ArgsT&&... args) -> Future> { return task_queue(index).enqueue(f, std::forward(args)...); } template auto enqueue(std::uint32_t index, MetaDataT&& md, F&& f, ArgsT&&... args) - -> Future::type> + -> Future> { return task_queue(index).enqueue(std::forward(md), std::forward(f), std::forward(args)...); } diff --git a/cpp/mrc/include/mrc/core/task_queue.hpp b/cpp/mrc/include/mrc/core/task_queue.hpp index 492f0a165..b9db8127d 100644 --- a/cpp/mrc/include/mrc/core/task_queue.hpp +++ b/cpp/mrc/include/mrc/core/task_queue.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,7 @@ class FiberTaskQueue virtual ~FiberTaskQueue() = default; template - auto enqueue(F&& f, ArgsT&&... args) -> Future::type> + auto enqueue(F&& f, ArgsT&&... args) -> Future> { FiberMetaData meta_data; return enqueue(meta_data, std::forward(f), std::forward(args)...); @@ -48,7 +48,7 @@ class FiberTaskQueue template auto enqueue(const FiberMetaData& meta_data, F&& f, ArgsT&&... args) - -> Future::type> + -> Future> { FiberMetaData copy = meta_data; return enqueue(std::move(copy), std::forward(f), std::forward(args)...); @@ -56,7 +56,7 @@ class FiberTaskQueue template auto enqueue(FiberMetaData&& meta_data, F&& f, ArgsT&&... args) - -> Future::type> + -> Future> { if (task_queue().is_closed()) { @@ -64,7 +64,7 @@ class FiberTaskQueue } using namespace boost::fibers; - using return_type_t = typename std::result_of::type; + using return_type_t = typename std::invoke_result_t; packaged_task task(std::bind(std::forward(f), std::forward(args)...)); future future = task.get_future(); diff --git a/cpp/mrc/include/mrc/edge/deferred_edge.hpp b/cpp/mrc/include/mrc/edge/deferred_edge.hpp index bf333b88b..c51aa2633 100644 --- a/cpp/mrc/include/mrc/edge/deferred_edge.hpp +++ b/cpp/mrc/include/mrc/edge/deferred_edge.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,7 @@ namespace mrc::edge { -class DeferredWritableMultiEdgeBase : public IMultiWritableAcceptorBase, +class DeferredWritableMultiEdgeBase : public virtual IMultiWritableAcceptorBase, public virtual IEdgeWritableBase, public virtual EdgeBase { diff --git a/cpp/mrc/include/mrc/edge/edge.hpp b/cpp/mrc/include/mrc/edge/edge.hpp index ea2a35e07..66a40ac3d 100644 --- a/cpp/mrc/include/mrc/edge/edge.hpp +++ b/cpp/mrc/include/mrc/edge/edge.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,7 @@ #include "mrc/exceptions/runtime_error.hpp" #include "mrc/type_traits.hpp" #include "mrc/utils/string_utils.hpp" +#include "mrc/utils/type_utils.hpp" #include #include @@ -257,13 +258,25 @@ class EdgeTypeInfo m_unwrapped_type(unwrapped_type), m_is_deferred(is_deferred) { + if (m_full_type.has_value()) + { + m_full_type_str = type_name(m_full_type.value()); + } + + if (m_unwrapped_type.has_value()) + { + m_unwrapped_type_str = type_name(m_unwrapped_type.value()); + } + CHECK((m_is_deferred && !m_full_type.has_value() && !m_unwrapped_type.has_value()) || (!m_is_deferred && m_full_type.has_value() && m_unwrapped_type.has_value())) << "Inconsistent deferred setting with concrete types"; } std::optional m_full_type; // Includes any wrappers like shared_ptr + std::string m_full_type_str; // For debugging purposes only std::optional m_unwrapped_type; // Excludes any wrappers like shared_ptr if they exist + std::string m_unwrapped_type_str; // For debugging purposes only bool m_is_deferred{false}; // Whether or not this type is deferred or concrete }; diff --git a/cpp/mrc/include/mrc/edge/edge_builder.hpp b/cpp/mrc/include/mrc/edge/edge_builder.hpp index 78e88b577..a4151dedc 100644 --- a/cpp/mrc/include/mrc/edge/edge_builder.hpp +++ b/cpp/mrc/include/mrc/edge/edge_builder.hpp @@ -363,6 +363,31 @@ class DeferredWritableMultiEdge : public MultiEdgeHolder, } protected: + bool has_writable_edge(const std::size_t& key) const override + { + return MultiEdgeHolder::has_edge_connection(key); + } + + void release_writable_edge(const std::size_t& key) override + { + return MultiEdgeHolder::release_edge_connection(key); + } + + void release_writable_edges() override + { + return MultiEdgeHolder::release_edge_connections(); + } + + size_t writable_edge_count() const override + { + return MultiEdgeHolder::edge_connection_count(); + } + + std::vector writable_edge_keys() const override + { + return MultiEdgeHolder::edge_connection_keys(); + } + std::shared_ptr> get_writable_edge(std::size_t edge_idx) const { return std::dynamic_pointer_cast>(this->get_connected_edge(edge_idx)); @@ -457,39 +482,79 @@ std::shared_ptr EdgeBuilder::adapt_readable_edge(std::shared // Put make edge in the mrc namespace since it is used so often namespace mrc { +/** + * @brief Since its not currently possible to dynamically build an error message for `static_assert`, provide all of the + * information that would be in the error message about the types as template parameters. This way the value of the + * template parameters is still displayed in the error message. + * + */ +namespace detail { +template +void display_make_edge_error_message() +{ + static_assert(!sizeof(SourceT), + "Arguments to make_edge were incorrect. Ensure you are providing either " + "WritableAcceptor->WritableProvider or ReadableProvider->ReadableAcceptor"); +} +} // namespace detail + template void make_edge(SourceT& source, SinkT& sink) { using source_full_t = SourceT; using sink_full_t = SinkT; - if constexpr (is_base_of_template::value && - is_base_of_template::value) + constexpr bool IsSourceIWritableAcceptor = is_base_of_template::value; + constexpr bool IsSourceIReadableProvider = is_base_of_template::value; + + constexpr bool IsSinkIWritableProvider = is_base_of_template::value; + constexpr bool IsSinkIReadableAcceptor = is_base_of_template::value; + + constexpr bool IsSourceIWritableAcceptorBase = std::is_base_of_v; + constexpr bool IsSourceIReadableProviderBase = std::is_base_of_v; + + constexpr bool IsSinkIWritableProviderBase = std::is_base_of_v; + constexpr bool IsSinkIReadableAcceptorBase = std::is_base_of_v; + + if constexpr (IsSourceIWritableAcceptor && IsSinkIWritableProvider) { // Call the typed version for ingress provider/acceptor edge::EdgeBuilder::make_edge_writable(source, sink); } - else if constexpr (is_base_of_template::value && - is_base_of_template::value) + else if constexpr (IsSourceIReadableProvider && IsSinkIReadableAcceptor) { // Call the typed version for egress provider/acceptor edge::EdgeBuilder::make_edge_readable(source, sink); } - else if constexpr (std::is_base_of_v && - std::is_base_of_v) + else if constexpr (IsSourceIWritableAcceptorBase && IsSinkIWritableProviderBase) { edge::EdgeBuilder::make_edge_writable_typeless(source, sink); } - else if constexpr (std::is_base_of_v && - std::is_base_of_v) + else if constexpr (IsSourceIReadableProviderBase && IsSinkIReadableAcceptorBase) { edge::EdgeBuilder::make_edge_readable_typeless(source, sink); } else { - static_assert(!sizeof(source_full_t), - "Arguments to make_edge were incorrect. Ensure you are providing either " - "WritableAcceptor->WritableProvider or ReadableProvider->ReadableAcceptor"); + detail::display_make_edge_error_message(); } } diff --git a/cpp/mrc/include/mrc/edge/edge_channel.hpp b/cpp/mrc/include/mrc/edge/edge_channel.hpp index 5da85d74c..d821d4112 100644 --- a/cpp/mrc/include/mrc/edge/edge_channel.hpp +++ b/cpp/mrc/include/mrc/edge/edge_channel.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ #include "mrc/edge/edge_readable.hpp" #include "mrc/edge/edge_writable.hpp" #include "mrc/edge/forward.hpp" +#include "mrc/utils/macros.hpp" #include @@ -89,6 +90,24 @@ class EdgeChannel { CHECK(m_channel) << "Cannot create an EdgeChannel from an empty pointer"; } + + EdgeChannel(EdgeChannel&& other) : m_channel(std::move(other.m_channel)) {} + + EdgeChannel& operator=(EdgeChannel&& other) + { + if (this == &other) + { + return *this; + } + + m_channel = std::move(other.m_channel); + + return *this; + } + + // This should not be copyable because it requires passing in a unique_ptr + DELETE_COPYABILITY(EdgeChannel); + virtual ~EdgeChannel() = default; [[nodiscard]] std::shared_ptr> get_reader() const diff --git a/cpp/mrc/include/mrc/edge/edge_connector.hpp b/cpp/mrc/include/mrc/edge/edge_connector.hpp index 6df92b1f2..be75019a5 100644 --- a/cpp/mrc/include/mrc/edge/edge_connector.hpp +++ b/cpp/mrc/include/mrc/edge/edge_connector.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -99,7 +99,7 @@ struct EdgeConnector EdgeAdapterRegistry::register_egress_converter( typeid(InputT), typeid(OutputT), - [lambda_fn](std::shared_ptr channel) { + [lambda_fn](std::shared_ptr channel) { std::shared_ptr> egress = std::dynamic_pointer_cast>( channel); diff --git a/cpp/mrc/include/mrc/edge/edge_holder.hpp b/cpp/mrc/include/mrc/edge/edge_holder.hpp index 0262a7e71..ac920b12f 100644 --- a/cpp/mrc/include/mrc/edge/edge_holder.hpp +++ b/cpp/mrc/include/mrc/edge/edge_holder.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -211,6 +211,18 @@ class MultiEdgeHolder edge_pair.init_owned_edge(std::move(edge)); } + void init_connected_edge(KeyT key, std::shared_ptr> edge) + { + auto& edge_pair = this->get_edge_pair(key, true); + + edge_pair.init_connected_edge(std::move(edge)); + } + + bool has_edge_connection(const KeyT& key) const + { + return m_edges.contains(key); + } + std::shared_ptr get_edge_connection(const KeyT& key) const { auto& edge_pair = this->get_edge_pair(key); diff --git a/cpp/mrc/include/mrc/edge/edge_readable.hpp b/cpp/mrc/include/mrc/edge/edge_readable.hpp index 2495341fd..2f68d9550 100644 --- a/cpp/mrc/include/mrc/edge/edge_readable.hpp +++ b/cpp/mrc/include/mrc/edge/edge_readable.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -192,8 +192,32 @@ class IReadableAcceptorBase virtual EdgeTypeInfo readable_acceptor_type() const = 0; }; +template +class IMultiReadableProviderBase +{ + public: + virtual bool has_readable_edge(const KeyT& key) const = 0; + virtual void release_readable_edge(const KeyT& key) = 0; + virtual void release_readable_edges() = 0; + virtual size_t readable_edge_count() const = 0; + virtual std::vector readable_edge_keys() const = 0; + virtual std::shared_ptr get_readable_edge_handle(KeyT key) const = 0; +}; + +template +class IMultiReadableAcceptorBase +{ + public: + virtual bool has_readable_edge(const KeyT& key) const = 0; + virtual void release_readable_edge(const KeyT& key) = 0; + virtual void release_readable_edges() = 0; + virtual size_t readable_edge_count() const = 0; + virtual std::vector readable_edge_keys() const = 0; + virtual void set_readable_edge_handle(KeyT key, std::shared_ptr egress) = 0; +}; + template -class IReadableProvider : public IReadableProviderBase +class IReadableProvider : public virtual IReadableProviderBase { public: EdgeTypeInfo readable_provider_type() const override @@ -203,7 +227,7 @@ class IReadableProvider : public IReadableProviderBase }; template -class IReadableAcceptor : public IReadableAcceptorBase +class IReadableAcceptor : public virtual IReadableAcceptorBase { public: EdgeTypeInfo readable_acceptor_type() const override @@ -212,4 +236,11 @@ class IReadableAcceptor : public IReadableAcceptorBase } }; +template +class IMultiReadableProvider : public virtual IMultiReadableProviderBase +{}; + +template +class IMultiReadableAcceptor : public virtual IMultiReadableAcceptorBase +{}; } // namespace mrc::edge diff --git a/cpp/mrc/include/mrc/edge/edge_writable.hpp b/cpp/mrc/include/mrc/edge/edge_writable.hpp index 04db574f9..9d0824279 100644 --- a/cpp/mrc/include/mrc/edge/edge_writable.hpp +++ b/cpp/mrc/include/mrc/edge/edge_writable.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -203,15 +203,32 @@ class IWritableAcceptorBase virtual EdgeTypeInfo writable_acceptor_type() const = 0; }; +template +class IMultiWritableProviderBase +{ + public: + virtual bool has_writable_edge(const KeyT& key) const = 0; + virtual void release_writable_edge(const KeyT& key) = 0; + virtual void release_writable_edges() = 0; + virtual size_t writable_edge_count() const = 0; + virtual std::vector writable_edge_keys() const = 0; + virtual std::shared_ptr get_writable_edge_handle(KeyT key) const = 0; +}; + template class IMultiWritableAcceptorBase { public: + virtual bool has_writable_edge(const KeyT& key) const = 0; + virtual void release_writable_edge(const KeyT& key) = 0; + virtual void release_writable_edges() = 0; + virtual size_t writable_edge_count() const = 0; + virtual std::vector writable_edge_keys() const = 0; virtual void set_writable_edge_handle(KeyT key, std::shared_ptr ingress) = 0; }; template -class IWritableProvider : public IWritableProviderBase +class IWritableProvider : public virtual IWritableProviderBase { public: EdgeTypeInfo writable_provider_type() const override @@ -221,7 +238,7 @@ class IWritableProvider : public IWritableProviderBase }; template -class IWritableAcceptor : public IWritableAcceptorBase +class IWritableAcceptor : public virtual IWritableAcceptorBase { public: EdgeTypeInfo writable_acceptor_type() const override @@ -230,8 +247,12 @@ class IWritableAcceptor : public IWritableAcceptorBase } }; -template -class IMultiWritableAcceptor : public IMultiWritableAcceptorBase +template +class IMultiWritableProvider : public virtual IMultiWritableProviderBase +{}; + +template +class IMultiWritableAcceptor : public virtual IMultiWritableAcceptorBase {}; } // namespace mrc::edge diff --git a/cpp/mrc/include/mrc/exceptions/checks.hpp b/cpp/mrc/include/mrc/exceptions/checks.hpp new file mode 100644 index 000000000..daef0d411 --- /dev/null +++ b/cpp/mrc/include/mrc/exceptions/checks.hpp @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace mrc::exceptions { + +void throw_failed_check_exception(const std::string& file, + const std::string& function, + unsigned int line, + const std::string& msg = ""); + +#define MRC_CHECK_THROW(condition) \ + for (std::stringstream ss; !(condition); \ + ::mrc::exceptions::throw_failed_check_exception(__FILE__, __func__, __LINE__, ss.str())) \ + ss + +} // namespace mrc::exceptions diff --git a/cpp/mrc/include/mrc/exceptions/exception_catcher.hpp b/cpp/mrc/include/mrc/exceptions/exception_catcher.hpp index 98c4a7d6d..75a3e5906 100644 --- a/cpp/mrc/include/mrc/exceptions/exception_catcher.hpp +++ b/cpp/mrc/include/mrc/exceptions/exception_catcher.hpp @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +15,12 @@ * limitations under the License. */ +#pragma once + #include #include #include -#pragma once - namespace mrc { /** diff --git a/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap.hpp b/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap.hpp index 8b0d12099..9124ca451 100644 --- a/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap.hpp +++ b/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,8 +88,8 @@ void MirrorTapModule::initialize(segment::IBuilder& builder) builder.make_edge(bcast, builder.get_egress(m_egress_name)); // to mirror tap // Register the submodules output as one of this module's outputs - register_input_port("input", bcast); - register_output_port("output", bcast); + builder.register_module_input("input", bcast); + builder.register_module_output("output", bcast); } template diff --git a/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap_stream.hpp b/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap_stream.hpp index b053a4098..1a6d8b68d 100644 --- a/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap_stream.hpp +++ b/cpp/mrc/include/mrc/experimental/modules/mirror_tap/mirror_tap_stream.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +89,7 @@ void MirrorTapStreamModule::initialize(segment::IBuilder& builder) builder.make_edge(mirror_ingress, m_stream_buffer->input_port("input")); - register_output_port("output", m_stream_buffer->output_port("output")); + builder.register_module_output("output", m_stream_buffer->output_port("output")); } template diff --git a/cpp/mrc/include/mrc/experimental/modules/stream_buffer/stream_buffer_module.hpp b/cpp/mrc/include/mrc/experimental/modules/stream_buffer/stream_buffer_module.hpp index d7eeece45..6feafe9ee 100644 --- a/cpp/mrc/include/mrc/experimental/modules/stream_buffer/stream_buffer_module.hpp +++ b/cpp/mrc/include/mrc/experimental/modules/stream_buffer/stream_buffer_module.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -138,8 +138,8 @@ void StreamBufferModule::initialize(segment::IBuil } }); - register_input_port("input", buffer_sink); - register_output_port("output", buffer_source); + builder.register_module_input("input", buffer_sink); + builder.register_module_output("output", buffer_source); } template class StreamBufferTypeT> diff --git a/cpp/mrc/include/mrc/manifold/egress.hpp b/cpp/mrc/include/mrc/manifold/egress.hpp index 781122d61..6b7b67168 100644 --- a/cpp/mrc/include/mrc/manifold/egress.hpp +++ b/cpp/mrc/include/mrc/manifold/egress.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,7 +53,7 @@ class TypedEgress : public EgressDelegate }; template -class RoundRobinEgress : public node::Router, public TypedEgress +class RoundRobinEgress : public node::DynamicRouterComponentBase, public TypedEgress { protected: SegmentAddress determine_key_for_value(const T& t) override diff --git a/cpp/mrc/include/mrc/modules/sample_modules.hpp b/cpp/mrc/include/mrc/modules/sample_modules.hpp index 23db4db57..3ba6e4bfd 100644 --- a/cpp/mrc/include/mrc/modules/sample_modules.hpp +++ b/cpp/mrc/include/mrc/modules/sample_modules.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -183,7 +183,7 @@ void TemplateModule::initialize(segment::IBuilder& builder) }); // Register the submodules output as one of this module's outputs - register_output_port("source", source); + builder.register_module_output("source", source); } template @@ -248,7 +248,7 @@ void TemplateWithInitModule::initialize(segment::IBuil }); // Register the submodules output as one of this module's outputs - register_output_port("source", source); + builder.register_module_output("source", source); } template diff --git a/cpp/mrc/include/mrc/modules/segment_modules.hpp b/cpp/mrc/include/mrc/modules/segment_modules.hpp index 3cabb0d03..508b6d5f9 100644 --- a/cpp/mrc/include/mrc/modules/segment_modules.hpp +++ b/cpp/mrc/include/mrc/modules/segment_modules.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -138,6 +138,23 @@ class SegmentModule */ virtual void initialize(segment::IBuilder& builder) = 0; + private: + /** + * @brief Registers an object with the module to keep it alive + * + * @param name The name of the object + * @param object The object to register + */ + void register_object(std::string name, std::shared_ptr object); + + /** + * @brief Find an object by name. Must be registered with the module + * + * @param name The name of the object + * @return segment::ObjectProperties& + */ + segment::ObjectProperties& find_object(const std::string& name) const; + /* Interface Functions */ /** * Register an input port that should be exposed for the module @@ -153,7 +170,6 @@ class SegmentModule */ void register_output_port(std::string output_name, std::shared_ptr object); - private: /** * Register an input port that should be exposed for the module, with explicit type index. This is * necessary for Objects that aren't explicit Source or Sink types (e.g. a custom object type) @@ -188,6 +204,9 @@ class SegmentModule segment_module_port_map_t m_input_ports{}; segment_module_port_map_t m_output_ports{}; + // Maintain a map of all objects to keep them alive. These are registered as internal names + std::map> m_objects; + const nlohmann::json m_config; friend class segment::BuilderDefinition; diff --git a/cpp/mrc/include/mrc/node/generic_source.hpp b/cpp/mrc/include/mrc/node/generic_source.hpp index 19956d422..7f16492d1 100644 --- a/cpp/mrc/include/mrc/node/generic_source.hpp +++ b/cpp/mrc/include/mrc/node/generic_source.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -68,7 +68,7 @@ GenericSource::GenericSource() : {} template -class GenericSourceComponent : public ForwardingEgressProvider +class GenericSourceComponent : public ForwardingReadableProvider { public: GenericSourceComponent() = default; diff --git a/cpp/mrc/include/mrc/node/node_parent.hpp b/cpp/mrc/include/mrc/node/node_parent.hpp new file mode 100644 index 000000000..280e5395d --- /dev/null +++ b/cpp/mrc/include/mrc/node/node_parent.hpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace mrc::node { + +template +class HomogeneousNodeParent +{ + public: + using child_node_t = ChildT; + + virtual std::map> get_children_refs( + std::optional child_name = std::nullopt) const = 0; +}; + +template +class HeterogeneousNodeParent +{ + public: + using child_types_t = std::tuple; + + virtual std::tuple>...> get_children_refs() const = 0; +}; + +} // namespace mrc::node diff --git a/cpp/mrc/include/mrc/node/operators/combine_latest.hpp b/cpp/mrc/include/mrc/node/operators/combine_latest.hpp index a5d50d217..cef6c0d3c 100644 --- a/cpp/mrc/include/mrc/node/operators/combine_latest.hpp +++ b/cpp/mrc/include/mrc/node/operators/combine_latest.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,11 @@ #pragma once #include "mrc/channel/status.hpp" +#include "mrc/node/node_parent.hpp" #include "mrc/node/sink_properties.hpp" #include "mrc/node/source_properties.hpp" +#include "mrc/types.hpp" +#include "mrc/utils/tuple_utils.hpp" #include "mrc/utils/type_utils.hpp" #include @@ -29,152 +32,88 @@ #include #include +// IWYU pragma: begin_exports + namespace mrc::node { -// template -// class ParameterPackIndexer -// { -// public: -// ParameterPackIndexer(TypesT... ts) : ParameterPackIndexer(std::make_index_sequence{}, ts...) -// {} - -// std::tuple...> tup; - -// private: -// template -// ParameterPackIndexer(std::index_sequence const& /*unused*/, TypesT... ts) : tup{std::make_tuple(ts, -// Is)...} -// {} -// }; - -// template -// constexpr size_t getTypeIndexInTemplateList() -// { -// if constexpr (std::is_same::value) -// { -// return 0; -// } -// else -// { -// return 1 + getTypeIndexInTemplateList(); -// } -// } - -namespace detail { -struct Surely +class CombineLatestTypelessBase { - template - auto operator()(const T&... t) const -> decltype(std::make_tuple(t.value()...)) - { - return std::make_tuple(t.value()...); - } + public: + virtual ~CombineLatestTypelessBase() = default; }; -} // namespace detail -// template -// inline auto surely(const std::tuple& tpl) -> decltype(rxcpp::util::apply(tpl, detail::surely())) -// { -// return rxcpp::util::apply(tpl, detail::surely()); -// } +template +class CombineLatestBase; -template -inline auto surely2(const std::tuple& tpl) -{ - return std::apply([](auto... args) { - return std::make_tuple(args.value()...); - }); -} - -// template -// static auto surely2(const std::tuple& tpl, std::index_sequence) -// { -// return std::make_tuple(std::make_shared>(*self)...); -// } - -// template -// struct IndexTypePair -// { -// static constexpr size_t index{i}; -// using Type = T; -// }; - -// template -// struct make_index_type_tuple_helper -// { -// template -// struct idx; - -// template -// struct idx> -// { -// using tuple_type = std::tuple...>; -// }; - -// using tuple_type = typename idx>::tuple_type; -// }; - -// template -// using make_index_type_tuple = typename make_index_type_tuple_helper::tuple_type; - -template -class CombineLatest : public WritableAcceptor> +template +class CombineLatestBase, OutputT> + : public CombineLatestTypelessBase, + public WritableAcceptor, + public HeterogeneousNodeParent...> { template - static auto build_ingress(CombineLatest* self, std::index_sequence /*unused*/) + static auto build_ingress(CombineLatestBase* self, std::index_sequence /*unused*/) { return std::make_tuple(std::make_shared>(*self)...); } - public: - CombineLatest() : - m_upstream_holders(build_ingress(const_cast(this), std::index_sequence_for{})) + template + static std::tuple>>...> + build_child_pairs(CombineLatestBase* self, std::index_sequence /*unused*/) { - // auto a = build_ingress(const_cast(this), std::index_sequence_for{}); + return std::make_tuple( + std::make_pair(MRC_CONCAT_STR("sink[" << Is << "]"), std::ref(*self->get_sink()))...); } - virtual ~CombineLatest() = default; + public: + using input_tuple_t = std::tuple; + using output_t = OutputT; + + CombineLatestBase() : + m_upstream_holders(build_ingress(const_cast(this), std::index_sequence_for{})) + {} + + ~CombineLatestBase() override = default; template - std::shared_ptr>> get_sink() const + std::shared_ptr>> get_sink() const { return std::get(m_upstream_holders); } + std::tuple>>...> get_children_refs() + const override + { + return build_child_pairs(const_cast(this), std::index_sequence_for{}); + } + protected: template - class Upstream : public WritableProvider> + class Upstream : public ForwardingWritableProvider> { - using upstream_t = NthTypeOf; + using upstream_t = NthTypeOf; public: - Upstream(CombineLatest& parent) + Upstream(CombineLatestBase& parent) : m_parent(parent) {} + + protected: + channel::Status on_next(upstream_t&& data) override { - this->init_owned_edge(std::make_shared(parent)); + return m_parent.upstream_await_write(std::move(data)); } - private: - class InnerEdge : public edge::IEdgeWritable> + void on_complete() override { - public: - InnerEdge(CombineLatest& parent) : m_parent(parent) {} - ~InnerEdge() - { - m_parent.edge_complete(); - } - - virtual channel::Status await_write(upstream_t&& data) - { - return m_parent.set_upstream_value(std::move(data)); - } - - private: - CombineLatest& m_parent; - }; + m_parent.edge_complete(); + } + + private: + CombineLatestBase& m_parent; }; private: template - channel::Status set_upstream_value(NthTypeOf value) + channel::Status upstream_await_write(NthTypeOf value) { std::unique_lock lock(m_mutex); @@ -191,11 +130,11 @@ class CombineLatest : public WritableAcceptor> channel::Status status = channel::Status::success; // Check if we should push the new value - if (m_values_set == sizeof...(TypesT)) + if (m_values_set == sizeof...(InputT)) { - // std::tuple new_val = surely2(m_state); + std::tuple new_val = utils::tuple_surely(m_state); - // status = this->get_writable_edge()->await_write(std::move(new_val)); + status = this->get_writable_edge()->await_write(this->convert_value(std::move(new_val))); } return status; @@ -207,18 +146,87 @@ class CombineLatest : public WritableAcceptor> m_completions++; - if (m_completions == sizeof...(TypesT)) + if (m_completions == sizeof...(InputT)) { - WritableAcceptor>::release_edge_connection(); + // Clear the held tuple to remove any dangling values + m_state = std::tuple...>(); + + WritableAcceptor::release_edge_connection(); } } - boost::fibers::mutex m_mutex; + virtual output_t convert_value(input_tuple_t&& data) = 0; + + mutable Mutex m_mutex; + + // The number of elements that have been set. Can start emitting when m_values_set == sizeof...(TypesT) size_t m_values_set{0}; + + // Counts the number of upstream completions. When m_completions == sizeof...(TypesT), the downstream edges are + // released size_t m_completions{0}; - std::tuple...> m_state; - std::tuple>...> m_upstream_holders; + // Holds onto the latest values to eventually push when new ones are emitted + std::tuple...> m_state; + + // Upstream edges + std::tuple>...> m_upstream_holders; +}; + +template +class CombineLatestComponent; + +template +class CombineLatestComponent, OutputT> : public CombineLatestBase, OutputT> +{ + public: + using base_t = CombineLatestBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; +}; + +// Specialization for CombineLatest with a default output type +template +class CombineLatestComponent> + : public CombineLatestBase, std::tuple> +{ + public: + using base_t = CombineLatestBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + + private: + output_t convert_value(input_tuple_t&& data) override + { + // No change to the output type + return std::move(data); + } +}; + +template +class CombineLatestTransformComponent; + +template +class CombineLatestTransformComponent, OutputT> + : public CombineLatestBase, OutputT> +{ + public: + using base_t = CombineLatestBase, OutputT>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + using transform_fn_t = std::function; + + CombineLatestTransformComponent(transform_fn_t transform_fn) : base_t(), m_transform_fn(std::move(transform_fn)) {} + + private: + output_t convert_value(input_tuple_t&& data) override + { + return m_transform_fn(std::move(data)); + } + + transform_fn_t m_transform_fn; }; } // namespace mrc::node + +// IWYU pragma: end_exports diff --git a/cpp/mrc/include/mrc/node/operators/conditional.hpp b/cpp/mrc/include/mrc/node/operators/conditional.hpp index 250942b7c..1c04e91a9 100644 --- a/cpp/mrc/include/mrc/node/operators/conditional.hpp +++ b/cpp/mrc/include/mrc/node/operators/conditional.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,19 +22,12 @@ namespace mrc::node { template -class Conditional : public Router +class Conditional : public LambdaDynamicRouterComponent { - public: - Conditional(std::function predicate) : m_predicate(std::move(predicate)) {} - - protected: - virtual CaseT determine_key_for_value(const T& t) - { - return m_predicate(t); - } + using base_t = LambdaDynamicRouterComponent; - private: - std::function m_predicate; + public: + using base_t::base_t; }; } // namespace mrc::node diff --git a/cpp/mrc/include/mrc/node/operators/router.hpp b/cpp/mrc/include/mrc/node/operators/router.hpp index 4261e8c4b..9230c5ca6 100644 --- a/cpp/mrc/include/mrc/node/operators/router.hpp +++ b/cpp/mrc/include/mrc/node/operators/router.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,146 +17,487 @@ #pragma once +#include "mrc/channel/buffered_channel.hpp" #include "mrc/channel/status.hpp" +#include "mrc/core/utils.hpp" +#include "mrc/edge/edge_writable.hpp" #include "mrc/exceptions/runtime_error.hpp" #include "mrc/node/forward.hpp" +#include "mrc/node/node_parent.hpp" +#include "mrc/node/sink_channel_owner.hpp" #include "mrc/node/sink_properties.hpp" #include "mrc/node/source_channel_owner.hpp" #include "mrc/node/source_properties.hpp" +#include "mrc/runnable/forward.hpp" +#include "mrc/runnable/runnable.hpp" +#include "mrc/utils/string_utils.hpp" + +#include #include #include +#include +#include #include +// IWYU pragma: begin_exports + namespace mrc::node { +template +class RouterDownstreamNode : public edge::IWritableAcceptor, + public edge::IReadableProvider, + public ISourceChannelOwner +{}; + template -class RouterBase : public ForwardingWritableProvider, public MultiSourceProperties +class RouterBase : public MultiWritableAcceptor, + public MultiReadableProvider, + public MultiSourceChannelOwner { public: - using input_data_t = InputT; - using output_data_t = OutputT; - - RouterBase() : ForwardingWritableProvider() {} - - std::shared_ptr> get_source(const KeyT& key) const - { - // Simply return an object that will set the message to upstream and go away - return std::make_shared(*const_cast*>(this), key); - } + virtual std::shared_ptr> get_source(const KeyT& key) const = 0; bool has_source(const KeyT& key) const { - return MultiSourceProperties::get_edge_pair(key).first; + return MultiSourceProperties::get_edge_pair(key).first; } - void drop_edge(const KeyT& key) + void drop_source(const KeyT& key) { - MultiSourceProperties::release_edge_connection(key); + MultiSourceProperties::release_edge_connection(key); } protected: - class DownstreamEdge : public edge::IWritableAcceptor + class Downstream : public RouterDownstreamNode { public: - DownstreamEdge(RouterBase& parent, KeyT key) : m_parent(parent), m_key(std::move(key)) {} + Downstream(RouterBase& parent, KeyT key) : m_parent(parent), m_key(std::move(key)) + { + this->set_channel(std::make_unique>()); + } + + void set_channel(std::unique_ptr> channel) override + { + m_parent.MultiSourceChannelOwner::set_channel(m_key, std::move(channel)); + } void set_writable_edge_handle(std::shared_ptr ingress) override { - // Make sure we do any type conversions as needed - auto adapted_ingress = edge::EdgeBuilder::adapt_writable_edge(std::move(ingress)); + m_parent.MultiWritableAcceptor::set_writable_edge_handle(m_key, std::move(ingress)); + } - m_parent.MultiSourceProperties::make_edge_connection(m_key, std::move(adapted_ingress)); + std::shared_ptr get_readable_edge_handle() const override + { + return m_parent.MultiReadableProvider::get_readable_edge_handle(m_key); } private: - RouterBase& m_parent; + RouterBase& m_parent; KeyT m_key; }; - void on_complete() override + virtual KeyT determine_key_for_value(const InputT& t) = 0; + + virtual OutputT convert_value(InputT&& data) = 0; + + channel::Status process_one(InputT&& data) { - MultiSourceProperties::release_edge_connections(); + try + { + KeyT key = this->determine_key_for_value(data); + + if constexpr (std::is_same_v || std::is_convertible_v) + { + return MultiSourceProperties::get_writable_edge(key)->await_write(std::move(data)); + } + else + { + OutputT output = this->convert_value(std::move(data)); + + return MultiSourceProperties::get_writable_edge(key)->await_write(std::move(output)); + } + + } catch (const std::exception& e) + { + LOG(ERROR) << "Caught exception: " << e.what() << std::endl; + return channel::Status::error; + } } }; template -class Router; +class ConvertingRouterBase; + +template +class ConvertingRouterBase && !std::is_convertible_v>> + : public RouterBase +{}; template -class Router && !std::is_convertible_v>> +class ConvertingRouterBase || std::is_convertible_v>> : public RouterBase { protected: - channel::Status on_next(InputT&& data) override + OutputT convert_value(InputT&& data) override { - KeyT key = this->determine_key_for_value(data); + // This is a no-op, we just return the data. This wont be used. + return std::move(data); + } +}; + +template +class LambdaRouterBase; - auto output = this->convert_value(std::move(data)); +template +class LambdaRouterBase && !std::is_convertible_v>> + : public virtual ConvertingRouterBase +{ + public: + using base_t = ConvertingRouterBase; + using key_fn_t = std::function; + using convert_fn_t = std::function; - return MultiSourceProperties::get_writable_edge(key)->await_write(std::move(output)); + LambdaRouterBase(key_fn_t key_fn, convert_fn_t convert_fn) : + base_t(), + m_key_fn(std::move(key_fn)), + m_convert_fn(std::move(convert_fn)) + {} + + protected: + KeyT determine_key_for_value(const InputT& t) override + { + return m_key_fn(t); } - virtual KeyT determine_key_for_value(const InputT& t) = 0; + OutputT convert_value(InputT&& data) override + { + return m_convert_fn(std::move(data)); + } - virtual OutputT convert_value(InputT&& data) = 0; + key_fn_t m_key_fn; + convert_fn_t m_convert_fn; }; template -class Router && std::is_convertible_v>> - : public RouterBase +class LambdaRouterBase || std::is_convertible_v>> + : public virtual ConvertingRouterBase { + public: + using base_t = ConvertingRouterBase; + using key_fn_t = std::function; + + LambdaRouterBase(key_fn_t key_fn) : base_t(), m_key_fn(std::move(key_fn)) {} + protected: - channel::Status on_next(InputT&& data) override + KeyT determine_key_for_value(const InputT& t) override + { + return m_key_fn(t); + } + + key_fn_t m_key_fn; +}; + +template +class StaticRouterBase : public virtual ConvertingRouterBase, + public HomogeneousNodeParent> +{ + public: + using base_t = ConvertingRouterBase; + using this_t = StaticRouterBase; + + StaticRouterBase(std::vector route_keys) { - KeyT key = this->determine_key_for_value(data); + // Create a downstream for each key + for (const auto& key : route_keys) + { + m_downstreams[key] = std::make_shared(*this, key); + } + } - return MultiSourceProperties::get_writable_edge(key)->await_write(std::move(data)); + std::shared_ptr> get_source(const KeyT& key) const override + { + if (!m_downstreams.contains(key)) + { + throw exceptions::MrcRuntimeError(MRC_CONCAT_STR("Key '" << key << "' found in router")); + } + + return m_downstreams.at(key); } - virtual KeyT determine_key_for_value(const InputT& t) = 0; + std::map> get_children_refs( + std::optional child_name = std::nullopt) const override + { + std::map> children; + + for (const auto& [key, downstream] : m_downstreams) + { + // Utilize MRC_CONCAT_STR to convert the type to a string as best we can + children.emplace(MRC_CONCAT_STR(key), std::ref(*downstream)); + } + + return children; + } + + protected: + std::map> m_downstreams; }; -template -class Router>> - : public RouterBase +template +class DynamicRouterBase : public virtual ConvertingRouterBase +{ + using this_t = DynamicRouterBase; + + public: + std::shared_ptr> get_source(const KeyT& key) const override + { + std::shared_ptr downstream; + + if (!m_downstreams.contains(key) || (downstream = m_downstreams.at(key).lock()) == nullptr) + { + // Cast away constness to create the downstream + auto non_const_this = const_cast(this); + + downstream = std::make_shared(*non_const_this, key); + + non_const_this->m_downstreams[key] = downstream; + + return downstream; + } + + return downstream; + } + + protected: + std::map> m_downstreams; +}; + +template +class ComponentRouterBase : public ForwardingWritableProvider, + public virtual ConvertingRouterBase { protected: channel::Status on_next(InputT&& data) override { - KeyT key = this->determine_key_for_value(data); + return this->process_one(std::move(data)); + } - return MultiSourceProperties::get_writable_edge(key)->await_write(std::move(data)); + void on_complete() override + { + MultiSourceProperties::release_edge_connections(); } +}; - virtual KeyT determine_key_for_value(const InputT& t) = 0; +template +class RunnableRouterBase : public WritableProvider, + public ReadableAcceptor, + public SinkChannelOwner, + public virtual ConvertingRouterBase, + public mrc::runnable::RunnableWithContext<> +{ + protected: + RunnableRouterBase() + { + SinkChannelOwner::set_channel(std::make_unique>()); + } + + // Allows for easier testing of this method + void do_run() + { + InputT data; + channel::Status read_status; + channel::Status write_status = channel::Status::success; // give an initial value + + // Loop until either the node has been killed or the upstream terminated + while (!m_stop_source.stop_requested() && + (read_status = this->get_readable_edge()->await_read(data)) == channel::Status::success && + write_status == channel::Status::success) + { + write_status = this->process_one(std::move(data)); + } + + // Drop all connections + + if (read_status == channel::Status::error) + { + throw exceptions::MrcRuntimeError("Failed to read from upstream"); + } + + if (write_status == channel::Status::error) + { + throw exceptions::MrcRuntimeError("Failed to write to downstream"); + } + } + + private: + /** + * @brief Runnable's entrypoint. + */ + void run(mrc::runnable::Context& ctx) override + { + Unwinder unwinder([&] { + ctx.barrier(); + + if (ctx.rank() == 0) + { + MultiSourceProperties::release_edge_connections(); + } + }); + + this->do_run(); + } + + /** + * @brief Runnable's state control, for stopping from MRC. + */ + void on_state_update(const mrc::runnable::Runnable::State& state) final + { + switch (state) + { + case mrc::runnable::Runnable::State::Stop: + // Do nothing, we wait for the upstream channel to return closed + // m_stop_source.request_stop(); + break; + + case mrc::runnable::Runnable::State::Kill: + m_stop_source.request_stop(); + break; + + default: + break; + } + } + + std::stop_source m_stop_source; +}; + +template +class StaticRouterComponentBase : public StaticRouterBase, + public ComponentRouterBase +{ + public: + StaticRouterComponentBase(std::vector route_keys) : + StaticRouterBase(std::move(route_keys)) + {} +}; + +template +class LambdaStaticRouterComponent; + +template +class LambdaStaticRouterComponent< + KeyT, + InputT, + OutputT, + std::enable_if_t && !std::is_convertible_v>> + : public LambdaRouterBase, public StaticRouterComponentBase +{ + public: + using key_fn_t = LambdaRouterBase::key_fn_t; + using convert_fn_t = LambdaRouterBase::convert_fn_t; + + LambdaStaticRouterComponent(std::vector route_keys, key_fn_t key_fn, convert_fn_t convert_fn) : + LambdaRouterBase(std::move(key_fn), std::move(convert_fn)), + StaticRouterComponentBase(std::move(route_keys)) + {} +}; + +template +class LambdaStaticRouterComponent< + KeyT, + InputT, + OutputT, + std::enable_if_t || std::is_convertible_v>> + : public LambdaRouterBase, public StaticRouterComponentBase +{ + public: + using key_fn_t = LambdaRouterBase::key_fn_t; + + LambdaStaticRouterComponent(std::vector route_keys, key_fn_t key_fn) : + LambdaRouterBase(std::move(key_fn)), + StaticRouterComponentBase(std::move(route_keys)) + {} +}; + +template +class StaticRouterRunnableBase : public StaticRouterBase, + public RunnableRouterBase +{ + public: + StaticRouterRunnableBase(std::vector route_keys) : + StaticRouterBase(std::move(route_keys)) + {} +}; + +template +class LambdaStaticRouterRunnable : public LambdaRouterBase, + public StaticRouterRunnableBase +{ + public: + using key_fn_t = std::function; + + LambdaStaticRouterRunnable(std::vector route_keys, key_fn_t key_fn) : + StaticRouterRunnableBase(std::move(route_keys)), + LambdaRouterBase(std::move(key_fn)) + {} +}; + +template +class DynamicRouterComponentBase : public DynamicRouterBase, + public ComponentRouterBase +{}; + +template +class LambdaDynamicRouterComponent : public LambdaRouterBase, + public DynamicRouterComponentBase +{ + public: + using LambdaRouterBase::LambdaRouterBase; +}; + +template +class DynamicRouterRunnableBase : public DynamicRouterBase, + public RunnableRouterBase +{}; + +template +class LambdaDynamicRouterRunnable : public LambdaRouterBase, + public DynamicRouterRunnableBase +{ + public: + using LambdaRouterBase::LambdaRouterBase; }; template -class TaggedRouter : public Router, T> +class TaggedRouter : public DynamicRouterComponentBase, T> { protected: - using typename RouterBase, T>::input_data_t; - using typename RouterBase, T>::output_data_t; - - KeyT determine_key_for_value(const input_data_t& data) override + KeyT determine_key_for_value(const std::pair& data) override { return data.first; } - output_data_t convert_value(input_data_t&& data) override + T convert_value(std::pair&& data) override { // TODO(MDD): Do we need to move the key too? - output_data_t tmp = std::move(data.second); + T tmp = std::move(data.second); return tmp; } }; } // namespace mrc::node + +// IWYU pragma: end_exports diff --git a/cpp/mrc/include/mrc/node/operators/with_latest_from.hpp b/cpp/mrc/include/mrc/node/operators/with_latest_from.hpp new file mode 100644 index 000000000..ca337373e --- /dev/null +++ b/cpp/mrc/include/mrc/node/operators/with_latest_from.hpp @@ -0,0 +1,309 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mrc/channel/buffered_channel.hpp" +#include "mrc/channel/status.hpp" +#include "mrc/core/utils.hpp" +#include "mrc/node/node_parent.hpp" +#include "mrc/node/sink_properties.hpp" +#include "mrc/node/source_properties.hpp" +#include "mrc/types.hpp" +#include "mrc/utils/tuple_utils.hpp" +#include "mrc/utils/type_utils.hpp" + +#include +#include + +#include +#include +#include +#include + +// IWYU pragma: begin_exports + +namespace mrc::node { + +class WithLatestFromTypelessBase +{ + public: + virtual ~WithLatestFromTypelessBase() = default; +}; + +template +class WithLatestFromBase +{}; + +template +class WithLatestFromBase, OutputT> + : public WithLatestFromTypelessBase, + public WritableAcceptor, + public HeterogeneousNodeParent...> +{ + public: + using input_tuple_t = std::tuple; + using output_t = OutputT; + + private: + template + using queue_t = BufferedChannel; + template + using wrapped_queue_t = std::unique_ptr>; + using queues_tuple_type = std::tuple...>; + + template + static auto build_ingress(WithLatestFromBase* self, std::index_sequence /*unused*/) + { + return std::make_tuple(std::make_shared>(*self)...); + } + + static auto build_queues(size_t channel_size) + { + return std::make_tuple(std::make_unique>(channel_size)...); + } + + template + static std::tuple>>...> + build_child_pairs(WithLatestFromBase* self, std::index_sequence /*unused*/) + { + return std::make_tuple( + std::make_pair(MRC_CONCAT_STR("sink[" << Is << "]"), std::ref(*self->get_sink()))...); + } + + public: + WithLatestFromBase(size_t max_outstanding = channel::default_channel_size()) : + m_primary_queue(std::make_unique>>(max_outstanding)), + m_upstream_holders(build_ingress(const_cast(this), std::index_sequence_for{})) + {} + + ~WithLatestFromBase() override = default; + + template + std::shared_ptr>> get_sink() const + { + return std::get(m_upstream_holders); + } + + std::tuple>>...> get_children_refs() + const override + { + return build_child_pairs(const_cast(this), std::index_sequence_for{}); + } + + protected: + template + class Upstream : public ForwardingWritableProvider> + { + using upstream_t = NthTypeOf; + + public: + Upstream(WithLatestFromBase& parent) : m_parent(parent) {} + + protected: + channel::Status on_next(upstream_t&& data) override + { + return m_parent.upstream_await_write(std::move(data)); + } + + void on_complete() override + { + m_parent.edge_complete(); + } + + private: + WithLatestFromBase& m_parent; + }; + + private: + template + channel::Status upstream_await_write(NthTypeOf value) + { + std::unique_lock lock(m_mutex); + + // Get a reference to the current value + auto& nth_val = std::get(m_state); + + // Check if we have fully initialized + if (m_values_set < sizeof...(InputT)) + { + if (!nth_val.has_value()) + { + ++m_values_set; + } + + // Move the value into the state + nth_val = std::move(value); + + // For the primary upstream only, move the value onto a queue + if constexpr (N == 0) + { + // Temporarily unlock to prevent deadlock + lock.unlock(); + + Unwinder relock([&]() { + lock.lock(); + }); + + // Move it into the queue + CHECK_EQ(m_primary_queue->await_write(std::move(nth_val.value())), channel::Status::success); + } + + // Check if this put us over the edge + if (m_values_set == sizeof...(InputT)) + { + // Need to complete initialization. First close the primary channel + m_primary_queue->close_channel(); + + auto& primary_val = std::get<0>(m_state); + + // Loop over the values in the queue, pushing each one + while (m_primary_queue->await_read(primary_val.value()) == channel::Status::success) + { + std::tuple new_val = utils::tuple_surely(m_state); + + CHECK_EQ(this->get_writable_edge()->await_write(this->convert_value(std::move(new_val))), + channel::Status::success); + } + } + } + else + { + // Move the value into the state + nth_val = std::move(value); + + // Only when we are the primary, do we push a new value + if constexpr (N == 0) + { + std::tuple new_val = utils::tuple_surely(m_state); + + return this->get_writable_edge()->await_write(this->convert_value(std::move(new_val))); + } + } + + return channel::Status::success; + } + + void edge_complete() + { + std::unique_lock lock(m_mutex); + + m_completions++; + + if (m_completions == sizeof...(InputT)) + { + NthTypeOf<0, InputT...> tmp; + bool had_values = false; + + // Try to clear out any values left in the channel + while (m_primary_queue->await_read(tmp) == channel::Status::success) + { + had_values = true; + } + + LOG_IF(ERROR, had_values) << "The primary source values were never pushed downstream. Ensure all upstream " + "sources pushed at least 1 value"; + + // Clear the held tuple to remove any dangling values + m_state = std::tuple...>(); + + WritableAcceptor::release_edge_connection(); + } + } + + virtual output_t convert_value(input_tuple_t&& data) = 0; + + mutable Mutex m_mutex; + + // The number of elements that have been set. Can start emitting when m_values_set == sizeof...(TypesT) + size_t m_values_set{0}; + + // Counts the number of upstream completions. When m_completions == sizeof...(TypesT), the downstream edges are + // released + size_t m_completions{0}; + + // Holds onto the latest values to eventually push when new ones are emitted + std::tuple...> m_state; + + // Queue to allow backpressure to upstreams. Only 1 queue for the primary is needed + wrapped_queue_t> m_primary_queue; + + // Upstream edges + std::tuple>...> m_upstream_holders; +}; + +template +class WithLatestFromComponent; + +template +class WithLatestFromComponent, OutputT> + : public WithLatestFromBase, OutputT> +{ + public: + using base_t = WithLatestFromBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; +}; + +// Specialization for WithLatestFromBase with a default output type +template +class WithLatestFromComponent> + : public WithLatestFromBase, std::tuple> +{ + public: + using base_t = WithLatestFromBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + + private: + output_t convert_value(input_tuple_t&& data) override + { + // No change to the output type + return std::move(data); + } +}; + +template +class WithLatestFromTransformComponent; + +template +class WithLatestFromTransformComponent, OutputT> + : public WithLatestFromBase, OutputT> +{ + public: + using base_t = WithLatestFromBase, OutputT>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + using transform_fn_t = std::function; + + WithLatestFromTransformComponent(transform_fn_t transform_fn, size_t max_outstanding = 64) : + base_t(max_outstanding), + m_transform_fn(std::move(transform_fn)) + {} + + private: + output_t convert_value(input_tuple_t&& data) override + { + return m_transform_fn(std::move(data)); + } + + transform_fn_t m_transform_fn; +}; + +} // namespace mrc::node + +// IWYU pragma: end_exports diff --git a/cpp/mrc/include/mrc/node/operators/zip.hpp b/cpp/mrc/include/mrc/node/operators/zip.hpp new file mode 100644 index 000000000..db6a359a3 --- /dev/null +++ b/cpp/mrc/include/mrc/node/operators/zip.hpp @@ -0,0 +1,353 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mrc/channel/buffered_channel.hpp" +#include "mrc/channel/channel.hpp" +#include "mrc/channel/status.hpp" +#include "mrc/node/node_parent.hpp" +#include "mrc/node/sink_properties.hpp" +#include "mrc/node/source_properties.hpp" +#include "mrc/types.hpp" +#include "mrc/utils/string_utils.hpp" +#include "mrc/utils/tuple_utils.hpp" +#include "mrc/utils/type_utils.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// IWYU pragma: begin_exports + +namespace mrc::node { + +class ZipTypelessBase +{ + public: + virtual ~ZipTypelessBase() = default; +}; + +template +class ZipBase +{}; + +template +class ZipBase, OutputT> : public ZipTypelessBase, + public WritableAcceptor, + public HeterogeneousNodeParent...> +{ + public: + using input_tuple_t = std::tuple; + using output_t = OutputT; + + private: + template + using queue_t = BufferedChannel; + template + using wrapped_queue_t = std::unique_ptr>; + using queues_tuple_type = std::tuple...>; + + template + static auto build_ingress(ZipBase* self, std::index_sequence /*unused*/) + { + return std::make_tuple(std::make_shared>(*self)...); + } + + static auto build_queues(size_t channel_size) + { + return std::make_tuple(std::make_unique>(channel_size)...); + } + + template + static std::tuple>>...> + build_child_pairs(ZipBase* self, std::index_sequence /*unused*/) + { + return std::make_tuple( + std::make_pair(MRC_CONCAT_STR("sink[" << Is << "]"), std::ref(*self->get_sink()))...); + } + + template + channel::Status tuple_pop_each(queues_tuple_type& queues_tuple, input_tuple_t& output_tuple) + { + channel::Status status = std::get(queues_tuple)->await_read(std::get(output_tuple)); + + if constexpr (I + 1 < sizeof...(InputT)) + { + // Iterate to the next index + channel::Status inner_status = tuple_pop_each(queues_tuple, output_tuple); + + // If the inner status failed, return that, otherwise return our status + status = inner_status == channel::Status::success ? status : inner_status; + } + + return status; + } + + public: + ZipBase(size_t max_outstanding = channel::default_channel_size()) : + m_queues(build_queues(max_outstanding)), + m_upstream_holders(build_ingress(const_cast(this), std::index_sequence_for{})) + { + // Must be sure to set any array values + m_queue_counts.fill(0); + } + + ~ZipBase() override = default; + + template + std::shared_ptr>> get_sink() const + { + return std::get(m_upstream_holders); + } + + std::tuple>>...> get_children_refs() + const override + { + return build_child_pairs(const_cast(this), std::index_sequence_for{}); + } + + protected: + template + class Upstream : public ForwardingWritableProvider> + { + using upstream_t = NthTypeOf; + + public: + Upstream(ZipBase& parent) : m_parent(parent) {} + + protected: + channel::Status on_next(upstream_t&& data) override + { + return m_parent.upstream_await_write(std::move(data)); + } + + void on_complete() override + { + m_parent.edge_complete(); + } + + private: + ZipBase& m_parent; + }; + + private: + template + channel::Status upstream_await_write(NthTypeOf value) + { + // Push before locking so we dont deadlock + auto push_status = std::get(m_queues)->await_write(std::move(value)); + + if (push_status != channel::Status::success) + { + return push_status; + } + + std::unique_lock lock(m_mutex); + + // Update the counts array + m_queue_counts[N]++; + + if (m_queue_counts[N] == m_max_queue_count) + { + // Close the queue to prevent pushing more messages + std::get(m_queues)->close_channel(); + } + + DCHECK_LE(m_queue_counts[N], m_max_queue_count) << "Queue count has surpassed the max count"; + + // See if we have values in every queue + auto all_queues_have_value = std::transform_reduce(m_queue_counts.begin(), + m_queue_counts.end(), + true, + std::logical_and<>(), + [this](const size_t& v) { + return v > m_pull_count; + }); + + channel::Status status = channel::Status::success; + + if (all_queues_have_value) + { + // For each tuple, pop a value off + std::tuple new_val; + + auto channel_status = tuple_pop_each(m_queues, new_val); + + DCHECK_EQ(channel_status, channel::Status::success) << "Queues returned failed status"; + + // Push the new value + status = this->get_writable_edge()->await_write(this->convert_value(std::move(new_val))); + + m_pull_count++; + } + + return status; + } + + template + void edge_complete() + { + std::unique_lock lock(m_mutex); + + if (m_queue_counts[N] < m_max_queue_count) + { + // We are setting a new lower limit. Check to make sure this isnt an issue + m_max_queue_count = m_queue_counts[N]; + + utils::tuple_for_each(m_queues, + [this](std::unique_ptr>& q, size_t idx) { + if (m_queue_counts[idx] >= m_max_queue_count) + { + // Close the channel + q->close_channel(); + + if (m_queue_counts[idx] > m_max_queue_count) + { + LOG(ERROR) + << "Unbalanced count in upstream sources for Zip operator. Upstream '" + << N << "' ended with " << m_queue_counts[N] << " elements but " + << m_queue_counts[idx] + << " elements have already been pushed by upstream '" << idx << "'"; + } + } + }); + } + + m_completions++; + + if (m_completions == sizeof...(InputT)) + { + // Warn on any left over values + auto left_over_messages = std::transform_reduce(m_queue_counts.begin(), + m_queue_counts.end(), + 0, + std::plus<>(), + [this](const size_t& v) { + return v - m_pull_count; + }); + if (left_over_messages > 0) + { + LOG(ERROR) << "Unbalanced count in upstream sources for Zip operator. " << left_over_messages + << " messages were left in the queues"; + } + + // Finally, drain the queues of any remaining values + utils::tuple_for_each(m_queues, + [](std::unique_ptr>& q, size_t idx) { + QueueValueT value; + + while (q->await_read(value) == channel::Status::success) {} + }); + + WritableAcceptor::release_edge_connection(); + } + } + + virtual output_t convert_value(input_tuple_t&& data) = 0; + + mutable Mutex m_mutex; + + // Once an upstream is closed, this is set representing the max number of values in a queue before its closed + size_t m_max_queue_count{std::numeric_limits::max()}; + + // Counts the number of upstream completions. When m_completions == sizeof...(TypesT), the downstream edges are + // released + size_t m_completions{0}; + + // Holds the number of values pushed to each queue + std::array m_queue_counts; + + // The number of messages pulled off the queue + size_t m_pull_count{0}; + + // Queue used to allow backpressure to upstreams + queues_tuple_type m_queues; + + // Upstream edges + std::tuple>...> m_upstream_holders; +}; + +template +class ZipComponent; + +template +class ZipComponent, OutputT> : public ZipBase, OutputT> +{ + public: + using base_t = ZipBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; +}; + +// Specialization for Zip with a default output type +template +class ZipComponent> : public ZipBase, std::tuple> +{ + public: + using base_t = ZipBase, std::tuple>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + + private: + output_t convert_value(input_tuple_t&& data) override + { + // No change to the output type + return std::move(data); + } +}; + +template +class ZipTransformComponent; + +template +class ZipTransformComponent, OutputT> : public ZipBase, OutputT> +{ + public: + using base_t = ZipBase, OutputT>; + using input_tuple_t = typename base_t::input_tuple_t; + using output_t = typename base_t::output_t; + using transform_fn_t = std::function; + + ZipTransformComponent(transform_fn_t transform_fn, size_t max_outstanding = 64) : + base_t(max_outstanding), + m_transform_fn(std::move(transform_fn)) + {} + + private: + output_t convert_value(input_tuple_t&& data) override + { + return m_transform_fn(std::move(data)); + } + + transform_fn_t m_transform_fn; +}; + +} // namespace mrc::node + +// IWYU pragma: end_exports diff --git a/cpp/mrc/include/mrc/node/sink_channel_owner.hpp b/cpp/mrc/include/mrc/node/sink_channel_owner.hpp index 8997e3a8d..909f9e710 100644 --- a/cpp/mrc/include/mrc/node/sink_channel_owner.hpp +++ b/cpp/mrc/include/mrc/node/sink_channel_owner.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,13 +38,13 @@ class SinkChannelOwner : public virtual SinkProperties { edge::EdgeChannel edge_channel(std::move(channel)); - this->do_set_channel(edge_channel); + this->do_set_channel(std::move(edge_channel)); } protected: SinkChannelOwner() = default; - void do_set_channel(edge::EdgeChannel& edge_channel) + void do_set_channel(edge::EdgeChannel edge_channel) { // Create 2 edges, one for reading and writing. On connection, persist the other to allow the node to still use // get_readable+edge diff --git a/cpp/mrc/include/mrc/node/sink_properties.hpp b/cpp/mrc/include/mrc/node/sink_properties.hpp index af56b0fe0..c71f70c1c 100644 --- a/cpp/mrc/include/mrc/node/sink_properties.hpp +++ b/cpp/mrc/include/mrc/node/sink_properties.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ #include "mrc/channel/status.hpp" // IWYU pragma: export #include "mrc/edge/edge_builder.hpp" #include "mrc/edge/edge_readable.hpp" +#include "mrc/edge/edge_writable.hpp" #include "mrc/node/forward.hpp" #include "mrc/type_traits.hpp" #include "mrc/utils/type_utils.hpp" @@ -121,6 +122,36 @@ class SinkProperties : public edge::EdgeHolder, public SinkPropertiesBase } }; +template +class MultiSinkProperties : public edge::MultiEdgeHolder, public SinkPropertiesBase +{ + public: + using sink_type_t = T; + + std::type_index sink_type(bool ignore_holder = false) const final + { + if (ignore_holder) + { + if constexpr (is_smart_ptr::value) + { + return typeid(typename T::element_type); + } + } + return typeid(T); + } + + std::string sink_type_name() const final + { + return std::string(type_name()); + } + + protected: + std::shared_ptr> get_readable_edge(KeyT edge_key) const + { + return std::dynamic_pointer_cast>(this->get_connected_edge(edge_key)); + } +}; + template class ReadableAcceptor : public virtual SinkProperties, public edge::IReadableAcceptor { @@ -131,7 +162,7 @@ class ReadableAcceptor : public virtual SinkProperties, public edge::IReadabl SinkProperties::operator=(std::move(other)); } - private: + protected: void set_readable_edge_handle(std::shared_ptr egress) override { // Do any conversion to the correct type here @@ -151,13 +182,90 @@ class WritableProvider : public virtual SinkProperties, public edge::IWritabl SinkProperties::operator=(std::move(other)); } - private: + protected: std::shared_ptr get_writable_edge_handle() const override { return edge::WritableEdgeHandle::from_typeless(SinkProperties::get_edge_connection()); } }; +template +class ReadableWritableSink : public WritableProvider, public ReadableAcceptor +{}; + +template +class MultiReadableAcceptor : public virtual MultiSinkProperties, public edge::IMultiReadableAcceptor +{ + public: + protected: + bool has_readable_edge(const KeyT& key) const override + { + return MultiSinkProperties::has_edge_connection(key); + } + + void release_readable_edge(const KeyT& key) override + { + return MultiSinkProperties::release_edge_connection(key); + } + + void release_readable_edges() override + { + return MultiSinkProperties::release_edge_connections(); + } + + size_t readable_edge_count() const override + { + return MultiSinkProperties::edge_connection_count(); + } + + std::vector readable_edge_keys() const override + { + return MultiSinkProperties::edge_connection_keys(); + } + + void set_readable_edge_handle(KeyT key, std::shared_ptr egress) override + { + auto adapted_egress = edge::EdgeBuilder::adapt_readable_edge(egress); + MultiSinkProperties::make_edge_connection(key, adapted_egress); + } +}; + +template +class MultiWritableProvider : public virtual MultiSinkProperties, public edge::IMultiWritableProvider +{ + public: + protected: + bool has_writable_edge(const KeyT& key) const override + { + return MultiSinkProperties::has_edge_connection(key); + } + + void release_writable_edge(const KeyT& key) override + { + return MultiSinkProperties::release_edge_connection(key); + } + + void release_writable_edges() override + { + return MultiSinkProperties::release_edge_connections(); + } + + size_t writable_edge_count() const override + { + return MultiSinkProperties::edge_connection_count(); + } + + std::vector writable_edge_keys() const override + { + return MultiSinkProperties::edge_connection_keys(); + } + + std::shared_ptr get_writable_edge_handle(KeyT key) const override + { + return edge::WritableEdgeHandle::from_typeless(MultiSinkProperties::get_edge_connection(key)); + } +}; + template class ForwardingWritableProvider : public WritableProvider { diff --git a/cpp/mrc/include/mrc/node/source_channel_owner.hpp b/cpp/mrc/include/mrc/node/source_channel_owner.hpp index 226492e5e..b85fb25c7 100644 --- a/cpp/mrc/include/mrc/node/source_channel_owner.hpp +++ b/cpp/mrc/include/mrc/node/source_channel_owner.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,28 +25,37 @@ namespace mrc::node { +template +class ISourceChannelOwner +{ + public: + virtual ~ISourceChannelOwner() = default; + + virtual void set_channel(std::unique_ptr> channel) = 0; +}; + /** * @brief Extends SourceProperties to hold a channel ingress which is the writing interface to an edge. * * @tparam T */ template -class SourceChannelOwner : public virtual SourceProperties +class SourceChannelOwner : public ISourceChannelOwner, public virtual SourceProperties { public: ~SourceChannelOwner() override = default; - void set_channel(std::unique_ptr> channel) + void set_channel(std::unique_ptr> channel) override { edge::EdgeChannel edge_channel(std::move(channel)); - this->do_set_channel(edge_channel); + this->do_set_channel(std::move(edge_channel)); } protected: SourceChannelOwner() = default; - void do_set_channel(edge::EdgeChannel& edge_channel) + void do_set_channel(edge::EdgeChannel edge_channel) { // Create 2 edges, one for reading and writing. On connection, persist the other to allow the node to still use // get_writable_edge @@ -64,4 +73,38 @@ class SourceChannelOwner : public virtual SourceProperties } }; +template +class MultiSourceChannelOwner : public virtual MultiSourceProperties +{ + public: + ~MultiSourceChannelOwner() override = default; + + void set_channel(KeyT key, std::unique_ptr> channel) + { + edge::EdgeChannel edge_channel(std::move(channel)); + + this->do_set_channel(std::move(key), std::move(edge_channel)); + } + + protected: + MultiSourceChannelOwner() = default; + + void do_set_channel(KeyT key, edge::EdgeChannel edge_channel) + { + // Create 2 edges, one for reading and writing. On connection, persist the other to allow the node to still use + // get_writable_edge + auto channel_reader = edge_channel.get_reader(); + auto channel_writer = edge_channel.get_writer(); + + channel_reader->add_connector([this, channel_writer, key]() { + // Finally, set the other half as the connected edge to allow writers the ability to push to the channel. + // Only do this after a full connection has been made to avoid writing to a channel that will never be + // read from. + this->MultiSourceProperties::init_connected_edge(key, channel_writer); + }); + + MultiSourceProperties::init_owned_edge(key, channel_reader); + } +}; + } // namespace mrc::node diff --git a/cpp/mrc/include/mrc/node/source_properties.hpp b/cpp/mrc/include/mrc/node/source_properties.hpp index 12166eb43..58faf513b 100644 --- a/cpp/mrc/include/mrc/node/source_properties.hpp +++ b/cpp/mrc/include/mrc/node/source_properties.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ #include "mrc/channel/ingress.hpp" #include "mrc/channel/status.hpp" // IWYU pragma: export #include "mrc/edge/edge_builder.hpp" +#include "mrc/edge/edge_readable.hpp" #include "mrc/edge/edge_writable.hpp" #include "mrc/node/forward.hpp" #include "mrc/type_traits.hpp" @@ -163,7 +164,7 @@ class ReadableProvider : public virtual SourceProperties, public edge::IReada SourceProperties::operator=(std::move(other)); } - private: + protected: std::shared_ptr get_readable_edge_handle() const override { return edge::ReadableEdgeHandle::from_typeless(SourceProperties::get_edge_connection()); @@ -180,7 +181,7 @@ class WritableAcceptor : public virtual SourceProperties, public edge::IWrita SourceProperties::operator=(std::move(other)); } - private: + protected: void set_writable_edge_handle(std::shared_ptr ingress) override { // Do any conversion to the correct type here @@ -190,28 +191,95 @@ class WritableAcceptor : public virtual SourceProperties, public edge::IWrita } }; -template -class MultiIngressAcceptor : public virtual MultiSourceProperties, public edge::IMultiWritableAcceptor +template +class ReadableWritableSource : public ReadableProvider, public WritableAcceptor +{}; + +template +class MultiReadableProvider : public virtual MultiSourceProperties, + public edge::IMultiReadableProvider +{ + public: + protected: + bool has_readable_edge(const KeyT& key) const override + { + return MultiSourceProperties::has_edge_connection(key); + } + + void release_readable_edge(const KeyT& key) override + { + return MultiSourceProperties::release_edge_connection(key); + } + + void release_readable_edges() override + { + return MultiSourceProperties::release_edge_connections(); + } + + size_t readable_edge_count() const override + { + return MultiSourceProperties::edge_connection_count(); + } + + std::vector readable_edge_keys() const override + { + return MultiSourceProperties::edge_connection_keys(); + } + + std::shared_ptr get_readable_edge_handle(KeyT key) const override + { + return edge::ReadableEdgeHandle::from_typeless(MultiSourceProperties::get_edge_connection(key)); + } +}; + +template +class MultiWritableAcceptor : public virtual MultiSourceProperties, + public edge::IMultiWritableAcceptor { public: - private: + protected: + bool has_writable_edge(const KeyT& key) const override + { + return MultiSourceProperties::has_edge_connection(key); + } + + void release_writable_edge(const KeyT& key) override + { + return MultiSourceProperties::release_edge_connection(key); + } + + void release_writable_edges() override + { + return MultiSourceProperties::release_edge_connections(); + } + + size_t writable_edge_count() const override + { + return MultiSourceProperties::edge_connection_count(); + } + + std::vector writable_edge_keys() const override + { + return MultiSourceProperties::edge_connection_keys(); + } + void set_writable_edge_handle(KeyT key, std::shared_ptr ingress) override { // Do any conversion to the correct type here auto adapted_ingress = edge::EdgeBuilder::adapt_writable_edge(ingress); - MultiSourceProperties::make_edge_connection(key, adapted_ingress); + MultiSourceProperties::make_edge_connection(key, adapted_ingress); } }; template -class ForwardingEgressProvider : public ReadableProvider +class ForwardingReadableProvider : public ReadableProvider { protected: class ForwardingEdge : public edge::IEdgeReadable { public: - ForwardingEdge(ForwardingEgressProvider& parent) : m_parent(parent) {} + ForwardingEdge(ForwardingReadableProvider& parent) : m_parent(parent) {} ~ForwardingEdge() = default; @@ -221,10 +289,10 @@ class ForwardingEgressProvider : public ReadableProvider } private: - ForwardingEgressProvider& m_parent; + ForwardingReadableProvider& m_parent; }; - ForwardingEgressProvider() + ForwardingReadableProvider() { auto inner_edge = std::make_shared(*this); diff --git a/cpp/mrc/include/mrc/segment/builder.hpp b/cpp/mrc/include/mrc/segment/builder.hpp index a35f571c9..e7d93720f 100644 --- a/cpp/mrc/include/mrc/segment/builder.hpp +++ b/cpp/mrc/include/mrc/segment/builder.hpp @@ -464,6 +464,16 @@ void IBuilder::make_edge(SourceObjectT source, SinkObjectT sink) auto& source_object = to_object_properties(source); auto& sink_object = to_object_properties(sink); + if (source_object.owning_builder() != this) + { + throw exceptions::MrcRuntimeError("Source object does not belong to this builder"); + } + + if (sink_object.owning_builder() != this) + { + throw exceptions::MrcRuntimeError("Sink object does not belong to this builder"); + } + // If we can determine the type from the actual object, use that, then fall back to hints or defaults. using deduced_source_type_t = first_non_void_type_t() << std::endl; VLOG(2) << "Deduced sink type: " << mrc::type_name() << std::endl; - if (source_object.is_writable_acceptor() && sink_object.is_writable_provider()) + if constexpr (std::is_void_v || std::is_void_v) { - mrc::make_edge(source_object.template writable_acceptor_typed(), - sink_object.template writable_provider_typed()); - return; + // Try typeless edge creation + if (source_object.is_writable_acceptor() && sink_object.is_writable_provider()) + { + mrc::make_edge_typeless(source_object.writable_acceptor_base(), sink_object.writable_provider_base()); + } + else if (source_object.is_readable_provider() && sink_object.is_readable_acceptor()) + { + mrc::make_edge_typeless(source_object.readable_provider_base(), sink_object.readable_acceptor_base()); + } + else + { + throw std::runtime_error( + "Invalid edges. Arguments to make_edge were incorrect. Ensure you are providing either " + "WritableAcceptor->WritableProvider or ReadableProvider->ReadableAcceptor"); + } } - - if (source_object.is_readable_provider() && sink_object.is_readable_acceptor()) + else { - mrc::make_edge(source_object.template readable_provider_typed(), - sink_object.template readable_acceptor_typed()); - return; - } + if (source_object.is_writable_acceptor() && sink_object.is_writable_provider()) + { + mrc::make_edge(source_object.template writable_acceptor_typed(), + sink_object.template writable_provider_typed()); + } - LOG(ERROR) << "Incompatible node types"; + else if (source_object.is_readable_provider() && sink_object.is_readable_acceptor()) + { + mrc::make_edge(source_object.template readable_provider_typed(), + sink_object.template readable_acceptor_typed()); + } + else + { + throw std::runtime_error( + "Invalid edges. Arguments to make_edge were incorrect. Ensure you are providing either " + "WritableAcceptor->WritableProvider or ReadableProvider->ReadableAcceptor"); + } + } } template +#include #include +#include #include #include +#include +#include namespace mrc::segment { -struct ObjectProperties +template +class SharedObject; + +template +class ReferencedObject; + +struct ObjectPropertiesState { - virtual ~ObjectProperties() = 0; + const std::string type_name; + + const bool is_sink; + const bool is_source; + + const bool is_writable_acceptor; + const bool is_writable_provider; + const bool is_readable_acceptor; + const bool is_readable_provider; + + const bool is_runnable; + + bool is_initialized() const + { + return m_is_initialized; + } + + const std::string& name() const + { + return m_name; + } - virtual void set_name(const std::string& name) = 0; - virtual std::string name() const = 0; - virtual std::string type_name() const = 0; + IBuilder* owning_builder() const + { + return m_owning_builder; + } - virtual bool is_sink() const = 0; - virtual bool is_source() const = 0; + void initialize(std::string name, IBuilder* owning_builder) + { + MRC_CHECK_THROW(!m_is_initialized) << "Object '" << name << "' is already initialized."; + + m_name = std::move(name); + m_owning_builder = owning_builder; + m_is_initialized = true; + } + + template + static std::shared_ptr create() + { + auto state = std::shared_ptr(new ObjectPropertiesState( + /*.type_name = */ std::string(::mrc::type_name()), + /*.is_sink = */ std::is_base_of_v, + /*.is_source = */ std::is_base_of_v, + /*.is_writable_acceptor = */ std::is_base_of_v, + /*.is_writable_provider = */ std::is_base_of_v, + /*.is_readable_acceptor = */ std::is_base_of_v, + /*.is_readable_provider = */ std::is_base_of_v, + /*.is_runnable = */ std::is_base_of_v)); + + return state; + } + + private: + ObjectPropertiesState(std::string type_name, + bool is_sink, + bool is_source, + bool is_writable_acceptor, + bool is_writable_provider, + bool is_readable_acceptor, + bool is_readable_provider, + bool is_runnable) : + type_name(std::move(type_name)), + is_sink(is_sink), + is_source(is_source), + is_writable_acceptor(is_writable_acceptor), + is_writable_provider(is_writable_provider), + is_readable_acceptor(is_readable_acceptor), + is_readable_provider(is_readable_provider), + is_runnable(is_runnable) + {} + + // Will be set by the builder class when the object is added to a segment + bool m_is_initialized{false}; + + std::string m_name; + + // The owning builder. Once set, name cannot be changed + IBuilder* m_owning_builder{nullptr}; +}; + +class ObjectProperties +{ + public: + virtual ~ObjectProperties() = default; + + void initialize(std::string name, IBuilder* owning_builder) + { + // Set our name first + this->get_state().initialize(name, owning_builder); + + // Initialize the children + this->init_children(); + } + + virtual std::string name() const + { + return this->get_state().name(); + } + + virtual std::string type_name() const + { + return this->get_state().type_name; + } + + virtual bool is_sink() const + { + return this->get_state().is_sink; + } + + virtual bool is_source() const + { + return this->get_state().is_source; + } + + virtual std::type_index sink_type(bool ignore_holder = false) const = 0; - virtual std::type_index sink_type(bool ignore_holder = false) const = 0; virtual std::type_index source_type(bool ignore_holder = false) const = 0; - virtual bool is_writable_acceptor() const = 0; - virtual bool is_writable_provider() const = 0; - virtual bool is_readable_acceptor() const = 0; - virtual bool is_readable_provider() const = 0; + bool is_writable_acceptor() const + { + return this->get_state().is_writable_acceptor; + } + bool is_writable_provider() const + { + return this->get_state().is_writable_provider; + } + bool is_readable_acceptor() const + { + return this->get_state().is_readable_acceptor; + } + bool is_readable_provider() const + { + return this->get_state().is_readable_provider; + } virtual edge::IWritableAcceptorBase& writable_acceptor_base() = 0; virtual edge::IWritableProviderBase& writable_provider_base() = 0; @@ -70,13 +204,34 @@ struct ObjectProperties template edge::IReadableAcceptor& readable_acceptor_typed(); - virtual bool is_runnable() const = 0; + virtual bool is_runnable() const + { + return this->get_state().is_runnable; + } + + virtual IBuilder* owning_builder() const + { + return this->get_state().owning_builder(); + } virtual runnable::LaunchOptions& launch_options() = 0; virtual const runnable::LaunchOptions& launch_options() const = 0; -}; -inline ObjectProperties::~ObjectProperties() = default; + virtual bool has_child(const std::string& name) const = 0; + virtual std::shared_ptr get_child(const std::string& name) const = 0; + virtual const std::map>& get_children() const = 0; + + protected: + ObjectProperties() = default; + + virtual const ObjectPropertiesState& get_state() const = 0; + virtual ObjectPropertiesState& get_state() = 0; + + private: + virtual void init_children() = 0; + + friend class IBuilder; +}; template edge::IWritableAcceptor& ObjectProperties::writable_acceptor_typed() @@ -146,38 +301,36 @@ edge::IReadableProvider& ObjectProperties::readable_provider_typed() return *readable_provider; } -// Object +// template +// std::type_index deduce_type_index(bool ignore_holder) +// { +// if (ignore_holder) +// { +// if constexpr (is_smart_ptr_v) +// { +// return std::type_index(typeid(typename T::element_type)); +// } +// } + +// return std::type_index(typeid(T)); +// } +// Object template -class Object : public virtual ObjectProperties +class Object : public virtual ObjectProperties, public std::enable_shared_from_this> { public: ObjectT& object(); - - std::string name() const final; - std::string type_name() const final; - - bool is_source() const final; - bool is_sink() const final; + const ObjectT& object() const; std::type_index sink_type(bool ignore_holder) const final; std::type_index source_type(bool ignore_holder) const final; - bool is_writable_acceptor() const final; - bool is_writable_provider() const final; - bool is_readable_acceptor() const final; - bool is_readable_provider() const final; - edge::IWritableAcceptorBase& writable_acceptor_base() final; edge::IWritableProviderBase& writable_provider_base() final; edge::IReadableAcceptorBase& readable_acceptor_base() final; edge::IReadableProviderBase& readable_provider_base() final; - bool is_runnable() const final - { - return static_cast(std::is_base_of_v); - } - runnable::LaunchOptions& launch_options() final { if (!is_runnable()) @@ -198,15 +351,145 @@ class Object : public virtual ObjectProperties return m_launch_options; } + bool has_child(const std::string& name) const override + { + // First, split the name into the local and child names + auto child_name_start_idx = name.find("/"); + + if (child_name_start_idx != std::string::npos) + { + auto local_name = name.substr(0, child_name_start_idx); + auto child_name = name.substr(child_name_start_idx + 1); + + // Check if the local name matches + auto found = m_children.find(local_name); + + if (found == m_children.end()) + { + return false; + } + + // Now check if the child exists + return found->second->has_child(child_name); + } + + return m_children.contains(name); + } + + std::shared_ptr get_child(const std::string& name) const override + { + auto local_name = name; + std::string child_name; + + // First, split the name into the local and child names + auto child_name_start_idx = name.find("/"); + + if (child_name_start_idx != std::string::npos) + { + local_name = name.substr(0, child_name_start_idx); + child_name = name.substr(child_name_start_idx + 1); + } + + auto found = m_children.find(local_name); + + if (found == m_children.end()) + { + throw exceptions::MrcRuntimeError("Child " + local_name + " not found in " + this->name()); + } + + if (!child_name.empty()) + { + return found->second->get_child(child_name); + } + + return found->second; + } + + const std::map>& get_children() const override + { + return m_children; + } + + template + requires std::derived_from + std::shared_ptr> as() const + { + auto shared_object = std::make_shared>(*const_cast(this)); + + return shared_object; + } + protected: - // Move to protected to allow only the IBuilder to set the name - void set_name(const std::string& name) override; + Object() : m_state(ObjectPropertiesState::create()) {} + + template + requires std::derived_from + Object(const Object& other) : + ObjectProperties(other), + m_state(ObjectPropertiesState::create()), + m_launch_options(other.m_launch_options), + m_children(other.m_children) + {} + + const ObjectPropertiesState& get_state() const override + { + return *m_state; + } - private: - std::string m_name{}; + ObjectPropertiesState& get_state() override + { + return *m_state; + } + private: virtual ObjectT* get_object() const = 0; + + void init_children() override + { + if constexpr (is_base_of_template::value) + { + using child_node_t = typename ObjectT::child_node_t; + + // Get a map of the name/reference pairs from the NodeParent + auto children_ref_pairs = this->object().get_children_refs(); + + // Now loop and add any new children + for (const auto& [name, child_ref] : children_ref_pairs) + { + auto child_obj = std::make_shared>(this->shared_from_this(), child_ref); + + m_children.emplace(name, std::move(child_obj)); + } + } + + if constexpr (is_base_of_template::value) + { + using child_types_t = typename ObjectT::child_types_t; + + // Get the name/reference pairs from the NodeParent + auto children_ref_pairs = this->object().get_children_refs(); + + // Finally, convert the tuple of name/ChildObject pairs into a map + utils::tuple_for_each( + children_ref_pairs, + [this](std::pair>& pair, + size_t idx) { + auto child_obj = std::make_shared>(this->shared_from_this(), pair.second); + + m_children.emplace(pair.first, std::move(child_obj)); + }); + } + } + + std::shared_ptr m_state; + runnable::LaunchOptions m_launch_options; + + std::map> m_children; + + // Allows converting to base classes + template + friend class Object; }; template @@ -223,33 +506,16 @@ ObjectT& Object::object() } template -void Object::set_name(const std::string& name) -{ - m_name = name; -} - -template -std::string Object::name() const +const ObjectT& Object::object() const { - return m_name; -} - -template -std::string Object::type_name() const -{ - return std::string(::mrc::type_name()); -} - -template -bool Object::is_source() const -{ - return std::is_base_of_v; -} - -template -bool Object::is_sink() const -{ - return std::is_base_of_v; + auto* node = get_object(); + if (node == nullptr) + { + LOG(ERROR) << "Error accessing the Object API; Nodes are moved from the Segment API to the Executor " + "when the pipeline is started."; + throw exceptions::MrcRuntimeError("Object API is unavailable - expected if the Pipeline is running."); + } + return *node; } template @@ -277,82 +543,79 @@ std::type_index Object::source_type(bool ignore_holder) const } template -bool Object::is_writable_acceptor() const -{ - return std::is_base_of_v; -} - -template -bool Object::is_writable_provider() const +edge::IWritableAcceptorBase& Object::writable_acceptor_base() { - return std::is_base_of_v; + auto* base = dynamic_cast(get_object()); + CHECK(base) << type_name() << " is not a IIngressAcceptorBase"; + return *base; } template -bool Object::is_readable_acceptor() const +edge::IWritableProviderBase& Object::writable_provider_base() { - return std::is_base_of_v; + auto* base = dynamic_cast(get_object()); + CHECK(base) << type_name() << " is not a IWritableProviderBase"; + return *base; } template -bool Object::is_readable_provider() const +edge::IReadableAcceptorBase& Object::readable_acceptor_base() { - return std::is_base_of_v; + auto* base = dynamic_cast(get_object()); + CHECK(base) << type_name() << " is not a IReadableAcceptorBase"; + return *base; } template -edge::IWritableAcceptorBase& Object::writable_acceptor_base() +edge::IReadableProviderBase& Object::readable_provider_base() { - if constexpr (!std::is_base_of_v) - { - LOG(ERROR) << type_name() << " is not a IIngressAcceptorBase"; - throw exceptions::MrcRuntimeError("Object is not a IIngressAcceptorBase"); - } - - auto* base = dynamic_cast(get_object()); - CHECK(base); + auto* base = dynamic_cast(get_object()); + CHECK(base) << type_name() << " is not a IReadableProviderBase"; return *base; } template -edge::IWritableProviderBase& Object::writable_provider_base() +class SharedObject final : public Object { - if constexpr (!std::is_base_of_v) + public: + SharedObject(std::shared_ptr owner, std::reference_wrapper resource) : + m_owner(std::move(owner)), + m_resource(std::move(resource)) + {} + ~SharedObject() final = default; + + private: + ObjectT* get_object() const final { - LOG(ERROR) << type_name() << " is not a IIngressProviderBase"; - throw exceptions::MrcRuntimeError("Object is not a IIngressProviderBase"); + return &m_resource.get(); } - auto* base = dynamic_cast(get_object()); - CHECK(base); - return *base; -} + std::shared_ptr m_owner; + std::reference_wrapper m_resource; +}; template -edge::IReadableAcceptorBase& Object::readable_acceptor_base() +class ReferencedObject final : public Object { - if constexpr (!std::is_base_of_v) - { - LOG(ERROR) << type_name() << " is not a IEgressAcceptorBase"; - throw exceptions::MrcRuntimeError("Object is not a IEgressAcceptorBase"); - } + public: + template + requires std::derived_from + ReferencedObject(Object& other) : + Object(other), + m_owner(other.shared_from_this()), + m_resource(other.object()) + {} - auto* base = dynamic_cast(get_object()); - CHECK(base); - return *base; -} + ~ReferencedObject() final = default; -template -edge::IReadableProviderBase& Object::readable_provider_base() -{ - if constexpr (!std::is_base_of_v) + private: + ObjectT* get_object() const final { - LOG(ERROR) << type_name() << " is not a IEgressProviderBase"; - throw exceptions::MrcRuntimeError("Object is not a IEgressProviderBase"); + return &m_resource.get(); } - auto* base = dynamic_cast(get_object()); - CHECK(base); - return *base; -} + std::shared_ptr m_owner; + std::reference_wrapper m_resource; +}; + } // namespace mrc::segment diff --git a/cpp/mrc/include/mrc/segment/runnable.hpp b/cpp/mrc/include/mrc/segment/runnable.hpp index ab5b590ca..0121bed65 100644 --- a/cpp/mrc/include/mrc/segment/runnable.hpp +++ b/cpp/mrc/include/mrc/segment/runnable.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,15 +37,15 @@ template class Runnable : public Object, public runnable::Launchable { public: - template - Runnable(ArgsT&&... args) : m_node(std::make_unique(std::forward(args)...)) - {} - Runnable(std::unique_ptr node) : m_node(std::move(node)) { CHECK(m_node); } + template + Runnable(ArgsT&&... args) : Runnable(std::make_unique(std::forward(args)...)) + {} + private: NodeT* get_object() const final; std::unique_ptr prepare_launcher(runnable::LaunchControl& launch_control) final; diff --git a/cpp/mrc/include/mrc/type_traits.hpp b/cpp/mrc/include/mrc/type_traits.hpp index 4f1477abf..9e86f60d8 100644 --- a/cpp/mrc/include/mrc/type_traits.hpp +++ b/cpp/mrc/include/mrc/type_traits.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2021-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -115,6 +115,10 @@ template