From d9bcb5708b8c3a8a3deb5a27c98b19e96f07d013 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 12:29:38 +1000 Subject: [PATCH 01/20] Began work on adding a prefix for the interprocess internal groups --- .../transport/detail/static_group_names.h | 20 ++++++++ src/middleware/transport/interprocess.h | 48 ++++++++++--------- 2 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 src/middleware/transport/detail/static_group_names.h diff --git a/src/middleware/transport/detail/static_group_names.h b/src/middleware/transport/detail/static_group_names.h new file mode 100644 index 00000000..ce3df0ae --- /dev/null +++ b/src/middleware/transport/detail/static_group_names.h @@ -0,0 +1,20 @@ + +namespace goby::middleware::detail +{ + +struct DefaultInterprocessTag +{ + inline static constexpr const char prefix[] = "goby::middleware::interprocess"; +}; + +// helper for concatenating prefix to group name +template +constexpr auto concat(const char (&a)[N1], const char (&b)[N2]) +{ + std::array out{}; + for (std::size_t i = 0; i < N1 - 1; ++i) out[i] = a[i]; + for (std::size_t i = 0; i < N2; ++i) out[i + N1 - 1] = b[i]; + return out; +} + +} // namespace goby::middleware::detail diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index 71926f6a..9c7f4362 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -36,6 +36,7 @@ #include "goby/middleware/group.h" #include "goby/middleware/marshalling/interface.h" +#include "goby/middleware/transport/detail/static_group_names.h" #include "goby/middleware/transport/identifier.h" #include "goby/middleware/transport/null.h" #include "goby/middleware/transport/poller.h" @@ -45,21 +46,26 @@ namespace goby { namespace middleware { + /// \brief Base class for implementing transporters (both portal and forwarder) for the interprocess layer /// /// \tparam Derived derived class (curiously recurring template pattern) /// \tparam InnerTransporter inner layer transporter type -template +/// \tparam ImplementationTag Distinguishes different implementations using different internal groups +template class InterProcessTransporterBase - : public StaticTransporterInterface, - InnerTransporter>, - public Poller> + : public StaticTransporterInterface< + InterProcessTransporterBase, + InnerTransporter>, + public Poller> { - using InterfaceType = - StaticTransporterInterface, - InnerTransporter>; + using InterfaceType = StaticTransporterInterface< + InterProcessTransporterBase, + InnerTransporter>; - using PollerType = Poller>; + using PollerType = + Poller>; public: InterProcessTransporterBase(InnerTransporter& inner) @@ -270,9 +276,16 @@ class InterProcessTransporterBase } protected: - static constexpr Group to_portal_group_{"goby::middleware::interprocess::to_portal"}; - static constexpr Group regex_group_{"goby::middleware::interprocess::regex"}; - static constexpr Group from_portal_group_{"goby::middleware::interprocess::from_portal"}; + inline static constexpr auto to_portal_group_name_ = + detail::concat(ImplementationTag::prefix, "::to_portal"); + inline static constexpr auto regex_group_name_ = + detail::concat(ImplementationTag::prefix, "::regex"); + inline static constexpr auto from_portal_group_name_ = + detail::concat(ImplementationTag::prefix, "::from_portal"); + + inline static constexpr Group to_portal_group_{to_portal_group_name_.data()}; + inline static constexpr Group regex_group_{regex_group_name_.data()}; + inline static constexpr Group from_portal_group_{from_portal_group_name_.data()}; private: friend PollerType; @@ -282,16 +295,6 @@ class InterProcessTransporterBase } }; -template -constexpr goby::middleware::Group - InterProcessTransporterBase::to_portal_group_; -template -constexpr goby::middleware::Group - InterProcessTransporterBase::regex_group_; -template -constexpr goby::middleware::Group - InterProcessTransporterBase::from_portal_group_; - /// \brief Implements the forwarder concept for the interprocess layer /// /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. For example, the main thread might instantiate a portal and then spawn several threads that instantiate forwarders. These auxiliary threads can then communicate on the interprocess layer as if they had a direct connection to other interprocess nodes. @@ -307,7 +310,8 @@ class InterProcessForwarder /// \brief Construct a forwarder for the interprocess layer /// /// \param inner A reference to the inner transporter used to forward messages to and from the portal - InterProcessForwarder(InnerTransporter& inner) : Base(inner), alive_(std::make_shared>(true)) + InterProcessForwarder(InnerTransporter& inner) + : Base(inner), alive_(std::make_shared>(true)) { this->inner() .template subscribe Date: Thu, 9 Apr 2026 03:17:31 +0000 Subject: [PATCH 02/20] Finalize ImplementationTag for InterProcessTransporterBase and related classes - Add 4 new implementation tags in static_group_names.h: ZeromqInterprocessTag, ZeromqIntermoduleTag, UdpmInterprocessTag, UdpmIntermoduleTag - Add include guards and include to static_group_names.h - Remove default ImplementationTag from InterProcessTransporterBase - Convert InterProcessForwarder to 2-arg template; add deprecated 1-arg specialisation (void tag) with deprecated constructor warning - Convert InterModuleForwarder to 2-arg template; add deprecated 1-arg specialisation (void tag) with deprecated constructor warning - Update InterModuleTransporterBase alias to 3-arg - Update InterProcessPortalBase to 3-arg template - Update InterModulePortalBase to 3-arg template - Update InterProcessPortalImplementation in zeromq and udpm to 3-arg template template parameter - Add zeromq::InterProcessForwarder and zeromq::InterModuleForwarder aliases - Add udpm::InterProcessForwarder and udpm::InterModuleForwarder aliases - Update zeromq/udpm InterProcessPortal and InterModulePortal aliases with explicit implementation tags - Update internal middleware files (simple_thread.h, driver_thread.h, driver_thread.cpp, io_transporters.h, io_interface.h) to use explicit ZeromqInterprocessTag to avoid deprecation warnings in library code Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/c6ca443b-8a07-4692-a671-3732170b90b2 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- Testing/Temporary/LastTest.log | 3 ++ src/middleware/application/simple_thread.h | 25 ++++++---- src/middleware/io/detail/io_interface.h | 2 +- src/middleware/io/detail/io_transporters.h | 6 ++- .../transport/detail/static_group_names.h | 27 +++++++++++ src/middleware/transport/intermodule.h | 47 +++++++++++++++---- src/middleware/transport/interprocess.h | 45 ++++++++++++++---- .../transport/intervehicle/driver_thread.cpp | 7 ++- .../transport/intervehicle/driver_thread.h | 6 ++- src/udpm/transport/intermodule.h | 10 +++- src/udpm/transport/interprocess.h | 22 ++++++--- src/zeromq/transport/intermodule.h | 10 +++- src/zeromq/transport/interprocess.h | 22 ++++++--- 13 files changed, 183 insertions(+), 49 deletions(-) create mode 100644 Testing/Temporary/LastTest.log diff --git a/Testing/Temporary/LastTest.log b/Testing/Temporary/LastTest.log new file mode 100644 index 00000000..f7e5c9fb --- /dev/null +++ b/Testing/Temporary/LastTest.log @@ -0,0 +1,3 @@ +Start testing: Apr 09 03:07 UTC +---------------------------------------------------------- +End testing: Apr 09 03:07 UTC diff --git a/src/middleware/application/simple_thread.h b/src/middleware/application/simple_thread.h index 07844e0a..1f0464db 100644 --- a/src/middleware/application/simple_thread.h +++ b/src/middleware/application/simple_thread.h @@ -41,11 +41,14 @@ namespace middleware /// Derive from this class to create standalone threads that can be launched and joined by MultiThreadApplication's launch_thread and join_thread methods. template class SimpleThread - : public Thread>>, + : public Thread>>, public coroner::Thread> { using SimpleThreadBase = - Thread>>; + Thread>>; friend class coroner::Thread>; @@ -70,9 +73,11 @@ class SimpleThread : SimpleThreadBase(cfg, loop_freq, index) { interthread_.reset(new InterThreadTransporter); - interprocess_.reset(new InterProcessForwarder(*interthread_)); + interprocess_.reset(new InterProcessForwarder(*interthread_)); intervehicle_.reset( - new InterVehicleForwarder>( + new InterVehicleForwarder< + InterProcessForwarder>( *interprocess_)); this->set_transporter(intervehicle_.get()); @@ -81,13 +86,15 @@ class SimpleThread } /// \brief Access the transporter on the intervehicle layer (which wraps interprocess and interthread) - InterVehicleForwarder>& intervehicle() + InterVehicleForwarder< + InterProcessForwarder>& + intervehicle() { return this->transporter(); } /// \brief Access the transporter on the interprocess layer (which wraps interthread) - InterProcessForwarder& interprocess() + InterProcessForwarder& interprocess() { return this->transporter().inner(); } @@ -97,8 +104,10 @@ class SimpleThread private: std::unique_ptr interthread_; - std::unique_ptr> interprocess_; - std::unique_ptr>> + std::unique_ptr> + interprocess_; + std::unique_ptr>> intervehicle_; }; } // namespace middleware diff --git a/src/middleware/io/detail/io_interface.h b/src/middleware/io/detail/io_interface.h index 5435404e..711a4de6 100644 --- a/src/middleware/io/detail/io_interface.h +++ b/src/middleware/io/detail/io_interface.h @@ -53,7 +53,7 @@ namespace middleware { class Group; class InterThreadTransporter; -template class InterProcessForwarder; +template class InterProcessForwarder; namespace io { enum class ThreadState diff --git a/src/middleware/io/detail/io_transporters.h b/src/middleware/io/detail/io_transporters.h index e2b7ee84..54c1466d 100644 --- a/src/middleware/io/detail/io_transporters.h +++ b/src/middleware/io/detail/io_transporters.h @@ -25,6 +25,7 @@ #define GOBY_MIDDLEWARE_IO_DETAIL_IO_TRANSPORTERS_H #include "goby/exception.h" +#include "goby/middleware/transport/detail/static_group_names.h" namespace goby { @@ -32,7 +33,7 @@ namespace middleware { class Group; class InterThreadTransporter; -template class InterProcessForwarder; +template class InterProcessForwarder; namespace io { enum class PubSubLayer @@ -66,7 +67,8 @@ template struct IOTransporterByLayer { protected: - using Transporter = InterProcessForwarder; + using Transporter = InterProcessForwarder; Transporter& io_transporter() { return static_cast(this)->interprocess(); } }; diff --git a/src/middleware/transport/detail/static_group_names.h b/src/middleware/transport/detail/static_group_names.h index ce3df0ae..e56ec4e9 100644 --- a/src/middleware/transport/detail/static_group_names.h +++ b/src/middleware/transport/detail/static_group_names.h @@ -1,4 +1,9 @@ +#ifndef GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H +#define GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H + +#include + namespace goby::middleware::detail { @@ -7,6 +12,26 @@ struct DefaultInterprocessTag inline static constexpr const char prefix[] = "goby::middleware::interprocess"; }; +struct ZeromqInterprocessTag +{ + inline static constexpr const char prefix[] = "goby::middleware::interprocess::zeromq"; +}; + +struct ZeromqIntermoduleTag +{ + inline static constexpr const char prefix[] = "goby::middleware::intermodule::zeromq"; +}; + +struct UdpmInterprocessTag +{ + inline static constexpr const char prefix[] = "goby::middleware::interprocess::udpm"; +}; + +struct UdpmIntermoduleTag +{ + inline static constexpr const char prefix[] = "goby::middleware::intermodule::udpm"; +}; + // helper for concatenating prefix to group name template constexpr auto concat(const char (&a)[N1], const char (&b)[N2]) @@ -18,3 +43,5 @@ constexpr auto concat(const char (&a)[N1], const char (&b)[N2]) } } // namespace goby::middleware::detail + +#endif diff --git a/src/middleware/transport/intermodule.h b/src/middleware/transport/intermodule.h index e20b2ba3..b451c38a 100644 --- a/src/middleware/transport/intermodule.h +++ b/src/middleware/transport/intermodule.h @@ -56,20 +56,31 @@ inline bool operator<(const SerializerTransporterKey& k1, const SerializerTransp } } // namespace protobuf -template -using InterModuleTransporterBase = InterProcessTransporterBase; +template +using InterModuleTransporterBase = + InterProcessTransporterBase; /// \brief Implements the forwarder concept for the intermodule layer /// /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node -template +/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqIntermoduleTag or detail::UdpmIntermoduleTag) +template +class InterModuleForwarder; + +/// \brief Implements the forwarder concept for the intermodule layer (implementation) +/// +/// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node +/// \tparam ImplementationTag Must be provided explicitly; use zeromq::InterModuleForwarder or udpm::InterModuleForwarder for the common cases +template class InterModuleForwarder - : public InterModuleTransporterBase, InnerTransporter> + : public InterModuleTransporterBase, + InnerTransporter, ImplementationTag> { public: using Base = - InterModuleTransporterBase, InnerTransporter>; + InterModuleTransporterBase, + InnerTransporter, ImplementationTag>; /// \brief Construct a forwarder for the intermodule layer /// @@ -187,12 +198,30 @@ class InterModuleForwarder subscriptions_; }; -template -class InterModulePortalBase : public InterModuleTransporterBase, - public InterProcessPortalCommon +/// \brief Deprecated: use zeromq::InterModuleForwarder or udpm::InterModuleForwarder instead +/// +/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. +/// It resolves to the zeromq implementation. New code should use zeromq::InterModuleForwarder<> +/// or udpm::InterModuleForwarder<> explicitly. +template +class InterModuleForwarder + : public InterModuleForwarder +{ + public: + using Base = InterModuleForwarder; + + [[deprecated("Use zeromq::InterModuleForwarder<> or udpm::InterModuleForwarder<> instead of " + "middleware::InterModuleForwarder<>")]] + explicit InterModuleForwarder(InnerTransporter& inner) : Base(inner) {} +}; + +template +class InterModulePortalBase + : public InterModuleTransporterBase, + public InterProcessPortalCommon { public: - using Base = InterModuleTransporterBase; + using Base = InterModuleTransporterBase; using Common = InterProcessPortalCommon; InterModulePortalBase(InnerTransporter& inner) : Base(inner) { _init(); } diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index 9c7f4362..6390f724 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -52,8 +52,7 @@ namespace middleware /// \tparam Derived derived class (curiously recurring template pattern) /// \tparam InnerTransporter inner layer transporter type /// \tparam ImplementationTag Distinguishes different implementations using different internal groups -template +template class InterProcessTransporterBase : public StaticTransporterInterface< InterProcessTransporterBase, @@ -299,13 +298,23 @@ class InterProcessTransporterBase /// /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. For example, the main thread might instantiate a portal and then spawn several threads that instantiate forwarders. These auxiliary threads can then communicate on the interprocess layer as if they had a direct connection to other interprocess nodes. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node -template +/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqInterprocessTag or detail::UdpmInterprocessTag) +template +class InterProcessForwarder; + +/// \brief Implements the forwarder concept for the interprocess layer (implementation) +/// +/// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node +/// \tparam ImplementationTag Must be provided explicitly; use zeromq::InterProcessForwarder or udpm::InterProcessForwarder for the common cases +template class InterProcessForwarder - : public InterProcessTransporterBase, InnerTransporter> + : public InterProcessTransporterBase, + InnerTransporter, ImplementationTag> { public: using Base = - InterProcessTransporterBase, InnerTransporter>; + InterProcessTransporterBase, + InnerTransporter, ImplementationTag>; /// \brief Construct a forwarder for the interprocess layer /// @@ -468,6 +477,23 @@ class InterProcessForwarder std::shared_ptr> alive_; }; +/// \brief Deprecated: use zeromq::InterProcessForwarder or udpm::InterProcessForwarder instead +/// +/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. +/// It resolves to the zeromq implementation. New code should use zeromq::InterProcessForwarder<> +/// or udpm::InterProcessForwarder<> explicitly. +template +class InterProcessForwarder + : public InterProcessForwarder +{ + public: + using Base = InterProcessForwarder; + + [[deprecated("Use zeromq::InterProcessForwarder<> or udpm::InterProcessForwarder<> instead of " + "middleware::InterProcessForwarder<>")]] + explicit InterProcessForwarder(InnerTransporter& inner) : Base(inner) {} +}; + template class InterProcessPortalCommon : public InterProcessIdentifierManager { @@ -726,12 +752,13 @@ class InterProcessPortalCommon : public InterProcessIdentifierManager regex_subscriptions_; }; -template -class InterProcessPortalBase : public InterProcessTransporterBase, - public InterProcessPortalCommon +template +class InterProcessPortalBase + : public InterProcessTransporterBase, + public InterProcessPortalCommon { public: - using Base = InterProcessTransporterBase; + using Base = InterProcessTransporterBase; using Common = InterProcessPortalCommon; InterProcessPortalBase(InnerTransporter& inner) : Base(inner) { _init(); } diff --git a/src/middleware/transport/intervehicle/driver_thread.cpp b/src/middleware/transport/intervehicle/driver_thread.cpp index 15b9bf5d..23a843d5 100644 --- a/src/middleware/transport/intervehicle/driver_thread.cpp +++ b/src/middleware/transport/intervehicle/driver_thread.cpp @@ -129,7 +129,8 @@ std::map goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( const intervehicle::protobuf::PortalConfig::LinkConfig& config) : goby::middleware::Thread>( + InterProcessForwarder>( config, 10 * boost::units::si::hertz), buffer_(cfg().modem_id()), mac_(cfg().modem_id()), @@ -141,7 +142,9 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( { goby::glog.add_group(glog_group_, util::Colors::blue); interthread_ = std::make_unique(); - interprocess_ = std::make_unique>(*interthread_); + interprocess_ = std::make_unique< + InterProcessForwarder>( + *interthread_); this->set_transporter(interprocess_.get()); interprocess_->subscribe( diff --git a/src/middleware/transport/intervehicle/driver_thread.h b/src/middleware/transport/intervehicle/driver_thread.h index 604fba40..2901c6c9 100644 --- a/src/middleware/transport/intervehicle/driver_thread.h +++ b/src/middleware/transport/intervehicle/driver_thread.h @@ -129,7 +129,8 @@ serialize_publication(const Data& d, const Group& group, const Publisher& class ModemDriverThread : public goby::middleware::Thread> + InterProcessForwarder> { public: using buffer_data_type = goby::middleware::protobuf::SerializerTransporterMessage; @@ -192,7 +193,8 @@ class ModemDriverThread private: std::unique_ptr interthread_; - std::unique_ptr> interprocess_; + std::unique_ptr> + interprocess_; std::multimap publisher_buffer_cfg_; diff --git a/src/udpm/transport/intermodule.h b/src/udpm/transport/intermodule.h index f9206e23..127ebf20 100644 --- a/src/udpm/transport/intermodule.h +++ b/src/udpm/transport/intermodule.h @@ -34,8 +34,14 @@ namespace udpm { template using InterModulePortal = - InterProcessPortalImplementation; -} + InterProcessPortalImplementation; + +template +using InterModuleForwarder = + middleware::InterModuleForwarder; + +} // namespace udpm } // namespace goby #endif diff --git a/src/udpm/transport/interprocess.h b/src/udpm/transport/interprocess.h index 07343fc4..20e30109 100644 --- a/src/udpm/transport/interprocess.h +++ b/src/udpm/transport/interprocess.h @@ -110,14 +110,18 @@ inline UDPMPacketHeader decode_header(const char* buf) } template class PortalBase> + template class PortalBase, + typename ImplementationTag> class InterProcessPortalImplementation - : public PortalBase, - InnerTransporter> + : public PortalBase, + InnerTransporter, ImplementationTag> { public: - using Base = PortalBase, - InnerTransporter>; + using Base = PortalBase, + InnerTransporter, ImplementationTag>; using IdentifierWildcard = middleware::IdentifierWildcard; @@ -538,7 +542,13 @@ class InterProcessPortalImplementation template using InterProcessPortal = - InterProcessPortalImplementation; + InterProcessPortalImplementation; + +template +using InterProcessForwarder = + middleware::InterProcessForwarder; } // namespace udpm } // namespace goby diff --git a/src/zeromq/transport/intermodule.h b/src/zeromq/transport/intermodule.h index c9e6c9be..00009ebe 100644 --- a/src/zeromq/transport/intermodule.h +++ b/src/zeromq/transport/intermodule.h @@ -34,8 +34,14 @@ namespace zeromq { template using InterModulePortal = - InterProcessPortalImplementation; -} + InterProcessPortalImplementation; + +template +using InterModuleForwarder = + middleware::InterModuleForwarder; + +} // namespace zeromq } // namespace goby #endif diff --git a/src/zeromq/transport/interprocess.h b/src/zeromq/transport/interprocess.h index 559f4be8..5ed5f891 100644 --- a/src/zeromq/transport/interprocess.h +++ b/src/zeromq/transport/interprocess.h @@ -208,14 +208,18 @@ class InterProcessPortalReadThread }; template class PortalBase> + template class PortalBase, + typename ImplementationTag> class InterProcessPortalImplementation - : public PortalBase, - InnerTransporter> + : public PortalBase, + InnerTransporter, ImplementationTag> { public: - using Base = PortalBase, - InnerTransporter>; + using Base = PortalBase, + InnerTransporter, ImplementationTag>; using IdentifierWildcard = middleware::IdentifierWildcard; InterProcessPortalImplementation(const protobuf::InterProcessPortalConfig& cfg) @@ -472,7 +476,13 @@ class Manager template using InterProcessPortal = - InterProcessPortalImplementation; + InterProcessPortalImplementation; + +template +using InterProcessForwarder = + middleware::InterProcessForwarder; } // namespace zeromq } // namespace goby From cc69bbc354151210aed324343d90e88ee45a0c0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 03:21:07 +0000 Subject: [PATCH 03/20] Remove extra blank lines in static_group_names.h Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/c6ca443b-8a07-4692-a671-3732170b90b2 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/middleware/transport/detail/static_group_names.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/middleware/transport/detail/static_group_names.h b/src/middleware/transport/detail/static_group_names.h index e56ec4e9..ff77af5e 100644 --- a/src/middleware/transport/detail/static_group_names.h +++ b/src/middleware/transport/detail/static_group_names.h @@ -1,4 +1,3 @@ - #ifndef GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H #define GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H From cf8b6477337b0e126114871efdf0c1a4c04656ef Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 13:58:26 +1000 Subject: [PATCH 04/20] Remove test log --- Testing/Temporary/LastTest.log | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Testing/Temporary/LastTest.log diff --git a/Testing/Temporary/LastTest.log b/Testing/Temporary/LastTest.log deleted file mode 100644 index f7e5c9fb..00000000 --- a/Testing/Temporary/LastTest.log +++ /dev/null @@ -1,3 +0,0 @@ -Start testing: Apr 09 03:07 UTC ----------------------------------------------------------- -End testing: Apr 09 03:07 UTC From d4c89f85070993f09d96b469fdc489b91100e29b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:18:48 +0000 Subject: [PATCH 05/20] WIP: address review comments - plan outlined Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/1e9e4359-992e-49b3-b502-88884c861876 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/udpm/transport/detail/tags.h | 50 ++++++++++++++++++++++++++++++ src/zeromq/transport/detail/tags.h | 50 ++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/udpm/transport/detail/tags.h create mode 100644 src/zeromq/transport/detail/tags.h diff --git a/src/udpm/transport/detail/tags.h b/src/udpm/transport/detail/tags.h new file mode 100644 index 00000000..3cbf6edf --- /dev/null +++ b/src/udpm/transport/detail/tags.h @@ -0,0 +1,50 @@ +// Copyright 2022-2024: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +#ifndef GOBY_UDPM_TRANSPORT_DETAIL_TAGS_H +#define GOBY_UDPM_TRANSPORT_DETAIL_TAGS_H + +namespace goby +{ +namespace udpm +{ +namespace detail +{ + +/// \brief ImplementationTag for udpm interprocess transporters +struct UdpmInterprocessTag +{ + inline static constexpr const char prefix[] = "goby::middleware::interprocess::udpm"; +}; + +/// \brief ImplementationTag for udpm intermodule transporters +struct UdpmIntermoduleTag +{ + inline static constexpr const char prefix[] = "goby::middleware::intermodule::udpm"; +}; + +} // namespace detail +} // namespace udpm +} // namespace goby + +#endif diff --git a/src/zeromq/transport/detail/tags.h b/src/zeromq/transport/detail/tags.h new file mode 100644 index 00000000..388c1014 --- /dev/null +++ b/src/zeromq/transport/detail/tags.h @@ -0,0 +1,50 @@ +// Copyright 2022-2024: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +#ifndef GOBY_ZEROMQ_TRANSPORT_DETAIL_TAGS_H +#define GOBY_ZEROMQ_TRANSPORT_DETAIL_TAGS_H + +namespace goby +{ +namespace zeromq +{ +namespace detail +{ + +/// \brief ImplementationTag for zeromq interprocess transporters +struct ZeromqInterprocessTag +{ + inline static constexpr const char prefix[] = "goby::middleware::interprocess::zeromq"; +}; + +/// \brief ImplementationTag for zeromq intermodule transporters +struct ZeromqIntermoduleTag +{ + inline static constexpr const char prefix[] = "goby::middleware::intermodule::zeromq"; +}; + +} // namespace detail +} // namespace zeromq +} // namespace goby + +#endif From 9b20b2341b8f85a01e83b3fa338d0b993ed86457 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:42:55 +0000 Subject: [PATCH 06/20] Address reviewer comments: template ModemDriverThread on ImplementationTag, add zeromq/udpm SimpleThread, move tags to zeromq/udpm detail headers Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/1e9e4359-992e-49b3-b502-88884c861876 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/middleware/application/simple_thread.h | 66 +++++--- src/middleware/io/detail/io_transporters.h | 3 +- .../transport/detail/static_group_names.h | 25 --- src/middleware/transport/intermodule.h | 6 +- src/middleware/transport/interprocess.h | 9 +- src/middleware/transport/intervehicle.h | 11 +- .../transport/intervehicle/driver_thread.cpp | 148 ++++++++++-------- .../transport/intervehicle/driver_thread.h | 19 +-- src/udpm/application/simple_thread.h | 43 +++++ src/udpm/transport/intermodule.h | 5 +- src/udpm/transport/interprocess.h | 6 +- src/zeromq/application/simple_thread.h | 43 +++++ src/zeromq/transport/intermodule.h | 5 +- src/zeromq/transport/interprocess.h | 6 +- 14 files changed, 262 insertions(+), 133 deletions(-) create mode 100644 src/udpm/application/simple_thread.h create mode 100644 src/zeromq/application/simple_thread.h diff --git a/src/middleware/application/simple_thread.h b/src/middleware/application/simple_thread.h index 1f0464db..9e836b55 100644 --- a/src/middleware/application/simple_thread.h +++ b/src/middleware/application/simple_thread.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023: +// Copyright 2022-2024: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: @@ -30,6 +30,7 @@ #include "goby/middleware/transport/interprocess.h" #include "goby/middleware/transport/interthread.h" #include "goby/middleware/transport/intervehicle.h" +#include "goby/zeromq/transport/detail/tags.h" namespace goby { @@ -38,19 +39,22 @@ namespace middleware /// \brief Implements Thread for a three layer middleware setup ([ intervehicle [ interprocess [ interthread ] ] ]) based around InterVehicleForwarder. /// /// \tparam Config Configuration type +/// \tparam ImplementationTag Tag type selecting the interprocess implementation (e.g. zeromq::detail::ZeromqInterprocessTag). Defaults to void (deprecated; use zeromq::SimpleThread or udpm::SimpleThread instead). +/// /// Derive from this class to create standalone threads that can be launched and joined by MultiThreadApplication's launch_thread and join_thread methods. -template +template class SimpleThread : public Thread>>, - public coroner::Thread> + InterVehicleForwarder>>, + public coroner::Thread> { using SimpleThreadBase = - Thread>>; + Thread>>; - friend class coroner::Thread>; + friend class coroner::Thread>; public: /// \brief Construct a thread with a given configuration, optionally a loop frequency and/or index @@ -73,11 +77,10 @@ class SimpleThread : SimpleThreadBase(cfg, loop_freq, index) { interthread_.reset(new InterThreadTransporter); - interprocess_.reset(new InterProcessForwarder(*interthread_)); + interprocess_.reset( + new InterProcessForwarder(*interthread_)); intervehicle_.reset( - new InterVehicleForwarder< - InterProcessForwarder>( + new InterVehicleForwarder>( *interprocess_)); this->set_transporter(intervehicle_.get()); @@ -86,15 +89,14 @@ class SimpleThread } /// \brief Access the transporter on the intervehicle layer (which wraps interprocess and interthread) - InterVehicleForwarder< - InterProcessForwarder>& + InterVehicleForwarder>& intervehicle() { return this->transporter(); } /// \brief Access the transporter on the interprocess layer (which wraps interthread) - InterProcessForwarder& interprocess() + InterProcessForwarder& interprocess() { return this->transporter().inner(); } @@ -104,12 +106,38 @@ class SimpleThread private: std::unique_ptr interthread_; - std::unique_ptr> - interprocess_; - std::unique_ptr>> + std::unique_ptr> interprocess_; + std::unique_ptr< + InterVehicleForwarder>> intervehicle_; }; + +/// \brief Deprecated 1-arg specialisation: use goby::zeromq::SimpleThread or goby::udpm::SimpleThread +template +class SimpleThread + : public SimpleThread +{ + public: + using Base = SimpleThread; + + [[deprecated( + "Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " + "goby::middleware::SimpleThread")]] + SimpleThread(const Config& cfg, double loop_freq_hertz = 0, int index = -1) + : Base(cfg, loop_freq_hertz, index) + { + } + + [[deprecated( + "Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " + "goby::middleware::SimpleThread")]] + SimpleThread(const Config& cfg, boost::units::quantity loop_freq, + int index = -1) + : Base(cfg, loop_freq, index) + { + } +}; + } // namespace middleware } // namespace goby diff --git a/src/middleware/io/detail/io_transporters.h b/src/middleware/io/detail/io_transporters.h index 54c1466d..9a03aa2e 100644 --- a/src/middleware/io/detail/io_transporters.h +++ b/src/middleware/io/detail/io_transporters.h @@ -26,6 +26,7 @@ #include "goby/exception.h" #include "goby/middleware/transport/detail/static_group_names.h" +#include "goby/zeromq/transport/detail/tags.h" namespace goby { @@ -68,7 +69,7 @@ struct IOTransporterByLayer { protected: using Transporter = InterProcessForwarder; + ::goby::zeromq::detail::ZeromqInterprocessTag>; Transporter& io_transporter() { return static_cast(this)->interprocess(); } }; diff --git a/src/middleware/transport/detail/static_group_names.h b/src/middleware/transport/detail/static_group_names.h index ff77af5e..d11087de 100644 --- a/src/middleware/transport/detail/static_group_names.h +++ b/src/middleware/transport/detail/static_group_names.h @@ -6,31 +6,6 @@ namespace goby::middleware::detail { -struct DefaultInterprocessTag -{ - inline static constexpr const char prefix[] = "goby::middleware::interprocess"; -}; - -struct ZeromqInterprocessTag -{ - inline static constexpr const char prefix[] = "goby::middleware::interprocess::zeromq"; -}; - -struct ZeromqIntermoduleTag -{ - inline static constexpr const char prefix[] = "goby::middleware::intermodule::zeromq"; -}; - -struct UdpmInterprocessTag -{ - inline static constexpr const char prefix[] = "goby::middleware::interprocess::udpm"; -}; - -struct UdpmIntermoduleTag -{ - inline static constexpr const char prefix[] = "goby::middleware::intermodule::udpm"; -}; - // helper for concatenating prefix to group name template constexpr auto concat(const char (&a)[N1], const char (&b)[N2]) diff --git a/src/middleware/transport/intermodule.h b/src/middleware/transport/intermodule.h index b451c38a..22e7bb91 100644 --- a/src/middleware/transport/intermodule.h +++ b/src/middleware/transport/intermodule.h @@ -38,6 +38,8 @@ #include "goby/middleware/transport/interprocess.h" #include "goby/middleware/transport/null.h" #include "goby/middleware/transport/poller.h" +#include "goby/zeromq/transport/detail/tags.h" +#include "goby/udpm/transport/detail/tags.h" #include "goby/middleware/transport/serialization_handlers.h" namespace goby @@ -205,10 +207,10 @@ class InterModuleForwarder /// or udpm::InterModuleForwarder<> explicitly. template class InterModuleForwarder - : public InterModuleForwarder + : public InterModuleForwarder { public: - using Base = InterModuleForwarder; + using Base = InterModuleForwarder; [[deprecated("Use zeromq::InterModuleForwarder<> or udpm::InterModuleForwarder<> instead of " "middleware::InterModuleForwarder<>")]] diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index 6390f724..67f071a5 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -41,6 +41,8 @@ #include "goby/middleware/transport/null.h" #include "goby/middleware/transport/poller.h" #include "goby/middleware/transport/serialization_handlers.h" +#include "goby/zeromq/transport/detail/tags.h" +#include "goby/udpm/transport/detail/tags.h" namespace goby { @@ -67,6 +69,9 @@ class InterProcessTransporterBase Poller>; public: + /// \brief The ImplementationTag for this transporter (allows InterVehiclePortal to match the driver thread's forwarder tag to the InnerTransporter's tag) + using implementation_tag = ImplementationTag; + InterProcessTransporterBase(InnerTransporter& inner) : InterfaceType(inner), PollerType(&this->inner()) { @@ -484,10 +489,10 @@ class InterProcessForwarder /// or udpm::InterProcessForwarder<> explicitly. template class InterProcessForwarder - : public InterProcessForwarder + : public InterProcessForwarder { public: - using Base = InterProcessForwarder; + using Base = InterProcessForwarder; [[deprecated("Use zeromq::InterProcessForwarder<> or udpm::InterProcessForwarder<> instead of " "middleware::InterProcessForwarder<>")]] diff --git a/src/middleware/transport/intervehicle.h b/src/middleware/transport/intervehicle.h index 70fb1435..092fde71 100644 --- a/src/middleware/transport/intervehicle.h +++ b/src/middleware/transport/intervehicle.h @@ -643,7 +643,11 @@ template class InterVehiclePortal : public InterVehicleTransporterBase, InnerTransporter> { - using modem_id_type = goby::middleware::intervehicle::ModemDriverThread::modem_id_type; + // Derive the ImplementationTag from InnerTransporter so that the modem driver thread + // uses the same InterProcessForwarder prefix as the portal's inner transporter. + using ImplementationTag = typename InnerTransporter::implementation_tag; + using modem_id_type = + typename goby::middleware::intervehicle::ModemDriverThread::modem_id_type; public: using Base = @@ -782,7 +786,8 @@ class InterVehiclePortal { try { - data.modem_driver_thread.reset(new intervehicle::ModemDriverThread(*link)); + data.modem_driver_thread.reset( + new intervehicle::ModemDriverThread(*link)); data.modem_driver_thread->run(data.driver_thread_alive); } catch (std::exception& e) @@ -897,7 +902,7 @@ class InterVehiclePortal struct ModemDriverData { std::unique_ptr underlying_thread; - std::unique_ptr modem_driver_thread; + std::unique_ptr> modem_driver_thread; std::atomic driver_thread_alive{true}; }; std::vector> modem_drivers_; diff --git a/src/middleware/transport/intervehicle/driver_thread.cpp b/src/middleware/transport/intervehicle/driver_thread.cpp index 23a843d5..4249322b 100644 --- a/src/middleware/transport/intervehicle/driver_thread.cpp +++ b/src/middleware/transport/intervehicle/driver_thread.cpp @@ -65,6 +65,8 @@ #include "goby/acomms/modemdriver/popoto_driver.h" // for PopotoDr... #endif #include "driver_thread.h" +#include "goby/zeromq/transport/detail/tags.h" +#include "goby/udpm/transport/detail/tags.h" using goby::glog; using namespace goby::util::logger; @@ -123,52 +125,57 @@ std::map load_plugins() return driver_plugins; } -std::map - goby::middleware::intervehicle::ModemDriverThread::driver_plugins_(load_plugins()); +// Driver plugin map singleton - shared across all ModemDriverThread template instantiations +static std::map& get_driver_plugins() +{ + static std::map plugins = load_plugins(); + return plugins; +} -goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( +template +goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( const intervehicle::protobuf::PortalConfig::LinkConfig& config) : goby::middleware::Thread>( + ImplementationTag>>( config, 10 * boost::units::si::hertz), - buffer_(cfg().modem_id()), - mac_(cfg().modem_id()), + buffer_(this->cfg().modem_id()), + mac_(this->cfg().modem_id()), glog_group_("goby::middleware::intervehicle::driver_thread::" + - goby::acomms::ModemDriverBase::driver_name(cfg().driver())), + goby::acomms::ModemDriverBase::driver_name(this->cfg().driver())), next_modem_report_time_(goby::time::SteadyClock::now()), modem_report_interval_(goby::time::convert_duration( - cfg().modem_report_interval_with_units())) + this->cfg().modem_report_interval_with_units())) { goby::glog.add_group(glog_group_, util::Colors::blue); interthread_ = std::make_unique(); interprocess_ = std::make_unique< - InterProcessForwarder>( + InterProcessForwarder>( *interthread_); this->set_transporter(interprocess_.get()); - interprocess_->subscribe( + interprocess_->template subscribe( [this](std::shared_ptr msg) { _buffer_message(std::move(msg)); }); - interprocess_->subscribetemplate subscribe( [this](const std::shared_ptr& subscription) { _forward_subscription(*subscription); }); - interprocess_->subscribetemplate subscribe( [this](const std::shared_ptr& subscription) { _accept_subscription(*subscription); }); - if (cfg().driver().has_driver_name()) + if (this->cfg().driver().has_driver_name()) { std::map::const_iterator driver_it = - driver_plugins_.find(cfg().driver().driver_name()); + get_driver_plugins().find(this->cfg().driver().driver_name()); - if (driver_it == driver_plugins_.end()) + if (driver_it == get_driver_plugins().end()) glog.is_die() && glog << "Could not find driver_plugin_name '" - << cfg().driver().driver_name() + << this->cfg().driver().driver_name() << "'. Make sure it is loaded using the GOBY_MODEMDRIVER_PLUGINS " "environmental var" << std::endl; @@ -181,7 +188,7 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( { glog.is(DIE) && glog << "Could not load goby::acomms::ModemDriverBase* " "goby_make_driver() for driver name '" - << cfg().driver().driver_name() << "'." << std::endl; + << this->cfg().driver().driver_name() << "'." << std::endl; } else { @@ -191,7 +198,7 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( } else { - switch (cfg().driver().driver_type()) + switch (this->cfg().driver().driver_type()) { case goby::acomms::protobuf::DRIVER_WHOI_MICROMODEM: driver_ = std::make_unique(); @@ -238,12 +245,12 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( case goby::acomms::protobuf::DRIVER_BLUEFIN_MOOS: throw(goby::Exception( "Unsupported driver type: " + - goby::acomms::protobuf::DriverType_Name(cfg().driver().driver_type()))); + goby::acomms::protobuf::DriverType_Name(this->cfg().driver().driver_type()))); break; default: throw(goby::Exception( "Please specify a supported driver type: " + - goby::acomms::protobuf::DriverType_Name(cfg().driver().driver_type()))); + goby::acomms::protobuf::DriverType_Name(this->cfg().driver().driver_type()))); break; } } @@ -252,36 +259,36 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( [&](const goby::acomms::protobuf::ModemTransmission& rx_msg) { protobuf::ModemTransmissionWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = rx_msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); driver_->signal_transmit_result.connect( [&](const goby::acomms::protobuf::ModemTransmission& tx_msg) { protobuf::ModemTransmissionWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = tx_msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); driver_->signal_raw_incoming.connect( [&](const goby::acomms::protobuf::ModemRaw& msg) { protobuf::ModemRawWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); driver_->signal_raw_outgoing.connect( [&](const goby::acomms::protobuf::ModemRaw& msg) { protobuf::ModemRawWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); driver_->signal_receive.connect([&](const goby::acomms::protobuf::ModemTransmission& rx_msg) @@ -296,24 +303,24 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( [&](const goby::acomms::protobuf::ModemTransmission& msg) { protobuf::ModemTransmissionWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); mac_.signal_slot_start.connect( [&](const goby::acomms::protobuf::ModemTransmission& msg) { protobuf::ModemTransmissionWithLinkID msg_with_id; - msg_with_id.set_link_modem_id(cfg().modem_id()); + msg_with_id.set_link_modem_id(this->cfg().modem_id()); *msg_with_id.mutable_data() = msg; - interprocess_->publish(msg_with_id); + interprocess_->template publish(msg_with_id); }); - mac_.startup(cfg().mac()); + mac_.startup(this->cfg().mac()); - auto driver_cfg = cfg().driver(); - driver_cfg.set_modem_id(_id_within_subnet(cfg().driver().modem_id())); + auto driver_cfg = this->cfg().driver(); + driver_cfg.set_modem_id(_id_within_subnet(this->cfg().driver().modem_id())); driver_->startup(driver_cfg); subscription_key_.set_marshalling_scheme(MarshallingScheme::DCCL); @@ -322,10 +329,11 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( goby::middleware::intervehicle::groups::subscription_forward.numeric()); goby::glog.is_debug1() && goby::glog << group(glog_group_) << "Driver ready" << std::endl; - interthread_->publish(true); + interthread_->template publish(true); } -void goby::middleware::intervehicle::ModemDriverThread::loop() +template +void goby::middleware::intervehicle::ModemDriverThread::loop() { auto expired = buffer_.expire(); @@ -344,14 +352,15 @@ void goby::middleware::intervehicle::ModemDriverThread::loop() if (now > next_modem_report_time_ + modem_report_interval_) { protobuf::ModemReportWithLinkID report_with_id; - report_with_id.set_link_modem_id(cfg().modem_id()); + report_with_id.set_link_modem_id(this->cfg().modem_id()); driver_->report(report_with_id.mutable_data()); - interprocess_->publish(report_with_id); + interprocess_->template publish(report_with_id); next_modem_report_time_ += modem_report_interval_; } } -void goby::middleware::intervehicle::ModemDriverThread::_expire_value( +template +void goby::middleware::intervehicle::ModemDriverThread::_expire_value( const goby::time::SteadyClock::time_point now, const goby::acomms::DynamicBuffer::Value& value, intervehicle::protobuf::ExpireData::ExpireReason reason) @@ -366,10 +375,11 @@ void goby::middleware::intervehicle::ModemDriverThread::_expire_value( expire_data.set_reason(reason); *expire_pair.mutable_serializer() = value.data; - interprocess_->publish(expire_pair); + interprocess_->template publish(expire_pair); } -void goby::middleware::intervehicle::ModemDriverThread::_forward_subscription( +template +void goby::middleware::intervehicle::ModemDriverThread::_forward_subscription( intervehicle::protobuf::Subscription subscription) { if (subscription.has_metadata()) @@ -378,7 +388,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_forward_subscription( if (subscription.intervehicle().broadcast()) subscription.mutable_header()->set_src(_broadcast_id()); else - subscription.mutable_header()->set_src(cfg().driver().modem_id()); + subscription.mutable_header()->set_src(this->cfg().driver().modem_id()); for (auto dest : subscription.header().dest()) { @@ -388,7 +398,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_forward_subscription( auto buffer_id = _create_buffer_id(subscription_key_); if (!subscription_subbuffers_.count(dest)) { - auto subscription_buffer_cfg = cfg().subscription_buffer(); + auto subscription_buffer_cfg = this->cfg().subscription_buffer(); if (!subscription_buffer_cfg.has_ack_required()) subscription_buffer_cfg.set_ack_required(true); @@ -421,7 +431,8 @@ void goby::middleware::intervehicle::ModemDriverThread::_forward_subscription( } } -void goby::middleware::intervehicle::ModemDriverThread::_data_request( +template +void goby::middleware::intervehicle::ModemDriverThread::_data_request( goby::acomms::protobuf::ModemTransmission* msg) { // erase any pending acks with greater frame numbers (we never received these) @@ -448,7 +459,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_data_request( auto buffer_value = buffer_.top(dest, msg->max_frame_bytes() - frame->size(), goby::time::convert_duration( - cfg().ack_timeout_with_units())); + this->cfg().ack_timeout_with_units())); dest = buffer_value.modem_id; *frame += buffer_value.data.data(); @@ -483,14 +494,16 @@ void goby::middleware::intervehicle::ModemDriverThread::_data_request( msg->set_dest(_id_within_subnet(dest)); } -goby::middleware::intervehicle::ModemDriverThread::subbuffer_id_type -goby::middleware::intervehicle::ModemDriverThread::_create_buffer_id(unsigned dccl_id, +template +typename goby::middleware::intervehicle::ModemDriverThread::subbuffer_id_type +goby::middleware::intervehicle::ModemDriverThread::_create_buffer_id(unsigned dccl_id, unsigned group) { return "/group:" + std::to_string(group) + "/id:" + std::to_string(dccl_id) + "/"; } -void goby::middleware::intervehicle::ModemDriverThread::_accept_subscription( +template +void goby::middleware::intervehicle::ModemDriverThread::_accept_subscription( const intervehicle::protobuf::Subscription& subscription) { auto buffer_id = _create_buffer_id(subscription); @@ -579,7 +592,8 @@ void goby::middleware::intervehicle::ModemDriverThread::_accept_subscription( _publish_subscription_report(subscription); } -void goby::middleware::intervehicle::ModemDriverThread::_try_create_or_update_buffer( +template +void goby::middleware::intervehicle::ModemDriverThread::_try_create_or_update_buffer( modem_id_type dest_id, const subbuffer_id_type& buffer_id) { auto pub_it_pair = publisher_buffer_cfg_.equal_range(buffer_id); @@ -626,7 +640,8 @@ void goby::middleware::intervehicle::ModemDriverThread::_try_create_or_update_bu } } -void goby::middleware::intervehicle::ModemDriverThread::_buffer_message( +template +void goby::middleware::intervehicle::ModemDriverThread::_buffer_message( const std::shared_ptr& msg) { if (msg->key().has_metadata()) @@ -640,7 +655,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_buffer_message( middleware::protobuf::SerializerMetadataRequest meta_request; *meta_request.mutable_key() = msg->key(); meta_request.set_request(middleware::protobuf::SerializerMetadataRequest::METADATA_INCLUDE); - interprocess_->publish(meta_request); + interprocess_->template publish(meta_request); glog.is_warn() && glog << group(glog_group_) << "Omitting message because we don't have the DCCL metadata. Sending request: " @@ -658,7 +673,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_buffer_message( glog.is_debug3() && glog << group(glog_group_) << "No need for more DCCL metadata. Sending request: " << meta_request.ShortDebugString() << std::endl; - interprocess_->publish(meta_request); + interprocess_->template publish(meta_request); } auto buffer_id = _create_buffer_id(dccl_id, msg->key().group_numeric()); @@ -709,12 +724,13 @@ void goby::middleware::intervehicle::ModemDriverThread::_buffer_message( else { auto now = goby::time::SteadyClock::now(); - _expire_value(now, {cfg().driver().modem_id(), buffer_id, now, *msg}, + _expire_value(now, {this->cfg().driver().modem_id(), buffer_id, now, *msg}, intervehicle::protobuf::ExpireData::EXPIRED_NO_SUBSCRIBERS); } } -void goby::middleware::intervehicle::ModemDriverThread::_receive( +template +void goby::middleware::intervehicle::ModemDriverThread::_receive( const goby::acomms::protobuf::ModemTransmission& rx_msg) { glog.is(DEBUG1) && glog << group(glog_group_) << "Received: " << rx_msg.ShortDebugString() @@ -725,7 +741,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_receive( if (rx_msg.type() == goby::acomms::protobuf::ModemTransmission::ACK) { - if (full_dest != cfg().driver().modem_id()) + if (full_dest != this->cfg().driver().modem_id()) { glog.is(WARN) && glog << group(glog_group_) << "ignoring ack for modem_id = " << full_dest << std::endl; @@ -765,7 +781,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_receive( goby::time::convert_duration(now - value.push_time)); *ack_pair.mutable_serializer() = value.data; - interprocess_->publish(ack_pair); + interprocess_->template publish(ack_pair); buffer_.erase(value); } pending_ack_.erase(values_to_ack_it); @@ -775,7 +791,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_receive( } else { - if (full_dest == _broadcast_id() || full_dest == cfg().driver().modem_id()) + if (full_dest == _broadcast_id() || full_dest == this->cfg().driver().modem_id()) { for (auto& frame : rx_msg.frame()) { @@ -786,7 +802,7 @@ void goby::middleware::intervehicle::ModemDriverThread::_receive( packets.mutable_header()->set_src(full_src); packets.mutable_header()->add_dest(full_dest); *packets.mutable_header()->mutable_modem_msg() = rx_msg; - interprocess_->publish(packets); + interprocess_->template publish(packets); } catch (const std::exception& e) { @@ -800,16 +816,24 @@ void goby::middleware::intervehicle::ModemDriverThread::_receive( } } -void goby::middleware::intervehicle::ModemDriverThread::_publish_subscription_report( +template +void goby::middleware::intervehicle::ModemDriverThread::_publish_subscription_report( const intervehicle::protobuf::Subscription& changed) { protobuf::SubscriptionReport report; - report.set_link_modem_id(cfg().modem_id()); + report.set_link_modem_id(this->cfg().modem_id()); for (const auto& sub_id_p : subscriber_buffer_cfg_) { for (const auto& subbuffer_sub_p : sub_id_p.second) *report.add_subscription() = subbuffer_sub_p.second; } *report.mutable_changed() = changed; - interprocess_->publish(report); + interprocess_->template publish(report); } + +// Explicit template instantiations for known implementation tags +namespace goby::middleware::intervehicle +{ +template class ModemDriverThread<::goby::zeromq::detail::ZeromqInterprocessTag>; +template class ModemDriverThread<::goby::udpm::detail::UdpmInterprocessTag>; +} // namespace goby::middleware::intervehicle diff --git a/src/middleware/transport/intervehicle/driver_thread.h b/src/middleware/transport/intervehicle/driver_thread.h index 2901c6c9..c69e3f23 100644 --- a/src/middleware/transport/intervehicle/driver_thread.h +++ b/src/middleware/transport/intervehicle/driver_thread.h @@ -127,10 +127,14 @@ serialize_publication(const Data& d, const Group& group, const Publisher& return msg; } +/// \brief Provides the modem driver thread used by InterVehiclePortal, templated on +/// ImplementationTag so it uses the same InterProcessForwarder prefix as the portal's +/// InnerTransporter. +template class ModemDriverThread : public goby::middleware::Thread> + ImplementationTag>> { public: using buffer_data_type = goby::middleware::protobuf::SerializerTransporterMessage; @@ -168,7 +172,7 @@ class ModemDriverThread void _try_create_or_update_buffer(modem_id_type dest_id, const subbuffer_id_type& buffer_id); - modem_id_type _broadcast_id() { return cfg().modem_id() & cfg().subnet_mask(); } + modem_id_type _broadcast_id() { return this->cfg().modem_id() & this->cfg().subnet_mask(); } // id within subnet modem_id_type _id_within_subnet(modem_id_type id) { return id - _broadcast_id(); } @@ -179,12 +183,12 @@ class ModemDriverThread bool _dest_is_in_subnet(modem_id_type dest_id) { bool dest_in_subnet = - (dest_id & cfg().subnet_mask()) == (cfg().modem_id() & cfg().subnet_mask()); + (dest_id & this->cfg().subnet_mask()) == (this->cfg().modem_id() & this->cfg().subnet_mask()); if (!dest_in_subnet) goby::glog.is_debug3() && goby::glog << "Dest: " << dest_id - << " is not in subnet (our id: " << cfg().modem_id() - << ", mask: " << cfg().subnet_mask() << ")" << std::endl; + << " is not in subnet (our id: " << this->cfg().modem_id() + << ", mask: " << this->cfg().subnet_mask() << ")" << std::endl; return dest_in_subnet; } @@ -193,8 +197,7 @@ class ModemDriverThread private: std::unique_ptr interthread_; - std::unique_ptr> - interprocess_; + std::unique_ptr> interprocess_; std::multimap publisher_buffer_cfg_; @@ -218,8 +221,6 @@ class ModemDriverThread std::string glog_group_; - static std::map driver_plugins_; - goby::time::SteadyClock::time_point next_modem_report_time_; const goby::time::SteadyClock::duration modem_report_interval_; }; diff --git a/src/udpm/application/simple_thread.h b/src/udpm/application/simple_thread.h new file mode 100644 index 00000000..5f8f5ec7 --- /dev/null +++ b/src/udpm/application/simple_thread.h @@ -0,0 +1,43 @@ +// Copyright 2022-2024: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +#ifndef GOBY_UDPM_APPLICATION_SIMPLE_THREAD_H +#define GOBY_UDPM_APPLICATION_SIMPLE_THREAD_H + +#include "goby/middleware/application/simple_thread.h" +#include "goby/udpm/transport/detail/tags.h" + +namespace goby +{ +namespace udpm +{ +/// \brief UDPM-backed SimpleThread. Derives from middleware::SimpleThread using UdpmInterprocessTag. +/// +/// \tparam Config Configuration type +template +using SimpleThread = middleware::SimpleThread; + +} // namespace udpm +} // namespace goby + +#endif diff --git a/src/udpm/transport/intermodule.h b/src/udpm/transport/intermodule.h index 127ebf20..8ad29154 100644 --- a/src/udpm/transport/intermodule.h +++ b/src/udpm/transport/intermodule.h @@ -26,6 +26,7 @@ #include "goby/middleware/transport/intermodule.h" +#include "goby/udpm/transport/detail/tags.h" #include "goby/udpm/transport/interprocess.h" namespace goby @@ -35,11 +36,11 @@ namespace udpm template using InterModulePortal = InterProcessPortalImplementation; + detail::UdpmIntermoduleTag>; template using InterModuleForwarder = - middleware::InterModuleForwarder; + middleware::InterModuleForwarder; } // namespace udpm } // namespace goby diff --git a/src/udpm/transport/interprocess.h b/src/udpm/transport/interprocess.h index 20e30109..0fb36109 100644 --- a/src/udpm/transport/interprocess.h +++ b/src/udpm/transport/interprocess.h @@ -50,6 +50,7 @@ #include "goby/util/debug_logger.h" #include "goby/udpm/protobuf/interprocess_config.pb.h" +#include "goby/udpm/transport/detail/tags.h" namespace goby { @@ -543,12 +544,11 @@ class InterProcessPortalImplementation template using InterProcessPortal = InterProcessPortalImplementation; + detail::UdpmInterprocessTag>; template using InterProcessForwarder = - middleware::InterProcessForwarder; + middleware::InterProcessForwarder; } // namespace udpm } // namespace goby diff --git a/src/zeromq/application/simple_thread.h b/src/zeromq/application/simple_thread.h new file mode 100644 index 00000000..3ec0a3ee --- /dev/null +++ b/src/zeromq/application/simple_thread.h @@ -0,0 +1,43 @@ +// Copyright 2022-2024: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +#ifndef GOBY_ZEROMQ_APPLICATION_SIMPLE_THREAD_H +#define GOBY_ZEROMQ_APPLICATION_SIMPLE_THREAD_H + +#include "goby/middleware/application/simple_thread.h" +#include "goby/zeromq/transport/detail/tags.h" + +namespace goby +{ +namespace zeromq +{ +/// \brief Zeromq-backed SimpleThread. Derives from middleware::SimpleThread using ZeromqInterprocessTag. +/// +/// \tparam Config Configuration type +template +using SimpleThread = middleware::SimpleThread; + +} // namespace zeromq +} // namespace goby + +#endif diff --git a/src/zeromq/transport/intermodule.h b/src/zeromq/transport/intermodule.h index 00009ebe..a2196f28 100644 --- a/src/zeromq/transport/intermodule.h +++ b/src/zeromq/transport/intermodule.h @@ -26,6 +26,7 @@ #include "goby/middleware/transport/intermodule.h" +#include "goby/zeromq/transport/detail/tags.h" #include "goby/zeromq/transport/interprocess.h" namespace goby @@ -35,11 +36,11 @@ namespace zeromq template using InterModulePortal = InterProcessPortalImplementation; + detail::ZeromqIntermoduleTag>; template using InterModuleForwarder = - middleware::InterModuleForwarder; + middleware::InterModuleForwarder; } // namespace zeromq } // namespace goby diff --git a/src/zeromq/transport/interprocess.h b/src/zeromq/transport/interprocess.h index 5ed5f891..8b36fb39 100644 --- a/src/zeromq/transport/interprocess.h +++ b/src/zeromq/transport/interprocess.h @@ -58,6 +58,7 @@ #include "goby/middleware/transport/subscriber.h" // for Subs... #include "goby/time/system_clock.h" // for Syst... #include "goby/util/debug_logger/flex_ostream.h" // for Flex... +#include "goby/zeromq/transport/detail/tags.h" // for ZeromqInterprocessTag #include "goby/util/debug_logger/flex_ostreambuf.h" // for lock #include "goby/zeromq/protobuf/interprocess_config.pb.h" // for Inte... #include "goby/zeromq/protobuf/interprocess_zeromq.pb.h" // for Inpr... @@ -477,12 +478,11 @@ class Manager template using InterProcessPortal = InterProcessPortalImplementation; + detail::ZeromqInterprocessTag>; template using InterProcessForwarder = - middleware::InterProcessForwarder; + middleware::InterProcessForwarder; } // namespace zeromq } // namespace goby From fe501b9277023333d3d15ac162a0ac5211b6675f Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 14:59:13 +1000 Subject: [PATCH 07/20] Copilot fetch with tags --- .github/workflows/copilot-setup-steps.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index aaba22cf..40c9edf7 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -28,6 +28,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v5 + with: + fetch-depth: 0 # required to ensure we have tags - name: Install dependencies run: | From 712f58d384aa01eac7e4022437b91ee5d50338eb Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 15:33:15 +1000 Subject: [PATCH 08/20] Add new StandaloneThread for use with the MultiThreadStandaloneApplication. Switch tests and other applications to use correct interprocess implementation --- .../rockblock_simulator.cpp | 10 ++++---- src/middleware/application/multi_thread.h | 24 +++++++++++++++++++ src/middleware/io/line_based/pty.h | 12 +++++----- src/middleware/io/line_based/serial.h | 15 ++++++------ src/middleware/io/line_based/tcp_client.h | 17 ++++++------- src/middleware/io/line_based/tcp_server.h | 22 +++++++++-------- src/middleware/io/mavlink/serial.h | 6 +++-- src/middleware/io/mavlink/udp.h | 6 +++-- src/moos/middleware/moos_plugin_translator.h | 4 ++-- .../test.cpp | 22 ++++++++--------- .../middleware/multi_thread_app1/test.cpp | 4 +++- src/test/zeromq/middleware_basic/test.cpp | 2 +- src/test/zeromq/middleware_regex/test.cpp | 16 ++++++++----- src/udpm/application/multi_thread.h | 1 + src/zeromq/application/multi_thread.h | 1 + src/zeromq/liaison/liaison_container.h | 4 ++-- 16 files changed, 103 insertions(+), 63 deletions(-) diff --git a/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp b/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp index 78b3c123..96dd6753 100644 --- a/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp +++ b/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp @@ -99,7 +99,7 @@ class RockBLOCKSimulator }; class RockBLOCKMTHTTPEndpointThread - : public goby::middleware::SimpleThread + : public goby::middleware::StandaloneThread { public: RockBLOCKMTHTTPEndpointThread(const protobuf::RockBLOCKSimulatorConfig cfg); @@ -113,11 +113,11 @@ class RockBLOCKMTHTTPEndpointThread class RockBLOCKPTYThread : public goby::middleware::io::detail::PTYThread< pty_in, pty_out, goby::middleware::io::PubSubLayer::INTERTHREAD, - goby::middleware::io::PubSubLayer::INTERTHREAD, goby::middleware::SimpleThread> + goby::middleware::io::PubSubLayer::INTERTHREAD, goby::middleware::StandaloneThread> { using Base = goby::middleware::io::detail::PTYThread< pty_in, pty_out, goby::middleware::io::PubSubLayer::INTERTHREAD, - goby::middleware::io::PubSubLayer::INTERTHREAD, goby::middleware::SimpleThread>; + goby::middleware::io::PubSubLayer::INTERTHREAD, goby::middleware::StandaloneThread>; public: RockBLOCKPTYThread(const goby::middleware::protobuf::PTYConfig& config, int index) @@ -314,7 +314,7 @@ void goby::apps::acomms::RockBLOCKSimulator::process_command_data( std::string rx_msg{0, SBD_FIELD_SIZE_BYTES}; unsigned message_size = modem_data.mt_message->size(); rx_msg[0] = (message_size >> SBD_BITS_IN_BYTE) & 0xFF; - rx_msg[1] = (message_size)&0xFF; + rx_msg[1] = (message_size) & 0xFF; rx_msg += *modem_data.mt_message; unsigned int csum = goby::acomms::iridium::sbd_csum(*modem_data.mt_message); const int bits_in_byte = 8; @@ -450,7 +450,7 @@ void goby::apps::acomms::RockBLOCKSimulator::write_mo_message(int index) goby::apps::acomms::RockBLOCKMTHTTPEndpointThread::RockBLOCKMTHTTPEndpointThread( const protobuf::RockBLOCKSimulatorConfig cfg) - : goby::middleware::SimpleThread(cfg) + : goby::middleware::StandaloneThread(cfg) { for (const auto& imei_to_id : cfg.imei_to_id()) imei_in_use_.insert(imei_to_id.imei()); diff --git a/src/middleware/application/multi_thread.h b/src/middleware/application/multi_thread.h index b1bb982a..7f8cfa97 100644 --- a/src/middleware/application/multi_thread.h +++ b/src/middleware/application/multi_thread.h @@ -411,6 +411,30 @@ template class MultiThreadTest : public MultiThreadStandaloneAppl InterThreadTransporter& intervehicle() { return Base::interthread(); } }; +/// \brief Class for use with MultiThreadStandaloneApplication (interthread only) +template class StandaloneThread : public Thread +{ + public: + StandaloneThread(const Config& cfg, double loop_freq_hertz = 0, int index = -1) + : StandaloneThread(cfg, loop_freq_hertz * boost::units::si::hertz, index) + { + } + + StandaloneThread(const Config& cfg, + boost::units::quantity loop_freq, int index = -1) + : Thread(cfg, loop_freq, index) + { + interthread_.reset(new InterThreadTransporter); + + this->set_transporter(interthread_.get()); + } + + InterThreadTransporter& interthread() { return this->transporter(); } + + private: + std::unique_ptr interthread_; +}; + } // namespace middleware template diff --git a/src/middleware/io/line_based/pty.h b/src/middleware/io/line_based/pty.h index e9415c9c..db65c655 100644 --- a/src/middleware/io/line_based/pty.h +++ b/src/middleware/io/line_based/pty.h @@ -34,6 +34,7 @@ #include "goby/middleware/io/detail/io_interface.h" // for PubSubLayer #include "goby/middleware/io/detail/pty_interface.h" // for PTYThread #include "goby/middleware/io/line_based/common.h" // for match_regex +#include "goby/zeromq/application/simple_thread.h" namespace goby { @@ -62,12 +63,11 @@ namespace io /// \brief Reads/Writes strings from/to serial port using a line-based (typically ASCII) protocol with a defined end-of-line regex. /// \tparam line_in_group goby::middleware::Group to publish to after receiving data from the serial port /// \tparam line_out_group goby::middleware::Group to subcribe to for data to send to the serial port -template class ThreadType = goby::middleware::SimpleThread, - bool use_indexed_groups = false> +template < + const goby::middleware::Group& line_in_group, const goby::middleware::Group& line_out_group, + PubSubLayer publish_layer = PubSubLayer::INTERPROCESS, + PubSubLayer subscribe_layer = PubSubLayer::INTERTHREAD, + template class ThreadType = goby::zeromq::SimpleThread, bool use_indexed_groups = false> class PTYThreadLineBased : public detail::PTYThread { diff --git a/src/middleware/io/line_based/serial.h b/src/middleware/io/line_based/serial.h index 39bcc26c..f52f3c81 100644 --- a/src/middleware/io/line_based/serial.h +++ b/src/middleware/io/line_based/serial.h @@ -34,6 +34,7 @@ #include "goby/middleware/io/detail/io_interface.h" // for PubSubLayer #include "goby/middleware/io/detail/serial_interface.h" // for SerialThread #include "goby/middleware/io/line_based/common.h" // for match_regex +#include "goby/zeromq/application/simple_thread.h" namespace goby { @@ -62,12 +63,11 @@ namespace io /// \brief Reads/Writes strings from/to serial port using a line-based (typically ASCII) protocol with a defined end-of-line regex. /// \tparam line_in_group goby::middleware::Group to publish to after receiving data from the serial port /// \tparam line_out_group goby::middleware::Group to subcribe to for data to send to the serial port -template class ThreadType = goby::middleware::SimpleThread, - bool use_indexed_groups = false> +template < + const goby::middleware::Group& line_in_group, const goby::middleware::Group& line_out_group, + PubSubLayer publish_layer = PubSubLayer::INTERPROCESS, + PubSubLayer subscribe_layer = PubSubLayer::INTERTHREAD, + template class ThreadType = goby::zeromq::SimpleThread, bool use_indexed_groups = false> class SerialThreadLineBased : public detail::SerialThread @@ -109,7 +109,8 @@ void goby::middleware::io::SerialThreadLineBasedmutable_serial_port(), buffer_, eol_matcher_, - [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { std::string bytes(bytes_transferred, 0); diff --git a/src/middleware/io/line_based/tcp_client.h b/src/middleware/io/line_based/tcp_client.h index 814ec62a..2e0f40c5 100644 --- a/src/middleware/io/line_based/tcp_client.h +++ b/src/middleware/io/line_based/tcp_client.h @@ -36,6 +36,7 @@ #include "goby/middleware/io/detail/tcp_client_interface.h" // for TCPClien... #include "goby/middleware/io/line_based/common.h" // for match_regex #include "goby/middleware/protobuf/io.pb.h" // for IOData +#include "goby/zeromq/application/simple_thread.h" namespace goby { @@ -64,13 +65,12 @@ namespace io /// \brief Reads/Writes strings from/to a TCP connection using a line-based (typically ASCII) protocol with a defined end-of-line regex. /// \tparam line_in_group goby::middleware::Group to publish to after receiving data from the TCP socket /// \tparam line_out_group goby::middleware::Group to subcribe to for data to send to the TCP socket -template class ThreadType = goby::middleware::SimpleThread, - bool use_indexed_groups = false> +template < + const goby::middleware::Group& line_in_group, const goby::middleware::Group& line_out_group, + PubSubLayer publish_layer = PubSubLayer::INTERPROCESS, + PubSubLayer subscribe_layer = PubSubLayer::INTERTHREAD, + typename Config = goby::middleware::protobuf::TCPClientConfig, + template class ThreadType = goby::zeromq::SimpleThread, bool use_indexed_groups = false> class TCPClientThreadLineBased : public detail::TCPClientThread @@ -113,7 +113,8 @@ void goby::middleware::io::TCPClientThreadLineBasedmutable_socket(), buffer_, eol_matcher_, - [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { auto io_msg = std::make_shared(); diff --git a/src/middleware/io/line_based/tcp_server.h b/src/middleware/io/line_based/tcp_server.h index 21e6a2c4..34a1588e 100644 --- a/src/middleware/io/line_based/tcp_server.h +++ b/src/middleware/io/line_based/tcp_server.h @@ -39,6 +39,8 @@ #include "goby/middleware/io/line_based/common.h" // for match_regex #include "goby/middleware/protobuf/io.pb.h" // for IOData #include "goby/middleware/protobuf/tcp_config.pb.h" // for TCPServe... +#include "goby/zeromq/application/simple_thread.h" + namespace goby { namespace middleware @@ -69,7 +71,8 @@ class TCPSessionLineBased : public detail::TCPSession auto self(this->shared_from_this()); boost::asio::async_read_until( this->mutable_socket(), buffer_, eol_matcher_, - [this, self](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this, self](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { auto io_msg = std::make_shared(); @@ -93,15 +96,14 @@ class TCPSessionLineBased : public detail::TCPSession boost::asio::streambuf buffer_; }; -template class ThreadType = goby::middleware::SimpleThread, - bool use_indexed_groups = false> +template < + const goby::middleware::Group& line_in_group, const goby::middleware::Group& line_out_group, + // by default publish all incoming traffic to interprocess for logging + PubSubLayer publish_layer = PubSubLayer::INTERPROCESS, + // but only subscribe on interthread for outgoing traffic + PubSubLayer subscribe_layer = PubSubLayer::INTERTHREAD, + typename Config = goby::middleware::protobuf::TCPServerConfig, + template class ThreadType = goby::zeromq::SimpleThread, bool use_indexed_groups = false> class TCPServerThreadLineBased : public detail::TCPServerThread diff --git a/src/middleware/io/mavlink/serial.h b/src/middleware/io/mavlink/serial.h index 89669baa..4dff2055 100644 --- a/src/middleware/io/mavlink/serial.h +++ b/src/middleware/io/mavlink/serial.h @@ -34,6 +34,7 @@ #include "goby/middleware/io/detail/io_interface.h" // for PubSubLayer #include "goby/middleware/io/detail/serial_interface.h" // for SerialThread #include "goby/middleware/io/mavlink/common.h" // for IOThreadMAVLink +#include "goby/zeromq/application/simple_thread.h" namespace goby { @@ -74,7 +75,7 @@ template class ThreadType = goby::middleware::SimpleThread> + template class ThreadType = goby::zeromq::SimpleThread> class SerialThreadMAVLink : public SerialThreadMAVLinkBase @@ -105,7 +106,8 @@ void goby::middleware::io::SerialThreadMAVLinkmutable_serial_port(), boost::asio::buffer(this->buffer()), boost::asio::transfer_at_least(1), - [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { this->try_parse(bytes_transferred); diff --git a/src/middleware/io/mavlink/udp.h b/src/middleware/io/mavlink/udp.h index 4190e3f1..94f7d6ec 100644 --- a/src/middleware/io/mavlink/udp.h +++ b/src/middleware/io/mavlink/udp.h @@ -33,6 +33,7 @@ #include "goby/middleware/io/detail/io_interface.h" // for PubSubLayer, Pub... #include "goby/middleware/io/mavlink/common.h" // for IOThreadMAVLink #include "goby/middleware/io/udp_point_to_point.h" // for UDPPointToPointT... +#include "goby/zeromq/application/simple_thread.h" namespace goby { @@ -74,7 +75,7 @@ template class ThreadType = goby::middleware::SimpleThread> + template class ThreadType = goby::zeromq::SimpleThread> class UDPThreadMAVLink : public UDPThreadMAVLinkBase { @@ -106,7 +107,8 @@ void goby::middleware::io::UDPThreadMAVLinkmutable_socket().async_receive_from( boost::asio::buffer(this->buffer()), sender_endpoint_, - [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { this->try_parse(bytes_transferred); diff --git a/src/moos/middleware/moos_plugin_translator.h b/src/moos/middleware/moos_plugin_translator.h index 5db8da0d..ca306116 100644 --- a/src/moos/middleware/moos_plugin_translator.h +++ b/src/moos/middleware/moos_plugin_translator.h @@ -38,9 +38,9 @@ #include // for frequency, hertz #include "MOOS/libMOOS/Comms/MOOSAsyncCommClient.h" // for MOOSAsyncComm... -#include "goby/middleware/application/multi_thread.h" // for SimpleThread #include "goby/moos/protobuf/moos_gateway_config.pb.h" // for GobyMOOSGatew... #include "goby/time/system_clock.h" // for SystemClock +#include "goby/zeromq/application/simple_thread.h" // for SimpleThread class CMOOSCommClient; @@ -152,7 +152,7 @@ class BasicTranslator : public TranslatorBase, void loop() override { this->TranslatorBase::loop(); } }; -using Translator = BasicTranslator; +using Translator = BasicTranslator; } // namespace moos } // namespace goby diff --git a/src/test/middleware/middleware_interprocess_forwarder/test.cpp b/src/test/middleware/middleware_interprocess_forwarder/test.cpp index 7871f034..f46ee839 100644 --- a/src/test/middleware/middleware_interprocess_forwarder/test.cpp +++ b/src/test/middleware/middleware_interprocess_forwarder/test.cpp @@ -33,8 +33,10 @@ #if defined(test_for_zeromq) #include "goby/zeromq/transport/interprocess.h" +using goby::zeromq::InterProcessForwarder; #elif defined(test_for_udpm) #include "goby/udpm/transport/interprocess.h" +using goby::udpm::InterProcessForwarder; #else #error "No test_for_ defined" #endif @@ -53,14 +55,12 @@ using goby::test::middleware::protobuf::Widget; // tests InterProcessForwarder // avoid static initialization order problem -goby::middleware::InterProcessForwarder& ipc_child() +InterProcessForwarder& ipc_child() { static std::unique_ptr inner( new goby::middleware::InterThreadTransporter); - static std::unique_ptr< - goby::middleware::InterProcessForwarder> - p(new goby::middleware::InterProcessForwarder( - *inner)); + static std::unique_ptr> p( + new InterProcessForwarder(*inner)); return *p; } @@ -100,7 +100,7 @@ namespace middleware void publisher() { goby::middleware::InterThreadTransporter inproc1; - goby::middleware::InterProcessForwarder ipc(inproc1); + InterProcessForwarder ipc(inproc1); double a = 0; while (hold) usleep(1e4); @@ -113,8 +113,8 @@ void publisher() auto lm = std::make_shared(); lm->set_index(0); lm->set_payload(std::string(large_msg_payload_bytes, 'X')); - glog.is(DEBUG1) && glog << "Publishing LargeMessage (" << lm->payload().size() - << " bytes)" << std::endl; + glog.is(DEBUG1) && glog << "Publishing LargeMessage (" << lm->payload().size() << " bytes)" + << std::endl; ipc.publish(lm); } #endif @@ -200,9 +200,9 @@ void subscriber() ipc_child().subscribe( [](const LargeMessage& lm) { - glog.is(DEBUG1) && - glog << "Received LargeMessage index=" << lm.index() - << ", payload size=" << lm.payload().size() << " bytes" << std::endl; + glog.is(DEBUG1) && glog << "Received LargeMessage index=" << lm.index() + << ", payload size=" << lm.payload().size() << " bytes" + << std::endl; assert(lm.index() == 0); assert(static_cast(lm.payload().size()) == large_msg_payload_bytes); assert(lm.payload() == std::string(large_msg_payload_bytes, 'X')); diff --git a/src/test/middleware/multi_thread_app1/test.cpp b/src/test/middleware/multi_thread_app1/test.cpp index 4685fde8..2b332aa2 100644 --- a/src/test/middleware/multi_thread_app1/test.cpp +++ b/src/test/middleware/multi_thread_app1/test.cpp @@ -29,9 +29,11 @@ #if defined(test_for_zeromq) #include "goby/test/middleware/multi_thread_app1/zeromq.pb.h" #include "goby/zeromq/application/multi_thread.h" +using goby::zeromq::SimpleThread; #elif defined(test_for_udpm) #include "goby/test/middleware/multi_thread_app1/udpm.pb.h" #include "goby/udpm/application/multi_thread.h" +using goby::udpm::SimpleThread; #else #error "No test_for_ defined" #endif @@ -100,7 +102,7 @@ class TestTxConfigurator : public goby::middleware::ProtobufConfigurator +class TestThreadRx : public SimpleThread { public: TestThreadRx(const TestConfig& cfg) : SimpleThread(cfg) diff --git a/src/test/zeromq/middleware_basic/test.cpp b/src/test/zeromq/middleware_basic/test.cpp index d33906bb..a3caae0a 100644 --- a/src/test/zeromq/middleware_basic/test.cpp +++ b/src/test/zeromq/middleware_basic/test.cpp @@ -63,7 +63,7 @@ int main(int /*argc*/, char* argv[]) // goby::ProtobufMarshaller pb; goby::middleware::InterThreadTransporter inproc; goby::zeromq::InterProcessPortal<> zmq_blank(zmq_cfg); - goby::middleware::InterProcessForwarder interprocess_default(inproc); + goby::zeromq::InterProcessForwarder interprocess_default(inproc); goby::zeromq::InterProcessPortal zmq(inproc, zmq_cfg); CTDSample s; diff --git a/src/test/zeromq/middleware_regex/test.cpp b/src/test/zeromq/middleware_regex/test.cpp index 75a1437f..655c29c0 100644 --- a/src/test/zeromq/middleware_regex/test.cpp +++ b/src/test/zeromq/middleware_regex/test.cpp @@ -64,7 +64,7 @@ constexpr goby::middleware::Group sample_special_chars{"[Sample]()"}; // thread 1 - parent process void publisher() { - goby::middleware::InterProcessForwarder ipc(inproc1); + goby::zeromq::InterProcessForwarder ipc(inproc1); double a = 0; while (publish_count < max_publish) { @@ -102,11 +102,12 @@ void handle_all(const std::vector& data, int scheme, const std::s void subscriber() { - goby::middleware::InterProcessForwarder ipc(inproc1); + goby::zeromq::InterProcessForwarder ipc(inproc1); ipc.subscribe_regex(&handle_all, {goby::middleware::MarshallingScheme::ALL_SCHEMES}); ipc.subscribe_type_regex( - [&](const std::shared_ptr& msg, const std::string& type) { + [&](const std::shared_ptr& msg, const std::string& type) + { glog.is(DEBUG1) && glog << "(template) InterProcessForwarder received publication of type: " << type << " with values: " << msg->ShortDebugString() << std::endl; @@ -136,7 +137,8 @@ void zmq_forward(const goby::zeromq::protobuf::InterProcessPortalConfig& cfg) goby::zeromq::InterProcessPortal ipc(inproc3, cfg); ipc.subscribe_regex( [&](const std::vector& data, int scheme, const std::string& type, - const goby::middleware::Group& group) { + const goby::middleware::Group& group) + { glog.is(DEBUG1) && glog << "InterProcessPortal received publication of " << data.size() << " bytes from group: " << group << " of type: " << type << " from scheme: " << scheme << std::endl; @@ -147,7 +149,8 @@ void zmq_forward(const goby::zeromq::protobuf::InterProcessPortalConfig& cfg) {goby::middleware::MarshallingScheme::PROTOBUF}, ".*Sample", "Sample1|Sample2"); ipc.subscribe_type_regex( - [&](const std::shared_ptr& msg, const std::string& type) { + [&](const std::shared_ptr& msg, const std::string& type) + { glog.is(DEBUG1) && glog << "(template) InterProcessPortal received publication of type: " << type << " with values: " << msg->ShortDebugString() << std::endl; @@ -159,7 +162,8 @@ void zmq_forward(const goby::zeromq::protobuf::InterProcessPortalConfig& cfg) ".*Sample"); ipc.subscribe_type_regex( - [&](const std::shared_ptr& msg, const std::string& type) { + [&](const std::shared_ptr& msg, const std::string& type) + { glog.is(DEBUG1) && glog << "(special chars) InterProcessPortal received publication of type: " << type << " with values: " << msg->ShortDebugString() << std::endl; diff --git a/src/udpm/application/multi_thread.h b/src/udpm/application/multi_thread.h index bf7ac004..0c98620c 100644 --- a/src/udpm/application/multi_thread.h +++ b/src/udpm/application/multi_thread.h @@ -26,6 +26,7 @@ #define GOBY_UDPM_APPLICATION_MULTI_THREAD_H #include "goby/middleware/application/multi_thread.h" +#include "goby/udpm/application/simple_thread.h" #include "goby/udpm/transport/interprocess.h" namespace goby diff --git a/src/zeromq/application/multi_thread.h b/src/zeromq/application/multi_thread.h index 545f48c5..99aebc0e 100644 --- a/src/zeromq/application/multi_thread.h +++ b/src/zeromq/application/multi_thread.h @@ -26,6 +26,7 @@ #define GOBY_ZEROMQ_APPLICATION_MULTI_THREAD_H #include "goby/middleware/application/multi_thread.h" +#include "goby/zeromq/application/simple_thread.h" #include "goby/zeromq/transport/interprocess.h" namespace goby diff --git a/src/zeromq/liaison/liaison_container.h b/src/zeromq/liaison/liaison_container.h index 65d97589..9ab5c09b 100644 --- a/src/zeromq/liaison/liaison_container.h +++ b/src/zeromq/liaison/liaison_container.h @@ -210,12 +210,12 @@ class LiaisonContainerWithComms : public LiaisonContainer template class LiaisonCommsThread - : public goby::middleware::SimpleThread + : public goby::zeromq::SimpleThread { public: LiaisonCommsThread(WtContainer* container, const goby::apps::zeromq::protobuf::LiaisonConfig& config, int index) - : goby::middleware::SimpleThread( + : goby::zeromq::SimpleThread( config, config.update_freq() * boost::units::si::hertz, index), container_(container) { From cfafb1f93ee4b16ffb5dcbd78dec791dd6bcdfe3 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 15:43:18 +1000 Subject: [PATCH 09/20] Replace more deprecated Forwarders. Move includes for middleware::InterProcess/Module to be more explicit about deprecated specialization --- src/middleware/application/simple_thread.h | 37 ++++++++------ src/middleware/transport/intermodule.h | 50 +++++++++++-------- src/test/zeromq/multi_thread_app2/test.cpp | 4 +- .../test.cpp | 4 +- 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/middleware/application/simple_thread.h b/src/middleware/application/simple_thread.h index 9e836b55..71dd6c66 100644 --- a/src/middleware/application/simple_thread.h +++ b/src/middleware/application/simple_thread.h @@ -30,7 +30,6 @@ #include "goby/middleware/transport/interprocess.h" #include "goby/middleware/transport/interthread.h" #include "goby/middleware/transport/intervehicle.h" -#include "goby/zeromq/transport/detail/tags.h" namespace goby { @@ -44,15 +43,13 @@ namespace middleware /// Derive from this class to create standalone threads that can be launched and joined by MultiThreadApplication's launch_thread and join_thread methods. template class SimpleThread - : public Thread>>, + : public Thread>>, public coroner::Thread> { - using SimpleThreadBase = - Thread>>; + using SimpleThreadBase = Thread< + Config, + InterVehicleForwarder>>; friend class coroner::Thread>; @@ -80,8 +77,8 @@ class SimpleThread interprocess_.reset( new InterProcessForwarder(*interthread_)); intervehicle_.reset( - new InterVehicleForwarder>( - *interprocess_)); + new InterVehicleForwarder< + InterProcessForwarder>(*interprocess_)); this->set_transporter(intervehicle_.get()); @@ -112,6 +109,16 @@ class SimpleThread intervehicle_; }; +} // namespace middleware +} // namespace goby + +#include "goby/zeromq/transport/detail/tags.h" + +namespace goby +{ +namespace middleware +{ + /// \brief Deprecated 1-arg specialisation: use goby::zeromq::SimpleThread or goby::udpm::SimpleThread template class SimpleThread @@ -120,17 +127,15 @@ class SimpleThread public: using Base = SimpleThread; - [[deprecated( - "Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " - "goby::middleware::SimpleThread")]] + [[deprecated("Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " + "goby::middleware::SimpleThread")]] SimpleThread(const Config& cfg, double loop_freq_hertz = 0, int index = -1) : Base(cfg, loop_freq_hertz, index) { } - [[deprecated( - "Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " - "goby::middleware::SimpleThread")]] + [[deprecated("Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " + "goby::middleware::SimpleThread")]] SimpleThread(const Config& cfg, boost::units::quantity loop_freq, int index = -1) : Base(cfg, loop_freq, index) diff --git a/src/middleware/transport/intermodule.h b/src/middleware/transport/intermodule.h index 22e7bb91..91757991 100644 --- a/src/middleware/transport/intermodule.h +++ b/src/middleware/transport/intermodule.h @@ -38,8 +38,6 @@ #include "goby/middleware/transport/interprocess.h" #include "goby/middleware/transport/null.h" #include "goby/middleware/transport/poller.h" -#include "goby/zeromq/transport/detail/tags.h" -#include "goby/udpm/transport/detail/tags.h" #include "goby/middleware/transport/serialization_handlers.h" namespace goby @@ -67,8 +65,7 @@ using InterModuleTransporterBase = /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node /// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqIntermoduleTag or detail::UdpmIntermoduleTag) -template -class InterModuleForwarder; +template class InterModuleForwarder; /// \brief Implements the forwarder concept for the intermodule layer (implementation) /// @@ -200,23 +197,6 @@ class InterModuleForwarder subscriptions_; }; -/// \brief Deprecated: use zeromq::InterModuleForwarder or udpm::InterModuleForwarder instead -/// -/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. -/// It resolves to the zeromq implementation. New code should use zeromq::InterModuleForwarder<> -/// or udpm::InterModuleForwarder<> explicitly. -template -class InterModuleForwarder - : public InterModuleForwarder -{ - public: - using Base = InterModuleForwarder; - - [[deprecated("Use zeromq::InterModuleForwarder<> or udpm::InterModuleForwarder<> instead of " - "middleware::InterModuleForwarder<>")]] - explicit InterModuleForwarder(InnerTransporter& inner) : Base(inner) {} -}; - template class InterModulePortalBase : public InterModuleTransporterBase, @@ -269,4 +249,32 @@ class InterModulePortalBase } // namespace middleware } // namespace goby +#include "goby/zeromq/transport/detail/tags.h" + +namespace goby +{ +namespace middleware +{ +/// \brief Deprecated: use zeromq::InterModuleForwarder or udpm::InterModuleForwarder instead +/// +/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. +/// It resolves to the zeromq implementation. New code should use zeromq::InterModuleForwarder<> +/// or udpm::InterModuleForwarder<> explicitly. +template +class InterModuleForwarder + : public InterModuleForwarder +{ + public: + using Base = InterModuleForwarder; + + [[deprecated("Use zeromq::InterModuleForwarder<> or udpm::InterModuleForwarder<> instead of " + "middleware::InterModuleForwarder<>")]] + explicit InterModuleForwarder(InnerTransporter& inner) + : Base(inner) + { + } +}; +} // namespace middleware +} // namespace goby + #endif diff --git a/src/test/zeromq/multi_thread_app2/test.cpp b/src/test/zeromq/multi_thread_app2/test.cpp index bbfd19a0..ed5e3f35 100644 --- a/src/test/zeromq/multi_thread_app2/test.cpp +++ b/src/test/zeromq/multi_thread_app2/test.cpp @@ -48,7 +48,7 @@ namespace test { namespace zeromq { -class TestThreadRx : public goby::middleware::SimpleThread +class TestThreadRx : public goby::zeromq::SimpleThread { public: TestThreadRx(const TestConfig& cfg, int index) : SimpleThread(cfg, 0, index) @@ -84,7 +84,7 @@ class TestThreadRx : public goby::middleware::SimpleThread int rx_count_{0}; }; -class TestThreadTx : public goby::middleware::SimpleThread +class TestThreadTx : public goby::zeromq::SimpleThread { public: TestThreadTx(const TestConfig& cfg, int index) : SimpleThread(cfg, 100000, index) diff --git a/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp b/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp index 1889df6b..59fe00e1 100644 --- a/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp +++ b/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp @@ -106,7 +106,7 @@ void portal_publisher(const goby::zeromq::protobuf::InterProcessPortalConfig& p_ void forwarder_publisher(const goby::zeromq::protobuf::InterProcessPortalConfig& p_cfg) { goby::zeromq::InterProcessPortal<> interprocess(p_cfg); - goby::middleware::InterModuleForwarder> intermodule( + goby::zeromq::InterModuleForwarder> intermodule( interprocess); intermodule.subscribe([](const Complete& complete) { @@ -195,7 +195,7 @@ void portal_subscriber(const goby::zeromq::protobuf::InterProcessPortalConfig& p void forwarder_subscriber(const goby::zeromq::protobuf::InterProcessPortalConfig& p_cfg) { goby::zeromq::InterProcessPortal<> interprocess(p_cfg); - goby::middleware::InterModuleForwarder> intermodule( + goby::zeromq::InterModuleForwarder> intermodule( interprocess); intermodule.subscribe([](const Sample& sample) { From 93ea914885f430fbeec33e28a8293d648f58d263 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 15:58:04 +1000 Subject: [PATCH 10/20] Template the HealthMonitorThread on the correct ImplementationTag; Separate deprecated InterProcessForwarder from rest of header. Isolate Boost.StateMachine deprecated warning so we can leave Wdeprecated-declarations enabled for everything else --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 2 +- src/acomms/modemdriver/iridium_driver.cpp | 15 ++++ src/middleware/application/multi_thread.h | 5 +- .../coroner/health_monitor_thread.cpp | 79 ------------------- .../coroner/health_monitor_thread.h | 59 +++++++++++++- src/middleware/src.cmake | 1 - src/middleware/transport/interprocess.h | 54 +++++++------ 8 files changed, 108 insertions(+), 109 deletions(-) delete mode 100644 src/middleware/coroner/health_monitor_thread.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ba4ae09..fe7c88d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,7 +133,7 @@ add_custom_command(OUTPUT ShareLink COMMAND ${CMAKE_COMMAND} -E create_symlink $ add_custom_target(share_link ALL DEPENDS ShareLink) -set(GOBY_DEBUG_OPTIONS -Wall -Werror -Wno-error=deprecated-declarations -Wno-sign-compare) +set(GOBY_DEBUG_OPTIONS -Wall -Werror -Wno-sign-compare) add_compile_options("$<$:${GOBY_DEBUG_OPTIONS}>") add_compile_options("$<$:-Wdocumentation>") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b1fa1ff..97b7fd4e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -211,7 +211,7 @@ endmacro() ## boost goby_find_required_package(Boost 1.58.0 COMPONENTS system date_time program_options filesystem) -include_directories(${Boost_INCLUDE_DIRS}) +include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) protobuf_generate_cpp_dccl(BASE_PROTO_SRCS BASE_PROTO_HDRS protobuf/option_extensions.proto diff --git a/src/acomms/modemdriver/iridium_driver.cpp b/src/acomms/modemdriver/iridium_driver.cpp index 7dad530a..9da1a4c2 100644 --- a/src/acomms/modemdriver/iridium_driver.cpp +++ b/src/acomms/modemdriver/iridium_driver.cpp @@ -31,7 +31,22 @@ #include // for function #include // for expired_slot #include // for signal + +// Deprecation warning in Boost 1.83 +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif #include // for state_mac... +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + #include // for errno #include // for operator/ #include // for strerror diff --git a/src/middleware/application/multi_thread.h b/src/middleware/application/multi_thread.h index 7f8cfa97..c7e53af3 100644 --- a/src/middleware/application/multi_thread.h +++ b/src/middleware/application/multi_thread.h @@ -314,7 +314,8 @@ class MultiThreadApplication this->app_cfg()); if (this->app_cfg().app().health_cfg().run_health_monitor_thread()) - this->template launch_thread_without_cfg(); + this->template launch_thread_without_cfg::implementation_tag>>(); } virtual ~MultiThreadApplication() {} @@ -419,7 +420,7 @@ template class StandaloneThread : public Thread loop_freq, int index = -1) : Thread(cfg, loop_freq, index) diff --git a/src/middleware/coroner/health_monitor_thread.cpp b/src/middleware/coroner/health_monitor_thread.cpp deleted file mode 100644 index c42dd544..00000000 --- a/src/middleware/coroner/health_monitor_thread.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022-2023: -// GobySoft, LLC (2013-) -// Community contributors (see AUTHORS file) -// File authors: -// Toby Schneider -// -// -// This file is part of the Goby Underwater Autonomy Project Libraries -// ("The Goby Libraries"). -// -// The Goby Libraries are free software: you can redistribute them and/or modify -// them under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 2.1 of the License, or -// (at your option) any later version. -// -// The Goby Libraries are distributed in the hope that they will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with Goby. If not, see . - -#include "goby/middleware/coroner/health_monitor_thread.h" - -goby::middleware::HealthMonitorThread::HealthMonitorThread() - : SimpleThread(NullConfig(), 1.0 * boost::units::si::hertz) -{ - // handle goby_coroner request - this->interprocess().template subscribe( - [this](const protobuf::HealthRequest& request) - { - this->interthread().template publish(protobuf::HealthRequest()); - waiting_for_responses_ = true; - last_health_request_time_ = goby::time::SteadyClock::now(); - - std::shared_ptr our_response(new protobuf::ThreadHealth); - this->thread_health(*our_response); - child_responses_[our_response->uid()] = our_response; - }); - - // handle response from main thread - this->interthread().template subscribe( - [this](std::shared_ptr response) - { health_response_ = *response; }); - - // handle response from child threads - this->interthread().template subscribe( - [this](std::shared_ptr response) - { child_responses_[response->uid()] = response; }); -} - -void goby::middleware::HealthMonitorThread::loop() -{ - if (waiting_for_responses_ && - goby::time::SteadyClock::now() > last_health_request_time_ + health_request_timeout_) - { - goby::middleware::protobuf::HealthState health_state = health_response_.main().state(); - - // overwrite any child responses we got - for (auto& thread_health : *health_response_.mutable_main()->mutable_child()) - { - if (child_responses_.count(thread_health.uid())) - thread_health = *child_responses_.at(thread_health.uid()); - - if (thread_health.state() > health_state) - health_state = thread_health.state(); - } - - health_response_.mutable_main()->set_state(health_state); - - if (health_response_.IsInitialized()) - this->interprocess().template publish(health_response_); - - waiting_for_responses_ = false; - child_responses_.clear(); - health_response_.Clear(); - } -} diff --git a/src/middleware/coroner/health_monitor_thread.h b/src/middleware/coroner/health_monitor_thread.h index fa538fb3..1e66aa70 100644 --- a/src/middleware/coroner/health_monitor_thread.h +++ b/src/middleware/coroner/health_monitor_thread.h @@ -38,13 +38,66 @@ struct NullConfig { }; -class HealthMonitorThread : public SimpleThread +template +class HealthMonitorThread : public SimpleThread { public: - HealthMonitorThread(); + HealthMonitorThread() + : SimpleThread(NullConfig(), 1.0 * boost::units::si::hertz) + { + // handle goby_coroner request + this->interprocess().template subscribe( + [this](const protobuf::HealthRequest& request) + { + this->interthread().template publish( + protobuf::HealthRequest()); + waiting_for_responses_ = true; + last_health_request_time_ = goby::time::SteadyClock::now(); + + std::shared_ptr our_response(new protobuf::ThreadHealth); + this->thread_health(*our_response); + child_responses_[our_response->uid()] = our_response; + }); + + // handle response from main thread + this->interthread().template subscribe( + [this](std::shared_ptr response) + { health_response_ = *response; }); + + // handle response from child threads + this->interthread().template subscribe( + [this](std::shared_ptr response) + { child_responses_[response->uid()] = response; }); + } private: - void loop() override; + void loop() override + { + if (waiting_for_responses_ && + goby::time::SteadyClock::now() > last_health_request_time_ + health_request_timeout_) + { + goby::middleware::protobuf::HealthState health_state = health_response_.main().state(); + + // overwrite any child responses we got + for (auto& thread_health : *health_response_.mutable_main()->mutable_child()) + { + if (child_responses_.count(thread_health.uid())) + thread_health = *child_responses_.at(thread_health.uid()); + + if (thread_health.state() > health_state) + health_state = thread_health.state(); + } + + health_response_.mutable_main()->set_state(health_state); + + if (health_response_.IsInitialized()) + this->interprocess().template publish(health_response_); + + waiting_for_responses_ = false; + child_responses_.clear(); + health_response_.Clear(); + } + } void initialize() override { this->set_name("health_monitor"); } private: diff --git a/src/middleware/src.cmake b/src/middleware/src.cmake index c77fa0bf..95bbd6d3 100644 --- a/src/middleware/src.cmake +++ b/src/middleware/src.cmake @@ -35,7 +35,6 @@ set(MIDDLEWARE_SRC middleware/application/tool.cpp middleware/log/log_entry.cpp middleware/frontseat/interface.cpp - middleware/coroner/health_monitor_thread.cpp ${MIDDLEWARE_PROTO_SRCS} ${MIDDLEWARE_PROTO_HDRS} ) diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index 67f071a5..5c94868e 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -41,8 +41,6 @@ #include "goby/middleware/transport/null.h" #include "goby/middleware/transport/poller.h" #include "goby/middleware/transport/serialization_handlers.h" -#include "goby/zeromq/transport/detail/tags.h" -#include "goby/udpm/transport/detail/tags.h" namespace goby { @@ -304,8 +302,7 @@ class InterProcessTransporterBase /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. For example, the main thread might instantiate a portal and then spawn several threads that instantiate forwarders. These auxiliary threads can then communicate on the interprocess layer as if they had a direct connection to other interprocess nodes. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node /// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqInterprocessTag or detail::UdpmInterprocessTag) -template -class InterProcessForwarder; +template class InterProcessForwarder; /// \brief Implements the forwarder concept for the interprocess layer (implementation) /// @@ -314,7 +311,7 @@ class InterProcessForwarder; template class InterProcessForwarder : public InterProcessTransporterBase, - InnerTransporter, ImplementationTag> + InnerTransporter, ImplementationTag> { public: using Base = @@ -482,23 +479,6 @@ class InterProcessForwarder std::shared_ptr> alive_; }; -/// \brief Deprecated: use zeromq::InterProcessForwarder or udpm::InterProcessForwarder instead -/// -/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. -/// It resolves to the zeromq implementation. New code should use zeromq::InterProcessForwarder<> -/// or udpm::InterProcessForwarder<> explicitly. -template -class InterProcessForwarder - : public InterProcessForwarder -{ - public: - using Base = InterProcessForwarder; - - [[deprecated("Use zeromq::InterProcessForwarder<> or udpm::InterProcessForwarder<> instead of " - "middleware::InterProcessForwarder<>")]] - explicit InterProcessForwarder(InnerTransporter& inner) : Base(inner) {} -}; - template class InterProcessPortalCommon : public InterProcessIdentifierManager { @@ -803,4 +783,34 @@ class InterProcessPortalBase } // namespace middleware } // namespace goby +#include "goby/zeromq/transport/detail/tags.h" + +namespace goby +{ +namespace middleware +{ + +/// \brief Deprecated: use zeromq::InterProcessForwarder or udpm::InterProcessForwarder instead +/// +/// This 1-argument specialisation (ImplementationTag = void) is kept for backwards compatibility. +/// It resolves to the zeromq implementation. New code should use zeromq::InterProcessForwarder<> +/// or udpm::InterProcessForwarder<> explicitly. +template +class InterProcessForwarder + : public InterProcessForwarder +{ + public: + using Base = InterProcessForwarder; + + [[deprecated("Use zeromq::InterProcessForwarder<> or udpm::InterProcessForwarder<> instead of " + "middleware::InterProcessForwarder<>")]] + explicit InterProcessForwarder(InnerTransporter& inner) + : Base(inner) + { + } +}; + +} // namespace middleware +} // namespace goby + #endif From 803742df3e8e7fc06c42d1268bbe0fd313a39d58 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 16:38:15 +1000 Subject: [PATCH 11/20] Various cleanups regarding use of zeromq tags where the generic case should be supported --- src/middleware/io/detail/io_interface.h | 63 ++++++++------ src/middleware/io/detail/io_transporters.h | 83 ++++++++++--------- src/middleware/io/groups.h | 4 +- src/middleware/transport/interthread.h | 2 + src/middleware/transport/intervehicle.h | 12 +-- .../transport/intervehicle/driver_thread.cpp | 65 ++++++++------- 6 files changed, 125 insertions(+), 104 deletions(-) diff --git a/src/middleware/io/detail/io_interface.h b/src/middleware/io/detail/io_interface.h index 711a4de6..f4664821 100644 --- a/src/middleware/io/detail/io_interface.h +++ b/src/middleware/io/detail/io_interface.h @@ -76,15 +76,18 @@ template class ThreadType, bool use_indexed_groups = false> -class IOThread : public ThreadType, - public IOPublishTransporter< - IOThread, - line_in_group, publish_layer, use_indexed_groups>, - public IOSubscribeTransporter< - IOThread, - line_out_group, subscribe_layer, use_indexed_groups> +class IOThread + : public ThreadType, + public IOPublishTransporter< + IOThread, + line_in_group, publish_layer, + typename ThreadType::Transporter::implementation_tag, use_indexed_groups>, + public IOSubscribeTransporter< + IOThread, + line_out_group, subscribe_layer, + typename ThreadType::Transporter::implementation_tag, use_indexed_groups> { public: /// \brief Constructs the thread. @@ -96,21 +99,26 @@ class IOThread : public ThreadType, IOPublishTransporter< IOThread, - line_in_group, publish_layer, use_indexed_groups>(index), + line_in_group, publish_layer, + typename ThreadType::Transporter::implementation_tag, use_indexed_groups>( + index), IOSubscribeTransporter< IOThread, - line_out_group, subscribe_layer, use_indexed_groups>(index), + line_out_group, subscribe_layer, + typename ThreadType::Transporter::implementation_tag, use_indexed_groups>( + index), glog_group_(glog_group + " / t" + std::to_string(goby::middleware::gettid())), thread_name_(glog_group) { auto data_out_callback = - [this](std::shared_ptr io_msg) { - if (!io_msg->has_index() || io_msg->index() == this->index()) - { - write(io_msg); - } - }; + [this](std::shared_ptr io_msg) + { + if (!io_msg->has_index() || io_msg->index() == this->index()) + { + write(io_msg); + } + }; this->template subscribe_out(data_out_callback); @@ -124,15 +132,17 @@ class IOThread : public ThreadType, void initialize() override { // thread to handle synchonization between boost::asio and goby condition_variable signaling - incoming_mail_notify_thread_.reset(new std::thread([this]() { - while (this->alive()) + incoming_mail_notify_thread_.reset(new std::thread( + [this]() { - std::unique_lock lock(incoming_mail_notify_mutex_); - this->interthread().cv()->wait(lock); - // post empty handler to cause loop() to return and allow incoming mail to be handled - io_.post([]() {}); - } - })); + while (this->alive()) + { + std::unique_lock lock(incoming_mail_notify_mutex_); + this->interthread().cv()->wait(lock); + // post empty handler to cause loop() to return and allow incoming mail to be handled + io_.post([]() {}); + } + })); this->set_name(thread_name_); } @@ -264,7 +274,8 @@ void basic_async_write(IOThreadImplementation* this_thread, boost::asio::async_write( this_thread->mutable_socket(), boost::asio::buffer(io_msg->data()), // capture io_msg in callback to ensure write buffer exists until async_write is done - [this_thread, io_msg](const boost::system::error_code& ec, std::size_t bytes_transferred) { + [this_thread, io_msg](const boost::system::error_code& ec, std::size_t bytes_transferred) + { if (!ec && bytes_transferred > 0) { this_thread->handle_write_success(bytes_transferred); diff --git a/src/middleware/io/detail/io_transporters.h b/src/middleware/io/detail/io_transporters.h index 9a03aa2e..4d4e3b59 100644 --- a/src/middleware/io/detail/io_transporters.h +++ b/src/middleware/io/detail/io_transporters.h @@ -26,7 +26,6 @@ #include "goby/exception.h" #include "goby/middleware/transport/detail/static_group_names.h" -#include "goby/zeromq/transport/detail/tags.h" namespace goby { @@ -52,51 +51,51 @@ enum class Direction }; // Direction template parameter required to avoid diamond inheritance problem with IOThread -template struct IOTransporterByLayer +template +struct IOTransporterByLayer { }; -template -struct IOTransporterByLayer +template +struct IOTransporterByLayer { - protected: using Transporter = InterThreadTransporter; Transporter& io_transporter() { return static_cast(this)->interthread(); } }; -template -struct IOTransporterByLayer +template +struct IOTransporterByLayer { - protected: - using Transporter = InterProcessForwarder; + using Transporter = InterProcessForwarder; Transporter& io_transporter() { return static_cast(this)->interprocess(); } }; template + typename ImplementationTag, bool use_indexed_group> struct IOPublishTransporter { }; -template -struct IOPublishTransporter - : IOTransporterByLayer +template +struct IOPublishTransporter + : IOTransporterByLayer { IOPublishTransporter(int index) {} - template < - typename Data, - int scheme = transporter_scheme< - Data, typename IOTransporterByLayer::Transporter>()> + template ::Transporter>()> void publish_in(std::shared_ptr data) { this->io_transporter().template publish(data); } }; -template -struct IOPublishTransporter - : IOTransporterByLayer +template +struct IOPublishTransporter + : IOTransporterByLayer { IOPublishTransporter(int index) @@ -106,10 +105,10 @@ struct IOPublishTransporter throw(goby::Exception("Index must be less than or equal to: " + std::to_string(Group::maximum_valid_group))); } - template < - typename Data, - int scheme = transporter_scheme< - Data, typename IOTransporterByLayer::Transporter>()> + template ::Transporter>()> void publish_in(std::shared_ptr data) { this->io_transporter().template publish_dynamic(data, in_group_); @@ -120,39 +119,42 @@ struct IOPublishTransporter }; template + typename ImplementationTag, bool use_indexed_group> struct IOSubscribeTransporter { }; -template -struct IOSubscribeTransporter - : IOTransporterByLayer +template +struct IOSubscribeTransporter + : IOTransporterByLayer { IOSubscribeTransporter(int index) {} template ::Transporter>(), + ImplementationTag, layer>::Transporter>(), Necessity necessity = Necessity::OPTIONAL> void subscribe_out(std::function)> f) { this->io_transporter().template subscribe(f); } - template ::Transporter>()> + template ::Transporter>()> void unsubscribe_out() { this->io_transporter().template unsubscribe(); } }; -template -struct IOSubscribeTransporter - : IOTransporterByLayer +template +struct IOSubscribeTransporter + : IOTransporterByLayer { IOSubscribeTransporter(int index) : out_group_(std::string(line_out_group), @@ -166,16 +168,17 @@ struct IOSubscribeTransporter template ::Transporter>(), + ImplementationTag, layer>::Transporter>(), Necessity necessity = Necessity::OPTIONAL> void subscribe_out(std::function)> f) { this->io_transporter().template subscribe_dynamic(f, out_group_); } - template ::Transporter>()> + template ::Transporter>()> void unsubscribe_out() { this->io_transporter().template unsubscribe_dynamic(out_group_); diff --git a/src/middleware/io/groups.h b/src/middleware/io/groups.h index d9ba511e..c2a33d7e 100644 --- a/src/middleware/io/groups.h +++ b/src/middleware/io/groups.h @@ -37,8 +37,8 @@ namespace io namespace groups { -constexpr goby::middleware::Group mavlink_raw_in{"goby::apps::zeromq::mavlink_raw_in"}; -constexpr goby::middleware::Group mavlink_raw_out{"goby::apps::zeromq::mavlink_raw_out"}; +constexpr goby::middleware::Group mavlink_raw_in{"goby::middleware::io::mavlink_raw_in"}; +constexpr goby::middleware::Group mavlink_raw_out{"goby::middleware::io::mavlink_raw_out"}; constexpr goby::middleware::Group nmea0183_in{"goby::middleware::io::nmea0183_in"}; constexpr goby::middleware::Group nmea0183_out{"goby::middleware::io::nmea0183_out"}; diff --git a/src/middleware/transport/interthread.h b/src/middleware/transport/interthread.h index 5f673b7b..f7ca58c0 100644 --- a/src/middleware/transport/interthread.h +++ b/src/middleware/transport/interthread.h @@ -64,6 +64,8 @@ class InterThreadTransporter }; public: + using implementation_tag = void; + InterThreadTransporter() : data_mutex_(std::make_shared()) {} virtual ~InterThreadTransporter() diff --git a/src/middleware/transport/intervehicle.h b/src/middleware/transport/intervehicle.h index 092fde71..f16da3fe 100644 --- a/src/middleware/transport/intervehicle.h +++ b/src/middleware/transport/intervehicle.h @@ -578,6 +578,8 @@ class InterVehicleForwarder : public InterVehicleTransporterBase, InnerTransporter> { public: + using implementation_tag = typename InnerTransporter::implementation_tag; + using Base = InterVehicleTransporterBase, InnerTransporter>; @@ -645,9 +647,9 @@ class InterVehiclePortal { // Derive the ImplementationTag from InnerTransporter so that the modem driver thread // uses the same InterProcessForwarder prefix as the portal's inner transporter. - using ImplementationTag = typename InnerTransporter::implementation_tag; - using modem_id_type = - typename goby::middleware::intervehicle::ModemDriverThread::modem_id_type; + using implementation_tag = typename InnerTransporter::implementation_tag; + using modem_id_type = typename goby::middleware::intervehicle::ModemDriverThread< + implementation_tag>::modem_id_type; public: using Base = @@ -787,7 +789,7 @@ class InterVehiclePortal try { data.modem_driver_thread.reset( - new intervehicle::ModemDriverThread(*link)); + new intervehicle::ModemDriverThread(*link)); data.modem_driver_thread->run(data.driver_thread_alive); } catch (std::exception& e) @@ -902,7 +904,7 @@ class InterVehiclePortal struct ModemDriverData { std::unique_ptr underlying_thread; - std::unique_ptr> modem_driver_thread; + std::unique_ptr> modem_driver_thread; std::atomic driver_thread_alive{true}; }; std::vector> modem_drivers_; diff --git a/src/middleware/transport/intervehicle/driver_thread.cpp b/src/middleware/transport/intervehicle/driver_thread.cpp index 4249322b..1cacea12 100644 --- a/src/middleware/transport/intervehicle/driver_thread.cpp +++ b/src/middleware/transport/intervehicle/driver_thread.cpp @@ -39,12 +39,12 @@ #include "goby/middleware/marshalling/protobuf.h" -#include "goby/acomms/acomms_constants.h" // for BROADCAS... -#include "goby/acomms/bind.h" // for bind -#include "goby/acomms/modemdriver/benthos_atm900_driver.h" // for BenthosA... -#include "goby/acomms/modemdriver/iridium_driver.h" // for IridiumD... -#include "goby/acomms/modemdriver/iridium_shore_driver.h" // for IridiumS... -#include "goby/acomms/modemdriver/mm_driver.h" // for MMDriver +#include "goby/acomms/acomms_constants.h" // for BROADCAS... +#include "goby/acomms/bind.h" // for bind +#include "goby/acomms/modemdriver/benthos_atm900_driver.h" // for BenthosA... +#include "goby/acomms/modemdriver/iridium_driver.h" // for IridiumD... +#include "goby/acomms/modemdriver/iridium_shore_driver.h" // for IridiumS... +#include "goby/acomms/modemdriver/mm_driver.h" // for MMDriver #include "goby/acomms/modemdriver/store_server_driver.h" #include "goby/acomms/modemdriver/udp_driver.h" // for UDPDriver #include "goby/acomms/modemdriver/udp_multicast_driver.h" // for UDPMulti... @@ -59,14 +59,12 @@ #include "goby/util/debug_logger/logger_manipulators.h" // for operator<< #include "goby/util/debug_logger/term_color.h" // for Colors #ifdef ENABLE_JANUS_ACOMMS -#include "goby/acomms/modemdriver/janus_driver.h" // for JanusDriver... +#include "goby/acomms/modemdriver/janus_driver.h" // for JanusDriver... #endif #ifdef ENABLE_POPOTO_ACOMMS -#include "goby/acomms/modemdriver/popoto_driver.h" // for PopotoDr... +#include "goby/acomms/modemdriver/popoto_driver.h" // for PopotoDr... #endif #include "driver_thread.h" -#include "goby/zeromq/transport/detail/tags.h" -#include "goby/udpm/transport/detail/tags.h" using goby::glog; using namespace goby::util::logger; @@ -136,8 +134,7 @@ template goby::middleware::intervehicle::ModemDriverThread::ModemDriverThread( const intervehicle::protobuf::PortalConfig::LinkConfig& config) : goby::middleware::Thread>( + InterProcessForwarder>( config, 10 * boost::units::si::hertz), buffer_(this->cfg().modem_id()), mac_(this->cfg().modem_id()), @@ -149,24 +146,26 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDrive { goby::glog.add_group(glog_group_, util::Colors::blue); interthread_ = std::make_unique(); - interprocess_ = std::make_unique< - InterProcessForwarder>( - *interthread_); + interprocess_ = + std::make_unique>( + *interthread_); this->set_transporter(interprocess_.get()); interprocess_->template subscribe( [this](std::shared_ptr msg) { _buffer_message(std::move(msg)); }); - interprocess_->template subscribetemplate subscribe( - [this](const std::shared_ptr& subscription) - { _forward_subscription(*subscription); }); + [this](const std::shared_ptr& subscription) + { _forward_subscription(*subscription); }); - interprocess_->template subscribetemplate subscribe( - [this](const std::shared_ptr& subscription) - { _accept_subscription(*subscription); }); + [this](const std::shared_ptr& subscription) + { _accept_subscription(*subscription); }); if (this->cfg().driver().has_driver_name()) { @@ -228,17 +227,17 @@ goby::middleware::intervehicle::ModemDriverThread::ModemDrive driver_ = std::make_unique(); break; - #ifdef ENABLE_POPOTO_ACOMMS +#ifdef ENABLE_POPOTO_ACOMMS case goby::acomms::protobuf::DRIVER_POPOTO: driver_ = std::make_unique(); break; - #endif +#endif - #ifdef ENABLE_JANUS_ACOMMS +#ifdef ENABLE_JANUS_ACOMMS case goby::acomms::protobuf::DRIVER_JANUS: driver_ = std::make_unique(); break; - #endif +#endif case goby::acomms::protobuf::DRIVER_NONE: case goby::acomms::protobuf::DRIVER_ABC_EXAMPLE_MODEM: case goby::acomms::protobuf::DRIVER_UFIELD_SIM_DRIVER: @@ -496,8 +495,8 @@ void goby::middleware::intervehicle::ModemDriverThread::_data template typename goby::middleware::intervehicle::ModemDriverThread::subbuffer_id_type -goby::middleware::intervehicle::ModemDriverThread::_create_buffer_id(unsigned dccl_id, - unsigned group) +goby::middleware::intervehicle::ModemDriverThread::_create_buffer_id( + unsigned dccl_id, unsigned group) { return "/group:" + std::to_string(group) + "/id:" + std::to_string(dccl_id) + "/"; } @@ -593,8 +592,9 @@ void goby::middleware::intervehicle::ModemDriverThread::_acce } template -void goby::middleware::intervehicle::ModemDriverThread::_try_create_or_update_buffer( - modem_id_type dest_id, const subbuffer_id_type& buffer_id) +void goby::middleware::intervehicle::ModemDriverThread< + ImplementationTag>::_try_create_or_update_buffer(modem_id_type dest_id, + const subbuffer_id_type& buffer_id) { auto pub_it_pair = publisher_buffer_cfg_.equal_range(buffer_id); auto& dest_subscriber_buffer_cfg = subscriber_buffer_cfg_[dest_id]; @@ -817,8 +817,8 @@ void goby::middleware::intervehicle::ModemDriverThread::_rece } template -void goby::middleware::intervehicle::ModemDriverThread::_publish_subscription_report( - const intervehicle::protobuf::Subscription& changed) +void goby::middleware::intervehicle::ModemDriverThread:: + _publish_subscription_report(const intervehicle::protobuf::Subscription& changed) { protobuf::SubscriptionReport report; report.set_link_modem_id(this->cfg().modem_id()); @@ -831,6 +831,9 @@ void goby::middleware::intervehicle::ModemDriverThread::_publ interprocess_->template publish(report); } +#include "goby/udpm/transport/detail/tags.h" +#include "goby/zeromq/transport/detail/tags.h" + // Explicit template instantiations for known implementation tags namespace goby::middleware::intervehicle { From 37b4ab38a20da9721cdde3106498bbf9b2d247fb Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 16:43:25 +1000 Subject: [PATCH 12/20] Rename tags and tag prefixes to align with Goby style --- src/middleware/application/simple_thread.h | 6 +++--- src/middleware/transport/intermodule.h | 6 +++--- src/middleware/transport/interprocess.h | 6 +++--- src/middleware/transport/intervehicle/driver_thread.cpp | 4 ++-- src/udpm/application/simple_thread.h | 4 ++-- src/udpm/transport/detail/tags.h | 8 ++++---- src/udpm/transport/intermodule.h | 4 ++-- src/udpm/transport/interprocess.h | 4 ++-- src/zeromq/application/simple_thread.h | 4 ++-- src/zeromq/transport/detail/tags.h | 8 ++++---- src/zeromq/transport/intermodule.h | 4 ++-- src/zeromq/transport/interprocess.h | 6 +++--- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/middleware/application/simple_thread.h b/src/middleware/application/simple_thread.h index 71dd6c66..bf5e3a82 100644 --- a/src/middleware/application/simple_thread.h +++ b/src/middleware/application/simple_thread.h @@ -38,7 +38,7 @@ namespace middleware /// \brief Implements Thread for a three layer middleware setup ([ intervehicle [ interprocess [ interthread ] ] ]) based around InterVehicleForwarder. /// /// \tparam Config Configuration type -/// \tparam ImplementationTag Tag type selecting the interprocess implementation (e.g. zeromq::detail::ZeromqInterprocessTag). Defaults to void (deprecated; use zeromq::SimpleThread or udpm::SimpleThread instead). +/// \tparam ImplementationTag Tag type selecting the interprocess implementation (e.g. zeromq::detail::InterProcessTag). Defaults to void (deprecated; use zeromq::SimpleThread or udpm::SimpleThread instead). /// /// Derive from this class to create standalone threads that can be launched and joined by MultiThreadApplication's launch_thread and join_thread methods. template @@ -122,10 +122,10 @@ namespace middleware /// \brief Deprecated 1-arg specialisation: use goby::zeromq::SimpleThread or goby::udpm::SimpleThread template class SimpleThread - : public SimpleThread + : public SimpleThread { public: - using Base = SimpleThread; + using Base = SimpleThread; [[deprecated("Use goby::zeromq::SimpleThread or goby::udpm::SimpleThread instead of " "goby::middleware::SimpleThread")]] diff --git a/src/middleware/transport/intermodule.h b/src/middleware/transport/intermodule.h index 91757991..69b12ceb 100644 --- a/src/middleware/transport/intermodule.h +++ b/src/middleware/transport/intermodule.h @@ -64,7 +64,7 @@ using InterModuleTransporterBase = /// /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node -/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqIntermoduleTag or detail::UdpmIntermoduleTag) +/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::InterModuleTag or detail::InterModuleTag) template class InterModuleForwarder; /// \brief Implements the forwarder concept for the intermodule layer (implementation) @@ -262,10 +262,10 @@ namespace middleware /// or udpm::InterModuleForwarder<> explicitly. template class InterModuleForwarder - : public InterModuleForwarder + : public InterModuleForwarder { public: - using Base = InterModuleForwarder; + using Base = InterModuleForwarder; [[deprecated("Use zeromq::InterModuleForwarder<> or udpm::InterModuleForwarder<> instead of " "middleware::InterModuleForwarder<>")]] diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index 5c94868e..fb08050b 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -301,7 +301,7 @@ class InterProcessTransporterBase /// /// The forwarder is intended to be used by inner nodes within the layer that do not connect directly to other nodes on that layer. For example, the main thread might instantiate a portal and then spawn several threads that instantiate forwarders. These auxiliary threads can then communicate on the interprocess layer as if they had a direct connection to other interprocess nodes. /// \tparam InnerTransporter The type of the inner transporter used to forward data to and from this node -/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::ZeromqInterprocessTag or detail::UdpmInterprocessTag) +/// \tparam ImplementationTag Distinguishes different implementations using different internal groups (e.g. detail::InterProcessTag or detail::InterProcessTag) template class InterProcessForwarder; /// \brief Implements the forwarder concept for the interprocess layer (implementation) @@ -797,10 +797,10 @@ namespace middleware /// or udpm::InterProcessForwarder<> explicitly. template class InterProcessForwarder - : public InterProcessForwarder + : public InterProcessForwarder { public: - using Base = InterProcessForwarder; + using Base = InterProcessForwarder; [[deprecated("Use zeromq::InterProcessForwarder<> or udpm::InterProcessForwarder<> instead of " "middleware::InterProcessForwarder<>")]] diff --git a/src/middleware/transport/intervehicle/driver_thread.cpp b/src/middleware/transport/intervehicle/driver_thread.cpp index 1cacea12..a3cea769 100644 --- a/src/middleware/transport/intervehicle/driver_thread.cpp +++ b/src/middleware/transport/intervehicle/driver_thread.cpp @@ -837,6 +837,6 @@ void goby::middleware::intervehicle::ModemDriverThread:: // Explicit template instantiations for known implementation tags namespace goby::middleware::intervehicle { -template class ModemDriverThread<::goby::zeromq::detail::ZeromqInterprocessTag>; -template class ModemDriverThread<::goby::udpm::detail::UdpmInterprocessTag>; +template class ModemDriverThread<::goby::zeromq::detail::InterProcessTag>; +template class ModemDriverThread<::goby::udpm::detail::InterProcessTag>; } // namespace goby::middleware::intervehicle diff --git a/src/udpm/application/simple_thread.h b/src/udpm/application/simple_thread.h index 5f8f5ec7..616a34c9 100644 --- a/src/udpm/application/simple_thread.h +++ b/src/udpm/application/simple_thread.h @@ -31,11 +31,11 @@ namespace goby { namespace udpm { -/// \brief UDPM-backed SimpleThread. Derives from middleware::SimpleThread using UdpmInterprocessTag. +/// \brief UDPM-backed SimpleThread. Derives from middleware::SimpleThread using InterProcessTag. /// /// \tparam Config Configuration type template -using SimpleThread = middleware::SimpleThread; +using SimpleThread = middleware::SimpleThread; } // namespace udpm } // namespace goby diff --git a/src/udpm/transport/detail/tags.h b/src/udpm/transport/detail/tags.h index 3cbf6edf..9b6b1d1e 100644 --- a/src/udpm/transport/detail/tags.h +++ b/src/udpm/transport/detail/tags.h @@ -32,15 +32,15 @@ namespace detail { /// \brief ImplementationTag for udpm interprocess transporters -struct UdpmInterprocessTag +struct InterProcessTag { - inline static constexpr const char prefix[] = "goby::middleware::interprocess::udpm"; + inline static constexpr const char prefix[] = "goby::udpm::interprocess"; }; /// \brief ImplementationTag for udpm intermodule transporters -struct UdpmIntermoduleTag +struct InterModuleTag { - inline static constexpr const char prefix[] = "goby::middleware::intermodule::udpm"; + inline static constexpr const char prefix[] = "goby::udpm::intermodule"; }; } // namespace detail diff --git a/src/udpm/transport/intermodule.h b/src/udpm/transport/intermodule.h index 8ad29154..fcf7fe47 100644 --- a/src/udpm/transport/intermodule.h +++ b/src/udpm/transport/intermodule.h @@ -36,11 +36,11 @@ namespace udpm template using InterModulePortal = InterProcessPortalImplementation; + detail::InterModuleTag>; template using InterModuleForwarder = - middleware::InterModuleForwarder; + middleware::InterModuleForwarder; } // namespace udpm } // namespace goby diff --git a/src/udpm/transport/interprocess.h b/src/udpm/transport/interprocess.h index 0fb36109..b59ea4de 100644 --- a/src/udpm/transport/interprocess.h +++ b/src/udpm/transport/interprocess.h @@ -544,11 +544,11 @@ class InterProcessPortalImplementation template using InterProcessPortal = InterProcessPortalImplementation; + detail::InterProcessTag>; template using InterProcessForwarder = - middleware::InterProcessForwarder; + middleware::InterProcessForwarder; } // namespace udpm } // namespace goby diff --git a/src/zeromq/application/simple_thread.h b/src/zeromq/application/simple_thread.h index 3ec0a3ee..817c4cf6 100644 --- a/src/zeromq/application/simple_thread.h +++ b/src/zeromq/application/simple_thread.h @@ -31,11 +31,11 @@ namespace goby { namespace zeromq { -/// \brief Zeromq-backed SimpleThread. Derives from middleware::SimpleThread using ZeromqInterprocessTag. +/// \brief Zeromq-backed SimpleThread. Derives from middleware::SimpleThread using InterProcessTag. /// /// \tparam Config Configuration type template -using SimpleThread = middleware::SimpleThread; +using SimpleThread = middleware::SimpleThread; } // namespace zeromq } // namespace goby diff --git a/src/zeromq/transport/detail/tags.h b/src/zeromq/transport/detail/tags.h index 388c1014..2a5453b3 100644 --- a/src/zeromq/transport/detail/tags.h +++ b/src/zeromq/transport/detail/tags.h @@ -32,15 +32,15 @@ namespace detail { /// \brief ImplementationTag for zeromq interprocess transporters -struct ZeromqInterprocessTag +struct InterProcessTag { - inline static constexpr const char prefix[] = "goby::middleware::interprocess::zeromq"; + inline static constexpr const char prefix[] = "goby::zeromq::interprocess"; }; /// \brief ImplementationTag for zeromq intermodule transporters -struct ZeromqIntermoduleTag +struct InterModuleTag { - inline static constexpr const char prefix[] = "goby::middleware::intermodule::zeromq"; + inline static constexpr const char prefix[] = "goby::zeromq::intermodule"; }; } // namespace detail diff --git a/src/zeromq/transport/intermodule.h b/src/zeromq/transport/intermodule.h index a2196f28..f47e71be 100644 --- a/src/zeromq/transport/intermodule.h +++ b/src/zeromq/transport/intermodule.h @@ -36,11 +36,11 @@ namespace zeromq template using InterModulePortal = InterProcessPortalImplementation; + detail::InterModuleTag>; template using InterModuleForwarder = - middleware::InterModuleForwarder; + middleware::InterModuleForwarder; } // namespace zeromq } // namespace goby diff --git a/src/zeromq/transport/interprocess.h b/src/zeromq/transport/interprocess.h index 8b36fb39..24191b7c 100644 --- a/src/zeromq/transport/interprocess.h +++ b/src/zeromq/transport/interprocess.h @@ -58,7 +58,7 @@ #include "goby/middleware/transport/subscriber.h" // for Subs... #include "goby/time/system_clock.h" // for Syst... #include "goby/util/debug_logger/flex_ostream.h" // for Flex... -#include "goby/zeromq/transport/detail/tags.h" // for ZeromqInterprocessTag +#include "goby/zeromq/transport/detail/tags.h" // for InterProcessTag #include "goby/util/debug_logger/flex_ostreambuf.h" // for lock #include "goby/zeromq/protobuf/interprocess_config.pb.h" // for Inte... #include "goby/zeromq/protobuf/interprocess_zeromq.pb.h" // for Inpr... @@ -478,11 +478,11 @@ class Manager template using InterProcessPortal = InterProcessPortalImplementation; + detail::InterProcessTag>; template using InterProcessForwarder = - middleware::InterProcessForwarder; + middleware::InterProcessForwarder; } // namespace zeromq } // namespace goby From d60dece07cc3c569cdf309bb043b57eb821484d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 07:00:08 +0000 Subject: [PATCH 13/20] Add attach_poller() to PollerInterface to poll additional transporters Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/025c0e4f-e8aa-46a0-8778-8e4bcde7eb36 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/middleware/transport/interface.h | 31 +++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/middleware/transport/interface.h b/src/middleware/transport/interface.h index d6362dc5..c84b9664 100644 --- a/src/middleware/transport/interface.h +++ b/src/middleware/transport/interface.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "goby/middleware/group.h" #include "goby/middleware/marshalling/interface.h" @@ -166,6 +167,20 @@ class PollerInterface /// \return pointer to the condition variable used for polling std::shared_ptr cv() { return cv_; } + /// \brief Attach another PollerInterface to this one so that its _transporter_poll() is also called during _poll_all() + /// + /// The attached PollerInterface must share the same poll_mutex() and cv() as this PollerInterface. + /// \param poller Pointer to the PollerInterface to attach + /// \throws goby::Exception if poll_mutex() or cv() of the attached poller do not match those of this poller + void attach_poller(PollerInterface* poller) + { + if (poller->poll_mutex() != poll_mutex_ || poller->cv() != cv_) + throw(goby::Exception( + "Cannot attach a PollerInterface whose poll_mutex() or cv() differ from this " + "PollerInterface")); + attached_pollers_.push_back(poller); + } + protected: PollerInterface(std::shared_ptr poll_mutex, std::shared_ptr cv) @@ -186,6 +201,8 @@ class PollerInterface std::shared_ptr poll_mutex_; // signaled when there's no data for this thread to read during _poll() std::shared_ptr cv_; + // additional PollerInterface instances to poll alongside this one + std::vector attached_pollers_; }; /// \brief Used to tag subscriptions based on their necessity (e.g. required for correct functioning, or optional) @@ -366,7 +383,15 @@ int goby::middleware::PollerInterface::_poll_all( new std::unique_lock(*poll_mutex_)); // std::cout << std::this_thread::get_id() << " _poll_all locking: " << poll_mutex_.get() << std::endl; - int poll_items = _transporter_poll(lock); + // poll this poller and all attached pollers + auto poll_all_transporters = [this](std::unique_ptr>& lock) { + int items = _transporter_poll(lock); + for (PollerInterface* attached : attached_pollers_) + items += attached->_transporter_poll(lock); + return items; + }; + + int poll_items = poll_all_transporters(lock); while (poll_items == 0) { if (!lock) @@ -376,7 +401,7 @@ int goby::middleware::PollerInterface::_poll_all( if (timeout == Clock::time_point::max()) { cv_->wait(*lock); // wait_until doesn't work well with time_point::max() - poll_items = _transporter_poll(lock); + poll_items = poll_all_transporters(lock); // TODO: fix this message now that zeromq::InterProcessPortal can have // a condition_variable trigger for REQUEST_HOLD_STATE @@ -388,7 +413,7 @@ int goby::middleware::PollerInterface::_poll_all( else { if (cv_->wait_until(*lock, timeout) == std::cv_status::no_timeout) - poll_items = _transporter_poll(lock); + poll_items = poll_all_transporters(lock); else return poll_items; } From 924996ddaa7e116056bbd9492da6bd8d2f68d559 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 07:03:46 +0000 Subject: [PATCH 14/20] Address code review: add null check and ownership comment for attach_poller() Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/025c0e4f-e8aa-46a0-8778-8e4bcde7eb36 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/middleware/transport/interface.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/middleware/transport/interface.h b/src/middleware/transport/interface.h index c84b9664..21329e7f 100644 --- a/src/middleware/transport/interface.h +++ b/src/middleware/transport/interface.h @@ -170,10 +170,13 @@ class PollerInterface /// \brief Attach another PollerInterface to this one so that its _transporter_poll() is also called during _poll_all() /// /// The attached PollerInterface must share the same poll_mutex() and cv() as this PollerInterface. - /// \param poller Pointer to the PollerInterface to attach - /// \throws goby::Exception if poll_mutex() or cv() of the attached poller do not match those of this poller + /// The attached poller must remain valid (outlive or have the same lifetime as this instance). + /// \param poller Non-null pointer to the PollerInterface to attach; must not be null + /// \throws goby::Exception if poller is null or if poll_mutex() or cv() of the attached poller do not match those of this poller void attach_poller(PollerInterface* poller) { + if (!poller) + throw(goby::Exception("Cannot attach a null PollerInterface")); if (poller->poll_mutex() != poll_mutex_ || poller->cv() != cv_) throw(goby::Exception( "Cannot attach a PollerInterface whose poll_mutex() or cv() differ from this " @@ -201,7 +204,8 @@ class PollerInterface std::shared_ptr poll_mutex_; // signaled when there's no data for this thread to read during _poll() std::shared_ptr cv_; - // additional PollerInterface instances to poll alongside this one + // non-owning pointers to additional PollerInterface instances to poll alongside this one; + // attached pollers must remain valid for the lifetime of this instance std::vector attached_pollers_; }; From 14c22bfb31112bca512ae1f9b5d4c1399c22e4ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 07:41:45 +0000 Subject: [PATCH 15/20] Add poller_attach_test: ZMQ + UDPM portals with attach_poller on each fork Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/dcd7e94d-36f4-414c-bf67-833374e174fe Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> --- src/test/zeromq/CMakeLists.txt | 1 + .../zeromq/poller_attach_test/CMakeLists.txt | 7 + src/test/zeromq/poller_attach_test/test.cpp | 317 ++++++++++++++++++ src/test/zeromq/poller_attach_test/test.proto | 8 + 4 files changed, 333 insertions(+) create mode 100644 src/test/zeromq/poller_attach_test/CMakeLists.txt create mode 100644 src/test/zeromq/poller_attach_test/test.cpp create mode 100644 src/test/zeromq/poller_attach_test/test.proto diff --git a/src/test/zeromq/CMakeLists.txt b/src/test/zeromq/CMakeLists.txt index 3d7d2190..6139fd5c 100644 --- a/src/test/zeromq/CMakeLists.txt +++ b/src/test/zeromq/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory(multi_thread_app1) add_subdirectory(multi_thread_app2) add_subdirectory(zeromq_intermodule_and_interprocess) +add_subdirectory(poller_attach_test) diff --git a/src/test/zeromq/poller_attach_test/CMakeLists.txt b/src/test/zeromq/poller_attach_test/CMakeLists.txt new file mode 100644 index 00000000..e3f670e7 --- /dev/null +++ b/src/test/zeromq/poller_attach_test/CMakeLists.txt @@ -0,0 +1,7 @@ +protobuf_generate_cpp_dccl(PROTO_SRCS PROTO_HDRS test.proto) + +add_executable(goby_test_poller_attach_test test.cpp ${PROTO_SRCS} ${PROTO_HDRS}) +target_link_libraries(goby_test_poller_attach_test goby goby_zeromq) + +add_test(goby_test_poller_attach_test ${goby_BIN_DIR}/goby_test_poller_attach_test) +set_tests_properties(goby_test_poller_attach_test PROPERTIES TIMEOUT 120) diff --git a/src/test/zeromq/poller_attach_test/test.cpp b/src/test/zeromq/poller_attach_test/test.cpp new file mode 100644 index 00000000..77132a47 --- /dev/null +++ b/src/test/zeromq/poller_attach_test/test.cpp @@ -0,0 +1,317 @@ +// Copyright 2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Copilot <198982749+Copilot@users.noreply.github.com> +// +// +// This file is part of the Goby Underwater Autonomy Project Binaries +// ("The Goby Binaries"). +// +// The Goby Binaries are free software: you can redistribute them and/or modify +// them under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// The Goby Binaries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Goby. If not, see . + +// Tests PollerInterface::attach_poller() by running one ZeroMQ portal and one +// UDPM portal on each fork, with the UDPM portal's poller attached to the ZeroMQ +// portal's poller. Only the ZeroMQ portal's poll() is called on each fork; +// the UDPM portal is polled automatically via the attach mechanism. +// +// Process layout: +// MANAGER_ROUTER – parent process: runs the ZMQ manager/router and waits for +// both child processes to complete. +// FORK1 – first child: ZeroMQ portal A + UDPM portal B. +// B is attached to A. Only A.poll() is ever called. +// A+B publish to FORK2 and subscribe to receive from FORK2. +// FORK2 – second child: ZeroMQ portal C + UDPM portal D. +// D is attached to C. Only C.poll() is ever called. +// C+D publish to FORK1 and subscribe to receive from FORK1. +// +// Message flow: +// A (ZMQ) ──► C (ZMQ) via group_a +// B (UDPM) ──► D (UDPM) via group_b +// C (ZMQ) ──► A (ZMQ) via group_c +// D (UDPM) ──► B (UDPM) via group_d + +#include +#include + +#include +#include +#include +#include + +#include "goby/middleware/marshalling/protobuf.h" +#include "goby/middleware/transport/null.h" +#include "goby/udpm/transport/interprocess.h" +#include "goby/util/debug_logger.h" +#include "goby/zeromq/transport/interprocess.h" + +#include "goby/test/zeromq/poller_attach_test/test.pb.h" + +#include + +using namespace goby::test::zeromq::poller_attach_test::protobuf; +using goby::glog; +using namespace goby::util::logger; + +// --------------------------------------------------------------------------- +// Groups +// --------------------------------------------------------------------------- +constexpr goby::middleware::Group group_a{"group_a"}; // A(ZMQ) → C(ZMQ) +constexpr goby::middleware::Group group_b{"group_b"}; // B(UDPM) → D(UDPM) +constexpr goby::middleware::Group group_c{"group_c"}; // C(ZMQ) → A(ZMQ) +constexpr goby::middleware::Group group_d{"group_d"}; // D(UDPM) → B(UDPM) + +static const int n_msgs = 5; + +// --------------------------------------------------------------------------- +// FORK1: ZMQ portal A + UDPM portal B, B attached to A, poll only A +// --------------------------------------------------------------------------- +void run_fork1(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, + const goby::udpm::protobuf::InterProcessPortalConfig& udpm_cfg) +{ + // Shared NullTransporter gives A and B the same poll_mutex and cv, which + // is required for attach_poller(). + goby::middleware::NullTransporter null_inner; + + goby::zeromq::InterProcessPortal portal_a(null_inner, + zmq_cfg); + goby::udpm::InterProcessPortal portal_b(null_inner, + udpm_cfg); + + // Attach B to A: portal_a.poll() will also call _transporter_poll() on B. + portal_a.attach_poller(&portal_b); + + std::atomic recv_a{0}; // messages A receives from C via ZMQ + std::atomic recv_b{0}; // messages B receives from D via UDPM + + portal_a.subscribe( + [&](const Sample& s) + { + glog.is(DEBUG1) && glog << "FORK1/A received from C (ZMQ): " << s.ShortDebugString() + << std::endl; + ++recv_a; + }); + + portal_b.subscribe( + [&](const Sample& s) + { + glog.is(DEBUG1) && glog << "FORK1/B received from D (UDPM): " << s.ShortDebugString() + << std::endl; + ++recv_b; + }); + + // Signal ZMQ manager that this client is ready. + portal_a.ready(); + + // Wait for ZMQ hold to be released (polls A, which also polls attached B). + while (portal_a.hold_state()) portal_a.poll(std::chrono::milliseconds(100)); + + // Extra settling time for UDPM multicast join. + sleep(1); + + // Publish and poll interleaved so that we both send and receive on the + // single polling thread. + for (int i = 0; i < n_msgs; ++i) + { + Sample s; + s.set_a(i); + portal_a.publish(s); // ZMQ → fork2's C + portal_b.publish(s); // UDPM → fork2's D + + // Poll A (also polls attached B). + portal_a.poll(std::chrono::milliseconds(200)); + } + + // Drain any remaining incoming messages. + auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(30); + while (recv_a < n_msgs || recv_b < n_msgs) + { + // Only A.poll() is called — B is polled automatically via attach. + portal_a.poll(std::chrono::seconds(1)); + if (std::chrono::system_clock::now() > timeout) + glog.is(DIE) && glog << "FORK1 timed out: recv_a=" << recv_a << " recv_b=" << recv_b + << " (expected " << n_msgs << " each)" << std::endl; + } + + glog.is(VERBOSE) && + glog << "FORK1 done: recv_a=" << recv_a << " recv_b=" << recv_b << std::endl; + assert(recv_a == n_msgs); + assert(recv_b == n_msgs); +} + +// --------------------------------------------------------------------------- +// FORK2: ZMQ portal C + UDPM portal D, D attached to C, poll only C +// --------------------------------------------------------------------------- +void run_fork2(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, + const goby::udpm::protobuf::InterProcessPortalConfig& udpm_cfg) +{ + goby::middleware::NullTransporter null_inner; + + goby::zeromq::InterProcessPortal portal_c(null_inner, + zmq_cfg); + goby::udpm::InterProcessPortal portal_d(null_inner, + udpm_cfg); + + // Attach D to C: portal_c.poll() will also call _transporter_poll() on D. + portal_c.attach_poller(&portal_d); + + std::atomic recv_c{0}; // messages C receives from A via ZMQ + std::atomic recv_d{0}; // messages D receives from B via UDPM + + portal_c.subscribe( + [&](const Sample& s) + { + glog.is(DEBUG1) && glog << "FORK2/C received from A (ZMQ): " << s.ShortDebugString() + << std::endl; + ++recv_c; + }); + + portal_d.subscribe( + [&](const Sample& s) + { + glog.is(DEBUG1) && glog << "FORK2/D received from B (UDPM): " << s.ShortDebugString() + << std::endl; + ++recv_d; + }); + + portal_c.ready(); + + while (portal_c.hold_state()) portal_c.poll(std::chrono::milliseconds(100)); + + sleep(1); + + for (int i = 0; i < n_msgs; ++i) + { + Sample s; + s.set_a(i); + portal_c.publish(s); // ZMQ → fork1's A + portal_d.publish(s); // UDPM → fork1's B + + // Poll C (also polls attached D). + portal_c.poll(std::chrono::milliseconds(200)); + } + + auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(30); + while (recv_c < n_msgs || recv_d < n_msgs) + { + // Only C.poll() is called — D is polled automatically via attach. + portal_c.poll(std::chrono::seconds(1)); + if (std::chrono::system_clock::now() > timeout) + glog.is(DIE) && glog << "FORK2 timed out: recv_c=" << recv_c << " recv_d=" << recv_d + << " (expected " << n_msgs << " each)" << std::endl; + } + + glog.is(VERBOSE) && + glog << "FORK2 done: recv_c=" << recv_c << " recv_d=" << recv_d << std::endl; + assert(recv_c == n_msgs); + assert(recv_d == n_msgs); +} + +// --------------------------------------------------------------------------- +// main +// --------------------------------------------------------------------------- +int main(int /*argc*/, char* argv[]) +{ + // ---- ZMQ configuration ---- + goby::zeromq::protobuf::InterProcessPortalConfig zmq_cfg; + zmq_cfg.set_platform("poller_attach_test"); + zmq_cfg.set_transport(goby::zeromq::protobuf::InterProcessPortalConfig::TCP); + zmq_cfg.set_ip_address("127.0.0.1"); + zmq_cfg.set_tcp_port(54330); + + // ---- UDPM configuration (different address/port from defaults) ---- + goby::udpm::protobuf::InterProcessPortalConfig udpm_cfg; + udpm_cfg.set_multicast_address("239.142.0.4"); + udpm_cfg.set_multicast_port(11147); + + // ---- Fork two child processes ---- + enum Role + { + MANAGER_ROUTER, + FORK1, + FORK2 + }; + Role role = MANAGER_ROUTER; + + if (fork() == 0) + role = FORK1; + else if (fork() == 0) + role = FORK2; + + // ---- Set up logging ---- + const char* role_str = + (role == MANAGER_ROUTER) ? "manager_router" : (role == FORK1) ? "fork1" : "fork2"; + + std::string log_path = + std::string("/tmp/goby_test_poller_attach_") + role_str + ".log"; + std::ofstream log_file(log_path); + goby::glog.add_stream(goby::util::logger::DEBUG3, &log_file); + goby::glog.set_name(std::string(argv[0]) + "_" + role_str); + goby::glog.set_lock_action(goby::util::logger_lock::lock); + + switch (role) + { + case MANAGER_ROUTER: + { + // Run the ZMQ manager and router; hold until both clients connect. + auto manager_context = std::make_unique(1); + auto router_context = std::make_unique(10); + + goby::zeromq::protobuf::InterProcessManagerHold hold; + hold.add_required_client("fork1"); + hold.add_required_client("fork2"); + + goby::zeromq::Router router(*router_context, zmq_cfg); + std::thread router_thread([&] { router.run(); }); + + goby::zeromq::Manager manager(*manager_context, zmq_cfg, router, hold); + std::thread manager_thread([&] { manager.run(); }); + + // Wait for both child processes. + int wstatus; + for (int i = 0; i < 2; ++i) + { + wait(&wstatus); + if (wstatus != 0) + exit(EXIT_FAILURE); + } + + // Resetting contexts causes manager/router threads to return. + router_context.reset(); + manager_context.reset(); + router_thread.join(); + manager_thread.join(); + break; + } + + case FORK1: + { + auto fork1_zmq_cfg = zmq_cfg; + fork1_zmq_cfg.set_client_name("fork1"); + run_fork1(fork1_zmq_cfg, udpm_cfg); + break; + } + + case FORK2: + { + auto fork2_zmq_cfg = zmq_cfg; + fork2_zmq_cfg.set_client_name("fork2"); + run_fork2(fork2_zmq_cfg, udpm_cfg); + break; + } + } + + glog.is(VERBOSE) && glog << role_str << ": all tests passed" << std::endl; + std::cout << role_str << ": all tests passed" << std::endl; +} diff --git a/src/test/zeromq/poller_attach_test/test.proto b/src/test/zeromq/poller_attach_test/test.proto new file mode 100644 index 00000000..17cfbe66 --- /dev/null +++ b/src/test/zeromq/poller_attach_test/test.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; + +package goby.test.zeromq.poller_attach_test.protobuf; + +message Sample +{ + optional int32 a = 1; +} From 0dc3e2848d0757a4d4cef2ce032b74f3755fb9f9 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 18:41:30 +1000 Subject: [PATCH 16/20] Minor name changes --- src/middleware/transport/interface.h | 29 +++++++++---------- .../zeromq/poller_attach_test/CMakeLists.txt | 8 ++--- src/test/zeromq/poller_attach_test/test.cpp | 24 +++++++-------- src/zeromq/transport/interprocess.cpp | 11 +++---- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/middleware/transport/interface.h b/src/middleware/transport/interface.h index 21329e7f..83df0f24 100644 --- a/src/middleware/transport/interface.h +++ b/src/middleware/transport/interface.h @@ -173,14 +173,15 @@ class PollerInterface /// The attached poller must remain valid (outlive or have the same lifetime as this instance). /// \param poller Non-null pointer to the PollerInterface to attach; must not be null /// \throws goby::Exception if poller is null or if poll_mutex() or cv() of the attached poller do not match those of this poller - void attach_poller(PollerInterface* poller) + void attach(PollerInterface* poller) { if (!poller) throw(goby::Exception("Cannot attach a null PollerInterface")); - if (poller->poll_mutex() != poll_mutex_ || poller->cv() != cv_) - throw(goby::Exception( - "Cannot attach a PollerInterface whose poll_mutex() or cv() differ from this " - "PollerInterface")); + + if (poller->cv() != cv() || poller->poll_mutex() != poll_mutex()) + throw(goby::Exception("Cannot attach PollerInterface with a different cv() and/or " + "poll_mutex(). Make sure the PollerInterface you are trying to " + "attach has the same innermost PollerInterface")); attached_pollers_.push_back(poller); } @@ -387,15 +388,13 @@ int goby::middleware::PollerInterface::_poll_all( new std::unique_lock(*poll_mutex_)); // std::cout << std::this_thread::get_id() << " _poll_all locking: " << poll_mutex_.get() << std::endl; - // poll this poller and all attached pollers - auto poll_all_transporters = [this](std::unique_ptr>& lock) { - int items = _transporter_poll(lock); - for (PollerInterface* attached : attached_pollers_) - items += attached->_transporter_poll(lock); - return items; + auto poll_all_once = [this, &lock]() + { + int poll_items = _transporter_poll(lock); + for (auto* poller : attached_pollers_) poll_items += poller->_transporter_poll(lock); + return poll_items; }; - - int poll_items = poll_all_transporters(lock); + int poll_items = poll_all_once(); while (poll_items == 0) { if (!lock) @@ -405,7 +404,7 @@ int goby::middleware::PollerInterface::_poll_all( if (timeout == Clock::time_point::max()) { cv_->wait(*lock); // wait_until doesn't work well with time_point::max() - poll_items = poll_all_transporters(lock); + poll_items = poll_all_once(); // TODO: fix this message now that zeromq::InterProcessPortal can have // a condition_variable trigger for REQUEST_HOLD_STATE @@ -417,7 +416,7 @@ int goby::middleware::PollerInterface::_poll_all( else { if (cv_->wait_until(*lock, timeout) == std::cv_status::no_timeout) - poll_items = poll_all_transporters(lock); + poll_items = poll_all_once(); else return poll_items; } diff --git a/src/test/zeromq/poller_attach_test/CMakeLists.txt b/src/test/zeromq/poller_attach_test/CMakeLists.txt index e3f670e7..1f8ede34 100644 --- a/src/test/zeromq/poller_attach_test/CMakeLists.txt +++ b/src/test/zeromq/poller_attach_test/CMakeLists.txt @@ -1,7 +1,7 @@ protobuf_generate_cpp_dccl(PROTO_SRCS PROTO_HDRS test.proto) -add_executable(goby_test_poller_attach_test test.cpp ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(goby_test_poller_attach_test goby goby_zeromq) +add_executable(goby_test_poller_attach test.cpp ${PROTO_SRCS} ${PROTO_HDRS}) +target_link_libraries(goby_test_poller_attach goby goby_zeromq) -add_test(goby_test_poller_attach_test ${goby_BIN_DIR}/goby_test_poller_attach_test) -set_tests_properties(goby_test_poller_attach_test PROPERTIES TIMEOUT 120) +add_test(goby_test_poller_attach ${goby_BIN_DIR}/goby_test_poller_attach) +set_tests_properties(goby_test_poller_attach PROPERTIES TIMEOUT 120) diff --git a/src/test/zeromq/poller_attach_test/test.cpp b/src/test/zeromq/poller_attach_test/test.cpp index 77132a47..3cc96bb6 100644 --- a/src/test/zeromq/poller_attach_test/test.cpp +++ b/src/test/zeromq/poller_attach_test/test.cpp @@ -21,7 +21,7 @@ // You should have received a copy of the GNU General Public License // along with Goby. If not, see . -// Tests PollerInterface::attach_poller() by running one ZeroMQ portal and one +// Tests PollerInterface::attach() by running one ZeroMQ portal and one // UDPM portal on each fork, with the UDPM portal's poller attached to the ZeroMQ // portal's poller. Only the ZeroMQ portal's poll() is called on each fork; // the UDPM portal is polled automatically via the attach mechanism. @@ -81,7 +81,7 @@ void run_fork1(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, const goby::udpm::protobuf::InterProcessPortalConfig& udpm_cfg) { // Shared NullTransporter gives A and B the same poll_mutex and cv, which - // is required for attach_poller(). + // is required for attach(). goby::middleware::NullTransporter null_inner; goby::zeromq::InterProcessPortal portal_a(null_inner, @@ -90,7 +90,7 @@ void run_fork1(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, udpm_cfg); // Attach B to A: portal_a.poll() will also call _transporter_poll() on B. - portal_a.attach_poller(&portal_b); + portal_a.attach(&portal_b); std::atomic recv_a{0}; // messages A receives from C via ZMQ std::atomic recv_b{0}; // messages B receives from D via UDPM @@ -144,8 +144,8 @@ void run_fork1(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, << " (expected " << n_msgs << " each)" << std::endl; } - glog.is(VERBOSE) && - glog << "FORK1 done: recv_a=" << recv_a << " recv_b=" << recv_b << std::endl; + glog.is(VERBOSE) && glog << "FORK1 done: recv_a=" << recv_a << " recv_b=" << recv_b + << std::endl; assert(recv_a == n_msgs); assert(recv_b == n_msgs); } @@ -164,7 +164,7 @@ void run_fork2(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, udpm_cfg); // Attach D to C: portal_c.poll() will also call _transporter_poll() on D. - portal_c.attach_poller(&portal_d); + portal_c.attach(&portal_d); std::atomic recv_c{0}; // messages C receives from A via ZMQ std::atomic recv_d{0}; // messages D receives from B via UDPM @@ -212,8 +212,8 @@ void run_fork2(const goby::zeromq::protobuf::InterProcessPortalConfig& zmq_cfg, << " (expected " << n_msgs << " each)" << std::endl; } - glog.is(VERBOSE) && - glog << "FORK2 done: recv_c=" << recv_c << " recv_d=" << recv_d << std::endl; + glog.is(VERBOSE) && glog << "FORK2 done: recv_c=" << recv_c << " recv_d=" << recv_d + << std::endl; assert(recv_c == n_msgs); assert(recv_d == n_msgs); } @@ -250,11 +250,11 @@ int main(int /*argc*/, char* argv[]) role = FORK2; // ---- Set up logging ---- - const char* role_str = - (role == MANAGER_ROUTER) ? "manager_router" : (role == FORK1) ? "fork1" : "fork2"; + const char* role_str = (role == MANAGER_ROUTER) ? "manager_router" + : (role == FORK1) ? "fork1" + : "fork2"; - std::string log_path = - std::string("/tmp/goby_test_poller_attach_") + role_str + ".log"; + std::string log_path = std::string("/tmp/goby_test_poller_attach_") + role_str + ".log"; std::ofstream log_file(log_path); goby::glog.add_stream(goby::util::logger::DEBUG3, &log_file); goby::glog.set_name(std::string(argv[0]) + "_" + role_str); diff --git a/src/zeromq/transport/interprocess.cpp b/src/zeromq/transport/interprocess.cpp index 252d53ff..6be730c2 100644 --- a/src/zeromq/transport/interprocess.cpp +++ b/src/zeromq/transport/interprocess.cpp @@ -44,12 +44,10 @@ #define GOBY_TSAN_ACQUIRE(p) __tsan_acquire(p) #else #define GOBY_TSAN_RELEASE(p) \ - do \ - { \ + do { \ } while (0) #define GOBY_TSAN_ACQUIRE(p) \ - do \ - { \ + do { \ } while (0) #endif @@ -113,8 +111,6 @@ void goby::zeromq::setup_socket(zmq::socket_t& socket, const protobuf::Socket& c endpoint = "tcp://" + (bind ? std::string("*") : formatted_address) + ":" + std::to_string(cfg.ethernet_port()); - std::cout << cfg.ShortDebugString() << ": " << endpoint << std::endl; - break; } @@ -530,7 +526,8 @@ unsigned goby::zeromq::Router::last_port(zmq::socket_t& socket) constexpr size_t last_endpoint_size = 100; size_t zmq_last_endpoint_size = last_endpoint_size; char last_endpoint[last_endpoint_size]; - int rc = zmq_getsockopt((void*)socket, ZMQ_LAST_ENDPOINT, &last_endpoint, &zmq_last_endpoint_size); + int rc = + zmq_getsockopt((void*)socket, ZMQ_LAST_ENDPOINT, &last_endpoint, &zmq_last_endpoint_size); if (rc != 0) throw(std::runtime_error("Could not retrieve ZMQ_LAST_ENDPOINT")); From c9dd95482153dd349e44eecfa9f42765971e9d13 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 19:38:00 +1000 Subject: [PATCH 17/20] Update src/middleware/transport/interface.h Check that poller isn't attaching itself or the same poller more than once. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/middleware/transport/interface.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/middleware/transport/interface.h b/src/middleware/transport/interface.h index 83df0f24..d1849d92 100644 --- a/src/middleware/transport/interface.h +++ b/src/middleware/transport/interface.h @@ -171,17 +171,25 @@ class PollerInterface /// /// The attached PollerInterface must share the same poll_mutex() and cv() as this PollerInterface. /// The attached poller must remain valid (outlive or have the same lifetime as this instance). - /// \param poller Non-null pointer to the PollerInterface to attach; must not be null - /// \throws goby::Exception if poller is null or if poll_mutex() or cv() of the attached poller do not match those of this poller + /// \param poller Non-null pointer to the PollerInterface to attach; must not be null and must not be this + /// \throws goby::Exception if poller is null, is this instance, is already attached, or if poll_mutex() or cv() of the attached poller do not match those of this poller void attach(PollerInterface* poller) { if (!poller) throw(goby::Exception("Cannot attach a null PollerInterface")); + if (poller == this) + throw(goby::Exception("Cannot attach a PollerInterface to itself")); + if (poller->cv() != cv() || poller->poll_mutex() != poll_mutex()) throw(goby::Exception("Cannot attach PollerInterface with a different cv() and/or " "poll_mutex(). Make sure the PollerInterface you are trying to " "attach has the same innermost PollerInterface")); + + std::lock_guard lock(*poll_mutex_); + if (std::find(attached_pollers_.begin(), attached_pollers_.end(), poller) != + attached_pollers_.end()) + throw(goby::Exception("Cannot attach the same PollerInterface more than once")); attached_pollers_.push_back(poller); } From a5235345edf89b7f717d630bb777842e98930880 Mon Sep 17 00:00:00 2001 From: Toby Schneider Date: Thu, 9 Apr 2026 19:38:39 +1000 Subject: [PATCH 18/20] Update copyright --- .circleci/config.yml | 4 +--- scripts/update_copyright.sh | 3 ++- src/acomms/amac/mac_manager.h | 2 +- src/acomms/modemdriver/iridium_driver.cpp | 2 +- src/acomms/modemdriver/mm_driver.h | 2 +- src/acomms/queue/queue.cpp | 1 - src/acomms/queue/queue_manager.h | 2 +- .../abc_modem_simulator.cpp | 2 +- .../rockblock_simulator.cpp | 2 +- src/middleware/application/multi_thread.h | 1 - src/middleware/application/simple_thread.h | 2 +- .../coroner/health_monitor_thread.h | 2 +- .../frontseat/bluefin/bluefin_incoming.cpp | 1 - src/middleware/group.h | 1 - src/middleware/io/detail/io_interface.h | 2 +- src/middleware/io/detail/io_transporters.h | 2 +- src/middleware/io/groups.h | 2 +- src/middleware/io/line_based/pty.h | 2 +- src/middleware/io/line_based/serial.h | 2 +- src/middleware/io/line_based/tcp_client.h | 2 +- src/middleware/io/line_based/tcp_server.h | 2 +- src/middleware/io/mavlink/serial.h | 2 +- src/middleware/io/mavlink/udp.h | 2 +- .../transport/detail/static_group_names.h | 23 +++++++++++++++++++ .../transport/detail/subscription_store.h | 1 - src/middleware/transport/identifier.h | 1 - src/middleware/transport/interface.h | 1 - src/middleware/transport/intermodule.h | 1 - src/middleware/transport/interprocess.h | 1 - src/middleware/transport/interthread.h | 1 - src/middleware/transport/intervehicle.h | 1 - .../transport/intervehicle/driver_thread.cpp | 2 +- .../transport/intervehicle/driver_thread.h | 2 +- src/middleware/transport/null.h | 1 - src/middleware/transport/poller.h | 1 - src/moos/middleware/moos_plugin_translator.h | 2 +- .../test.cpp | 1 - .../middleware/multi_thread_app1/test.cpp | 1 - src/test/zeromq/middleware_basic/test.cpp | 2 +- src/test/zeromq/middleware_regex/test.cpp | 2 +- src/test/zeromq/multi_thread_app2/test.cpp | 2 +- src/test/zeromq/poller_attach_test/test.cpp | 2 +- .../test.cpp | 2 +- src/udpm/application/simple_thread.h | 2 +- src/udpm/transport/detail/tags.h | 2 +- src/udpm/transport/interprocess.h | 1 - src/zeromq/application/multi_thread.h | 2 +- src/zeromq/application/simple_thread.h | 2 +- src/zeromq/liaison/liaison_container.h | 2 +- src/zeromq/transport/detail/tags.h | 2 +- src/zeromq/transport/intermodule.h | 2 +- src/zeromq/transport/interprocess.cpp | 1 - src/zeromq/transport/interprocess.h | 1 - 53 files changed, 58 insertions(+), 54 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 96df6a90..41cb0d2f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -214,10 +214,8 @@ workflows: <<: *filter-template-non-master - amd64-noble-build: <<: *filter-template-non-master - - - amd64-noble-minimal-build: - <<: *filter-template-master-only + <<: *filter-template-non-master - get-orig-source: <<: *filter-template-master-only diff --git a/scripts/update_copyright.sh b/scripts/update_copyright.sh index 1507776d..f9bc1c9d 100755 --- a/scripts/update_copyright.sh +++ b/scripts/update_copyright.sh @@ -1,5 +1,6 @@ #!/bin/bash + here=`pwd` header_strip() @@ -57,7 +58,7 @@ EOF if [ ! -z "$proper_email" ]; then proper_email=" <${proper_email}>" fi - if ! grep -q $proper_email /tmp/goby_authors.tmp; then + if ! grep -q "$proper_email" /tmp/goby_authors.tmp; then echo "// $proper_author$proper_email" >> /tmp/goby_authors.tmp fi done diff --git a/src/acomms/amac/mac_manager.h b/src/acomms/amac/mac_manager.h index 3ac83833..b75a3e5b 100644 --- a/src/acomms/amac/mac_manager.h +++ b/src/acomms/amac/mac_manager.h @@ -1,4 +1,4 @@ -// Copyright 2010-2023: +// Copyright 2010-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/acomms/modemdriver/iridium_driver.cpp b/src/acomms/modemdriver/iridium_driver.cpp index 9da1a4c2..6ad8d8ec 100644 --- a/src/acomms/modemdriver/iridium_driver.cpp +++ b/src/acomms/modemdriver/iridium_driver.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2025: +// Copyright 2013-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/acomms/modemdriver/mm_driver.h b/src/acomms/modemdriver/mm_driver.h index 03a7536c..2eeab5aa 100644 --- a/src/acomms/modemdriver/mm_driver.h +++ b/src/acomms/modemdriver/mm_driver.h @@ -1,4 +1,4 @@ -// Copyright 2009-2021: +// Copyright 2009-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/acomms/queue/queue.cpp b/src/acomms/queue/queue.cpp index 075bd6ed..1496a017 100644 --- a/src/acomms/queue/queue.cpp +++ b/src/acomms/queue/queue.cpp @@ -4,7 +4,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/acomms/queue/queue_manager.h b/src/acomms/queue/queue_manager.h index 28ce549f..3c4e5e8d 100644 --- a/src/acomms/queue/queue_manager.h +++ b/src/acomms/queue/queue_manager.h @@ -1,4 +1,4 @@ -// Copyright 2009-2025: +// Copyright 2009-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/apps/acomms/abc_modem_simulator/abc_modem_simulator.cpp b/src/apps/acomms/abc_modem_simulator/abc_modem_simulator.cpp index c00d91ec..7eaf0ac7 100644 --- a/src/apps/acomms/abc_modem_simulator/abc_modem_simulator.cpp +++ b/src/apps/acomms/abc_modem_simulator/abc_modem_simulator.cpp @@ -1,4 +1,4 @@ -// Copyright 2011-2021: +// Copyright 2011-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp b/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp index 96dd6753..6983e512 100644 --- a/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp +++ b/src/apps/acomms/rockblock_simulator/rockblock_simulator.cpp @@ -1,4 +1,4 @@ -// Copyright 2023: +// Copyright 2023-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/application/multi_thread.h b/src/middleware/application/multi_thread.h index c7e53af3..1b0af903 100644 --- a/src/middleware/application/multi_thread.h +++ b/src/middleware/application/multi_thread.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // James D. Turner // // diff --git a/src/middleware/application/simple_thread.h b/src/middleware/application/simple_thread.h index bf5e3a82..ea3377b0 100644 --- a/src/middleware/application/simple_thread.h +++ b/src/middleware/application/simple_thread.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024: +// Copyright 2022-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/coroner/health_monitor_thread.h b/src/middleware/coroner/health_monitor_thread.h index 1e66aa70..14eec9a4 100644 --- a/src/middleware/coroner/health_monitor_thread.h +++ b/src/middleware/coroner/health_monitor_thread.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023: +// Copyright 2017-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/frontseat/bluefin/bluefin_incoming.cpp b/src/middleware/frontseat/bluefin/bluefin_incoming.cpp index c3d3e69a..8f5d8daa 100644 --- a/src/middleware/frontseat/bluefin/bluefin_incoming.cpp +++ b/src/middleware/frontseat/bluefin/bluefin_incoming.cpp @@ -4,7 +4,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/group.h b/src/middleware/group.h index b1ad6916..f8ff5b46 100644 --- a/src/middleware/group.h +++ b/src/middleware/group.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // Ryan Govostes // // diff --git a/src/middleware/io/detail/io_interface.h b/src/middleware/io/detail/io_interface.h index f4664821..c8d25d33 100644 --- a/src/middleware/io/detail/io_interface.h +++ b/src/middleware/io/detail/io_interface.h @@ -1,4 +1,4 @@ -// Copyright 2019-2022: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/detail/io_transporters.h b/src/middleware/io/detail/io_transporters.h index 4d4e3b59..3ae69f32 100644 --- a/src/middleware/io/detail/io_transporters.h +++ b/src/middleware/io/detail/io_transporters.h @@ -1,4 +1,4 @@ -// Copyright 2021: +// Copyright 2021-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/groups.h b/src/middleware/io/groups.h index c2a33d7e..558bdcb5 100644 --- a/src/middleware/io/groups.h +++ b/src/middleware/io/groups.h @@ -1,4 +1,4 @@ -// Copyright 2009-2021: +// Copyright 2009-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/middleware/io/line_based/pty.h b/src/middleware/io/line_based/pty.h index db65c655..25249582 100644 --- a/src/middleware/io/line_based/pty.h +++ b/src/middleware/io/line_based/pty.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/line_based/serial.h b/src/middleware/io/line_based/serial.h index f52f3c81..b5c22bf1 100644 --- a/src/middleware/io/line_based/serial.h +++ b/src/middleware/io/line_based/serial.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/line_based/tcp_client.h b/src/middleware/io/line_based/tcp_client.h index 2e0f40c5..7cd86e73 100644 --- a/src/middleware/io/line_based/tcp_client.h +++ b/src/middleware/io/line_based/tcp_client.h @@ -1,4 +1,4 @@ -// Copyright 2019-2021: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/line_based/tcp_server.h b/src/middleware/io/line_based/tcp_server.h index 34a1588e..bd23c14f 100644 --- a/src/middleware/io/line_based/tcp_server.h +++ b/src/middleware/io/line_based/tcp_server.h @@ -1,4 +1,4 @@ -// Copyright 2020-2021: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/mavlink/serial.h b/src/middleware/io/mavlink/serial.h index 4dff2055..2809117d 100644 --- a/src/middleware/io/mavlink/serial.h +++ b/src/middleware/io/mavlink/serial.h @@ -1,4 +1,4 @@ -// Copyright 2019-2021: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/io/mavlink/udp.h b/src/middleware/io/mavlink/udp.h index 94f7d6ec..97b05b9c 100644 --- a/src/middleware/io/mavlink/udp.h +++ b/src/middleware/io/mavlink/udp.h @@ -1,4 +1,4 @@ -// Copyright 2019-2021: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/transport/detail/static_group_names.h b/src/middleware/transport/detail/static_group_names.h index d11087de..92d5ffc4 100644 --- a/src/middleware/transport/detail/static_group_names.h +++ b/src/middleware/transport/detail/static_group_names.h @@ -1,3 +1,26 @@ +// Copyright 2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + #ifndef GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H #define GOBY_MIDDLEWARE_TRANSPORT_DETAIL_STATIC_GROUP_NAMES_H diff --git a/src/middleware/transport/detail/subscription_store.h b/src/middleware/transport/detail/subscription_store.h index ebb5bcc5..3e30bdaa 100644 --- a/src/middleware/transport/detail/subscription_store.h +++ b/src/middleware/transport/detail/subscription_store.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/identifier.h b/src/middleware/transport/identifier.h index d171c269..d7005c09 100644 --- a/src/middleware/transport/identifier.h +++ b/src/middleware/transport/identifier.h @@ -21,7 +21,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with Goby. If not, see . - #ifndef GOBY_MIDDLEWARE_TRANSPORT_IDENTIFIER_H #define GOBY_MIDDLEWARE_TRANSPORT_IDENTIFIER_H diff --git a/src/middleware/transport/interface.h b/src/middleware/transport/interface.h index d1849d92..e3bc769d 100644 --- a/src/middleware/transport/interface.h +++ b/src/middleware/transport/interface.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/intermodule.h b/src/middleware/transport/intermodule.h index 69b12ceb..1eb26d37 100644 --- a/src/middleware/transport/intermodule.h +++ b/src/middleware/transport/intermodule.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/interprocess.h b/src/middleware/transport/interprocess.h index fb08050b..f5cf8c26 100644 --- a/src/middleware/transport/interprocess.h +++ b/src/middleware/transport/interprocess.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // Ryan Govostes // // diff --git a/src/middleware/transport/interthread.h b/src/middleware/transport/interthread.h index f7ca58c0..d5d09ac3 100644 --- a/src/middleware/transport/interthread.h +++ b/src/middleware/transport/interthread.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/intervehicle.h b/src/middleware/transport/intervehicle.h index f16da3fe..d373af8d 100644 --- a/src/middleware/transport/intervehicle.h +++ b/src/middleware/transport/intervehicle.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/intervehicle/driver_thread.cpp b/src/middleware/transport/intervehicle/driver_thread.cpp index a3cea769..4455b0ce 100644 --- a/src/middleware/transport/intervehicle/driver_thread.cpp +++ b/src/middleware/transport/intervehicle/driver_thread.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2025: +// Copyright 2017-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/transport/intervehicle/driver_thread.h b/src/middleware/transport/intervehicle/driver_thread.h index c69e3f23..dd6badcd 100644 --- a/src/middleware/transport/intervehicle/driver_thread.h +++ b/src/middleware/transport/intervehicle/driver_thread.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023: +// Copyright 2019-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/middleware/transport/null.h b/src/middleware/transport/null.h index ae40dfc7..1bef8905 100644 --- a/src/middleware/transport/null.h +++ b/src/middleware/transport/null.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/middleware/transport/poller.h b/src/middleware/transport/poller.h index ccdcac5f..a20efbad 100644 --- a/src/middleware/transport/poller.h +++ b/src/middleware/transport/poller.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // Ryan Govostes // // diff --git a/src/moos/middleware/moos_plugin_translator.h b/src/moos/middleware/moos_plugin_translator.h index ca306116..427ed0b2 100644 --- a/src/moos/middleware/moos_plugin_translator.h +++ b/src/moos/middleware/moos_plugin_translator.h @@ -1,4 +1,4 @@ -// Copyright 2017-2022: +// Copyright 2017-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/test/middleware/middleware_interprocess_forwarder/test.cpp b/src/test/middleware/middleware_interprocess_forwarder/test.cpp index f46ee839..221374d6 100644 --- a/src/test/middleware/middleware_interprocess_forwarder/test.cpp +++ b/src/test/middleware/middleware_interprocess_forwarder/test.cpp @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Binaries diff --git a/src/test/middleware/multi_thread_app1/test.cpp b/src/test/middleware/multi_thread_app1/test.cpp index 2b332aa2..26c5329c 100644 --- a/src/test/middleware/multi_thread_app1/test.cpp +++ b/src/test/middleware/multi_thread_app1/test.cpp @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Binaries diff --git a/src/test/zeromq/middleware_basic/test.cpp b/src/test/zeromq/middleware_basic/test.cpp index a3caae0a..f0dbbf86 100644 --- a/src/test/zeromq/middleware_basic/test.cpp +++ b/src/test/zeromq/middleware_basic/test.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2021: +// Copyright 2016-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/test/zeromq/middleware_regex/test.cpp b/src/test/zeromq/middleware_regex/test.cpp index 655c29c0..de6bfc0f 100644 --- a/src/test/zeromq/middleware_regex/test.cpp +++ b/src/test/zeromq/middleware_regex/test.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2021: +// Copyright 2017-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/test/zeromq/multi_thread_app2/test.cpp b/src/test/zeromq/multi_thread_app2/test.cpp index ed5e3f35..45bf4580 100644 --- a/src/test/zeromq/multi_thread_app2/test.cpp +++ b/src/test/zeromq/multi_thread_app2/test.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2021: +// Copyright 2017-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/test/zeromq/poller_attach_test/test.cpp b/src/test/zeromq/poller_attach_test/test.cpp index 3cc96bb6..523d5e26 100644 --- a/src/test/zeromq/poller_attach_test/test.cpp +++ b/src/test/zeromq/poller_attach_test/test.cpp @@ -2,7 +2,7 @@ // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: -// Copilot <198982749+Copilot@users.noreply.github.com> +// Toby Schneider // // // This file is part of the Goby Underwater Autonomy Project Binaries diff --git a/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp b/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp index 59fe00e1..16e57105 100644 --- a/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp +++ b/src/test/zeromq/zeromq_intermodule_and_interprocess/test.cpp @@ -1,4 +1,4 @@ -// Copyright 2020-2023: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/udpm/application/simple_thread.h b/src/udpm/application/simple_thread.h index 616a34c9..107230db 100644 --- a/src/udpm/application/simple_thread.h +++ b/src/udpm/application/simple_thread.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/udpm/transport/detail/tags.h b/src/udpm/transport/detail/tags.h index 9b6b1d1e..b3296cf0 100644 --- a/src/udpm/transport/detail/tags.h +++ b/src/udpm/transport/detail/tags.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/udpm/transport/interprocess.h b/src/udpm/transport/interprocess.h index b59ea4de..8ff13302 100644 --- a/src/udpm/transport/interprocess.h +++ b/src/udpm/transport/interprocess.h @@ -2,7 +2,6 @@ // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: -// Copilot <198982749+Copilot@users.noreply.github.com> // Toby Schneider // // diff --git a/src/zeromq/application/multi_thread.h b/src/zeromq/application/multi_thread.h index 99aebc0e..1f4c2f02 100644 --- a/src/zeromq/application/multi_thread.h +++ b/src/zeromq/application/multi_thread.h @@ -1,4 +1,4 @@ -// Copyright 2009-2021: +// Copyright 2009-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/zeromq/application/simple_thread.h b/src/zeromq/application/simple_thread.h index 817c4cf6..c2452326 100644 --- a/src/zeromq/application/simple_thread.h +++ b/src/zeromq/application/simple_thread.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/zeromq/liaison/liaison_container.h b/src/zeromq/liaison/liaison_container.h index 9ab5c09b..30462ba3 100644 --- a/src/zeromq/liaison/liaison_container.h +++ b/src/zeromq/liaison/liaison_container.h @@ -1,4 +1,4 @@ -// Copyright 2013-2025: +// Copyright 2013-2026: // GobySoft, LLC (2013-) // Massachusetts Institute of Technology (2007-2014) // Community contributors (see AUTHORS file) diff --git a/src/zeromq/transport/detail/tags.h b/src/zeromq/transport/detail/tags.h index 2a5453b3..d803c653 100644 --- a/src/zeromq/transport/detail/tags.h +++ b/src/zeromq/transport/detail/tags.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/zeromq/transport/intermodule.h b/src/zeromq/transport/intermodule.h index f47e71be..2d2be498 100644 --- a/src/zeromq/transport/intermodule.h +++ b/src/zeromq/transport/intermodule.h @@ -1,4 +1,4 @@ -// Copyright 2020-2021: +// Copyright 2020-2026: // GobySoft, LLC (2013-) // Community contributors (see AUTHORS file) // File authors: diff --git a/src/zeromq/transport/interprocess.cpp b/src/zeromq/transport/interprocess.cpp index 6be730c2..326dcde8 100644 --- a/src/zeromq/transport/interprocess.cpp +++ b/src/zeromq/transport/interprocess.cpp @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries diff --git a/src/zeromq/transport/interprocess.h b/src/zeromq/transport/interprocess.h index 24191b7c..15b6000b 100644 --- a/src/zeromq/transport/interprocess.h +++ b/src/zeromq/transport/interprocess.h @@ -3,7 +3,6 @@ // Community contributors (see AUTHORS file) // File authors: // Toby Schneider -// Copilot <198982749+Copilot@users.noreply.github.com> // // // This file is part of the Goby Underwater Autonomy Project Libraries From def7d82727b2aedb523c885c16b6f66aaaa89067 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:02:49 +1000 Subject: [PATCH 19/20] Merge pull request #383 from GobySoft/copilot/add-udpm-subscribe-publish-commands Add `goby udpm publish` and `goby udpm subscribe` commands --- src/apps/CMakeLists.txt | 1 + src/apps/middleware/goby_tool/tool.proto | 4 + src/apps/udpm/CMakeLists.txt | 1 + src/apps/udpm/udpm_tool/CMakeLists.txt | 4 + src/apps/udpm/udpm_tool/udpm_tool.cpp | 170 ++++++++++++++ src/apps/zeromq/zeromq_tool/zeromq_tool.cpp | 143 +----------- src/middleware/protobuf/tool_config.proto | 105 +++++++++ src/middleware/src.cmake | 1 + src/middleware/tool/publish_subscribe_tool.h | 227 +++++++++++++++++++ src/udpm/protobuf/tool_config.proto | 62 +++++ src/udpm/src.cmake | 1 + 11 files changed, 580 insertions(+), 139 deletions(-) create mode 100644 src/apps/udpm/CMakeLists.txt create mode 100644 src/apps/udpm/udpm_tool/CMakeLists.txt create mode 100644 src/apps/udpm/udpm_tool/udpm_tool.cpp create mode 100644 src/middleware/protobuf/tool_config.proto create mode 100644 src/middleware/tool/publish_subscribe_tool.h create mode 100644 src/udpm/protobuf/tool_config.proto diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 533064ec..55ec3e6b 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(acomms) add_subdirectory(util) add_subdirectory(middleware) +add_subdirectory(udpm) if(build_zeromq) add_subdirectory(zeromq) diff --git a/src/apps/middleware/goby_tool/tool.proto b/src/apps/middleware/goby_tool/tool.proto index b947113b..42c2e2b3 100644 --- a/src/apps/middleware/goby_tool/tool.proto +++ b/src/apps/middleware/goby_tool/tool.proto @@ -41,6 +41,10 @@ message GobyToolConfig short_help_msg: "Interact with ZeroMQ Goby pub/sub", external_command: "goby_zeromq_tool" }]; + udpm = 11 [(goby.ev).cfg = { + short_help_msg: "Interact with UDPM Goby pub/sub", + external_command: "goby_udpm_tool" + }]; // middleware/marshalling protobuf = 20 [(goby.ev).cfg = { diff --git a/src/apps/udpm/CMakeLists.txt b/src/apps/udpm/CMakeLists.txt new file mode 100644 index 00000000..2c6bcbd8 --- /dev/null +++ b/src/apps/udpm/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(udpm_tool) diff --git a/src/apps/udpm/udpm_tool/CMakeLists.txt b/src/apps/udpm/udpm_tool/CMakeLists.txt new file mode 100644 index 00000000..9a3b0fa7 --- /dev/null +++ b/src/apps/udpm/udpm_tool/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(goby_udpm_tool + udpm_tool.cpp + ) +target_link_libraries(goby_udpm_tool goby) diff --git a/src/apps/udpm/udpm_tool/udpm_tool.cpp b/src/apps/udpm/udpm_tool/udpm_tool.cpp new file mode 100644 index 00000000..ebdc2052 --- /dev/null +++ b/src/apps/udpm/udpm_tool/udpm_tool.cpp @@ -0,0 +1,170 @@ +// Copyright 2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Binaries +// ("The Goby Binaries"). +// +// The Goby Binaries are free software: you can redistribute them and/or modify +// them under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// The Goby Binaries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Goby. If not, see . + +#include "goby/middleware/application/configuration_reader.h" +#include "goby/middleware/application/interface.h" +#include "goby/middleware/application/tool.h" +#include "goby/middleware/protobuf/tool_config.pb.h" +#include "goby/middleware/tool/publish_subscribe_tool.h" +#include "goby/udpm/application/single_thread.h" +#include "goby/udpm/protobuf/tool_config.pb.h" + +namespace goby +{ +namespace apps +{ +namespace udpm +{ +class UDPMToolConfigurator + : public goby::middleware::ProtobufConfigurator +{ + public: + UDPMToolConfigurator(int argc, char* argv[]) + : goby::middleware::ProtobufConfigurator(argc, argv) + { + auto& cfg = mutable_cfg(); + if (!cfg.app().glog_config().has_tty_verbosity()) + cfg.mutable_app()->mutable_glog_config()->set_tty_verbosity( + goby::util::protobuf::GLogConfig::WARN); + } +}; + +class UDPMTool : public goby::middleware::Application +{ + public: + UDPMTool(); + ~UDPMTool() override {} + + private: + // never gets called + void run() override { assert(false); } + + private: +}; + +class UDPMPublishTool + : public goby::udpm::SingleThreadApplication, + public goby::middleware::ToolSharedLibraryLoader +{ + public: + UDPMPublishTool(); + ~UDPMPublishTool() override {} + void loop() override; + + private: +}; + +class UDPMSubscribeTool + : public goby::udpm::SingleThreadApplication, + public goby::middleware::ToolSharedLibraryLoader +{ + public: + UDPMSubscribeTool(); + ~UDPMSubscribeTool() override {} + + private: + std::map> plugins_; +}; + +} // namespace udpm +} // namespace apps +} // namespace goby + +int main(int argc, char* argv[]) +{ + return goby::run( + goby::apps::udpm::UDPMToolConfigurator(argc, argv)); +} + +goby::apps::udpm::UDPMTool::UDPMTool() +{ + goby::middleware::ToolHelper tool_helper( + app_cfg().app().binary(), app_cfg().app().tool_cfg(), + goby::udpm::protobuf::UDPMToolConfig::Action_descriptor()); + + if (!tool_helper.perform_action(app_cfg().action())) + { + switch (app_cfg().action()) + { + case goby::udpm::protobuf::UDPMToolConfig::help: + int action_for_help; + if (!tool_helper.help(&action_for_help)) + { + switch (action_for_help) + { + case goby::udpm::protobuf::UDPMToolConfig::publish: + tool_helper.help(action_for_help); + break; + + case goby::udpm::protobuf::UDPMToolConfig::subscribe: + tool_helper.help(action_for_help); + break; + + default: + throw(goby::Exception( + "Help was expected to be handled by external tool")); + break; + } + } + break; + + case goby::udpm::protobuf::UDPMToolConfig::publish: + tool_helper.run_subtool(); + break; + + case goby::udpm::protobuf::UDPMToolConfig::subscribe: + tool_helper.run_subtool(); + break; + + default: + // perform action will call 'exec' if an external tool performs the action, + // so if we are continuing, this didn't happen + throw(goby::Exception("Action was expected to be handled by external tool")); + break; + } + } + + quit(0); +} + +goby::apps::udpm::UDPMPublishTool::UDPMPublishTool() + : goby::udpm::SingleThreadApplication( + 1.0 * boost::units::si::hertz), + goby::middleware::ToolSharedLibraryLoader(app_cfg().load_shared_library()) +{ + goby::middleware::tool::publish_tool_impl(interprocess(), cfg(), "goby udpm publish"); +} + +void goby::apps::udpm::UDPMPublishTool::loop() +{ + static int i = 0; + ++i; + if (i > 1) // exit on second call of loop, plenty of time for publish to go through + quit(0); +} + +goby::apps::udpm::UDPMSubscribeTool::UDPMSubscribeTool() + : goby::middleware::ToolSharedLibraryLoader(app_cfg().load_shared_library()) +{ + goby::middleware::tool::subscribe_tool_impl(interprocess(), cfg(), plugins_); +} diff --git a/src/apps/zeromq/zeromq_tool/zeromq_tool.cpp b/src/apps/zeromq/zeromq_tool/zeromq_tool.cpp index ee08a631..d898c91c 100644 --- a/src/apps/zeromq/zeromq_tool/zeromq_tool.cpp +++ b/src/apps/zeromq/zeromq_tool/zeromq_tool.cpp @@ -21,22 +21,13 @@ // You should have received a copy of the GNU General Public License // along with Goby. If not, see . -#include "goby/middleware/marshalling/dccl.h" -#include "goby/middleware/marshalling/json.h" -#include "goby/middleware/marshalling/protobuf.h" - -#include "goby/middleware/log/dccl_log_plugin.h" -#include "goby/middleware/log/json_log_plugin.h" -#include "goby/middleware/log/protobuf_log_plugin.h" - #include "goby/middleware/application/configuration_reader.h" #include "goby/middleware/application/interface.h" #include "goby/middleware/application/tool.h" +#include "goby/middleware/tool/publish_subscribe_tool.h" #include "goby/zeromq/application/single_thread.h" #include "goby/zeromq/protobuf/tool_config.pb.h" -using goby::glog; - namespace goby { namespace apps @@ -157,83 +148,8 @@ goby::apps::zeromq::PublishTool::PublishTool() : goby::zeromq::SingleThreadApplication(1.0 * boost::units::si::hertz), goby::middleware::ToolSharedLibraryLoader(app_cfg().load_shared_library()) - { - std::string type_scheme_str = cfg().type(); - std::string type; - int scheme{0}; - - std::string::size_type slash_pos = type_scheme_str.find('/'); - if (slash_pos == std::string::npos) - { - // special cases - if (type_scheme_str == "JSON") - { - scheme = goby::middleware::MarshallingScheme::JSON; - } - else if (type_scheme_str.find("protobuf.") != std::string::npos) - { - scheme = goby::middleware::MarshallingScheme::PROTOBUF; - type = type_scheme_str; - } - } - else - { - scheme = - goby::middleware::MarshallingScheme::from_string(type_scheme_str.substr(0, slash_pos)); - type = type_scheme_str.substr(slash_pos + 1); - } - - goby::middleware::DynamicGroup group(cfg().group()); - switch (scheme) - { - case goby::middleware::MarshallingScheme::DCCL: - case goby::middleware::MarshallingScheme::PROTOBUF: - { - // use TextFormat - auto pb_msg = dccl::DynamicProtobufManager::new_protobuf_message< - std::shared_ptr>(type); - google::protobuf::TextFormat::Parser parser; - goby::util::FlexOStreamErrorCollector error_collector(cfg().value()); - parser.RecordErrorsTo(&error_collector); - parser.AllowPartialMessage(false); - parser.ParseFromString(cfg().value(), pb_msg.get()); - - if (scheme == goby::middleware::MarshallingScheme::DCCL) - interprocess() - .publish_dynamic(pb_msg, group); - else if (scheme == goby::middleware::MarshallingScheme::PROTOBUF) - interprocess() - .publish_dynamic(pb_msg, group); - break; - } - - case goby::middleware::MarshallingScheme::JSON: - { - auto j = nlohmann::json::parse(cfg().value()); - if (type.empty() || type == "nlohmann::json") - { - interprocess().publish_dynamic(j, group); - } - else - { - // allow for specialized types, e.g. goby_json_type = "NavigationReport"; - std::vector bytes = goby::middleware::SerializerParserHelper< - nlohmann::json, goby::middleware::MarshallingScheme::JSON>::serialize(j); - - interprocess().publish_serialized(type, goby::middleware::MarshallingScheme::JSON, - bytes, group); - } - - break; - } - - default: - glog.is_die() && glog << "Scheme " << scheme - << " is not implemented for 'goby zeromq publish'" << std::endl; - } + goby::middleware::tool::publish_tool_impl(interprocess(), cfg(), "goby zeromq publish"); } void goby::apps::zeromq::PublishTool::loop() @@ -246,58 +162,7 @@ void goby::apps::zeromq::PublishTool::loop() goby::apps::zeromq::SubscribeTool::SubscribeTool() : goby::middleware::ToolSharedLibraryLoader(app_cfg().load_shared_library()) - { - std::set schemes{goby::middleware::MarshallingScheme::ALL_SCHEMES}; - - if (cfg().has_scheme()) - { - int scheme = goby::middleware::MarshallingScheme::from_string(cfg().scheme()); - schemes = {scheme}; - } - - plugins_[goby::middleware::MarshallingScheme::PROTOBUF] = - std::make_unique(); - plugins_[goby::middleware::MarshallingScheme::DCCL] = - std::make_unique(); - plugins_[goby::middleware::MarshallingScheme::JSON] = - std::make_unique(); - - interprocess().subscribe_regex( - [this](const std::vector& bytes, int scheme, const std::string& type, - const goby::middleware::Group& group) - { - std::regex exclude_pattern("goby::zeromq::_internal.*"); - if (std::regex_match(std::string(group), exclude_pattern) && - !cfg().include_internal_groups()) - return; - - goby::middleware::log::LogEntry log_entry(bytes, scheme, type, group); - std::string debug_text; - - auto plugin = plugins_.find(log_entry.scheme()); - if (plugin == plugins_.end()) - { - debug_text = std::string("Message of " + std::to_string(bytes.size()) + " bytes"); - } - else - { - try - { - debug_text = plugin->second->debug_text_message(log_entry); - } - catch (goby::middleware::log::LogException& e) - { - debug_text = "Unable to parse message of " + - std::to_string(log_entry.data().size()) + - " bytes. Reason: " + e.what(); - } - } - - // use similar format to goby_log_tool DEBUG_TEXT - std::cout << scheme << " | " << group << " | " << type << " | " - << goby::time::convert(log_entry.timestamp()) - << " | " << debug_text << std::endl; - }, - schemes, cfg().type_regex(), cfg().group_regex()); + goby::middleware::tool::subscribe_tool_impl(interprocess(), cfg(), plugins_, + "goby::zeromq::_internal.*"); } diff --git a/src/middleware/protobuf/tool_config.proto b/src/middleware/protobuf/tool_config.proto new file mode 100644 index 00000000..3f893820 --- /dev/null +++ b/src/middleware/protobuf/tool_config.proto @@ -0,0 +1,105 @@ +// Copyright 2024-2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +syntax = "proto2"; +import "goby/middleware/protobuf/app_config.proto"; +import "goby/protobuf/option_extensions.proto"; +import "goby/udpm/protobuf/interprocess_config.proto"; + +package goby.middleware.protobuf; + +message PublishToolConfig +{ + option (goby.msg).cfg.tool = { + is_tool: true + has_subtools: false + has_help_action: false + }; + + optional goby.middleware.protobuf.AppConfig app = 1 + [(goby.field) = { cfg { action: DEVELOPER } }]; + optional goby.udpm.protobuf.InterProcessPortalConfig interprocess = 2 + [(goby.field) = { cfg { env: "GOBY_INTERPROCESS" } }]; + + required string group = 10 [(goby.field) = { + description: "Group as string", + cfg { position: { enable: true }, cli_short: "g" } + }]; + + required string type = 11 [(goby.field) = { + description: "Scheme (as name: PROTOBUF, DCCL, JSON) followed by '/' and type name, if required. For example, 'PROTOBUF/NavigationReport', 'DCCL/NavigationReport', 'JSON/NavigationReport'. For JSON, you can omit '/' to publish as generic nlohmann::json. If string contains 'protobuf.', you can omit PROTOBUF/; e.g., 'goby.middleware.protobuf.IOData'", + cfg { position: { enable: true }, cli_short: "t" } + }]; + + required string value = 12 [(goby.field) = { + description: "Value as string", + cfg { position: { enable: true } } + }]; + + repeated string load_shared_library = 20 [(goby.field) = { + description: "Load this shared library containing compiled Protobuf/DCCL messages.", + cfg { cli_short: "l" env: "GOBY_TOOL_LOAD_SHARED_LIBRARY" } + }]; +} + +message SubscribeToolConfig +{ + option (goby.msg).cfg.tool = { + is_tool: true + has_subtools: false + has_help_action: false + }; + + optional goby.middleware.protobuf.AppConfig app = 1 + [(goby.field) = { cfg { action: DEVELOPER } }]; + optional goby.udpm.protobuf.InterProcessPortalConfig interprocess = 2 + [(goby.field) = { cfg { env: "GOBY_INTERPROCESS" } }]; + + optional string group_regex = 10 [ + default = ".*", + (goby.field) = { + description: "Group as string or regex", + cfg { position: { enable: true }, cli_short: "g" } + } + ]; + + optional string type_regex = 11 [ + default = ".*", + (goby.field) = { + description: "Type as string or regex", + cfg { position: { enable: true }, cli_short: "t" } + } + ]; + + optional string scheme = 12 [(goby.field) = { + description: "Only subscribe to this marshalling scheme as string or integer [PROTOBUF|1, DCCL|2, JSON|7]. If omitted, all supported schemes are used.", + cfg { position: { enable: true }, cli_short: "s" } + }]; + + repeated string load_shared_library = 20 [(goby.field) = { + description: "Load this shared library containing compiled Protobuf/DCCL messages.", + cfg { cli_short: "l" env: "GOBY_TOOL_LOAD_SHARED_LIBRARY" } + }]; + + optional bool include_internal_groups = 30 [default = false]; +} diff --git a/src/middleware/src.cmake b/src/middleware/src.cmake index 95bbd6d3..4498af29 100644 --- a/src/middleware/src.cmake +++ b/src/middleware/src.cmake @@ -23,6 +23,7 @@ protobuf_generate_cpp_dccl(MIDDLEWARE_PROTO_SRCS MIDDLEWARE_PROTO_HDRS middleware/protobuf/pty_config.proto middleware/protobuf/navigation.proto middleware/protobuf/logger.proto + middleware/protobuf/tool_config.proto ) set(MIDDLEWARE_SRC diff --git a/src/middleware/tool/publish_subscribe_tool.h b/src/middleware/tool/publish_subscribe_tool.h new file mode 100644 index 00000000..413080a3 --- /dev/null +++ b/src/middleware/tool/publish_subscribe_tool.h @@ -0,0 +1,227 @@ +// Copyright 2024-2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +#ifndef GOBY_MIDDLEWARE_TOOL_PUBLISH_SUBSCRIBE_TOOL_H +#define GOBY_MIDDLEWARE_TOOL_PUBLISH_SUBSCRIBE_TOOL_H + +#include +#include +#include +#include +#include +#include + +#include + +#include "dccl/dynamic_protobuf_manager.h" + +#include "goby/middleware/log/dccl_log_plugin.h" +#include "goby/middleware/log/json_log_plugin.h" +#include "goby/middleware/log/log_entry.h" +#include "goby/middleware/log/log_plugin.h" +#include "goby/middleware/log/protobuf_log_plugin.h" +#include "goby/middleware/marshalling/interface.h" +#include "goby/middleware/marshalling/json.h" +#include "goby/middleware/marshalling/protobuf.h" +#include "goby/time/convert.h" +#include "goby/util/debug_logger.h" +#include "goby/util/debug_logger/flex_ostream.h" + +namespace goby +{ +namespace middleware +{ +namespace tool +{ + +/// \brief Publish a single message on the interprocess layer using the provided publish config. +/// +/// \tparam InterprocessType Transport-specific interprocess portal type +/// \tparam PublishConfig Config type with group(), type(), value(), and load_shared_library() +/// accessors (e.g. goby::middleware::protobuf::PublishToolConfig or a superset) +/// \param interprocess Reference to the interprocess portal +/// \param cfg Publish configuration +/// \param tool_name Name of the tool for error messages (e.g. "goby zeromq publish") +template +void publish_tool_impl(InterprocessType& interprocess, const PublishConfig& cfg, + const std::string& tool_name) +{ + using goby::glog; + std::string type_scheme_str = cfg.type(); + std::string type; + int scheme{0}; + + std::string::size_type slash_pos = type_scheme_str.find('/'); + if (slash_pos == std::string::npos) + { + // special cases + if (type_scheme_str == "JSON") + { + scheme = goby::middleware::MarshallingScheme::JSON; + } + else if (type_scheme_str.find("protobuf.") != std::string::npos) + { + scheme = goby::middleware::MarshallingScheme::PROTOBUF; + type = type_scheme_str; + } + } + else + { + scheme = + goby::middleware::MarshallingScheme::from_string(type_scheme_str.substr(0, slash_pos)); + type = type_scheme_str.substr(slash_pos + 1); + } + + goby::middleware::DynamicGroup group(cfg.group()); + switch (scheme) + { + case goby::middleware::MarshallingScheme::DCCL: + case goby::middleware::MarshallingScheme::PROTOBUF: + { + // use TextFormat + auto pb_msg = dccl::DynamicProtobufManager::new_protobuf_message< + std::shared_ptr>(type); + google::protobuf::TextFormat::Parser parser; + goby::util::FlexOStreamErrorCollector error_collector(cfg.value()); + parser.RecordErrorsTo(&error_collector); + parser.AllowPartialMessage(false); + parser.ParseFromString(cfg.value(), pb_msg.get()); + + if (scheme == goby::middleware::MarshallingScheme::DCCL) + interprocess + .template publish_dynamic(pb_msg, + group); + else if (scheme == goby::middleware::MarshallingScheme::PROTOBUF) + interprocess + .template publish_dynamic(pb_msg, + group); + break; + } + + case goby::middleware::MarshallingScheme::JSON: + { + auto j = nlohmann::json::parse(cfg.value()); + if (type.empty() || type == "nlohmann::json") + { + interprocess.template publish_dynamic(j, group); + } + else + { + // allow for specialized types, e.g. goby_json_type = "NavigationReport"; + std::vector bytes = goby::middleware::SerializerParserHelper< + nlohmann::json, goby::middleware::MarshallingScheme::JSON>::serialize(j); + + interprocess.publish_serialized(type, goby::middleware::MarshallingScheme::JSON, + bytes, group); + } + + break; + } + + default: + glog.is_die() && glog << "Scheme " << scheme << " is not implemented for '" << tool_name + << "'" << std::endl; + } +} + +/// \brief Set up subscriptions for the interprocess subscribe tool. +/// +/// \tparam InterprocessType Transport-specific interprocess portal type +/// \tparam SubscribeConfig Config type with group_regex(), type_regex(), scheme(), +/// has_scheme(), include_internal_groups() accessors +/// \param interprocess Reference to the interprocess portal +/// \param cfg Subscribe configuration +/// \param plugins Map of scheme->LogPlugin for deserializing messages +/// \param internal_group_regex Regex pattern for internal groups to filter unless +/// include_internal_groups() is set. Pass empty string to disable filtering. +template +void subscribe_tool_impl( + InterprocessType& interprocess, const SubscribeConfig& cfg, + std::map>& plugins, + const std::string& internal_group_regex = "") +{ + std::set schemes{goby::middleware::MarshallingScheme::ALL_SCHEMES}; + + if (cfg.has_scheme()) + { + int scheme = goby::middleware::MarshallingScheme::from_string(cfg.scheme()); + schemes = {scheme}; + } + + plugins[goby::middleware::MarshallingScheme::PROTOBUF] = + std::make_unique(); + plugins[goby::middleware::MarshallingScheme::DCCL] = + std::make_unique(); + plugins[goby::middleware::MarshallingScheme::JSON] = + std::make_unique(); + + interprocess.subscribe_regex( + [&cfg, &plugins, internal_group_regex](const std::vector& bytes, int scheme, + const std::string& type, + const goby::middleware::Group& group) + { + if (!internal_group_regex.empty()) + { + std::regex exclude_pattern(internal_group_regex); + if (std::regex_match(std::string(group), exclude_pattern) && + !cfg.include_internal_groups()) + return; + } + + goby::middleware::log::LogEntry log_entry(bytes, scheme, type, group); + std::string debug_text; + + auto plugin = plugins.find(log_entry.scheme()); + if (plugin == plugins.end()) + { + debug_text = std::string("Message of " + std::to_string(bytes.size()) + " bytes"); + } + else + { + try + { + debug_text = plugin->second->debug_text_message(log_entry); + } + catch (goby::middleware::log::LogException& e) + { + debug_text = "Unable to parse message of " + + std::to_string(log_entry.data().size()) + + " bytes. Reason: " + e.what(); + } + } + + // use similar format to goby_log_tool DEBUG_TEXT + std::cout << scheme << " | " << group << " | " << type << " | " + << goby::time::convert(log_entry.timestamp()) + << " | " << debug_text << std::endl; + }, + schemes, cfg.type_regex(), cfg.group_regex()); +} + +} // namespace tool +} // namespace middleware +} // namespace goby + +#endif diff --git a/src/udpm/protobuf/tool_config.proto b/src/udpm/protobuf/tool_config.proto new file mode 100644 index 00000000..60c83ab1 --- /dev/null +++ b/src/udpm/protobuf/tool_config.proto @@ -0,0 +1,62 @@ +// Copyright 2026: +// GobySoft, LLC (2013-) +// Community contributors (see AUTHORS file) +// File authors: +// Toby Schneider +// +// +// This file is part of the Goby Underwater Autonomy Project Libraries +// ("The Goby Libraries"). +// +// The Goby Libraries are free software: you can redistribute them and/or modify +// them under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or +// (at your option) any later version. +// +// The Goby Libraries are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Goby. If not, see . + +syntax = "proto2"; +import "goby/middleware/protobuf/app_config.proto"; +import "goby/protobuf/option_extensions.proto"; + +package goby.udpm.protobuf; + +message UDPMToolConfig +{ + option (goby.msg).cfg.tool = { + is_tool: true + has_subtools: true + has_help_action: true + }; + + optional goby.middleware.protobuf.AppConfig app = 1 + [(goby.field) = { cfg { action: DEVELOPER } }]; + + enum Action + { + // tools + help = 0 [(goby.ev).cfg = { + short_help_msg: "Print usage information", + full_help_msg: "Usage: \"goby udpm help [action]\"\n" + }]; + publish = 1 [(goby.ev).cfg = { + short_help_msg: "Publish a message (on interprocess)", + }]; + subscribe = 2 [(goby.ev).cfg = { + short_help_msg: "Subscribe to messages (on interprocess)", + }]; + } + optional Action action = 2 [ + default = help, + (goby.field) = { + description: "The action this tool should take [can omit --action if 1st parameter]", + cfg { position: { enable: true }, cli_short: "A", action: HIDDEN } + } + ]; +} diff --git a/src/udpm/src.cmake b/src/udpm/src.cmake index 67e9a1c6..74c7cf55 100644 --- a/src/udpm/src.cmake +++ b/src/udpm/src.cmake @@ -1,5 +1,6 @@ protobuf_generate_cpp_dccl(UDPM_PROTO_SRCS UDPM_PROTO_HDRS udpm/protobuf/interprocess_config.proto + udpm/protobuf/tool_config.proto ) set(UDPM_SRC From 1db6d484c2750da67383f53911134104bebe003e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:03:07 +1000 Subject: [PATCH 20/20] gen_goby.jl: Support array form of interprocess/interthread/intermodule layers (#382) * Support array form of interprocess/interthread/intermodule in gen_goby.jl Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/50f5f12a-fb9f-47f5-9d4c-00ebabcce4f2 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> * Add README.md for Goby.jl Julia support package Agent-Logs-Url: https://github.com/GobySoft/goby3/sessions/0ceeca83-a92e-4a80-b138-c8f93678d9d0 Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> * Edit Julia markdown doc --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tsaubergine <732276+tsaubergine@users.noreply.github.com> Co-authored-by: Toby Schneider --- src/share/Goby.jl/README.md | 239 ++++++++++++++++++++++++++++++ src/share/Goby.jl/src/gen_goby.jl | 15 +- 2 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 src/share/Goby.jl/README.md diff --git a/src/share/Goby.jl/README.md b/src/share/Goby.jl/README.md new file mode 100644 index 00000000..8c2c9c4a --- /dev/null +++ b/src/share/Goby.jl/README.md @@ -0,0 +1,239 @@ +# Goby.jl – Julia Support for Goby + +This package provides the ability to run Goby applications from the Julia language. The Julia CxxWrap module is used to wrap the Goby C++ implementation (except for the `interthread` layer which is directly implemented in Julia) in order to keep the Julia implementation lightweight and feature-compliant with the C++ implementation. + +--- + +## File Overview + +| File | Purpose | +|------|---------| +| `src/Goby.jl` | **Main Julia module.** Wraps the CxxWrap'd C++ Goby application class and exposes `publish`, `subscribe`, `run`, `cfg`, and `read_cli_cfg` to Julia user code. | +| `src/gen_goby.jl` | **Code generator.** Reads an `interface.yml` file and emits a C++ source file containing the `publish`/`subscribe` glue code for all declared transport layers. Invoked at build time by CMake. | +| `src/GobyMultiThread.jl` | `MultiThread` sub-module. Not directly included from user code. Implements interthread communication using Julia `Channel`s and `ThreadPools`, enabling multi-threaded Julia Goby apps where each task runs on a dedicated Julia thread. | +| `src/pkg.jl` | Minimal helper script used by CMake to install/instantiate the Julia package dependencies (`Pkg.instantiate()`). | +| `Project.toml` | Julia package manifest; declares dependencies (`CxxWrap`, `ProtoBuf`, `YAML`, `ThreadPools`) and package metadata. | + +--- + +## `interface.yml` Reference + +Julia is not a static language, but C++ is, and Goby3 relies heavily on the benefits of static analysis (e.g., through the use of constexpr Groups). + +To reconcile this for Julia, we require that the application author write a definition of the interfaces that the Julia application will use to publish and subscribe on. This is the `interface.yml` file and `gen_goby.jl` reads this file and generates the C++ glue code. + +Thus, the Julia application author does not need to directly write any C++ code, just this .yml file and the Julia code. + +### Top-level keys + +| Key | Required | Description | +|-----|----------|-------------| +| `application` | **Yes** | Application metadata (see below). | +| `interthread` | No | Interthread-layer publish/subscribe declarations. | +| `interprocess` | No | Interprocess-layer publish/subscribe declarations. | +| `intermodule` | No | Intermodule-layer publish/subscribe declarations. | + +### `application` + +```yaml +application: + name: # used as the C++ class name and Julia type name + cpp_type: # e.g. goby::middleware::SingleThreadApplication + config: # configuration at launch, same as Config template parameter to the cpp_type class + scheme: PROTOBUF # only PROTOBUF is currently supported + type: # dot-separated (Julia/proto) or ::-separated (C++) +``` + +### Transport layers (`interthread`, `interprocess`, `intermodule`) + +Each layer accepts either a **single mapping** (one portal) or an **array of mappings** (multiple portals, each with its own `alias` which is corresponds to the portal function name in the class given to `application: cpp_type`). For example, if your `interprocess` portal is accessed via `interblock()`, use `alias: interblock`). For the Goby3 reference implementation, these function names are identical to the layer names and so alias should be omitted. + +#### Single portal (mapping form): typical usage + +```yaml +interprocess: + publishes: + - group: + scheme: PROTOBUF + type: + subscribes: + - group: + scheme: PROTOBUF + type: +``` + +#### Multiple portals (array form): advanced usage + +When running a custom Application class that supports multiple INTERPROCESS layer transporters, you will need to use this form to distinguish them. + +```yaml +interprocess: + - alias: interprocess_zeromq # uses Application::interprocess_zeromq() + publishes: + - group: + scheme: PROTOBUF + type: + - alias: interprocess_udpm # uses Application::interprocess_udpm() + subscribes: + - group: + scheme: PROTOBUF + type: +``` + +Each entry in the array may independently declare `publishes` and/or `subscribes`, and uses its own `alias` as the C++ accessor function name (i.e., the `LAYER_FUNCTION` in the generated macros). + +#### Per-publication / per-subscription required fields + +| Field | Required | Description | +|-------|----------|-------------| +| `group` | **Yes** | C++ group expression (e.g. `project::groups::my_group`). | +| `scheme` | **Yes** | Marshalling scheme. Currently only `PROTOBUF` is supported. | +| `type` | **Yes** | Protobuf message type. May use either dot-separated (Julia/proto) notation (`project.protobuf.MyMsg`) or C++ double-colon notation (`project::protobuf::MyMsg`). Both are normalised to `::` in the generated C++ code. | + +--- + +### Complete `interface.yml` example + +```yaml +application: + name: JuliaDemo + cpp_type: goby::middleware::SingleThreadApplication + config: + scheme: PROTOBUF + type: project.config.protobuf.JuliaDemoConfig + +interprocess: + publishes: + - group: project::groups::modem_tx + scheme: PROTOBUF + type: project.protobuf.CommsTx + subscribes: + - group: project::groups::modem_rx + scheme: PROTOBUF + type: project.protobuf.CommsRx +``` + +### Generated C++ output (excerpt) + +For the `interprocess` block above the generator emits: + +```cpp +// publish() method body +GOBY_JULIA_IF_PUBLICATION(PROTOBUF, INTERPROCESS, interprocess, project::groups::modem_tx, project::protobuf::CommsTx) + +// subscribe() method body +GOBY_JULIA_IF_SUBSCRIPTION(PROTOBUF, INTERPROCESS, interprocess, project::groups::modem_rx, project::protobuf::CommsRx) +``` + +--- + +## Build Flow + +``` +interface.yml + │ + │ gen_goby.jl (Julia, typically invoked by CMake add_custom_command) + ▼ +JuliaDemo.cpp ← autogenerated C++ glue + │ + │ C++ compiler (links against goby, goby_zeromq, JlCxx::cxxwrap_julia) + ▼ +libJuliaDemo.so ← shared library exposing CxxWrap module "JuliaDemo" + │ + │ Julia `using JuliaDemo` (at runtime) + ▼ +Julia user script (JuliaDemo.jl) +``` + +### Step-by-step + +1. **Package installation** – CMake runs `pkg.jl` via Julia to call `Pkg.instantiate()`, downloading all dependencies declared in `Project.toml` into the build directory's `Manifest.toml`. + +2. **Proto generation** – Use the Julia Protobuf.jl compiler to produce `*_pb.jl` files alongside the C++ `.pb.h`/`.pb.cc` files. + +3. **C++ glue generation** – Use gen_goby.jl: + ``` + julia --project= -L gen_goby.jl \ + -e 'goby_gen_cpp("interface.yml", "JuliaDemo.cpp", ["proto_header.pb.h"])' + ``` + This produces `JuliaDemo.cpp`, which: + - `#include`s `` and any additional headers passed as the third argument. + - Defines `CONFIG_TYPE`, `APPLICATION_TYPE`, and `APPLICATION_NAME` macros. + - Declares a class `JuliaDemo` extending `goby::middleware::julia::Application` with `publish()` and `subscribe()` bodies built from `GOBY_JULIA_IF_PUBLICATION` / `GOBY_JULIA_IF_SUBSCRIPTION` macros. + - Registers the CxxWrap module entry point via `GOBY_JULIA_DEFINE_MODULE(JuliaDemo)`. + +4. **Shared library** – Compile `JuliaDemo.cpp` into `libJuliaDemo.so`, link against Goby, ZeroMQ transport, and JlCxx. + +5. **Julia runtime** – The user Julia script loads `libJuliaDemo.so` through `CxxWrap` (typically via a thin wrapper module), then uses the `Goby` module (`Goby.jl`) to publish and subscribe. + +--- + +## Single-Threaded Julia Usage Example + +This example corresponds to the `interface.yml` above. + +```julia +using Goby +# Load the generated CxxWrap module and application-specific protobuf types +# (exact using/import statements depend on your project layout) + + +# ---------- Callbacks ---------- + +function publish_outgoing_msg() + msg = project.protobuf.CommsTx(request_id = 42) + Goby.publish(goby_app, Goby.INTERPROCESS, "project::groups::modem_tx", msg) + println("PUBLISHED: $msg") +end + +function receive_incoming_msg(msg::project.protobuf.CommsRx) + println("RECEIVED: $msg") +end + +# Called periodically at `loop_frequency` Hz +function loop() + t = time() + println("$t: Julia loop") + publish_outgoing_msg() +end + +# ---------- Initialisation ---------- + +# Read the Protobuf TextFormat config file whose path is given as the first CLI argument +goby_app = Goby.JuliaDemo(Goby.read_cli_cfg()) + +# Pull protobuf config out of the app +pb_cfg = Goby.cfg(goby_app, project.config.protobuf.JuliaDemoConfig) +println("Configuration: my_value_a=$(pb_cfg.my_value_a); my_value_b=$(pb_cfg.my_value_b)") + +goby_cfg = Dict( + :loop_function => loop, # called at loop_frequency Hz + :loop_frequency => 0.1, # Hz + :pb => pb_cfg, +) + +# ---------- Subscriptions ---------- + +function start() + Goby.subscribe(goby_app, Goby.INTERPROCESS, + "project::groups::comms_rx", + receive_incoming_msg) +end + +# ---------- Run ---------- + +# Blocks until the application is terminated (goby_terminate, SIGINT, or SIGTERM) +Goby.run(goby_app) +``` + +### Key `Goby` Julia API + +| Function | Description | +|----------|-------------| +| `Goby.read_cli_cfg()` | Reads the TextFormat config file given as the first CLI positional argument and returns it as a `String`. | +| `Goby.JuliaDemo(cfg_str)` | Constructs the CxxWrap'd application object, parsing `cfg_str` as a Protobuf TextFormat message. Returns `app` used by the remaining functions. | +| `Goby.cfg(app, PbType)` | Deserialises and returns the application's configuration as a `PbType` protobuf message. | +| `Goby.publish(app, layer, group, msg)` | Publishes protobuf `msg` on `layer` (e.g. `Goby.INTERPROCESS`) to the given `group` string. | +| `Goby.subscribe(app, layer, group, callback)` | Subscribes to messages on `layer`/`group`; `callback` must accept a single argument of the expected protobuf type. | +| `Goby.run(app)` | Starts the Goby event loop (blocking). Calls `start()` and then enters the C++ run loop, invoking `loop()` at `goby_cfg[:loop_frequency]` Hz. | +| `Goby.run(app, Main, [TaskModuleA, …])` | Multi-threaded variant; each `TaskModule` is spawned on its own Julia thread. Requires `julia -t ` where N is at least the number of TaskModules plus 3 (e.g., -t 5 for an application with two TaskModules). | diff --git a/src/share/Goby.jl/src/gen_goby.jl b/src/share/Goby.jl/src/gen_goby.jl index 1a4d3cb4..34b20e46 100644 --- a/src/share/Goby.jl/src/gen_goby.jl +++ b/src/share/Goby.jl/src/gen_goby.jl @@ -142,9 +142,18 @@ function goby_gen_cpp(in_yaml::String, out_cpp::String, includes) subscribe = Vector{String}() for layer in layers if haskey(interface_yaml, layer) - p, s = collect_layer(layer, interface_yaml[layer]) - append!(publish, p) - append!(subscribe, s) + layer_value = interface_yaml[layer] + if isa(layer_value, Vector) + for entry in layer_value + p, s = collect_layer(layer, entry) + append!(publish, p) + append!(subscribe, s) + end + else + p, s = collect_layer(layer, layer_value) + append!(publish, p) + append!(subscribe, s) + end end end gen_class(io_out, interface_yaml["application"]["name"], publish, subscribe)