From 37cbdfb2af59cf293ca7d7a3277830ae7779349e Mon Sep 17 00:00:00 2001 From: Mitzi Morris Date: Sat, 8 Mar 2025 16:29:32 -0500 Subject: [PATCH 1/6] new dispatcher class, unit tests --- src/stan/callbacks/dispatcher.hpp | 207 ++++++++++++++++ src/test/unit/callbacks/dispatcher_test.cpp | 247 ++++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 src/stan/callbacks/dispatcher.hpp create mode 100644 src/test/unit/callbacks/dispatcher_test.cpp diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp new file mode 100644 index 0000000000..154e33f589 --- /dev/null +++ b/src/stan/callbacks/dispatcher.hpp @@ -0,0 +1,207 @@ +#ifndef STAN_CALLBACKS_DISPATCHER_HPP +#define STAN_CALLBACKS_DISPATCHER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace stan { +namespace callbacks { + +/** + * The dispatcher class manages a set of callbacks + * for all outputs of one run of a Stan service. + * Calls to the dispatcher's `dispatch` method are forwarded to + * the callback registered on the channel. + */ + + +/** + * Enum `info_type` holds output type labels which are used by + * the dispatcher class to map outputs to output channels. + */ +enum class info_type { + CONFIG, // series of string messages + SAMPLE, // draw from posterior + SAMPLE_RAW, // draw from posterior + METRIC, // struct with kv pairs 'metric_type', 'stepsize', 'inv_metric' + ALGORITHM_STATE, // sampler state for returned draw +}; + +/** + * Efficient enum type lookups + */ +struct info_type_hash { + std::size_t operator()(const info_type& type) const { + return std::hash()(static_cast(type)); + } +}; + + +/** + * Base type for all callbacks, needed for type erasure. + */ +class channel { + public: + virtual ~channel() = default; +}; + + +/** + * A `writer_channel` holds a reference to a stan::callbacks::writer object + * and forwards information to the writer's operator (). + */ +class writer_channel : public channel { + private: + stan::callbacks::writer* writer_; + + public: + explicit writer_channel(stan::callbacks::writer* w) : writer_(w) { + if (!w) { + throw std::runtime_error("config error, null writer"); + } + } + + // Handle all types that writer supports via operator() + void dispatch() { (*writer_)(); } + void dispatch(const std::string& value) { (*writer_)(value); } + void dispatch(const std::vector& value) { (*writer_)(value); } + void dispatch(const std::vector& value) { (*writer_)(value); } + + // Handle any Eigen Matrix type + template + void dispatch(const Eigen::Matrix& value) { + (*writer_)(value); + } + + // No key-value support for plain writers + template + void dispatch(const std::string&, const T&) {} +}; + +/** + * A `structured writer_channel` holds a reference to a + * stan::callbacks::structured_writer object and forwards + * information to the appropriate method. + */ +class structured_writer_channel : public channel { + private: + stan::callbacks::structured_writer* writer_; + + public: + explicit structured_writer_channel(stan::callbacks::structured_writer* sw) + : writer_(sw) { + if (!sw) + throw std::runtime_error("config error, null writer"); + } + // Forward all key-value calls directly to the writer + void dispatch(const std::string& key) { writer_->write(key); } + // Perfect forwarding for any key-value pair + template + void dispatch(const std::string& key, T&& value) { + writer_->write(key, std::forward(value)); + } + void begin_record() { writer_->begin_record(); } + void begin_record(const std::string& key) { writer_->begin_record(key); } + void end_record() { writer_->end_record(); } +}; + +/** + * The `dispatcher` class provides methods to register and find output channels + * and overloads method `dispatch` which forwards outputs to callbacks. + */ +class dispatcher { + private: + /* Lookup registered channels for info_type. + * Returns nullptr if no channel found. + */ + template + channel_type* find_channel(info_type type) { + auto it = channels_.find(type); + if (it == channels_.end()) + return nullptr; + return dynamic_cast(it->second.get()); + } + + std::unordered_map, info_type_hash> + channels_; + + public: + dispatcher() = default; + ~dispatcher() = default; + + /* Add channel to map. + * Assumes a 1:1 mapping between info type and callback. + */ + void register_channel(info_type type, std::unique_ptr channel) { + channels_[type] = std::move(channel); + } + + // no-arg call to writer operator () + void dispatch(info_type type) { + if (auto* wc = find_channel(type)) + wc->dispatch(); + } + + // Value is std::vector, or std::vector + // clang-format off + template , std::vector> + || std::is_same_v, std::vector>>> // NOLINT + // clang-format on + void dispatch(info_type type, T&& value) { + if (auto* wc = find_channel(type)) + wc->dispatch(std::forward(value)); + } + + // Value is Eigen vector or matrix + template + void dispatch(info_type type, const Eigen::Matrix& value) { + if (auto* wc = find_channel(type)) + wc->dispatch(value); + } + + // Value is std::string + void dispatch(info_type type, const std::string& value) { + if (auto* wc = find_channel(type)) + wc->dispatch(value); + else if (auto* sw = find_channel(type)) + sw->dispatch(value); // (sic: actually the key part of k-v pair) + } + + // Key-value pairs (forward to structured writers) + template + void dispatch(info_type type, const std::string& key, T&& value) { + if (auto* sw = find_channel(type)) + sw->dispatch(key, std::forward(value)); + } + + // Record operations + void begin_record(info_type type) { + if (auto* sw = find_channel(type)) + sw->begin_record(); + } + + void begin_record(info_type type, const std::string& key) { + if (auto* sw = find_channel(type)) + sw->begin_record(key); + } + + void end_record(info_type type) { + if (auto* sw = find_channel(type)) + sw->end_record(); + } +}; + +} // namespace callbacks +} // namespace stan + +#endif // STAN_CALLBACKS_DISPATCHER_HPP diff --git a/src/test/unit/callbacks/dispatcher_test.cpp b/src/test/unit/callbacks/dispatcher_test.cpp new file mode 100644 index 0000000000..b24340ef90 --- /dev/null +++ b/src/test/unit/callbacks/dispatcher_test.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using stan::callbacks::dispatcher; +using stan::callbacks::info_type; + +struct deleter_noop { + template + constexpr void operator()(T* arg) const {} +}; + +class DispatcherTest : public ::testing::Test { + public: + DispatcherTest() + : ss_sample(), + ss_config(), + ss_metric(), + writer_sample(ss_sample), + writer_config(ss_config), + writer_metric( + std::unique_ptr(&ss_metric)), + dispatcher() {} + + void SetUp() { + ss_sample.str(std::string()); + ss_sample.clear(); + ss_config.str(std::string()); + ss_config.clear(); + ss_metric.str(std::string()); + ss_metric.clear(); + + dispatcher.register_channel( + info_type::CONFIG, + std::unique_ptr( + new stan::callbacks::writer_channel(&writer_config))); + + dispatcher.register_channel( + info_type::SAMPLE, + std::unique_ptr( + new stan::callbacks::writer_channel(&writer_sample))); + + dispatcher.register_channel( + info_type::METRIC, + std::unique_ptr( + new stan::callbacks::structured_writer_channel(&writer_metric))); + } + + void TearDown() {} + + std::stringstream ss_sample; + std::stringstream ss_config; + std::stringstream ss_metric; + + stan::callbacks::stream_writer writer_sample; + stan::callbacks::stream_writer writer_config; + stan::callbacks::json_writer writer_metric; + stan::callbacks::dispatcher dispatcher; +}; + +// Test basic string dispatch to plain writer +TEST_F(DispatcherTest, StringDispatch) { + dispatcher.dispatch(info_type::CONFIG, std::string("Message1")); + EXPECT_EQ(ss_config.str(), "Message1\n"); +} + +// Test multiple string dispatches +TEST_F(DispatcherTest, MultipleStringDispatch) { + dispatcher.dispatch(info_type::CONFIG, std::string("Message1")); + dispatcher.dispatch(info_type::CONFIG, std::string("Message2")); + dispatcher.dispatch(info_type::CONFIG, std::string("Message3")); + EXPECT_EQ(ss_config.str(), "Message1\nMessage2\nMessage3\n"); +} + +// Test empty call dispatch +TEST_F(DispatcherTest, EmptyDispatch) { + dispatcher.dispatch(info_type::CONFIG); + // Empty dispatch should produce just a newline in stream_writer + EXPECT_EQ(ss_config.str(), "\n"); +} + +// Test vector of doubles dispatch +TEST_F(DispatcherTest, VectorDoubleDispatch) { + std::vector values = {1.1, 2.2, 3.3}; + dispatcher.dispatch(info_type::SAMPLE, values); + std::string output = ss_sample.str(); + EXPECT_NE(output.find("1.1"), std::string::npos); + EXPECT_NE(output.find("2.2"), std::string::npos); + EXPECT_NE(output.find("3.3"), std::string::npos); +} + +// Test vector of strings dispatch +TEST_F(DispatcherTest, VectorStringDispatch) { + std::vector names = {"alpha", "beta", "gamma"}; + dispatcher.dispatch(info_type::SAMPLE, names); + std::string output = ss_sample.str(); + EXPECT_NE(output.find("alpha"), std::string::npos); + EXPECT_NE(output.find("beta"), std::string::npos); + EXPECT_NE(output.find("gamma"), std::string::npos); +} + +// Test Eigen matrix dispatch +TEST_F(DispatcherTest, EigenMatrixDispatch) { + Eigen::MatrixXd matrix(2, 2); + matrix << 1.0, 2.0, 3.0, 4.0; + dispatcher.dispatch(info_type::SAMPLE, matrix); + std::string output = ss_sample.str(); + EXPECT_NE(output.find("1"), std::string::npos); + EXPECT_NE(output.find("2"), std::string::npos); + EXPECT_NE(output.find("3"), std::string::npos); + EXPECT_NE(output.find("4"), std::string::npos); +} + +// Test Eigen vector dispatch +TEST_F(DispatcherTest, EigenVectorDispatch) { + Eigen::VectorXd vector(3); + vector << 1.0, 2.0, 3.0; + dispatcher.dispatch(info_type::SAMPLE, vector); + std::string output = ss_sample.str(); + EXPECT_NE(output.find("1"), std::string::npos); + EXPECT_NE(output.find("2"), std::string::npos); + EXPECT_NE(output.find("3"), std::string::npos); +} + +// Test Eigen row vector dispatch +TEST_F(DispatcherTest, EigenRowVectorDispatch) { + Eigen::RowVectorXd vector(3); + vector << 1.0, 2.0, 3.0; + dispatcher.dispatch(info_type::SAMPLE, vector); + std::string output = ss_sample.str(); + EXPECT_NE(output.find("1"), std::string::npos); + EXPECT_NE(output.find("2"), std::string::npos); + EXPECT_NE(output.find("3"), std::string::npos); +} + +// Test structured writer begin/end record +TEST_F(DispatcherTest, StructuredBeginEndRecord) { + dispatcher.begin_record(info_type::METRIC); + dispatcher.end_record(info_type::METRIC); + std::string output = ss_metric.str(); + // JSON output should contain opening and closing braces + EXPECT_NE(output.find("{"), std::string::npos); + EXPECT_NE(output.find("}"), std::string::npos); +} + +TEST_F(DispatcherTest, MetricStructuredKeyValueRecord) { + // For METRIC (structured writer), open a record, dispatch key/value pairs, + // then close the record. + dispatcher.begin_record(info_type::METRIC); + dispatcher.dispatch(info_type::METRIC, "metric_type", std::string("diag")); + dispatcher.dispatch(info_type::METRIC, "stepsize", 0.6789); + std::vector inv_metric = {0.1, 0.2, 0.3}; + dispatcher.dispatch(info_type::METRIC, "inv_metric", inv_metric); + dispatcher.end_record(info_type::METRIC); + // Expected output: + // Begin record marker, followed by key/value pairs each formatted as + // "key:value;" and then end record marker. + std::string output = ss_metric.str(); + EXPECT_NE(output.find("metric_type"), std::string::npos); + EXPECT_NE(output.find("diag"), std::string::npos); +} + +// Test structured writer with multiple key-value types +TEST_F(DispatcherTest, StructuredMultipleValueTypes) { + dispatcher.begin_record(info_type::METRIC); + dispatcher.dispatch(info_type::METRIC, "string_key", + std::string("string_value")); + dispatcher.dispatch(info_type::METRIC, "int_key", 42); + dispatcher.dispatch(info_type::METRIC, "double_key", 3.14159); + dispatcher.dispatch(info_type::METRIC, "bool_key", true); + dispatcher.end_record(info_type::METRIC); + + std::string output = ss_metric.str(); + EXPECT_NE(output.find("string_key"), std::string::npos); + EXPECT_NE(output.find("string_value"), std::string::npos); + EXPECT_NE(output.find("int_key"), std::string::npos); + EXPECT_NE(output.find("42"), std::string::npos); + EXPECT_NE(output.find("double_key"), std::string::npos); + EXPECT_NE(output.find("3.14159"), std::string::npos); + EXPECT_NE(output.find("bool_key"), std::string::npos); + EXPECT_NE(output.find("true"), std::string::npos); +} + +// Test structured writer with Eigen values +TEST_F(DispatcherTest, StructuredEigenValues) { + dispatcher.begin_record(info_type::METRIC); + + Eigen::MatrixXd matrix(2, 2); + matrix << 1.0, 2.0, 3.0, 4.0; + dispatcher.dispatch(info_type::METRIC, "matrix", matrix); + + Eigen::VectorXd vector(3); + vector << 5.0, 6.0, 7.0; + dispatcher.dispatch(info_type::METRIC, "vector", vector); + + dispatcher.end_record(info_type::METRIC); + + std::string output = ss_metric.str(); + EXPECT_NE(output.find("matrix"), std::string::npos); + EXPECT_NE(output.find("1"), std::string::npos); + EXPECT_NE(output.find("4"), std::string::npos); + EXPECT_NE(output.find("vector"), std::string::npos); + EXPECT_NE(output.find("5"), std::string::npos); + EXPECT_NE(output.find("7"), std::string::npos); +} + +// Test unregistered channel +TEST_F(DispatcherTest, UnregisteredChannel) { + // Dispatch to unregistered channel should silently do nothing + dispatcher.dispatch(info_type::ALGORITHM_STATE, std::string("Message")); + dispatcher.dispatch(info_type::ALGORITHM_STATE, std::vector{1.0, 2.0}); + dispatcher.begin_record(info_type::ALGORITHM_STATE); + dispatcher.dispatch(info_type::ALGORITHM_STATE, "key", "value"); + dispatcher.end_record(info_type::ALGORITHM_STATE); + + // No exceptions should be thrown +} + +// Test named record +TEST_F(DispatcherTest, NamedRecord) { + dispatcher.begin_record(info_type::METRIC, "record_name"); + dispatcher.dispatch(info_type::METRIC, "key", "value"); + dispatcher.end_record(info_type::METRIC); + + std::string output = ss_metric.str(); + EXPECT_NE(output.find("record_name"), std::string::npos); + EXPECT_NE(output.find("key"), std::string::npos); + EXPECT_NE(output.find("value"), std::string::npos); +} + +// Test that begin_record and end_record on a plain writer channel are +// silently ignored +TEST_F(DispatcherTest, RecordOperationsOnPlainWriter) { + dispatcher.begin_record(info_type::CONFIG); + dispatcher.end_record(info_type::CONFIG); + + // Should not generate any output + EXPECT_EQ(ss_config.str(), ""); +} From abfa383c0fd25e23691e48ffeb8ae0cb9c41b2bf Mon Sep 17 00:00:00 2001 From: Stan Jenkins Date: Sat, 8 Mar 2025 16:45:19 -0500 Subject: [PATCH 2/6] [Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1 --- src/stan/callbacks/dispatcher.hpp | 7 ++----- src/test/unit/callbacks/dispatcher_test.cpp | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp index 154e33f589..7b01c5164f 100644 --- a/src/stan/callbacks/dispatcher.hpp +++ b/src/stan/callbacks/dispatcher.hpp @@ -22,7 +22,6 @@ namespace callbacks { * the callback registered on the channel. */ - /** * Enum `info_type` holds output type labels which are used by * the dispatcher class to map outputs to output channels. @@ -44,7 +43,6 @@ struct info_type_hash { } }; - /** * Base type for all callbacks, needed for type erasure. */ @@ -53,7 +51,6 @@ class channel { virtual ~channel() = default; }; - /** * A `writer_channel` holds a reference to a stan::callbacks::writer object * and forwards information to the writer's operator (). @@ -121,7 +118,7 @@ class dispatcher { private: /* Lookup registered channels for info_type. * Returns nullptr if no channel found. - */ + */ template channel_type* find_channel(info_type type) { auto it = channels_.find(type); @@ -139,7 +136,7 @@ class dispatcher { /* Add channel to map. * Assumes a 1:1 mapping between info type and callback. - */ + */ void register_channel(info_type type, std::unique_ptr channel) { channels_[type] = std::move(channel); } diff --git a/src/test/unit/callbacks/dispatcher_test.cpp b/src/test/unit/callbacks/dispatcher_test.cpp index b24340ef90..7ffbe95cd0 100644 --- a/src/test/unit/callbacks/dispatcher_test.cpp +++ b/src/test/unit/callbacks/dispatcher_test.cpp @@ -216,7 +216,8 @@ TEST_F(DispatcherTest, StructuredEigenValues) { TEST_F(DispatcherTest, UnregisteredChannel) { // Dispatch to unregistered channel should silently do nothing dispatcher.dispatch(info_type::ALGORITHM_STATE, std::string("Message")); - dispatcher.dispatch(info_type::ALGORITHM_STATE, std::vector{1.0, 2.0}); + dispatcher.dispatch(info_type::ALGORITHM_STATE, + std::vector{1.0, 2.0}); dispatcher.begin_record(info_type::ALGORITHM_STATE); dispatcher.dispatch(info_type::ALGORITHM_STATE, "key", "value"); dispatcher.end_record(info_type::ALGORITHM_STATE); From 16bed96f5eaa7f6f421ba4a678d3d8ad90d04edc Mon Sep 17 00:00:00 2001 From: Mitzi Morris Date: Sun, 9 Mar 2025 10:10:01 -0400 Subject: [PATCH 3/6] add utility to configure a dispatcher --- src/stan/callbacks/dispatcher.hpp | 55 +++++++-- .../services/util/configure_dispatcher.hpp | 92 +++++++++++++++ src/test/unit/callbacks/dispatcher_test.cpp | 38 +++--- .../util/configure_dispatcher_test.cpp | 110 ++++++++++++++++++ 4 files changed, 270 insertions(+), 25 deletions(-) create mode 100644 src/stan/services/util/configure_dispatcher.hpp create mode 100644 src/test/unit/services/util/configure_dispatcher_test.cpp diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp index 7b01c5164f..4d8981fdd5 100644 --- a/src/stan/callbacks/dispatcher.hpp +++ b/src/stan/callbacks/dispatcher.hpp @@ -32,6 +32,8 @@ enum class info_type { SAMPLE_RAW, // draw from posterior METRIC, // struct with kv pairs 'metric_type', 'stepsize', 'inv_metric' ALGORITHM_STATE, // sampler state for returned draw + DIAGNOSTIC, // parameter gradients + UNCONSTRAINED_INITS // unconstrained parameter values }; /** @@ -118,7 +120,7 @@ class dispatcher { private: /* Lookup registered channels for info_type. * Returns nullptr if no channel found. - */ + */ template channel_type* find_channel(info_type type) { auto it = channels_.find(type); @@ -129,14 +131,45 @@ class dispatcher { std::unordered_map, info_type_hash> channels_; + + // Store managed resources to ensure they live as long as the dispatcher + std::vector> managed_resources_; public: dispatcher() = default; + + // Delete copy constructor and assignment operator since we have unique_ptrs + dispatcher(const dispatcher&) = delete; + dispatcher& operator=(const dispatcher&) = delete; + + // Add move constructor and assignment operator + dispatcher(dispatcher&& other) noexcept + : channels_(std::move(other.channels_)), + managed_resources_(std::move(other.managed_resources_)) {} + + dispatcher& operator=(dispatcher&& other) noexcept { + if (this != &other) { + channels_ = std::move(other.channels_); + managed_resources_ = std::move(other.managed_resources_); + } + return *this; + } + ~dispatcher() = default; + /** + * Add a resource to be managed by the dispatcher. + * The resource will be kept alive for the lifetime of the dispatcher. + * + * @param resource Shared pointer to the resource to manage + */ + void add_managed_resource(std::shared_ptr resource) { + managed_resources_.push_back(std::move(resource)); + } + /* Add channel to map. * Assumes a 1:1 mapping between info type and callback. - */ + */ void register_channel(info_type type, std::unique_ptr channel) { channels_[type] = std::move(channel); } @@ -147,16 +180,16 @@ class dispatcher { wc->dispatch(); } - // Value is std::vector, or std::vector - // clang-format off - template , std::vector> - || std::is_same_v, std::vector>>> // NOLINT - // clang-format on - void dispatch(info_type type, T&& value) { + // Dispatch for vector + void dispatch(info_type type, const std::vector& value) { + if (auto* wc = find_channel(type)) + wc->dispatch(value); + } + + // Dispatch for vector + void dispatch(info_type type, const std::vector& value) { if (auto* wc = find_channel(type)) - wc->dispatch(std::forward(value)); + wc->dispatch(value); } // Value is Eigen vector or matrix diff --git a/src/stan/services/util/configure_dispatcher.hpp b/src/stan/services/util/configure_dispatcher.hpp new file mode 100644 index 0000000000..e0d752168c --- /dev/null +++ b/src/stan/services/util/configure_dispatcher.hpp @@ -0,0 +1,92 @@ +#ifndef STAN_SERVICES_UTIL_CONFIGURE_DISPATCHER_HPP +#define STAN_SERVICES_UTIL_CONFIGURE_DISPATCHER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace stan { +namespace services { +namespace util { + +/** + * Creates and configures a dispatcher with appropriate channels based on + * the provided mapping from info_type to output streams. + * + * @param[in] output_streams Map from info_type to shared_ptr + * @return A configured dispatcher object + */ +callbacks::dispatcher configure_dispatcher( + std::unordered_map, + callbacks::info_type_hash> output_streams) { + callbacks::dispatcher dispatcher; + + for (auto& pair : output_streams) { + callbacks::info_type type = pair.first; + std::shared_ptr stream_ptr = pair.second; + + if (!stream_ptr) { + std::stringstream ss; + ss << "Stream for info_type " << static_cast(type) << " is null"; + throw std::runtime_error(ss.str()); + } + + // Create appropriate channel based on info_type + switch (type) { + case callbacks::info_type::METRIC: { + // For METRIC, use a structured_writer_channel with json_writer + struct deleter_noop { + void operator()(std::ostream* ptr) const {} + }; + + auto json_writer = std::make_shared>( + std::unique_ptr(stream_ptr.get())); + + // Add the writer to the managed resources + dispatcher.add_managed_resource(json_writer); + + // Create channel using the raw pointer from the shared_ptr + auto channel = std::make_unique(json_writer.get()); + dispatcher.register_channel(type, std::move(channel)); + break; + } + case callbacks::info_type::UNCONSTRAINED_INITS: + case callbacks::info_type::SAMPLE: + case callbacks::info_type::SAMPLE_RAW: + case callbacks::info_type::CONFIG: + case callbacks::info_type::DIAGNOSTIC: { + // For other types, use a writer_channel with unique_stream_writer + struct deleter_noop { + void operator()(std::ostream* ptr) const {} + }; + + auto stream_writer = std::make_shared>( + std::unique_ptr(stream_ptr.get())); + + // Add the writer to the managed resources + dispatcher.add_managed_resource(stream_writer); + + // Create channel using the raw pointer from the shared_ptr + auto channel = std::make_unique(stream_writer.get()); + dispatcher.register_channel(type, std::move(channel)); + break; + } + default: + std::stringstream ss; + ss << "Unknown info_type " << static_cast(type) << " in configure_dispatcher"; + throw std::runtime_error(ss.str()); + } + } + + return dispatcher; +} + +} // namespace util +} // namespace services +} // namespace stan +#endif diff --git a/src/test/unit/callbacks/dispatcher_test.cpp b/src/test/unit/callbacks/dispatcher_test.cpp index 7ffbe95cd0..4a45b51206 100644 --- a/src/test/unit/callbacks/dispatcher_test.cpp +++ b/src/test/unit/callbacks/dispatcher_test.cpp @@ -24,11 +24,13 @@ class DispatcherTest : public ::testing::Test { : ss_sample(), ss_config(), ss_metric(), - writer_sample(ss_sample), - writer_config(ss_config), - writer_metric( - std::unique_ptr(&ss_metric)), - dispatcher() {} + dispatcher() { + // Create shared writers + writer_sample = std::make_shared(ss_sample); + writer_config = std::make_shared(ss_config); + writer_metric = std::make_shared>( + std::unique_ptr(&ss_metric)); + } void SetUp() { ss_sample.str(std::string()); @@ -38,20 +40,26 @@ class DispatcherTest : public ::testing::Test { ss_metric.str(std::string()); ss_metric.clear(); + // Add managed resources + dispatcher.add_managed_resource(writer_sample); + dispatcher.add_managed_resource(writer_config); + dispatcher.add_managed_resource(writer_metric); + + // Register channels dispatcher.register_channel( info_type::CONFIG, std::unique_ptr( - new stan::callbacks::writer_channel(&writer_config))); - + new stan::callbacks::writer_channel(writer_config.get()))); + dispatcher.register_channel( info_type::SAMPLE, std::unique_ptr( - new stan::callbacks::writer_channel(&writer_sample))); - + new stan::callbacks::writer_channel(writer_sample.get()))); + dispatcher.register_channel( info_type::METRIC, std::unique_ptr( - new stan::callbacks::structured_writer_channel(&writer_metric))); + new stan::callbacks::structured_writer_channel(writer_metric.get()))); } void TearDown() {} @@ -59,13 +67,15 @@ class DispatcherTest : public ::testing::Test { std::stringstream ss_sample; std::stringstream ss_config; std::stringstream ss_metric; - - stan::callbacks::stream_writer writer_sample; - stan::callbacks::stream_writer writer_config; - stan::callbacks::json_writer writer_metric; + + std::shared_ptr writer_sample; + std::shared_ptr writer_config; + std::shared_ptr> writer_metric; + stan::callbacks::dispatcher dispatcher; }; + // Test basic string dispatch to plain writer TEST_F(DispatcherTest, StringDispatch) { dispatcher.dispatch(info_type::CONFIG, std::string("Message1")); diff --git a/src/test/unit/services/util/configure_dispatcher_test.cpp b/src/test/unit/services/util/configure_dispatcher_test.cpp new file mode 100644 index 0000000000..a23a492121 --- /dev/null +++ b/src/test/unit/services/util/configure_dispatcher_test.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include + +class ConfigureDispatcherTest : public ::testing::Test { +protected: + void SetUp() override { + // Create our stringstreams as shared_ptrs + sample_stream = std::make_shared(); + metric_stream = std::make_shared(); + diagnostic_stream = std::make_shared(); + init_stream = std::make_shared(); + } + + std::shared_ptr sample_stream; + std::shared_ptr metric_stream; + std::shared_ptr diagnostic_stream; + std::shared_ptr init_stream; +}; + +TEST_F(ConfigureDispatcherTest, BasicFunctionality) { + // Create a map of info_types to stream pointers + std::unordered_map, + stan::callbacks::info_type_hash> output_streams; + + // Add our streams to the map - cast to std::ostream base class + output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; + output_streams[stan::callbacks::info_type::METRIC] = metric_stream; + output_streams[stan::callbacks::info_type::DIAGNOSTIC] = diagnostic_stream; + output_streams[stan::callbacks::info_type::UNCONSTRAINED_INITS] = init_stream; + + // Call configure_dispatcher + auto dispatcher = stan::services::util::configure_dispatcher(output_streams); + + // Test the functionality + dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, std::string("sample_message")); + dispatcher.dispatch(stan::callbacks::info_type::DIAGNOSTIC, std::string("diagnostic_message")); + + dispatcher.begin_record(stan::callbacks::info_type::METRIC); + dispatcher.dispatch(stan::callbacks::info_type::METRIC, "key", "value"); + dispatcher.end_record(stan::callbacks::info_type::METRIC); + + dispatcher.dispatch(stan::callbacks::info_type::UNCONSTRAINED_INITS, std::string("init_message")); + + // Verify output in our streams + EXPECT_TRUE(sample_stream->str().find("sample_message") != std::string::npos); + EXPECT_TRUE(diagnostic_stream->str().find("diagnostic_message") != std::string::npos); + EXPECT_TRUE(metric_stream->str().find("key") != std::string::npos); + EXPECT_TRUE(metric_stream->str().find("value") != std::string::npos); + EXPECT_TRUE(init_stream->str().find("init_message") != std::string::npos); +} + +TEST_F(ConfigureDispatcherTest, NullStream) { + std::unordered_map, + stan::callbacks::info_type_hash> output_streams; + + // Add a null shared_ptr + output_streams[stan::callbacks::info_type::SAMPLE] = nullptr; + + // Should throw an exception + EXPECT_THROW( + stan::services::util::configure_dispatcher(output_streams), + std::runtime_error + ); +} + +TEST_F(ConfigureDispatcherTest, MoveSemantics) { + // Create a map of info_types to stream pointers + std::unordered_map, + stan::callbacks::info_type_hash> output_streams; + + // Add our streams to the map + output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; + + // Create first dispatcher + auto dispatcher1 = stan::services::util::configure_dispatcher(output_streams); + + // Move dispatcher to a new object + auto dispatcher2 = std::move(dispatcher1); + + // Test that the moved dispatcher works + dispatcher2.dispatch(stan::callbacks::info_type::SAMPLE, std::string("moved_message")); + + // Verify output + EXPECT_TRUE(sample_stream->str().find("moved_message") != std::string::npos); +} + +// Test that resources are properly managed +TEST_F(ConfigureDispatcherTest, ResourceManagement) { + // Create a scope for the dispatcher + { + std::unordered_map, + stan::callbacks::info_type_hash> output_streams; + + // Add our streams to the map + output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; + + // Create the dispatcher (will go out of scope at the end of this block) + auto dispatcher = stan::services::util::configure_dispatcher(output_streams); + + // Use the dispatcher + dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, std::string("test_message")); + } + + // The stream should still be valid and contain our message + EXPECT_TRUE(sample_stream->good()); + EXPECT_TRUE(sample_stream->str().find("test_message") != std::string::npos); +} From adffc1d51dab2e0727030547c7820ccde63f1f6e Mon Sep 17 00:00:00 2001 From: Stan Jenkins Date: Sun, 9 Mar 2025 10:10:17 -0400 Subject: [PATCH 4/6] [Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1 --- src/stan/callbacks/dispatcher.hpp | 22 ++--- .../services/util/configure_dispatcher.hpp | 50 ++++++----- src/test/unit/callbacks/dispatcher_test.cpp | 27 +++--- .../util/configure_dispatcher_test.cpp | 86 +++++++++++-------- 4 files changed, 99 insertions(+), 86 deletions(-) diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp index 4d8981fdd5..c74f6f39b6 100644 --- a/src/stan/callbacks/dispatcher.hpp +++ b/src/stan/callbacks/dispatcher.hpp @@ -31,8 +31,8 @@ enum class info_type { SAMPLE, // draw from posterior SAMPLE_RAW, // draw from posterior METRIC, // struct with kv pairs 'metric_type', 'stepsize', 'inv_metric' - ALGORITHM_STATE, // sampler state for returned draw - DIAGNOSTIC, // parameter gradients + ALGORITHM_STATE, // sampler state for returned draw + DIAGNOSTIC, // parameter gradients UNCONSTRAINED_INITS // unconstrained parameter values }; @@ -120,7 +120,7 @@ class dispatcher { private: /* Lookup registered channels for info_type. * Returns nullptr if no channel found. - */ + */ template channel_type* find_channel(info_type type) { auto it = channels_.find(type); @@ -131,22 +131,22 @@ class dispatcher { std::unordered_map, info_type_hash> channels_; - + // Store managed resources to ensure they live as long as the dispatcher std::vector> managed_resources_; public: dispatcher() = default; - + // Delete copy constructor and assignment operator since we have unique_ptrs dispatcher(const dispatcher&) = delete; dispatcher& operator=(const dispatcher&) = delete; - + // Add move constructor and assignment operator - dispatcher(dispatcher&& other) noexcept + dispatcher(dispatcher&& other) noexcept : channels_(std::move(other.channels_)), managed_resources_(std::move(other.managed_resources_)) {} - + dispatcher& operator=(dispatcher&& other) noexcept { if (this != &other) { channels_ = std::move(other.channels_); @@ -154,13 +154,13 @@ class dispatcher { } return *this; } - + ~dispatcher() = default; /** * Add a resource to be managed by the dispatcher. * The resource will be kept alive for the lifetime of the dispatcher. - * + * * @param resource Shared pointer to the resource to manage */ void add_managed_resource(std::shared_ptr resource) { @@ -169,7 +169,7 @@ class dispatcher { /* Add channel to map. * Assumes a 1:1 mapping between info type and callback. - */ + */ void register_channel(info_type type, std::unique_ptr channel) { channels_[type] = std::move(channel); } diff --git a/src/stan/services/util/configure_dispatcher.hpp b/src/stan/services/util/configure_dispatcher.hpp index e0d752168c..46d827587f 100644 --- a/src/stan/services/util/configure_dispatcher.hpp +++ b/src/stan/services/util/configure_dispatcher.hpp @@ -15,27 +15,28 @@ namespace services { namespace util { /** - * Creates and configures a dispatcher with appropriate channels based on + * Creates and configures a dispatcher with appropriate channels based on * the provided mapping from info_type to output streams. - * + * * @param[in] output_streams Map from info_type to shared_ptr * @return A configured dispatcher object */ callbacks::dispatcher configure_dispatcher( - std::unordered_map, - callbacks::info_type_hash> output_streams) { + std::unordered_map, + callbacks::info_type_hash> + output_streams) { callbacks::dispatcher dispatcher; - + for (auto& pair : output_streams) { callbacks::info_type type = pair.first; std::shared_ptr stream_ptr = pair.second; - + if (!stream_ptr) { std::stringstream ss; ss << "Stream for info_type " << static_cast(type) << " is null"; throw std::runtime_error(ss.str()); } - + // Create appropriate channel based on info_type switch (type) { case callbacks::info_type::METRIC: { @@ -43,15 +44,17 @@ callbacks::dispatcher configure_dispatcher( struct deleter_noop { void operator()(std::ostream* ptr) const {} }; - - auto json_writer = std::make_shared>( + + auto json_writer = std::make_shared< + callbacks::json_writer>( std::unique_ptr(stream_ptr.get())); - + // Add the writer to the managed resources dispatcher.add_managed_resource(json_writer); - + // Create channel using the raw pointer from the shared_ptr - auto channel = std::make_unique(json_writer.get()); + auto channel = std::make_unique( + json_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } @@ -64,29 +67,32 @@ callbacks::dispatcher configure_dispatcher( struct deleter_noop { void operator()(std::ostream* ptr) const {} }; - - auto stream_writer = std::make_shared>( + + auto stream_writer = std::make_shared< + callbacks::unique_stream_writer>( std::unique_ptr(stream_ptr.get())); - + // Add the writer to the managed resources dispatcher.add_managed_resource(stream_writer); - + // Create channel using the raw pointer from the shared_ptr - auto channel = std::make_unique(stream_writer.get()); + auto channel + = std::make_unique(stream_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } default: std::stringstream ss; - ss << "Unknown info_type " << static_cast(type) << " in configure_dispatcher"; + ss << "Unknown info_type " << static_cast(type) + << " in configure_dispatcher"; throw std::runtime_error(ss.str()); } } - + return dispatcher; } -} // namespace util -} // namespace services -} // namespace stan +} // namespace util +} // namespace services +} // namespace stan #endif diff --git a/src/test/unit/callbacks/dispatcher_test.cpp b/src/test/unit/callbacks/dispatcher_test.cpp index 4a45b51206..23707cbe61 100644 --- a/src/test/unit/callbacks/dispatcher_test.cpp +++ b/src/test/unit/callbacks/dispatcher_test.cpp @@ -20,15 +20,12 @@ struct deleter_noop { class DispatcherTest : public ::testing::Test { public: - DispatcherTest() - : ss_sample(), - ss_config(), - ss_metric(), - dispatcher() { + DispatcherTest() : ss_sample(), ss_config(), ss_metric(), dispatcher() { // Create shared writers writer_sample = std::make_shared(ss_sample); writer_config = std::make_shared(ss_config); - writer_metric = std::make_shared>( + writer_metric = std::make_shared< + stan::callbacks::json_writer>( std::unique_ptr(&ss_metric)); } @@ -50,16 +47,16 @@ class DispatcherTest : public ::testing::Test { info_type::CONFIG, std::unique_ptr( new stan::callbacks::writer_channel(writer_config.get()))); - + dispatcher.register_channel( info_type::SAMPLE, std::unique_ptr( new stan::callbacks::writer_channel(writer_sample.get()))); - + dispatcher.register_channel( - info_type::METRIC, - std::unique_ptr( - new stan::callbacks::structured_writer_channel(writer_metric.get()))); + info_type::METRIC, std::unique_ptr( + new stan::callbacks::structured_writer_channel( + writer_metric.get()))); } void TearDown() {} @@ -67,15 +64,15 @@ class DispatcherTest : public ::testing::Test { std::stringstream ss_sample; std::stringstream ss_config; std::stringstream ss_metric; - + std::shared_ptr writer_sample; std::shared_ptr writer_config; - std::shared_ptr> writer_metric; - + std::shared_ptr> + writer_metric; + stan::callbacks::dispatcher dispatcher; }; - // Test basic string dispatch to plain writer TEST_F(DispatcherTest, StringDispatch) { dispatcher.dispatch(info_type::CONFIG, std::string("Message1")); diff --git a/src/test/unit/services/util/configure_dispatcher_test.cpp b/src/test/unit/services/util/configure_dispatcher_test.cpp index a23a492121..c4ad502522 100644 --- a/src/test/unit/services/util/configure_dispatcher_test.cpp +++ b/src/test/unit/services/util/configure_dispatcher_test.cpp @@ -5,7 +5,7 @@ #include class ConfigureDispatcherTest : public ::testing::Test { -protected: + protected: void SetUp() override { // Create our stringstreams as shared_ptrs sample_stream = std::make_shared(); @@ -13,7 +13,7 @@ class ConfigureDispatcherTest : public ::testing::Test { diagnostic_stream = std::make_shared(); init_stream = std::make_shared(); } - + std::shared_ptr sample_stream; std::shared_ptr metric_stream; std::shared_ptr diagnostic_stream; @@ -22,67 +22,73 @@ class ConfigureDispatcherTest : public ::testing::Test { TEST_F(ConfigureDispatcherTest, BasicFunctionality) { // Create a map of info_types to stream pointers - std::unordered_map, - stan::callbacks::info_type_hash> output_streams; - + std::unordered_map, + stan::callbacks::info_type_hash> + output_streams; + // Add our streams to the map - cast to std::ostream base class output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; output_streams[stan::callbacks::info_type::METRIC] = metric_stream; output_streams[stan::callbacks::info_type::DIAGNOSTIC] = diagnostic_stream; output_streams[stan::callbacks::info_type::UNCONSTRAINED_INITS] = init_stream; - + // Call configure_dispatcher auto dispatcher = stan::services::util::configure_dispatcher(output_streams); - + // Test the functionality - dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, std::string("sample_message")); - dispatcher.dispatch(stan::callbacks::info_type::DIAGNOSTIC, std::string("diagnostic_message")); - + dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, + std::string("sample_message")); + dispatcher.dispatch(stan::callbacks::info_type::DIAGNOSTIC, + std::string("diagnostic_message")); + dispatcher.begin_record(stan::callbacks::info_type::METRIC); dispatcher.dispatch(stan::callbacks::info_type::METRIC, "key", "value"); dispatcher.end_record(stan::callbacks::info_type::METRIC); - - dispatcher.dispatch(stan::callbacks::info_type::UNCONSTRAINED_INITS, std::string("init_message")); - + + dispatcher.dispatch(stan::callbacks::info_type::UNCONSTRAINED_INITS, + std::string("init_message")); + // Verify output in our streams EXPECT_TRUE(sample_stream->str().find("sample_message") != std::string::npos); - EXPECT_TRUE(diagnostic_stream->str().find("diagnostic_message") != std::string::npos); + EXPECT_TRUE(diagnostic_stream->str().find("diagnostic_message") + != std::string::npos); EXPECT_TRUE(metric_stream->str().find("key") != std::string::npos); EXPECT_TRUE(metric_stream->str().find("value") != std::string::npos); EXPECT_TRUE(init_stream->str().find("init_message") != std::string::npos); } TEST_F(ConfigureDispatcherTest, NullStream) { - std::unordered_map, - stan::callbacks::info_type_hash> output_streams; - + std::unordered_map, + stan::callbacks::info_type_hash> + output_streams; + // Add a null shared_ptr output_streams[stan::callbacks::info_type::SAMPLE] = nullptr; - + // Should throw an exception - EXPECT_THROW( - stan::services::util::configure_dispatcher(output_streams), - std::runtime_error - ); + EXPECT_THROW(stan::services::util::configure_dispatcher(output_streams), + std::runtime_error); } TEST_F(ConfigureDispatcherTest, MoveSemantics) { // Create a map of info_types to stream pointers - std::unordered_map, - stan::callbacks::info_type_hash> output_streams; - + std::unordered_map, + stan::callbacks::info_type_hash> + output_streams; + // Add our streams to the map output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; - + // Create first dispatcher auto dispatcher1 = stan::services::util::configure_dispatcher(output_streams); - + // Move dispatcher to a new object auto dispatcher2 = std::move(dispatcher1); - + // Test that the moved dispatcher works - dispatcher2.dispatch(stan::callbacks::info_type::SAMPLE, std::string("moved_message")); - + dispatcher2.dispatch(stan::callbacks::info_type::SAMPLE, + std::string("moved_message")); + // Verify output EXPECT_TRUE(sample_stream->str().find("moved_message") != std::string::npos); } @@ -91,19 +97,23 @@ TEST_F(ConfigureDispatcherTest, MoveSemantics) { TEST_F(ConfigureDispatcherTest, ResourceManagement) { // Create a scope for the dispatcher { - std::unordered_map, - stan::callbacks::info_type_hash> output_streams; - + std::unordered_map, + stan::callbacks::info_type_hash> + output_streams; + // Add our streams to the map output_streams[stan::callbacks::info_type::SAMPLE] = sample_stream; - + // Create the dispatcher (will go out of scope at the end of this block) - auto dispatcher = stan::services::util::configure_dispatcher(output_streams); - + auto dispatcher + = stan::services::util::configure_dispatcher(output_streams); + // Use the dispatcher - dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, std::string("test_message")); + dispatcher.dispatch(stan::callbacks::info_type::SAMPLE, + std::string("test_message")); } - + // The stream should still be valid and contain our message EXPECT_TRUE(sample_stream->good()); EXPECT_TRUE(sample_stream->str().find("test_message") != std::string::npos); From ab716bf4d4c5ea49b0edf248bc91f6018229456e Mon Sep 17 00:00:00 2001 From: Mitzi Morris Date: Sun, 9 Mar 2025 10:58:55 -0400 Subject: [PATCH 5/6] code, comment tweaks --- src/stan/callbacks/dispatcher.hpp | 77 +++++++++++++++---- .../services/util/configure_dispatcher.hpp | 72 ++++++++--------- 2 files changed, 97 insertions(+), 52 deletions(-) diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp index c74f6f39b6..5424d283f7 100644 --- a/src/stan/callbacks/dispatcher.hpp +++ b/src/stan/callbacks/dispatcher.hpp @@ -132,17 +132,15 @@ class dispatcher { std::unordered_map, info_type_hash> channels_; - // Store managed resources to ensure they live as long as the dispatcher + // neccesary for proper handling of shared ptrs std::vector> managed_resources_; public: dispatcher() = default; - // Delete copy constructor and assignment operator since we have unique_ptrs dispatcher(const dispatcher&) = delete; dispatcher& operator=(const dispatcher&) = delete; - // Add move constructor and assignment operator dispatcher(dispatcher&& other) noexcept : channels_(std::move(other.channels_)), managed_resources_(std::move(other.managed_resources_)) {} @@ -158,8 +156,7 @@ class dispatcher { ~dispatcher() = default; /** - * Add a resource to be managed by the dispatcher. - * The resource will be kept alive for the lifetime of the dispatcher. + * Managed resources are kept alive for the lifetime of the dispatcher. * * @param resource Shared pointer to the resource to manage */ @@ -167,39 +164,71 @@ class dispatcher { managed_resources_.push_back(std::move(resource)); } - /* Add channel to map. + + /** + * Add channel to map. * Assumes a 1:1 mapping between info type and callback. + * + * @param[in] type The info_type to associate with the channel + * @param[in] channel A unique_ptr to the channel to register */ void register_channel(info_type type, std::unique_ptr channel) { channels_[type] = std::move(channel); } - // no-arg call to writer operator () + /** + * Dispatches a no-argument call to the writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + */ void dispatch(info_type type) { if (auto* wc = find_channel(type)) wc->dispatch(); } - // Dispatch for vector + /** + * Dispatches a vector of doubles to the writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] value Vector of doubles to be written + */ void dispatch(info_type type, const std::vector& value) { if (auto* wc = find_channel(type)) wc->dispatch(value); } - // Dispatch for vector + /** + * Dispatches a vector of strings to the writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] value Vector of strings to be written + */ void dispatch(info_type type, const std::vector& value) { if (auto* wc = find_channel(type)) wc->dispatch(value); } - // Value is Eigen vector or matrix + /** + * Dispatches an Eigen matrix to the writer associated with the given type. + * + * @tparam R Number of rows in the matrix (-1 for dynamic) + * @tparam C Number of columns in the matrix (-1 for dynamic) + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] value Eigen matrix to be written + */ template void dispatch(info_type type, const Eigen::Matrix& value) { if (auto* wc = find_channel(type)) wc->dispatch(value); } - // Value is std::string + /** + * Dispatches a string to the writer associated with the given type. + * For structured writers, the string is treated as a key. + * + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] value String to be written + */ void dispatch(info_type type, const std::string& value) { if (auto* wc = find_channel(type)) wc->dispatch(value); @@ -207,24 +236,46 @@ class dispatcher { sw->dispatch(value); // (sic: actually the key part of k-v pair) } - // Key-value pairs (forward to structured writers) + /** + * Dispatches a key-value pair to the structured writer associated with the given type. + * + * @tparam T Type of the value to be written + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] key Key string for the key-value pair + * @param[in] value Value to be written + */ template void dispatch(info_type type, const std::string& key, T&& value) { if (auto* sw = find_channel(type)) sw->dispatch(key, std::forward(value)); } - // Record operations + /** + * Begins a record in the structured writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + */ void begin_record(info_type type) { if (auto* sw = find_channel(type)) sw->begin_record(); } + /** + * Begins a named record in the structured writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + * @param[in] key Name of the record to begin + */ void begin_record(info_type type, const std::string& key) { if (auto* sw = find_channel(type)) sw->begin_record(key); } + /** + * Ends a record in the structured writer associated with the given type. + * + * @param[in] type The info_type identifying the channel to dispatch to + */ void end_record(info_type type) { if (auto* sw = find_channel(type)) sw->end_record(); diff --git a/src/stan/services/util/configure_dispatcher.hpp b/src/stan/services/util/configure_dispatcher.hpp index 46d827587f..1d1564cca2 100644 --- a/src/stan/services/util/configure_dispatcher.hpp +++ b/src/stan/services/util/configure_dispatcher.hpp @@ -15,46 +15,50 @@ namespace services { namespace util { /** - * Creates and configures a dispatcher with appropriate channels based on + * Custom deleter that doesn't delete the pointer. + * + * This is used to create unique_ptr wrappers around stream pointers that are + * already managed by shared_ptr objects. The writers need to take ownership + * via unique_ptr, but we don't want double-deletion when the dispatcher is + * destroyed. Safe because the shared_ptr in output_streams maintains the + * actual ownership and controls the lifetime of these streams. + */ +struct deleter_noop { + template + void operator()(T* ptr) const {} +}; + + +/** + * Creates and configures a dispatcher with appropriate channels based on * the provided mapping from info_type to output streams. - * + * * @param[in] output_streams Map from info_type to shared_ptr * @return A configured dispatcher object */ callbacks::dispatcher configure_dispatcher( - std::unordered_map, - callbacks::info_type_hash> - output_streams) { + std::unordered_map, + callbacks::info_type_hash> output_streams) { callbacks::dispatcher dispatcher; - + for (auto& pair : output_streams) { callbacks::info_type type = pair.first; std::shared_ptr stream_ptr = pair.second; - + if (!stream_ptr) { std::stringstream ss; ss << "Stream for info_type " << static_cast(type) << " is null"; throw std::runtime_error(ss.str()); } - - // Create appropriate channel based on info_type + switch (type) { case callbacks::info_type::METRIC: { - // For METRIC, use a structured_writer_channel with json_writer - struct deleter_noop { - void operator()(std::ostream* ptr) const {} - }; - - auto json_writer = std::make_shared< - callbacks::json_writer>( + auto json_writer = std::make_shared>( std::unique_ptr(stream_ptr.get())); - // Add the writer to the managed resources dispatcher.add_managed_resource(json_writer); - // Create channel using the raw pointer from the shared_ptr - auto channel = std::make_unique( - json_writer.get()); + auto channel = std::make_unique(json_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } @@ -63,36 +67,26 @@ callbacks::dispatcher configure_dispatcher( case callbacks::info_type::SAMPLE_RAW: case callbacks::info_type::CONFIG: case callbacks::info_type::DIAGNOSTIC: { - // For other types, use a writer_channel with unique_stream_writer - struct deleter_noop { - void operator()(std::ostream* ptr) const {} - }; - - auto stream_writer = std::make_shared< - callbacks::unique_stream_writer>( + auto stream_writer = std::make_shared>( std::unique_ptr(stream_ptr.get())); - - // Add the writer to the managed resources + dispatcher.add_managed_resource(stream_writer); - - // Create channel using the raw pointer from the shared_ptr - auto channel - = std::make_unique(stream_writer.get()); + + auto channel = std::make_unique(stream_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } default: std::stringstream ss; - ss << "Unknown info_type " << static_cast(type) - << " in configure_dispatcher"; + ss << "Unknown info_type " << static_cast(type) << " in configure_dispatcher"; throw std::runtime_error(ss.str()); } } - + return dispatcher; } -} // namespace util -} // namespace services -} // namespace stan +} // namespace util +} // namespace services +} // namespace stan #endif From c425cc654afbb5d77b6121cef823d2cffaef0ddc Mon Sep 17 00:00:00 2001 From: Stan Jenkins Date: Sun, 9 Mar 2025 10:59:07 -0400 Subject: [PATCH 6/6] [Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1 --- src/stan/callbacks/dispatcher.hpp | 13 +++-- .../services/util/configure_dispatcher.hpp | 47 ++++++++++--------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/stan/callbacks/dispatcher.hpp b/src/stan/callbacks/dispatcher.hpp index 5424d283f7..d8f8d391ae 100644 --- a/src/stan/callbacks/dispatcher.hpp +++ b/src/stan/callbacks/dispatcher.hpp @@ -164,7 +164,6 @@ class dispatcher { managed_resources_.push_back(std::move(resource)); } - /** * Add channel to map. * Assumes a 1:1 mapping between info type and callback. @@ -187,7 +186,8 @@ class dispatcher { } /** - * Dispatches a vector of doubles to the writer associated with the given type. + * Dispatches a vector of doubles to the writer associated with the given + * type. * * @param[in] type The info_type identifying the channel to dispatch to * @param[in] value Vector of doubles to be written @@ -198,7 +198,8 @@ class dispatcher { } /** - * Dispatches a vector of strings to the writer associated with the given type. + * Dispatches a vector of strings to the writer associated with the given + * type. * * @param[in] type The info_type identifying the channel to dispatch to * @param[in] value Vector of strings to be written @@ -237,7 +238,8 @@ class dispatcher { } /** - * Dispatches a key-value pair to the structured writer associated with the given type. + * Dispatches a key-value pair to the structured writer associated with the + * given type. * * @tparam T Type of the value to be written * @param[in] type The info_type identifying the channel to dispatch to @@ -261,7 +263,8 @@ class dispatcher { } /** - * Begins a named record in the structured writer associated with the given type. + * Begins a named record in the structured writer associated with the given + * type. * * @param[in] type The info_type identifying the channel to dispatch to * @param[in] key Name of the record to begin diff --git a/src/stan/services/util/configure_dispatcher.hpp b/src/stan/services/util/configure_dispatcher.hpp index 1d1564cca2..cc6c32d45f 100644 --- a/src/stan/services/util/configure_dispatcher.hpp +++ b/src/stan/services/util/configure_dispatcher.hpp @@ -16,11 +16,11 @@ namespace util { /** * Custom deleter that doesn't delete the pointer. - * + * * This is used to create unique_ptr wrappers around stream pointers that are * already managed by shared_ptr objects. The writers need to take ownership * via unique_ptr, but we don't want double-deletion when the dispatcher is - * destroyed. Safe because the shared_ptr in output_streams maintains the + * destroyed. Safe because the shared_ptr in output_streams maintains the * actual ownership and controls the lifetime of these streams. */ struct deleter_noop { @@ -28,37 +28,39 @@ struct deleter_noop { void operator()(T* ptr) const {} }; - /** - * Creates and configures a dispatcher with appropriate channels based on + * Creates and configures a dispatcher with appropriate channels based on * the provided mapping from info_type to output streams. - * + * * @param[in] output_streams Map from info_type to shared_ptr * @return A configured dispatcher object */ callbacks::dispatcher configure_dispatcher( - std::unordered_map, - callbacks::info_type_hash> output_streams) { + std::unordered_map, + callbacks::info_type_hash> + output_streams) { callbacks::dispatcher dispatcher; - + for (auto& pair : output_streams) { callbacks::info_type type = pair.first; std::shared_ptr stream_ptr = pair.second; - + if (!stream_ptr) { std::stringstream ss; ss << "Stream for info_type " << static_cast(type) << " is null"; throw std::runtime_error(ss.str()); } - + switch (type) { case callbacks::info_type::METRIC: { - auto json_writer = std::make_shared>( + auto json_writer = std::make_shared< + callbacks::json_writer>( std::unique_ptr(stream_ptr.get())); dispatcher.add_managed_resource(json_writer); - auto channel = std::make_unique(json_writer.get()); + auto channel = std::make_unique( + json_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } @@ -67,26 +69,29 @@ callbacks::dispatcher configure_dispatcher( case callbacks::info_type::SAMPLE_RAW: case callbacks::info_type::CONFIG: case callbacks::info_type::DIAGNOSTIC: { - auto stream_writer = std::make_shared>( + auto stream_writer = std::make_shared< + callbacks::unique_stream_writer>( std::unique_ptr(stream_ptr.get())); - + dispatcher.add_managed_resource(stream_writer); - - auto channel = std::make_unique(stream_writer.get()); + + auto channel + = std::make_unique(stream_writer.get()); dispatcher.register_channel(type, std::move(channel)); break; } default: std::stringstream ss; - ss << "Unknown info_type " << static_cast(type) << " in configure_dispatcher"; + ss << "Unknown info_type " << static_cast(type) + << " in configure_dispatcher"; throw std::runtime_error(ss.str()); } } - + return dispatcher; } -} // namespace util -} // namespace services -} // namespace stan +} // namespace util +} // namespace services +} // namespace stan #endif