diff --git a/pdi/AUTHORS b/pdi/AUTHORS index 918e59df0..eef83bbb7 100644 --- a/pdi/AUTHORS +++ b/pdi/AUTHORS @@ -15,7 +15,8 @@ Benoit Martin - CEA (bmartin@cea.fr) * Add support for const data in `PDI_share`, `PDI_expose` and `PDI_multi_expose` Jacques Morice - CEA (jacques.morice@cea.fr) -* Add Add operator== in Context::Iterator() +* Add operator== in Context::Iterator() +* Delay data events in multi_expose when the last data is exposed François-Xavier Mordant - CEA (francois-xavier.mordant@cea.fr) * Fixed CMake issues, internal API enhancement diff --git a/pdi/CHANGELOG.md b/pdi/CHANGELOG.md index 82641778b..a5172e97b 100644 --- a/pdi/CHANGELOG.md +++ b/pdi/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to #### Added #### Changed +* Delay data events in multi_expose when the last data is exposed + [#514](https://github.com/pdidev/pdi/issues/514) #### Deprecated diff --git a/pdi/include/pdi/data_descriptor.h b/pdi/include/pdi/data_descriptor.h index 5870597f0..66f1ee63d 100644 --- a/pdi/include/pdi/data_descriptor.h +++ b/pdi/include/pdi/data_descriptor.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,9 +30,14 @@ #include #include +#include +#include +#include #include #include +// #include "global_context.h" + namespace PDI { class PDI_EXPORT Data_descriptor @@ -85,6 +90,14 @@ class PDI_EXPORT Data_descriptor */ virtual void share(void* data, bool read, bool write) = 0; + /** Shares some data with PDI + * \param[in,out] data the shared data + * \param read whether read access is granted to other references + * \param write whether write access is granted to other references + * \param delayed_callbacks a class that allow to delay trigger_delayed_data_callbacks for a list of a data + */ + virtual void share(void* data, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) = 0; + /** Shares some data with PDI * \param[in,out] ref a reference to the shared data * \param read whether the stored reference should have read access @@ -93,6 +106,19 @@ class PDI_EXPORT Data_descriptor */ virtual void* share(Ref ref, bool read, bool write) = 0; + /** Shares some data with PDI + * \param[in,out] ref a reference to the shared data + * \param read whether the stored reference should have read access + * \param write whether the stored reference should have write access + * \param delayed_callbacks a class that allow to delay trigger_delayed_data_callbacks for a list of a data + * \return the just shared buffer + */ + virtual void* share(Ref ref, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) = 0; + + /** function to call "data_callbacks" for the shared data. + */ + virtual void trigger_delayed_data_callbacks() = 0; + /** Releases ownership of a data shared with PDI. PDI is then responsible to * free the associated memory whenever necessary. */ diff --git a/pdi/include/pdi/delayed_data_callbacks.h b/pdi/include/pdi/delayed_data_callbacks.h new file mode 100644 index 000000000..72179c72e --- /dev/null +++ b/pdi/include/pdi/delayed_data_callbacks.h @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of CEA nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#ifndef PDI_DELAYED_DATA_CALLBACK_H_ +#define PDI_DELAYED_DATA_CALLBACK_H_ + +#include +#include + +#include +#include "pdi/context.h" +#include "pdi/datatype.h" +#include "pdi/error.h" +#include "pdi/plugin.h" +#include "pdi/ref_any.h" +#include "pdi/scalar_datatype.h" +#include +#include +#include +#include + +#include + +#include "global_context.h" + +namespace PDI { + +class PDI_EXPORT Delayed_data_callbacks +{ + /// friend class Global_context; + std::vector m_datanames; + + /// The context this descriptor is part of + Global_context& m_context; + +public: + /// constructor used in: + /// - the case of PDI_multi_expose + /// - when a user want to define is own "Delayed_data_callbacks" object + /// Remark: This constructor doesn't work if PDI_init is not called. + Delayed_data_callbacks(Global_context& ctx) + : m_datanames{} + , m_context{ctx} + {} + + // In the destructor, we need to throw an error message in case of the callback on the data doesn't work (trigger function) + // (example: error in the config.yml for a plugin, error due to external library incompatibility) + ~Delayed_data_callbacks() noexcept(false) + { + try { + this->trigger(); + } catch (Error& e) { + if (std::uncaught_exceptions()) { + // An exception is throwing. Print simple message to avoid std::terminate. + m_context.logger().error("Error in the destructor of Delayed_data_callbacks, {}", e.what()); + } else { + // No exception is throwing. Throw an error. + throw Error(e.status(), "Error in the destructor of Delayed_data_callbacks, {}", e.what()); + } + } catch (const std::exception& e) { + if (std::uncaught_exceptions()) { + // An exception is throwing. Print simple message to avoid std::terminate. + m_context.logger().error("Error in the destructor of Delayed_data_callbacks, {}", e.what()); + } else { + // No exception is throwing. Throw an error. + m_context.logger().error("Error in the destructor of Delayed_data_callbacks."); + throw; + } + } catch (...) { + if (std::uncaught_exceptions()) { + // An exception is throwing. Print simple message to avoid std::terminate. + m_context.logger().error("Error in the destructor of Delayed_data_callbacks."); + } else { + // No exception is throwing. Throw an error. + m_context.logger().error("Error in the destructor of Delayed_data_callbacks."); + throw; + } + } + } + + // add element to the list + void add_dataname(const std::string& name) { m_datanames.emplace_back(name); } + + // trigger_delayed_data_callbacks() + void trigger() + try { + int i = 0; + size_t number_of_elements = m_datanames.size(); + for (auto&& element_name: m_datanames) { + m_context.logger().trace("Trigger data callback `{}' ({}/{})", element_name.c_str(), ++i, number_of_elements); + m_context[element_name.c_str()].trigger_delayed_data_callbacks(); + } + this->cancel(); // clear the m_datanames + } catch (Error& e) { + this->cancel(); // clear the m_datanames + throw Error(e.status(), "Error in trigger data callback: `{}'", e.what()); + } catch (...) { + this->cancel(); // clear the m_datanames + throw; + } + + // clear the element in the list + void cancel() { m_datanames.clear(); } +}; // class Delayed_data_callbacks + +} // namespace PDI +#endif // PDI_DELAYED_DATA_CALLBACK_H_ diff --git a/pdi/include/pdi/pdi_fwd.h b/pdi/include/pdi/pdi_fwd.h index b3634b737..583962af1 100644 --- a/pdi/include/pdi/pdi_fwd.h +++ b/pdi/include/pdi/pdi_fwd.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2021 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -79,6 +79,11 @@ using Datatype_template_sptr = std::shared_ptr; using Datatype_sptr = std::shared_ptr; +/** + * A class to delayed the moment to call call_data_callback +*/ +class Delayed_data_callbacks; + /** A data descriptors with a name and a value, it contains an implicit type * template that is used when exposing untyped data */ diff --git a/pdi/src/data_descriptor_impl.cxx b/pdi/src/data_descriptor_impl.cxx index 8cd1d2afa..220ac5125 100644 --- a/pdi/src/data_descriptor_impl.cxx +++ b/pdi/src/data_descriptor_impl.cxx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2021 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -32,6 +32,7 @@ #include "pdi/context.h" #include "pdi/datatype.h" +#include "pdi/delayed_data_callbacks.h" #include "pdi/error.h" #include "pdi/plugin.h" #include "pdi/ref_any.h" @@ -151,13 +152,47 @@ bool Data_descriptor_impl::empty() return m_refs.empty(); } +void Data_descriptor_impl::trigger_delayed_data_callbacks() +try { + assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); + if (m_refs.empty() || (m_refs.size() == 1 && metadata())) { + throw State_error{"Cannot call trigger_delayed_data_callbacks on a non shared value: : `{}'", m_name}; + } + try { + m_context.callbacks().call_data_callbacks(m_name, ref()); + } catch (const exception&) { + m_refs.pop(); + throw; + } catch (...) { + m_context.logger().trace("Error in trigger_delayed_data_callbacks\n"); + m_refs.pop(); + throw; + } + + assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); +} catch (Error& e) { + throw Error(e.status(), "Unable to execute data_callbacks on data `{}', {}", name(), e.what()); +} + + void Data_descriptor_impl::share(void* data, bool read, bool write) +try { + assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); + + share(data, read, write, Delayed_data_callbacks(m_context)); + + assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); +} catch (Error& e) { + throw; +} + +void Data_descriptor_impl::share(void* data, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) try { assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); Ref r{data, &free, m_type->evaluate(m_context), read, write}; try { m_context.logger().trace("Sharing `{}' Ref with rights: R = {}, W = {}", m_name, read, write); - share(r, false, false); + share(r, false, false, std::move(delayed_callbacks)); } catch (...) { // on error, do not free the data as would be done automatically otherwise r.release(); @@ -170,6 +205,17 @@ try { } void* Data_descriptor_impl::share(Ref data_ref, bool read, bool write) +try { + assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); + return share(data_ref, read, write, Delayed_data_callbacks(m_context)); +} catch (Error& e) { + throw Error(e.status(), "Unable to share `{}', {}", name(), e.what()); + ; +} catch (...) { + throw std::runtime_error("void* Data_descriptor_impl::share(Ref data_ref, bool read, bool write)"); +} + +void* Data_descriptor_impl::share(Ref data_ref, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) try { assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); // metadata must provide read access @@ -201,12 +247,7 @@ try { throw Right_error{"Unable to grant requested rights"}; } - try { - m_context.callbacks().call_data_callbacks(m_name, ref()); - } catch (const exception&) { - m_refs.pop(); - throw; - } + delayed_callbacks.add_dataname(m_name); assert((!metadata() || !m_refs.empty()) && "metadata descriptors should always keep a placeholder"); return result; diff --git a/pdi/src/data_descriptor_impl.h b/pdi/src/data_descriptor_impl.h index 5f2119c11..4a041baa3 100644 --- a/pdi/src/data_descriptor_impl.h +++ b/pdi/src/data_descriptor_impl.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,8 +43,10 @@ namespace PDI { class PDI_EXPORT Data_descriptor_impl: public Data_descriptor { friend class Global_context; + friend class Delayed_data_callback; friend class Descriptor_test_handler; + struct PDI_NO_EXPORT Ref_holder; /// The context this descriptor is part of @@ -59,7 +61,6 @@ class PDI_EXPORT Data_descriptor_impl: public Data_descriptor bool m_metadata; - /** Create an empty descriptor */ Data_descriptor_impl(Global_context& ctx, const char* name); @@ -91,12 +92,17 @@ class PDI_EXPORT Data_descriptor_impl: public Data_descriptor void share(void* data, bool read, bool write) override; + void share(void* data, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) override; + void* share(Ref ref, bool read, bool write) override; + void* share(Ref ref, bool read, bool write, Delayed_data_callbacks&& delayed_callbacks) override; + + void trigger_delayed_data_callbacks() override; + void release() override; void* reclaim() override; - }; // class Data_descriptor } // namespace PDI diff --git a/pdi/src/global_context.h b/pdi/src/global_context.h index 42e6a560e..18af976e5 100644 --- a/pdi/src/global_context.h +++ b/pdi/src/global_context.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2021 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -49,6 +49,7 @@ namespace PDI { class PDI_EXPORT Global_context: public Context { private: + friend class Delayed_data_callbacks; friend class Data_descriptor_impl; /// The singleton Context instance diff --git a/pdi/src/pdi.cxx b/pdi/src/pdi.cxx index 2b9824ca4..7a548f69e 100644 --- a/pdi/src/pdi.cxx +++ b/pdi/src/pdi.cxx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2015-2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2021 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -38,6 +37,7 @@ #include "pdi/context.h" #include "pdi/data_descriptor.h" #include "pdi/datatype.h" +#include "pdi/delayed_data_callbacks.h" #include "pdi/error.h" #include "pdi/paraconf_wrapper.h" #include "pdi/plugin.h" @@ -143,6 +143,34 @@ void warn_status(PDI_status_t status, const char* message, void*) } } +/** A structure to reclaim the datas properly in case of error + */ +struct Var_to_reclaim { + std::vector m_varnames; + Var_to_reclaim() = default; + + ~Var_to_reclaim() noexcept + try { + int counter = 0; + for (auto&& it = m_varnames.rbegin(); it != m_varnames.rend(); it++) { + Global_context::context().logger().trace("Multi expose: Reclaiming `{}' ({}/{})", it->c_str(), ++counter, m_varnames.size()); + Global_context::context()[it->c_str()].reclaim(); + } + m_varnames.clear(); + } catch (const Error& e) { + g_error_context.return_err(e); + } catch (const exception& e) { + g_error_context.return_err(e); + } catch (...) { + g_error_context.return_err(); + } + + size_t size() const { return m_varnames.size(); } + + void emplace_back(const string& name) { m_varnames.emplace_back(name); } +}; + + } // namespace extern "C" { @@ -324,37 +352,31 @@ PDI_status_t PDI_multi_expose(const char* event_name, const char* name, const vo try { Paraconf_wrapper fw; va_list ap; - list transaction_data; - PDI_status_t status; - if ((status = PDI_share(name, data, access))) return status; - transaction_data.emplace_back(name); + + Var_to_reclaim list_names; // list of variable that will be reclaimed at the end of this function + Delayed_data_callbacks delayed_callbacks(Global_context::context()); + int i = -1; + Global_context::context().logger().trace("Multi expose: Sharing `{}' ({}/{})", name, ++i, list_names.size()); + Global_context::context()[name].share(const_cast(data), access & PDI_OUT, access & PDI_IN, std::move(delayed_callbacks)); + list_names.emplace_back(name); va_start(ap, access); - int i = 0; while (const char* v_name = va_arg(ap, const char*)) { void* v_data = va_arg(ap, void*); PDI_inout_t v_access = static_cast(va_arg(ap, int)); - Global_context::context().logger().trace("Multi expose: Sharing `{}' ({}/{})", v_name, ++i, transaction_data.size()); - if ((status = PDI_share(v_name, v_data, v_access))) { - break; - } - transaction_data.emplace_back(v_name); + Global_context::context().logger().trace("Multi expose: Sharing `{}' ({}/{})", v_name, ++i, list_names.size()); + Global_context::context()[v_name].share(v_data, v_access & PDI_OUT, v_access & PDI_IN, std::move(delayed_callbacks)); + list_names.emplace_back(v_name); } va_end(ap); - if (!status) { //trigger event only when all data is available - Global_context::context().logger().trace("Multi expose: Calling event `{}'", event_name); - status = PDI_event(event_name); - } + delayed_callbacks.trigger(); - i = 0; - for (auto&& it = transaction_data.rbegin(); it != transaction_data.rend(); it++) { - Global_context::context().logger().trace("Multi expose: Reclaiming `{}' ({}/{})", it->c_str(), ++i, transaction_data.size()); - PDI_status_t r_status = PDI_reclaim(it->c_str()); - status = !status ? r_status : status; //if it is first error, save its status (try to reclaim other desc anyway) - } - //the status of the first error is returned - return status; + Global_context::context().logger().trace("Multi expose: Calling event `{}'", event_name); + Global_context::context().event(event_name); + + // remark: The reclaim of the datas are done in the destructor of the Var_to_reclaim (see struct Var_to_reclaim) + return PDI_OK; } catch (const Error& e) { return g_error_context.return_err(e); } catch (const exception& e) { diff --git a/pdi/tests/PDI_callbacks.cxx b/pdi/tests/PDI_callbacks.cxx index d99af3e98..485a6b006 100644 --- a/pdi/tests/PDI_callbacks.cxx +++ b/pdi/tests/PDI_callbacks.cxx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2020-2021 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2020-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2018 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -165,6 +166,7 @@ TEST_F(CallbacksTest, add_data_callback) }); ASSERT_EQ(x, 0); this->test_context->desc("data_x").share(&x, true, true); + ASSERT_EQ(x, 42); this->test_context->desc("data_x").reclaim(); ASSERT_EQ(x, 42); } @@ -649,3 +651,151 @@ TEST_F(CallbacksTest, add_data_remove_callback_release_remove) this->test_context->desc("data_x").release(); ASSERT_EQ(x, 0); } + +/* + * Name: CallbacksTest.callbacks().multiple_delayed_data_callbacks + * + * Tested functions: PDI::Context::callbacks() + * + * + * Description: Checks if callback is + * correctly called on data when a data + * + */ +TEST_F(CallbacksTest, multiple_delayed_data_callbacks) +{ + string data_x{"data_x"}; + string data_y{"data_y"}; + Data_descriptor& desc_x = this->test_context->desc(data_x); + Data_descriptor& desc_y = this->test_context->desc(data_y); + this->test_context->desc(data_x).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + this->test_context->desc(data_y).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + + Delayed_data_callbacks delayed_callbacks(*dynamic_cast(this->test_context.get())); + + int x = 0; + int y = 0; + + this->test_context->callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* x = static_cast(ref_write.get()); + *x += 42; + std::cout << "name=" << name.c_str() << " == data_x" << std::endl; + ASSERT_STREQ(name.c_str(), "data_x"); + }, + "data_x" + ); + + this->test_context->callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* y = static_cast(ref_write.get()); + *y += 53; + std::cout << "name=" << name.c_str() << " == data_y" << std::endl; + ASSERT_STREQ(name.c_str(), "data_y"); + }, + "data_y" + ); + + ASSERT_EQ(x, 0); + ASSERT_EQ(y, 0); + std::cout << "share data_x=" << std::endl; + this->test_context->desc("data_x").share(&x, true, true, std::move(delayed_callbacks)); + std::cout << "share data_y=" << std::endl; + this->test_context->desc("data_y").share(&y, true, true, std::move(delayed_callbacks)); + ASSERT_EQ(x, 0); + ASSERT_EQ(y, 0); + this->test_context->desc("data_x").trigger_delayed_data_callbacks(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 0); + this->test_context->desc("data_y").trigger_delayed_data_callbacks(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 53); + delayed_callbacks.cancel(); + this->test_context->desc("data_y").reclaim(); + this->test_context->desc("data_x").reclaim(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 53); +} + +/* + * Name: CallbacksTest.callbacks().multiple_delayed_data_callbacks_with_error + * + * Tested functions: PDI::Context::callbacks() + * + * + * Description: Checks if callback is + * correctly called on data when a data + * + */ +TEST_F(CallbacksTest, multiple_delayed_data_callbacks_with_error) +{ + string data_x{"data_x"}; + string data_y{"data_y"}; + Data_descriptor& desc_x = this->test_context->desc(data_x); + Data_descriptor& desc_y = this->test_context->desc(data_y); + this->test_context->desc(data_x).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + this->test_context->desc(data_y).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + + Delayed_data_callbacks delayed_callbacks(*dynamic_cast(this->test_context.get())); + + int x = 0; + int y = 0; + + this->test_context->callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* x = static_cast(ref_write.get()); + *x += 42; + std::cout << "name=" << name.c_str() << " == data_x" << std::endl; + ASSERT_STREQ(name.c_str(), "data_x"); + }, + "data_x" + ); + + this->test_context->callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* y = static_cast(ref_write.get()); + *y += 53; + std::cout << "name=" << name.c_str() << " == data_y" << std::endl; + ASSERT_STREQ(name.c_str(), "data_y"); + }, + "data_y" + ); + + ASSERT_EQ(x, 0); + ASSERT_EQ(y, 0); + try { + this->test_context->desc("data_x").share(&x, true, true, std::move(delayed_callbacks)); + throw std::runtime_error("forced throw error for testing"); + this->test_context->desc("data_x").trigger_delayed_data_callbacks(); + } catch (const std::runtime_error& e) { + EXPECT_STREQ("forced throw error for testing", e.what()); + this->test_context->desc("data_x").reclaim(); + } catch (...) { + FAIL() << "The error throwing must be std::runtime_error."; + } + + // callback for data_y + ASSERT_EQ(x, 0); + + this->test_context->desc("data_y").share(&y, true, true, std::move(delayed_callbacks)); + ASSERT_EQ(y, 0); + this->test_context->desc("data_y").trigger_delayed_data_callbacks(); + ASSERT_EQ(y, 53); + delayed_callbacks.cancel(); + this->test_context->desc("data_y").reclaim(); + ASSERT_EQ(y, 53); + + // callback for data_x + this->test_context->desc("data_x").share(&x, true, true, std::move(delayed_callbacks)); + ASSERT_EQ(x, 0); + this->test_context->desc("data_x").trigger_delayed_data_callbacks(); + ASSERT_EQ(x, 42); + delayed_callbacks.cancel(); + this->test_context->desc("data_x").reclaim(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 53); +} diff --git a/pdi/tests/PDI_data_descriptor.cxx b/pdi/tests/PDI_data_descriptor.cxx index e41aafc68..632752166 100644 --- a/pdi/tests/PDI_data_descriptor.cxx +++ b/pdi/tests/PDI_data_descriptor.cxx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021-2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2021-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2018 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -41,18 +42,15 @@ using namespace std; namespace PDI { //handler to private fields of Descriptor struct Descriptor_test_handler { - static unique_ptr default_desc(Global_context& global_ctx) - { - return unique_ptr{new Data_descriptor_impl{global_ctx, "default_desc"}}; - } + static Data_descriptor* default_desc(Global_context& global_ctx) { return &global_ctx.desc("default_desc"); } - static Datatype_sptr desc_get_type(unique_ptr& desc, Global_context& global_ctx) + static Datatype_sptr desc_get_type(Data_descriptor* desc, Global_context& global_ctx) { - Datatype_template_sptr desc_template = dynamic_cast(desc.get())->m_type; + Datatype_template_sptr desc_template = dynamic_cast(desc)->m_type; return desc_template->evaluate(global_ctx); } - static int desc_get_refs_number(unique_ptr& desc) { return dynamic_cast(desc.get())->m_refs.size(); } + static int desc_get_refs_number(Data_descriptor* desc) { return dynamic_cast(desc)->m_refs.size(); } }; } // namespace PDI @@ -64,8 +62,8 @@ struct DataDescTest: public ::testing::Test { PC_tree_t array_config{PC_parse_string("{ size: 10, type: array, subtype: int }")}; shared_ptr array_datatype{Array_datatype::make(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int)), 10)}; PDI::Paraconf_wrapper fw; - Global_context global_ctx{PC_parse_string("")}; - unique_ptr m_desc_default = Descriptor_test_handler::default_desc(global_ctx); + Global_context global_ctx{PC_parse_string("logging: debug")}; + Data_descriptor* m_desc_default = Descriptor_test_handler::default_desc(global_ctx); }; /* @@ -336,3 +334,75 @@ TEST_F(DataDescTest, multi_read_share_meta) ptr = Ref_r{this->m_desc_default->ref()}.get(); ASSERT_NE(this->array, ptr); } + +/* + * Struct prepared for DataDescDelayedCallbacksTest + */ +struct DataDescDelayedCallbacksTest: public ::testing::Test { + Paraconf_wrapper fw; + Global_context context{PC_parse_string("logging: trace")}; +}; + +/* + * Name: CallbacksTest.callbacks().multiple_delayed_data_callbacks + * + * Tested functions: PDI::Data_descriptor::share(void*, bool, bool, bool) + * PDI::Data_descriptor::share(Ref, bool, bool) + * PDI::Data_descriptor::trigger_delayed_data_callbacks() + * PDI::Data_descriptor::release() + * + * Description: Checks if callback is correctly called when a data is delayed + * with trigger_delayed_data_callbacks() + * + */ +TEST_F(DataDescDelayedCallbacksTest, multiple_delayed_data_callbacks) +{ + string data_x{"data_x"}; + string data_y{"data_y"}; + Data_descriptor& desc_x = context.desc(data_x); + Data_descriptor& desc_y = context.desc(data_y); + context.desc(data_x).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + context.desc(data_y).default_type(Scalar_datatype::make(Scalar_kind::SIGNED, sizeof(int))); + int x = 0; + int y = 0; + + context.callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* x = static_cast(ref_write.get()); + *x += 42; + ASSERT_STREQ(name.c_str(), "data_x"); + }, + "data_x" + ); + + context.callbacks().add_data_callback( + [](const std::string& name, Ref ref) { + Ref_w ref_write{ref}; + int* y = static_cast(ref_write.get()); + *y += 53; + ASSERT_STREQ(name.c_str(), "data_y"); + }, + "data_y" + ); + + ASSERT_EQ(x, 0); + ASSERT_EQ(y, 0); + + Delayed_data_callbacks delayed_callbacks_x(context); + Delayed_data_callbacks delayed_callbacks_y(context); + context.desc("data_x").share(&x, true, true, std::move(delayed_callbacks_x)); + context.desc("data_y").share(&y, true, true, std::move(delayed_callbacks_y)); + ASSERT_EQ(x, 0); + ASSERT_EQ(y, 0); + delayed_callbacks_x.trigger(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 0); + delayed_callbacks_y.trigger(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 53); + context.desc("data_y").reclaim(); + context.desc("data_x").reclaim(); + ASSERT_EQ(x, 42); + ASSERT_EQ(y, 53); +} diff --git a/pdi/tests/mocks/data_descriptor_mock.h b/pdi/tests/mocks/data_descriptor_mock.h index 32f8f263b..4ac817556 100644 --- a/pdi/tests/mocks/data_descriptor_mock.h +++ b/pdi/tests/mocks/data_descriptor_mock.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021-2024 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * Copyright (C) 2021-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) * Copyright (C) 2018 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) * All rights reserved. * @@ -29,6 +29,7 @@ #include #include #include +#include struct MockDataDescriptor: public PDI::Data_descriptor { MOCK_METHOD1(default_type, void(PDI::Datatype_template_sptr)); @@ -40,6 +41,9 @@ struct MockDataDescriptor: public PDI::Data_descriptor { MOCK_METHOD0(empty, bool()); MOCK_METHOD3(share, void(void*, bool, bool)); MOCK_METHOD3(share, void*(PDI::Ref, bool, bool)); + MOCK_METHOD4(share, void(void*, bool, bool, PDI::Delayed_data_callbacks&&)); + MOCK_METHOD4(share, void*(PDI::Ref, bool, bool, PDI::Delayed_data_callbacks&&)); + MOCK_METHOD0(trigger_delayed_data_callbacks, void()); MOCK_METHOD0(release, void()); MOCK_METHOD0(reclaim, void*()); }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 887f0fc97..01ab861f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,7 +24,13 @@ #============================================================================= cmake_minimum_required(VERSION 3.22...4.2) -project(pdi_tests LANGUAGES C) +project(pdi_tests LANGUAGES C CXX) + +if(NOT TARGET GTest::gtest) + option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" OFF) + add_subdirectory("../vendor/googletest-56efe39/" "googletest" EXCLUDE_FROM_ALL) +endif() +include(GoogleTest) set(RUNTEST_DIR "${CMAKE_CURRENT_LIST_DIR}/../cmake/runtest-dir") @@ -64,3 +70,12 @@ add_test(NAME test_05_C COMMAND "$" "${CMAKE_CURRENT_SOUR set_tests_properties(test_05_C PROPERTIES DISABLED TRUE) endif("${BUILD_DECL_NETCDF_PLUGIN}" AND "${BUILD_SERIALIZE_PLUGIN}") + +if("${BUILD_USER_CODE_PLUGIN}") + +add_executable(test_06_user_code_multi_expose_CXX test_06_user_code_multi_expose.cxx) +target_link_libraries(test_06_user_code_multi_expose_CXX PDI::PDI_C GTest::gtest GTest::gtest_main) +set_target_properties(test_06_user_code_multi_expose_CXX PROPERTIES ENABLE_EXPORTS TRUE) +gtest_discover_tests(test_06_user_code_multi_expose_CXX) + +endif("${BUILD_USER_CODE_PLUGIN}") \ No newline at end of file diff --git a/tests/test_06_user_code_multi_expose.cxx b/tests/test_06_user_code_multi_expose.cxx new file mode 100644 index 000000000..bf71bfda0 --- /dev/null +++ b/tests/test_06_user_code_multi_expose.cxx @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of CEA nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include + +// Function to check the value +void check_value(const char* var_name, int& var, const int expected_value) +{ + EXPECT_EQ(var, expected_value) << "Wrong value of " << var_name << ": " << var << " != " << expected_value; +} + +// Define user_code function +extern "C" { + +// Test access of the first argument var1 +void test_access_var1(void) +{ + int* value; + PDI_access("value", (void**)&value, PDI_IN); // Read something from input + PDI_release("value"); + check_value("var1", *value, -4); +} + +// Test access of the last argument var2 +void test_access_var2(void) +{ + int* value; + PDI_access("value", (void**)&value, PDI_IN); // Read something from input + PDI_release("value"); + check_value("var2", *value, 3); +} + +} // end extern "C" + +/* + * Name: user_code_multi_expose_test + * + * Description: Verify that the value of data in a multi_expose are given to PDI + * before the loop "on_data" event. + */ + + +TEST(test_06_user_code_multi_expose_test, 01) +{ + const char* CONFIG_YAML + = "logging: trace \n" + "data: \n" + " var1: int \n" + " var2: int \n" + "plugins: \n" + " user_code: \n" + " on_data: \n" + " var2: \n" + " test_access_var1: { value: $var1 }\n" + " var1: \n" + " test_access_var2: { value: $var2 }\n"; + + PDI_init(PC_parse_string(CONFIG_YAML)); + + int var1 = -4; + int var2 = 3; + + PDI_multi_expose("my_test", "var1", &var1, PDI_OUT, "var2", &var2, PDI_OUT, NULL); + + PDI_finalize(); +}