From 5c9e3df135aac3a278abf6bdd4cb1519f76a276f Mon Sep 17 00:00:00 2001 From: Mina Hamdi Date: Tue, 30 Dec 2025 21:46:09 +0200 Subject: [PATCH 01/18] PoC implementation of the Generic Skeleton for Events and with initial partial restart handling implementation. --- .../design/events_fields/NestedCallbacks.svg | 0 score/mw/com/example/ipc_bridge/main.cpp | 6 +- .../ipc_bridge/sample_sender_receiver.cpp | 86 +++++++++ .../ipc_bridge/sample_sender_receiver.h | 4 + score/mw/com/impl/BUILD | 75 ++++++++ score/mw/com/impl/bindings/lola/BUILD | 4 + .../bindings/lola/generic_skeleton_event.cpp | 98 ++++++++++ .../bindings/lola/generic_skeleton_event.h | 71 +++++++ score/mw/com/impl/bindings/lola/skeleton.cpp | 178 ++++++++++++++---- score/mw/com/impl/bindings/lola/skeleton.h | 82 ++++---- .../com/impl/bindings/mock_binding/skeleton.h | 10 + score/mw/com/impl/generic_skeleton.cpp | 92 +++++++++ score/mw/com/impl/generic_skeleton.h | 67 +++++++ score/mw/com/impl/generic_skeleton_event.cpp | 64 +++++++ score/mw/com/impl/generic_skeleton_event.h | 44 +++++ .../com/impl/generic_skeleton_event_binding.h | 38 ++++ score/mw/com/impl/generic_skeleton_test.cpp | 103 ++++++++++ .../com/impl/plumbing/sample_allocatee_ptr.h | 159 ++++++++++++++++ score/mw/com/impl/size_info.h | 26 +++ score/mw/com/impl/skeleton_base.h | 5 + score/mw/com/impl/skeleton_binding.h | 10 + 21 files changed, 1143 insertions(+), 79 deletions(-) mode change 100755 => 100644 score/mw/com/design/events_fields/NestedCallbacks.svg create mode 100644 score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp create mode 100644 score/mw/com/impl/bindings/lola/generic_skeleton_event.h create mode 100644 score/mw/com/impl/generic_skeleton.cpp create mode 100644 score/mw/com/impl/generic_skeleton.h create mode 100644 score/mw/com/impl/generic_skeleton_event.cpp create mode 100644 score/mw/com/impl/generic_skeleton_event.h create mode 100644 score/mw/com/impl/generic_skeleton_event_binding.h create mode 100644 score/mw/com/impl/generic_skeleton_test.cpp create mode 100644 score/mw/com/impl/size_info.h diff --git a/score/mw/com/design/events_fields/NestedCallbacks.svg b/score/mw/com/design/events_fields/NestedCallbacks.svg old mode 100755 new mode 100644 diff --git a/score/mw/com/example/ipc_bridge/main.cpp b/score/mw/com/example/ipc_bridge/main.cpp index 8711a3cbd..7c282c8f0 100644 --- a/score/mw/com/example/ipc_bridge/main.cpp +++ b/score/mw/com/example/ipc_bridge/main.cpp @@ -51,7 +51,7 @@ Params ParseCommandLineArguments(const int argc, const char** argv) "Number of cycles that are executed before determining success or failure. 0 indicates no limit."); options.add_options()("mode,m", po::value(), - "Set to either send/skeleton or recv/proxy to determine the role of the process"); + "Set to: send/skeleton (typed skeleton), gen_skeleton (generic skeleton) or recv/proxy to determine the role of the process"); options.add_options()("cycle-time,t", po::value(), "Cycle time in milliseconds for sending/polling"); options.add_options()( "service_instance_manifest,s", po::value(), "Path to the com configuration file"); @@ -119,6 +119,10 @@ int main(const int argc, const char** argv) { return event_sender_receiver.RunAsSkeleton(instance_specifier, cycle_time, cycles); } + else if (mode == "gen_skeleton") + { + return event_sender_receiver.RunAsGenericSkeleton(instance_specifier, cycle_time, cycles); + } else if (mode == "recv" || mode == "proxy") { return event_sender_receiver.RunAsProxy(instance_specifier, cycle_time, cycles, false, check_sample_hash); diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index db24972df..1445473a6 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -13,6 +13,7 @@ #include "sample_sender_receiver.h" #include "score/mw/com/impl/generic_proxy.h" #include "score/mw/com/impl/generic_proxy_event.h" +#include "score/mw/com/impl/generic_skeleton.h" #include "score/mw/com/impl/handle_type.h" #include "score/concurrency/notification.h" @@ -268,6 +269,36 @@ Result> PrepareMapLaneSample(IpcBridgeSke return sample; } +Result> PrepareMapLaneSample(impl::GenericSkeletonEvent& event, + const std::size_t cycle) +{ + const std::default_random_engine::result_type seed{static_cast( + std::chrono::steady_clock::now().time_since_epoch().count())}; + std::default_random_engine rng{seed}; + + auto sample_result = event.Allocate(); + + if (!sample_result.has_value()) + { + return sample_result; + } + auto sample = std::move(sample_result).value(); + auto* typed_sample = static_cast(sample.Get()); + typed_sample->hash_value = START_HASH; + typed_sample->x = static_cast(cycle); + + std::cout << ToString("Sending sample: ", typed_sample->x, "\n"); + for (MapApiLaneData& lane : typed_sample->lanes) { + for (LaneIdType& successor : lane.successor_lanes) + { + successor = std::uniform_int_distribution()(rng); + } + + HashArray(lane.successor_lanes, typed_sample->hash_value); + } + return sample; +} + } // namespace template @@ -447,6 +478,61 @@ int EventSenderReceiver::RunAsSkeleton(const score::mw::com::InstanceSpecifier& return EXIT_SUCCESS; } +int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier, + const std::chrono::milliseconds cycle_time, + const std::size_t num_cycles) +{ + auto create_result = impl::GenericSkeleton::Create(instance_specifier); + if (!create_result.has_value()) + { + std::cerr << "Unable to construct skeleton: " << create_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + auto& skeleton = create_result.value(); + + const auto event_name = "map_api_lanes_stamped"; + const SizeInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; + auto event_result = skeleton.AddEvent(event_name, size_info); + if (!event_result.has_value()) + { + std::cerr << "Unable to add event to skeleton: " << event_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + auto& event = *event_result.value(); + + const auto offer_result = skeleton.OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Unable to offer service for skeleton: " << offer_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + std::cout << "Starting to send data\n"; + + for (std::size_t cycle = 0U; cycle < num_cycles || num_cycles == 0U; ++cycle) + { + auto sample_result = PrepareMapLaneSample(event, cycle); + if (!sample_result.has_value()) + { + std::cerr << "No sample received. Exiting.\n"; + return EXIT_FAILURE; + } + auto sample = std::move(sample_result).value(); + + { + std::lock_guard lock{event_sending_mutex_}; + event.Send(std::move(sample)); + event_published_ = true; + } + std::this_thread::sleep_for(cycle_time); + } + + std::cout << "Stop offering service..."; + skeleton.StopOfferService(); + std::cout << "and terminating, bye bye\n"; + + return EXIT_SUCCESS; +} + template int EventSenderReceiver::RunAsProxy>( const score::mw::com::InstanceSpecifier&, const score::cpp::optional, diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.h b/score/mw/com/example/ipc_bridge/sample_sender_receiver.h index 541ad6dec..6ce7fc04e 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.h +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.h @@ -33,6 +33,10 @@ class EventSenderReceiver const std::chrono::milliseconds cycle_time, const std::size_t num_cycles); + int RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier, + const std::chrono::milliseconds cycle_time, + const std::size_t num_cycles); + template > int RunAsProxy(const score::mw::com::InstanceSpecifier& instance_specifier, diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 908c0aa29..a03ad876c 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -30,6 +30,7 @@ cc_library( ":generic_proxy_event", ":proxy_event", ":proxy_field", + ":generic_skeleton", ":skeleton_event", ":skeleton_field", ":traits", @@ -110,6 +111,78 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton", + srcs = ["generic_skeleton.cpp"], + hdrs = ["generic_skeleton.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":error", + "//score/mw/com/impl/plumbing", + ], + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":generic_skeleton_event", + ":instance_identifier", + ":instance_specifier", + ":runtime", + ":skeleton_base", + ":skeleton_binding", + ":size_info", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "generic_skeleton_event", + srcs = ["generic_skeleton_event.cpp"], + hdrs = ["generic_skeleton_event.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":generic_skeleton_event_binding", + ":skeleton_event_base", + ":skeleton_event_binding", + ":size_info", + "//score/mw/com/impl/plumbing:sample_allocatee_ptr", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "generic_skeleton_event_binding", + hdrs = [ + "generic_skeleton_event_binding.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl/bindings/lola:__pkg__", + "//score/mw/com/impl/bindings/mock_binding:__pkg__", + ], + deps = [ + ":skeleton_event_binding", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "size_info", + hdrs = ["size_info.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__pkg__", + "//score/mw/com/impl:__subpackages__", + ], +) + cc_library( name = "skeleton_event", srcs = ["skeleton_event.cpp"], @@ -381,6 +454,8 @@ cc_library( ], deps = [ ":binding_type", + ":generic_skeleton_event_binding", + ":size_info", "//score/mw/com/impl/configuration", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/memory/shared:i_shared_memory_resource", diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index 02c5c3fe1..584a435b9 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -247,12 +247,14 @@ cc_library( "skeleton_event.cpp", "skeleton_event_properties.cpp", "skeleton_method.cpp", + "generic_skeleton_event.cpp", ], hdrs = [ "skeleton.h", "skeleton_event.h", "skeleton_event_properties.h", "skeleton_method.h", + "generic_skeleton_event.h", ], features = COMPILER_WARNING_FEATURES, tags = ["FFI"], @@ -282,6 +284,8 @@ cc_library( "//score/mw/com/impl/plumbing:sample_allocatee_ptr", "//score/mw/com/impl/tracing:skeleton_event_tracing", "//score/mw/com/impl/util:arithmetic_utils", + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:error", "@score_baselibs//score/filesystem", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/language/safecpp/safe_math", diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp new file mode 100644 index 000000000..1ec2b3e32 --- /dev/null +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" + +namespace score::mw::com::impl::lola +{ +GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, + const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, + const SizeInfo& size_info) + : parent_(parent), size_info_(size_info), event_properties_(event_properties), event_fqn_(event_fqn) +{ + +} + +ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept +{ + auto [data_storage, control_composite] = + parent_.RegisterGeneric(event_fqn_, event_properties_, size_info_.size, size_info_.alignment); + + data_storage_ = data_storage; + control_.emplace(std::move(control_composite)); + + return {}; +} + +Result GenericSkeletonEvent::Send(const void* /*data*/) noexcept +{ + + return MakeUnexpected(ComErrc::kIllegalUseOfAllocate); +} + +Result GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); + control_.value().EventReady(control_slot_indicator, ++current_timestamp_); + return {}; +} + +Result> GenericSkeletonEvent::Allocate() noexcept +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); + auto slot = control_.value().AllocateNextSlot(); + + if (!slot.IsValidQM() && !slot.IsValidAsilB()) + { + return MakeUnexpected>( + ComErrc::kSampleAllocationFailure); + } + + + auto* data_storage = data_storage_.get>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(data_storage != nullptr); + auto* data_base_ptr = data_storage->data(); + void* data_ptr = data_base_ptr + (slot.GetIndex() * size_info_.size); + return std::make_pair(data_ptr, slot); +} + +void GenericSkeletonEvent::Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); + control_.value().Discard(control_slot_indicator); +} + +std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept +{ + return {size_info_.size, size_info_.alignment}; +} + +void GenericSkeletonEvent::PrepareStopOffer() noexcept +{ + control_.reset(); + data_storage_ = nullptr; +} + +BindingType GenericSkeletonEvent::GetBindingType() const noexcept +{ + return BindingType::kLoLa; +} + +void GenericSkeletonEvent::SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept +{ + tracing_data_ = tracing_data; +} + +} // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h new file mode 100644 index 000000000..8ad349aae --- /dev/null +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/element_fq_id.h" +#include "score/mw/com/impl/bindings/lola/event_data_storage.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/memory/shared/offset_ptr.h" +#include "score/mw/com/impl/bindings/lola/event_slot_status.h" + +#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" + +namespace score::mw::com::impl::lola +{ + +class Skeleton; + +/// @brief The LoLa binding implementation for a generic skeleton event. +class GenericSkeletonEvent : public GenericSkeletonEventBinding +{ + public: + GenericSkeletonEvent(Skeleton& parent, + const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, + const SizeInfo& size_info); + + Result Send(const void* data) noexcept override; + + Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; + + Result> Allocate() noexcept override; + + void Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; + + std::pair GetSizeInfo() const noexcept override; + + + ResultBlank PrepareOffer() noexcept override; + void PrepareStopOffer() noexcept override; + BindingType GetBindingType() const noexcept override; + void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override; + + std::size_t GetMaxSize() const noexcept override + { + return size_info_.size; + } + + private: + Skeleton& parent_; + SizeInfo size_info_; + const SkeletonEventProperties event_properties_; + const ElementFqId event_fqn_; + score::cpp::optional control_; + std::atomic current_timestamp_{0U}; + score::memory::shared::OffsetPtr data_storage_{nullptr}; + impl::tracing::SkeletonEventTracingData tracing_data_{}; +}; + +} // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index ccc61605f..c68dc3f3e 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -26,6 +26,7 @@ #include "score/mw/com/impl/com_error.h" #include "score/mw/com/impl/configuration/lola_event_instance_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/configuration/lola_service_type_deployment.h" #include "score/mw/com/impl/configuration/quality_type.h" #include "score/mw/com/impl/runtime.h" @@ -74,20 +75,6 @@ const LolaServiceTypeDeployment& GetLolaServiceTypeDeployment(const InstanceIden return *lola_service_type_deployment_ptr; } -const LolaServiceInstanceDeployment& GetLolaServiceInstanceDeployment(const InstanceIdentifier& identifier) -{ - const auto& instance_depl_info = InstanceIdentifierView{identifier}.GetServiceInstanceDeployment(); - const auto* lola_service_instance_deployment_ptr = - std::get_if(&instance_depl_info.bindingInfo_); - if (lola_service_instance_deployment_ptr == nullptr) - { - score::mw::log::LogError("lola") << "GetLolaServiceInstanceDeployment: Wrong Binding! ServiceInstanceDeployment " - "doesn't contain a LoLa deployment!"; - std::terminate(); - } - return *lola_service_instance_deployment_ptr; -} - ServiceDataControl* GetServiceDataControlSkeletonSide(const memory::shared::ManagedMemoryResource& control) { // Suppress "AUTOSAR C++14 M5-2-8" rule. The rule declares: @@ -143,22 +130,6 @@ bool CreatePartialRestartDirectory(const score::filesystem::Filesystem& filesyst return true; } -std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( - const LolaServiceInstanceId::InstanceId lola_instance_id, - const IPartialRestartPathBuilder& partial_restart_path_builder) -{ - auto service_instance_existence_marker_file_path = - partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); - - // The instance existence marker file can be opened in the case that another skeleton of the same service currently - // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try - // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can - // exclusively flock the file. - bool take_ownership{false}; - return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), - take_ownership); -} - std::optional CreateOrOpenServiceInstanceUsageMarkerFile( const LolaServiceInstanceId::InstanceId lola_instance_id, const IPartialRestartPathBuilder& partial_restart_path_builder) @@ -213,7 +184,7 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, std::unique_ptr partial_restart_path_builder) { SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(partial_restart_path_builder != nullptr, - "Skeleton::Create: partial restart path builder pointer is Null"); + "Skeleton::Create: partial restart path builder pointer is Null"); const auto partial_restart_dir_creation_result = CreatePartialRestartDirectory(filesystem, *partial_restart_path_builder); if (!partial_restart_dir_creation_result) @@ -222,10 +193,23 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, return nullptr; } - const auto& lola_service_instance_deployment = GetLolaServiceInstanceDeployment(identifier); + // --- FIX 1: Only declare these ONCE --- + const auto& instance_depl_info = InstanceIdentifierView{identifier}.GetServiceInstanceDeployment(); + const auto* lola_service_instance_deployment_ptr = + std::get_if(&instance_depl_info.bindingInfo_); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_service_instance_deployment_ptr != nullptr); + const auto& lola_service_instance_deployment = *lola_service_instance_deployment_ptr; + const auto lola_instance_id = lola_service_instance_deployment.instance_id_.value().GetId(); - auto service_instance_existence_marker_file = - CreateOrOpenServiceInstanceExistenceMarkerFile(lola_instance_id, *partial_restart_path_builder); + auto service_instance_existence_marker_file_path = + partial_restart_path_builder->GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); + + // --- FIX 2: Correct LockFile logic (only one declaration) --- + bool take_ownership{true}; + auto service_instance_existence_marker_file = memory::shared::LockFile::CreateOrOpen( + std::move(service_instance_existence_marker_file_path), + take_ownership); + if (!service_instance_existence_marker_file.has_value()) { score::mw::log::LogError("lola") << "Could not create or open service instance existence marker file."; @@ -238,15 +222,13 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, if (!service_instance_existence_mutex_and_lock->TryLock()) { score::mw::log::LogError("lola") - << "Flock try_lock failed: Another Skeleton could have already flocked the marker file and is " - "actively offering the same service instance."; + << "Flock try_lock failed: Another Skeleton could have already flocked the marker file."; return nullptr; } + // --- FIX 3: Use the helper to get the type deployment --- const auto& lola_service_type_deployment = GetLolaServiceTypeDeployment(identifier); - // Since we were able to flock the existence marker file, it means that either we created it or the skeleton that - // created it previously crashed. Either way, we take ownership of the LockFile so that it's destroyed when this - // Skeleton is destroyed. + service_instance_existence_marker_file.value().TakeOwnership(); return std::make_unique(identifier, lola_service_instance_deployment, @@ -848,6 +830,18 @@ score::cpp::optional Skeleton::GetEventMetaInfo(const ElementFqId } } +bool Skeleton::IsEventControlRegistered(const ElementFqId element_fq_id) const noexcept +{ + if (control_qm_ == nullptr) + { + return false; + } + const bool found_qm = (control_qm_->event_controls_.count(element_fq_id) > 0); + + // For ASIL-B, it must be in both. For QM, control_asil_b_ is nullptr. + return found_qm && (control_asil_b_ == nullptr || control_asil_b_->event_controls_.count(element_fq_id) > 0); +} + QualityType Skeleton::GetInstanceQualityType() const { return InstanceIdentifierView{identifier_}.GetServiceInstanceDeployment().asilLevel_; @@ -919,6 +913,106 @@ void Skeleton::InitializeSharedMemoryForControl( control = memory->construct(memory->getMemoryResourceProxy()); } +Result> +Skeleton::CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept +{ + const auto& event_deployment = GetServiceElementInstanceDeployment( + lola_service_instance_deployment_, std::string(event_name)); + + const SkeletonEventProperties event_properties{event_deployment.GetNumberOfSampleSlots().value_or(1U), + event_deployment.max_subscribers_.value_or(1U), + event_deployment.enforce_max_samples_}; + + const ElementFqId event_fqn{ + GetLolaServiceId(), GetLolaEventTypeId(event_name), GetLolaInstanceId(), ServiceElementType::EVENT}; + + return std::make_unique(*this, + event_properties, + event_fqn, + SizeInfo{size, alignment}); +} + +EventDataControlComposite Skeleton::CreateEventControlComposite(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept +{ + if (was_old_shm_region_reopened_) { + auto it_qm = control_qm_->event_controls_.find(element_fq_id); + if (it_qm != control_qm_->event_controls_.end()) { + + it_qm->second.data_control.RemoveAllocationsForWriting(); + + EventDataControl* asil_ctrl = nullptr; + if (control_asil_b_ != nullptr) { + auto it_asil = control_asil_b_->event_controls_.find(element_fq_id); + if (it_asil != control_asil_b_->event_controls_.end()) { + it_asil->second.data_control.RemoveAllocationsForWriting(); + asil_ctrl = &it_asil->second.data_control; + } + } + return EventDataControlComposite{&it_qm->second.data_control, asil_ctrl}; + } + } + + auto control_qm = control_qm_->event_controls_.emplace(std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(element_properties.number_of_slots, + element_properties.max_subscribers, + element_properties.enforce_max_samples, + control_qm_resource_->getMemoryResourceProxy())); + + EventDataControl* control_asil_result{nullptr}; + if (control_asil_resource_ != nullptr) + { + auto iterator = control_asil_b_->event_controls_.emplace(std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(element_properties.number_of_slots, + element_properties.max_subscribers, + element_properties.enforce_max_samples, + control_asil_resource_->getMemoryResourceProxy())); + control_asil_result = &iterator.first->second.data_control; + } + return EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}; +} + +std::pair, EventDataControlComposite> +Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept +{ + // 1. Check if we are REOPENING an existing region + if (was_old_shm_region_reopened_) { + // Find existing data instead of constructing + auto it = storage_->events_.find(element_fq_id); + if (it != storage_->events_.end()) { + return {it->second, CreateEventControlComposite(element_fq_id, element_properties)}; + } + } + +// 2. If it's a fresh start OR the event wasn't found, ONLY THEN construct + auto* data_storage = storage_resource_->construct>( + sample_size * element_properties.number_of_slots, + memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy())); + + storage_->events_.emplace(element_fq_id, data_storage); + + const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; + void* const event_data_raw_array = data_storage->data(); + storage_->events_metainfo_.emplace(element_fq_id, EventMetaInfo{sample_meta_info, event_data_raw_array}); + + return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; +} + +std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept +{ + return CreateEventDataFromOpenedSharedMemory( + element_fq_id, element_properties, sample_size, sample_alignment); +} + ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& proxy_instance_identifier, uid_t proxy_uid, const QualityType asil_level, @@ -1000,7 +1094,11 @@ ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& bool Skeleton::IsProxyInAllowedConsumerList(const uid_t proxy_uid, const QualityType asil_level) const { - const auto& lola_service_instance_deployment = GetLolaServiceInstanceDeployment(identifier_); + const auto& instance_depl_info = InstanceIdentifierView{identifier_}.GetServiceInstanceDeployment(); + const auto* lola_service_instance_deployment_ptr = + std::get_if(&instance_depl_info.bindingInfo_); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_service_instance_deployment_ptr != nullptr); + const auto& lola_service_instance_deployment = *lola_service_instance_deployment_ptr; const auto& allowed_consumer = lola_service_instance_deployment.allowed_consumer_; // Check if there is an allowed consumer list for the specified quality (ASIL-B / QM) diff --git a/score/mw/com/impl/bindings/lola/skeleton.h b/score/mw/com/impl/bindings/lola/skeleton.h index ecbc9e07e..f7746095c 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.h +++ b/score/mw/com/impl/bindings/lola/skeleton.h @@ -107,6 +107,15 @@ class Skeleton final : public SkeletonBinding return BindingType::kLoLa; }; + Result> + CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept override; + + std::pair, EventDataControlComposite> RegisterGeneric( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept; + /// \brief Enables dynamic registration of Events at the Skeleton. /// \tparam SampleType The type of the event /// \param element_fq_id The full qualified of the element (event or field) that shall be registered @@ -126,6 +135,31 @@ class Skeleton final : public SkeletonBinding /// \return Events meta-info, if it has been registered, null else. score::cpp::optional GetEventMetaInfo(const ElementFqId element_fq_id) const; + /// @brief Checks if an event's control block is registered. + /// @param element_fq_id The fully qualified ID of the event. + /// @return True if the control block is registered, false otherwise. + bool IsEventControlRegistered(const ElementFqId element_fq_id) const noexcept; + + const LolaServiceInstanceDeployment& GetLolaServiceInstanceDeployment() const noexcept + { + return lola_service_instance_deployment_; + } + + LolaServiceId GetLolaServiceId() const noexcept + { + return lola_service_id_; + } + + LolaEventId GetLolaEventTypeId(std::string_view event_name) const noexcept + { + return lola_service_type_deployment_.events_.at(std::string(event_name)); + } + + LolaServiceInstanceId::InstanceId GetLolaInstanceId() const noexcept + { + return lola_instance_id_; + } + QualityType GetInstanceQualityType() const; /// \brief Cleans up all allocated slots for this SkeletonEvent of any previous running instance @@ -169,6 +203,15 @@ class Skeleton final : public SkeletonBinding const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties); + EventDataControlComposite CreateEventControlComposite(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept; + + std::pair, EventDataControlComposite> + CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept; + class ShmResourceStorageSizes { public: @@ -385,44 +428,7 @@ auto Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_f std::forward_as_tuple(sample_meta_info, event_data_raw_array)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - auto control_qm = - control_qm_->event_controls_.emplace(std::piecewise_construct, - std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(element_properties.number_of_slots, - element_properties.max_subscribers, - element_properties.enforce_max_samples, - control_qm_resource_->getMemoryResourceProxy())); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section."); - - EventDataControl* control_asil_result{nullptr}; - if (control_asil_resource_ != nullptr) - { - auto iterator = control_asil_b_->event_controls_.emplace( - std::piecewise_construct, - std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(element_properties.number_of_slots, - element_properties.max_subscribers, - element_properties.enforce_max_samples, - control_asil_resource_->getMemoryResourceProxy())); - - // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares: - // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined - // within the function. - // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to - // another object that may persist after the first object has ceased to exist. - // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive. - // coverity[autosar_cpp14_m7_5_1_violation] - // coverity[autosar_cpp14_m7_5_2_violation] - // coverity[autosar_cpp14_a3_8_1_violation] - control_asil_result = &iterator.first->second.data_control; - } - // clang-format off - // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive. - // coverity[autosar_cpp14_m7_5_1_violation] - // coverity[autosar_cpp14_m7_5_2_violation] - // coverity[autosar_cpp14_a3_8_1_violation] - return {typed_event_data_storage_ptr, EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}}; - // clang-format on + return {typed_event_data_storage_ptr, CreateEventControlComposite(element_fq_id, element_properties)}; } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/mock_binding/skeleton.h b/score/mw/com/impl/bindings/mock_binding/skeleton.h index bc75857bd..613f00fff 100644 --- a/score/mw/com/impl/bindings/mock_binding/skeleton.h +++ b/score/mw/com/impl/bindings/mock_binding/skeleton.h @@ -34,6 +34,10 @@ class Skeleton : public SkeletonBinding (noexcept, override, final)); MOCK_METHOD(void, PrepareStopOffer, (std::optional), (noexcept, override, final)); MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override, final)); + MOCK_METHOD(Result>, + CreateGenericEventBinding, + (std::string_view event_name, size_t size, size_t alignment), + (noexcept, override, final)); }; class SkeletonFacade : public SkeletonBinding @@ -59,6 +63,12 @@ class SkeletonFacade : public SkeletonBinding return skeleton_.GetBindingType(); } + Result> + CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept override final + { + return skeleton_.CreateGenericEventBinding(event_name, size, alignment); + } + private: Skeleton& skeleton_; }; diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp new file mode 100644 index 000000000..40828bb0a --- /dev/null +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" + +#include "score/mw/com/impl/com_error.h" +#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/skeleton_binding.h" + +namespace score::mw::com::impl +{ + +Result GenericSkeleton::Create(const InstanceSpecifier& specifier) noexcept +{ + const auto identifier_result = Runtime::getInstance().resolve(specifier); + if (identifier_result.empty()) + { + return MakeUnexpected(ComErrc::kInstanceIDCouldNotBeResolved); + } + + if (identifier_result.size() > 1) + { + score::mw::log::LogWarn("com") << "InstanceSpecifier resolved to more than one InstanceIdentifier. Using the first one."; + } + + return Create(identifier_result[0]); +} + +Result GenericSkeleton::Create(const InstanceIdentifier& identifier) noexcept +{ + auto binding = SkeletonBindingFactory::Create(identifier); + if (!binding) + { + return MakeUnexpected(ComErrc::kBindingFailure); + } + + return GenericSkeleton(identifier, std::move(binding)); +} + +Result GenericSkeleton::AddEvent(std::string_view name, const SizeInfo& size_info) noexcept +{ + if (SkeletonBaseView{*this}.IsOffered()) + { + return MakeUnexpected(ComErrc::kNotOffered); + } + + auto event_binding_result = + SkeletonBaseView{*this}.GetBinding()->CreateGenericEventBinding(name, size_info.size, size_info.alignment); + if (!event_binding_result.has_value()) + { + return MakeUnexpected(ComErrc::kNotOffered); + } + + auto emplace_result = events_.emplace( + name, std::make_unique(*this, name, std::move(event_binding_result).value())); + if (!emplace_result.second) + { + return MakeUnexpected(ComErrc::kEventNotExisting); + } + auto& event = *emplace_result.first->second; + SkeletonBaseView{*this}.RegisterEvent(name, event); + + return &event; +} + +Result GenericSkeleton::OfferService() noexcept +{ + return SkeletonBase::OfferService(); +} + +void GenericSkeleton::StopOfferService() noexcept +{ + SkeletonBase::StopOfferService(); +} + +GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding) + : SkeletonBase(std::move(binding), identifier) +{ +} + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h new file mode 100644 index 000000000..f696418ac --- /dev/null +++ b/score/mw/com/impl/generic_skeleton.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/generic_skeleton_event.h" +#include "score/mw/com/impl/instance_identifier.h" +#include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/size_info.h" + +#include "score/result/result.h" + +#include +#include + +namespace score::mw::com::impl +{ + +class SkeletonBinding; + + +class GenericSkeleton : public SkeletonBase +{ + public: + /// @brief Creates a GenericSkeleton for a given instance specifier. + /// @param specifier The instance specifier. + /// @return A GenericSkeleton or an error. + static Result Create(const InstanceSpecifier& specifier) noexcept; + + /// @brief Creates a GenericSkeleton for a given instance identifier. + /// @param identifier The instance identifier. + /// @return A GenericSkeleton or an error. + static Result Create(const InstanceIdentifier& identifier) noexcept; + + /// @brief Adds a type-erased event to the skeleton. + /// + /// This must be called before OfferService(). + /// + /// @param name The name of the event. + /// @param size_info The size and alignment requirements for the event's sample data. + /// @return A reference to the created event or an error. + Result AddEvent(std::string_view name, const SizeInfo& size_info) noexcept; + + /// @brief Offers the service instance. + /// @return A blank result, or an error if offering fails. + Result OfferService() noexcept; + + /// @brief Stops offering the service instance. + void StopOfferService() noexcept; + + private: + GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding); + + std::map> events_; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp new file mode 100644 index 000000000..92c730d1f --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton_event.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" + +#include + +namespace score::mw::com::impl +{ + +GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, + const std::string_view event_name, + std::unique_ptr binding) + : SkeletonEventBase(skeleton_base, event_name, std::move(binding)) +{ +} + +Result GenericSkeletonEvent::Send(SampleAllocateePtr sample) noexcept +{ + auto* const binding = static_cast(binding_.get()); + std::cout<<"auto* const binding = static_cast(binding_.get());"<{sample}.As(); + std::cout<<"auto* const generic_sample_ptr = SampleAllocateePtrView{sample}.As();"<Send(generic_sample_ptr->GetControlSlotIndicator()); + return result; +} + +Result> GenericSkeletonEvent::Allocate() noexcept +{ + auto* binding = static_cast(binding_.get()); + auto result = binding->Allocate(); + if (!result.has_value()) + { + return MakeUnexpected>(ComErrc::kSampleAllocationFailure); + } + + auto deallocator = [binding_ptr = binding_.get()](lola::ControlSlotCompositeIndicator indicator) { + auto* const generic_binding = static_cast(binding_ptr); + generic_binding->Deallocate(indicator); + }; + + return MakeSampleAllocateePtr( + GenericEventSamplePtr(result.value().first, result.value().second, std::move(deallocator))); +} + +SizeInfo GenericSkeletonEvent::GetSizeInfo() const noexcept +{ + const auto* const binding = static_cast(binding_.get()); + const auto size_info_pair = binding->GetSizeInfo(); + return {size_info_pair.first, size_info_pair.second}; +} + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h new file mode 100644 index 000000000..a4a12af03 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/skeleton_event_base.h" +#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" +#include "score/mw/com/impl/size_info.h" + +#include "score/result/result.h" + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBinding; + + +class GenericSkeletonEvent : public SkeletonEventBase +{ + public: + GenericSkeletonEvent(SkeletonBase& skeleton_base, + const std::string_view event_name, + std::unique_ptr binding); + + + Result Send(SampleAllocateePtr sample) noexcept; + + + Result> Allocate() noexcept; + + + SizeInfo GetSizeInfo() const noexcept; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h new file mode 100644 index 000000000..c3a55361f --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h" +#include "score/result/result.h" + +#include + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBinding : public SkeletonEventBindingBase +{ + public: + virtual Result Send(const void* data) noexcept = 0; + + virtual Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; + + virtual Result> Allocate() noexcept = 0; + + virtual void Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; + + virtual std::pair GetSizeInfo() const noexcept = 0; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp new file mode 100644 index 000000000..072717d98 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" + +#include "score/mw/com/impl/bindings/mock_binding/skeleton.h" +#include "score/mw/com/impl/runtime_mock.h" +#include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" +#include "score/mw/com/impl/test/runtime_mock_guard.h" +#include "score/mw/com/impl/test/skeleton_binding_factory_mock_guard.h" + +#include +#include + +namespace score::mw::com::impl +{ +namespace +{ + +using ::testing::_; +using ::testing::ByMove; +using ::testing::Return; + +class GenericSkeletonTest : public ::testing::Test +{ + public: + GenericSkeletonTest() + { + auto skeleton_binding_mock = std::make_unique(); + skeleton_binding_mock_ = skeleton_binding_mock.get(); + + ON_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) + .WillByDefault(Return(ByMove(std::move(skeleton_binding_mock)))); + } + + RuntimeMockGuard runtime_mock_guard_{}; + SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{}; + mock_binding::Skeleton* skeleton_binding_mock_{nullptr}; + DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_{}; +}; + +TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifier) +{ + auto instance_specifier = InstanceSpecifier::Create("a/b/c").value(); + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, ResolveInstanceIdentifier(instance_specifier)) + .WillOnce(Return(instance_identifier)); + + auto skeleton_result = GenericSkeleton::Create(instance_specifier); + ASSERT_TRUE(skeleton_result.has_value()); +} + +TEST_F(GenericSkeletonTest, CreateWithInstanceIdentifier) +{ + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + auto skeleton_result = GenericSkeleton::Create(instance_identifier); + ASSERT_TRUE(skeleton_result.has_value()); +} + +TEST_F(GenericSkeletonTest, AddEvent) +{ + auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); + const SizeInfo size_info{16, 8}; + auto event_result = skeleton.AddEvent("MyEvent", size_info); + ASSERT_TRUE(event_result.has_value()); + + // Adding the same event again fails + auto second_event_result = skeleton.AddEvent("MyEvent", size_info); + ASSERT_FALSE(second_event_result.has_value()); +} + +TEST_F(GenericSkeletonTest, OfferAndStopOfferService) +{ + auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); + + EXPECT_CALL(*skeleton_binding_mock_, OfferService()).WillOnce(Return(score::result::Blank{})); + auto offer_result = skeleton.OfferService(); + EXPECT_TRUE(offer_result.has_value()); + + EXPECT_CALL(*skeleton_binding_mock_, StopOfferService()); + skeleton.StopOfferService(); +} + +TEST_F(GenericSkeletonTest, CreateFailsIfBindingFails) +{ + EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)).WillOnce(Return(ByMove(nullptr))); + auto skeleton_result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()); + ASSERT_FALSE(skeleton_result.has_value()); + EXPECT_EQ(skeleton_result.error(), ComErrc::kBindingFailure); +} + +} // namespace +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index c12d712d1..6878847d6 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,56 @@ namespace score::mw::com::impl { +/// @brief An RAII wrapper for a type-erased sample allocated by a GenericSkeletonEvent. +class GenericEventSamplePtr +{ + public: + using element_type = void; + + GenericEventSamplePtr(void* data, + lola::ControlSlotCompositeIndicator control_slot_indicator, + std::function deallocator) + : data_(data), control_slot_indicator_(control_slot_indicator), deallocator_(std::move(deallocator)) + { + } + + ~GenericEventSamplePtr() + { + if (data_ != nullptr) + { + deallocator_(control_slot_indicator_); + } + } + + GenericEventSamplePtr(const GenericEventSamplePtr&) = delete; + GenericEventSamplePtr& operator=(const GenericEventSamplePtr&) = delete; + + GenericEventSamplePtr(GenericEventSamplePtr&& other) noexcept + : data_(other.data_), + control_slot_indicator_(other.control_slot_indicator_), + deallocator_(std::move(other.deallocator_)) + { + other.data_ = nullptr; + } + + GenericEventSamplePtr& operator=(GenericEventSamplePtr&&) = default; + + void* get() const + { + return data_; + } + + lola::ControlSlotCompositeIndicator GetControlSlotIndicator() const + { + return control_slot_indicator_; + } + + private: + void* data_; + lola::ControlSlotCompositeIndicator control_slot_indicator_; + std::function deallocator_; +}; + /// \brief Pointer to a data sample allocated by the Communication Management implementation (mimics std::unique_ptr) /// /// \details We try to implement certain functionality that facades an std::unique_ptr, but some functionalities (e.g. @@ -338,6 +389,114 @@ auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr{std::move(ptr)}; } +/// \brief Template specialization of SampleAllocateePtr for void. +template <> +class SampleAllocateePtr +{ + public: + using pointer = void*; + using element_type = void; + + constexpr SampleAllocateePtr() noexcept : SampleAllocateePtr(score::cpp::blank{}) {} + constexpr explicit SampleAllocateePtr(std::nullptr_t) noexcept : SampleAllocateePtr() {} + + SampleAllocateePtr(const SampleAllocateePtr&) = delete; + SampleAllocateePtr& operator=(const SampleAllocateePtr&) & = delete; + + SampleAllocateePtr(SampleAllocateePtr&& other) noexcept : SampleAllocateePtr() + { + this->Swap(other); + } + + SampleAllocateePtr& operator=(SampleAllocateePtr&& other) & noexcept + { + this->Swap(other); + return *this; + } + + SampleAllocateePtr& operator=(std::nullptr_t) noexcept + { + reset(); + return *this; + } + + ~SampleAllocateePtr() noexcept = default; + + void reset() noexcept + { + auto visitor = score::cpp::overload( + [](lola::SampleAllocateePtr& internal_ptr) noexcept -> void { internal_ptr.reset(); }, + [](GenericEventSamplePtr& internal_ptr) noexcept -> void { + GenericEventSamplePtr temp = std::move(internal_ptr); + }, + [](const score::cpp::blank&) noexcept -> void {}); + std::visit(visitor, internal_); + } + + void Swap(SampleAllocateePtr& other) noexcept + { + using std::swap; + swap(internal_, other.internal_); + } + + pointer Get() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const GenericEventSamplePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const score::cpp::blank&) noexcept -> pointer { return nullptr; }); + return std::visit(visitor, internal_); + } + + explicit operator bool() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> bool { + return static_cast(internal_ptr); + }, + [](const GenericEventSamplePtr& internal_ptr) noexcept -> bool { return internal_ptr.get() != nullptr; }, + [](const score::cpp::blank&) noexcept -> bool { return false; }); + return std::visit(visitor, internal_); + } + + // operator* is intentionally omitted for void specialization. + + pointer operator->() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const GenericEventSamplePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const score::cpp::blank&) noexcept -> pointer { + std::terminate(); + return nullptr; + }); + return std::visit(visitor, internal_); + } + + private: + template + constexpr explicit SampleAllocateePtr(T ptr) : internal_{std::move(ptr)} + { + } + + template + friend auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr; + + template + friend class SampleAllocateePtrView; + + template + friend class SampleAllocateePtrMutableView; + + std::variant, GenericEventSamplePtr> internal_; +}; + +template <> +inline void swap(SampleAllocateePtr& lhs, SampleAllocateePtr& rhs) noexcept +{ + lhs.Swap(rhs); +} + /// \brief SampleAllocateePtr is user facing, in order to interact with its internals we provide a view towards it template class SampleAllocateePtrView diff --git a/score/mw/com/impl/size_info.h b/score/mw/com/impl/size_info.h new file mode 100644 index 000000000..d69131fa4 --- /dev/null +++ b/score/mw/com/impl/size_info.h @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include + +namespace score::mw::com +{ +/// @brief A struct to hold size and alignment information for generic type-erased data. +struct SizeInfo +{ + size_t size; + size_t alignment; +}; + +} // namespace score::mw::com \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index e949652d6..a5a057286 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -146,6 +146,11 @@ class SkeletonBaseView return skeleton_base_.binding_.get(); } + bool IsOffered() const + { + return skeleton_base_.service_offered_flag_.IsSet(); + } + void RegisterEvent(const std::string_view event_name, SkeletonEventBase& event) { const auto result = skeleton_base_.events_.emplace(event_name, event); diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index bc111ebe6..67f867ea5 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -16,7 +16,9 @@ #include "score/memory/shared/i_shared_memory_resource.h" #include "score/result/result.h" #include "score/mw/com/impl/binding_type.h" +#include "score/mw/com/impl/size_info.h" #include "score/mw/com/impl/service_element_type.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" #include #include @@ -96,6 +98,14 @@ class SkeletonBinding /// \brief Gets the binding type of the binding virtual BindingType GetBindingType() const noexcept = 0; + + /// @brief Creates a binding for a generic event. + /// @param event_name The name of the event. + /// @param size The size for the event's data. + /// @param alignment The alignment for the event's data. + /// @return A unique pointer to the created binding or an error. + virtual Result> + CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept = 0; }; } // namespace score::mw::com::impl From 3edd8b65f8562c880eeb0d4a5f7717fdc3f6fbae Mon Sep 17 00:00:00 2001 From: Abhishek GOYAL Date: Fri, 9 Jan 2026 14:40:30 +0100 Subject: [PATCH 02/18] Enhance Generic Skeleton Code --- score/mw/com/impl/BUILD | 4 + score/mw/com/impl/bindings/lola/BUILD | 20 ++ .../bindings/lola/generic_skeleton_event.cpp | 82 ++++--- .../bindings/lola/generic_skeleton_event.h | 70 +++++- score/mw/com/impl/bindings/lola/skeleton.cpp | 208 +++++++++--------- score/mw/com/impl/bindings/lola/skeleton.h | 34 +-- .../com/impl/bindings/lola/skeleton_event.h | 189 ++++++++++------ .../com/impl/bindings/mock_binding/skeleton.h | 10 - score/mw/com/impl/com_error.h | 4 + score/mw/com/impl/generic_skeleton.cpp | 51 +++-- score/mw/com/impl/generic_skeleton.h | 12 +- score/mw/com/impl/generic_skeleton_event.cpp | 62 ++++-- .../com/impl/generic_skeleton_event_binding.h | 5 +- score/mw/com/impl/plumbing/BUILD | 15 ++ .../generic_skeleton_event_binding_factory.h | 48 ++++ .../com/impl/plumbing/sample_allocatee_ptr.h | 58 +---- ...ton_service_element_binding_factory_impl.h | 59 ++++- score/mw/com/impl/skeleton_binding.h | 7 - 18 files changed, 583 insertions(+), 355 deletions(-) create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index a03ad876c..5b03bb490 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -118,6 +118,7 @@ cc_library( features = COMPILER_WARNING_FEATURES, implementation_deps = [ ":error", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory", "//score/mw/com/impl/plumbing", ], tags = ["FFI"], @@ -146,7 +147,9 @@ cc_library( "//score/mw/com:__subpackages__", ], deps = [ + "//score/mw/com/impl/bindings/lola:generic_skeleton_event", ":generic_skeleton_event_binding", + ":skeleton_base", ":skeleton_event_base", ":skeleton_event_binding", ":size_info", @@ -165,6 +168,7 @@ cc_library( visibility = [ "//score/mw/com/impl/bindings/lola:__pkg__", "//score/mw/com/impl/bindings/mock_binding:__pkg__", + "//score/mw/com/impl/plumbing:__pkg__", ], deps = [ ":skeleton_event_binding", diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index 584a435b9..5ec4eecce 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -240,6 +240,26 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event", + srcs = ["generic_skeleton_event.cpp"], + hdrs = ["generic_skeleton_event.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + ":event", + ":skeleton", + "//score/mw/com/impl:error", + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:size_info", + "//score/mw/com/impl/tracing:skeleton_event_tracing", + ":type_erased_sample_ptrs_guard", + ], +) + cc_library( name = "skeleton", srcs = [ diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index 1ec2b3e32..ad701b673 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -12,7 +12,10 @@ ********************************************************************************/ #include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" namespace score::mw::com::impl::lola { @@ -27,18 +30,15 @@ GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept { - auto [data_storage, control_composite] = + std::tie(data_storage_, control_) = parent_.RegisterGeneric(event_fqn_, event_properties_, size_info_.size, size_info_.alignment); - - data_storage_ = data_storage; - control_.emplace(std::move(control_composite)); + PrepareOfferImpl(*this); return {}; } Result GenericSkeletonEvent::Send(const void* /*data*/) noexcept { - return MakeUnexpected(ComErrc::kIllegalUseOfAllocate); } @@ -46,32 +46,65 @@ Result GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndica { SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); control_.value().EventReady(control_slot_indicator, ++current_timestamp_); + + // Only call NotifyEvent if there are any registered receive handlers for each quality level. + // This avoids the expensive lock operation in the common case where no handlers are registered. + // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent + // handler registration, the next Send() will pick it up. + if (qm_event_update_notifications_registered_.load() && !qm_disconnect_) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .NotifyEvent(QualityType::kASIL_QM, event_fqn_); + } + if (asil_b_event_update_notifications_registered_.load() && parent_.GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .NotifyEvent(QualityType::kASIL_B, event_fqn_); + } + return {}; } -Result> GenericSkeletonEvent::Allocate() noexcept +Result> GenericSkeletonEvent::Allocate() noexcept { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); - auto slot = control_.value().AllocateNextSlot(); - - if (!slot.IsValidQM() && !slot.IsValidAsilB()) + if (!control_.has_value()) { - return MakeUnexpected>( - ComErrc::kSampleAllocationFailure); + ::score::mw::log::LogError("lola") << "Tried to allocate event, but the EventDataControl does not exist!"; + return MakeUnexpected(ComErrc::kBindingFailure); } + const auto slot = control_.value().AllocateNextSlot(); - - auto* data_storage = data_storage_.get>(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(data_storage != nullptr); - auto* data_base_ptr = data_storage->data(); - void* data_ptr = data_base_ptr + (slot.GetIndex() * size_info_.size); - return std::make_pair(data_ptr, slot); -} + if (!qm_disconnect_ && control_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM()) + { + qm_disconnect_ = true; + score::mw::log::LogWarn("lola") + << __func__ << __LINE__ + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_fqn_; + parent_.DisconnectQmConsumers(); + } -void GenericSkeletonEvent::Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept -{ - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); - control_.value().Discard(control_slot_indicator); + if (slot.IsValidQM() || slot.IsValidAsilB()) + { + auto* data_storage = data_storage_.get>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(data_storage != nullptr); + // The at() method on EventDataStorage (which is a DynamicArray) correctly handles + // the pointer arithmetic based on its template type (std::uint8_t). + // We multiply by size_info_.size here because the underlying storage is a byte array. + void* data_ptr = &data_storage->at(static_cast(slot.GetIndex()) * size_info_.size); + return lola::SampleAllocateePtr(data_ptr, control_.value(), slot); + } + else + { + if (!event_properties_.enforce_max_samples) + { + ::score::mw::log::LogError("lola") + << "GenericSkeletonEvent: Allocation of event slot failed. Hint: enforceMaxSamples was " + "disabled by config. Might be the root cause!"; + } + return MakeUnexpected(ComErrc::kBindingFailure); + } } std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept @@ -81,8 +114,7 @@ std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept void GenericSkeletonEvent::PrepareStopOffer() noexcept { - control_.reset(); - data_storage_ = nullptr; + PrepareStopOfferImpl(*this); } BindingType GenericSkeletonEvent::GetBindingType() const noexcept diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index 8ad349aae..15c9abde4 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -20,11 +20,14 @@ #include "score/memory/shared/offset_ptr.h" #include "score/mw/com/impl/bindings/lola/event_slot_status.h" +#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" +#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" namespace score::mw::com::impl::lola { +class TransactionLogRegistrationGuard; class Skeleton; /// @brief The LoLa binding implementation for a generic skeleton event. @@ -40,9 +43,7 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; - Result> Allocate() noexcept override; - - void Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; + Result> Allocate() noexcept override; std::pair GetSizeInfo() const noexcept override; @@ -62,10 +63,69 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding SizeInfo size_info_; const SkeletonEventProperties event_properties_; const ElementFqId event_fqn_; - score::cpp::optional control_; - std::atomic current_timestamp_{0U}; + score::cpp::optional control_{}; + EventSlotStatus::EventTimeStamp current_timestamp_{0U}; score::memory::shared::OffsetPtr data_storage_{nullptr}; + bool qm_disconnect_{false}; impl::tracing::SkeletonEventTracingData tracing_data_{}; + std::atomic qm_event_update_notifications_registered_{false}; + std::atomic asil_b_event_update_notifications_registered_{false}; + std::optional transaction_log_registration_guard_{}; + std::optional type_erased_sample_ptrs_guard_{}; + + public: + // The following methods are public but intended for use by PrepareOfferImpl helper + impl::tracing::SkeletonEventTracingData& GetTracingData() + { + return tracing_data_; + } + + void EmplaceTransactionLogRegistrationGuard() + { + score::cpp::ignore = transaction_log_registration_guard_.emplace( + TransactionLogRegistrationGuard::Create(control_.value().GetQmEventDataControl())); + } + + void EmplaceTypeErasedSamplePtrsGuard() + { + score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(tracing_data_.service_element_tracing_data); + } + + void UpdateCurrentTimestamp() + { + current_timestamp_ = control_.value().GetLatestTimestamp(); + } + + Skeleton& GetParent() + { + return parent_; + } + + const ElementFqId& GetElementFQId() const + { + return event_fqn_; + } + + void SetQmNotificationsRegistered(bool value) + { + qm_event_update_notifications_registered_.store(value); + } + + void SetAsilBNotificationsRegistered(bool value) + { + asil_b_event_update_notifications_registered_.store(value); + } + + void ResetGuards() noexcept + { + type_erased_sample_ptrs_guard_.reset(); + if (control_.has_value()) + { + transaction_log_registration_guard_.reset(); + } + control_.reset(); + data_storage_ = nullptr; + } }; } // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index c68dc3f3e..8b9a0cb39 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -75,6 +75,20 @@ const LolaServiceTypeDeployment& GetLolaServiceTypeDeployment(const InstanceIden return *lola_service_type_deployment_ptr; } +const LolaServiceInstanceDeployment& GetLolaServiceInstanceDeployment(const InstanceIdentifier& identifier) +{ + const auto& instance_depl_info = InstanceIdentifierView{identifier}.GetServiceInstanceDeployment(); + const auto* lola_service_instance_deployment_ptr = + std::get_if(&instance_depl_info.bindingInfo_); + if (lola_service_instance_deployment_ptr == nullptr) + { + score::mw::log::LogError("lola") << "GetLolaServiceInstanceDeployment: Wrong Binding! ServiceInstanceDeployment " + "doesn't contain a LoLa deployment!"; + std::terminate(); + } + return *lola_service_instance_deployment_ptr; +} + ServiceDataControl* GetServiceDataControlSkeletonSide(const memory::shared::ManagedMemoryResource& control) { // Suppress "AUTOSAR C++14 M5-2-8" rule. The rule declares: @@ -98,6 +112,22 @@ ServiceDataStorage* GetServiceDataStorageSkeletonSide(const memory::shared::Mana return service_data_storage; } +std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( + const LolaServiceInstanceId::InstanceId lola_instance_id, + const IPartialRestartPathBuilder& partial_restart_path_builder) +{ + auto service_instance_existence_marker_file_path = + partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); + + // The instance existence marker file can be opened in the case that another skeleton of the same service currently + // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try + // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can + // exclusively flock the file. + bool take_ownership{false}; + return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), + take_ownership); +} + enum class ShmObjectType : std::uint8_t { kControl_QM = 0x00, @@ -193,42 +223,32 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, return nullptr; } - // --- FIX 1: Only declare these ONCE --- - const auto& instance_depl_info = InstanceIdentifierView{identifier}.GetServiceInstanceDeployment(); - const auto* lola_service_instance_deployment_ptr = - std::get_if(&instance_depl_info.bindingInfo_); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_service_instance_deployment_ptr != nullptr); - const auto& lola_service_instance_deployment = *lola_service_instance_deployment_ptr; - + const auto& lola_service_instance_deployment = GetLolaServiceInstanceDeployment(identifier); const auto lola_instance_id = lola_service_instance_deployment.instance_id_.value().GetId(); - auto service_instance_existence_marker_file_path = - partial_restart_path_builder->GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); - - // --- FIX 2: Correct LockFile logic (only one declaration) --- - bool take_ownership{true}; - auto service_instance_existence_marker_file = memory::shared::LockFile::CreateOrOpen( - std::move(service_instance_existence_marker_file_path), - take_ownership); + auto service_instance_existence_marker_file = + CreateOrOpenServiceInstanceExistenceMarkerFile(lola_instance_id, *partial_restart_path_builder); if (!service_instance_existence_marker_file.has_value()) { score::mw::log::LogError("lola") << "Could not create or open service instance existence marker file."; return nullptr; } - + auto service_instance_existence_mutex_and_lock = std::make_unique>( *service_instance_existence_marker_file); if (!service_instance_existence_mutex_and_lock->TryLock()) { score::mw::log::LogError("lola") - << "Flock try_lock failed: Another Skeleton could have already flocked the marker file."; + << "Flock try_lock failed: Another Skeleton could have already flocked the marker file and is " + "actively offering the same service instance."; return nullptr; } - - // --- FIX 3: Use the helper to get the type deployment --- + const auto& lola_service_type_deployment = GetLolaServiceTypeDeployment(identifier); - + // Since we were able to flock the existence marker file, it means that either we created it or the skeleton that + // created it previously crashed. Either way, we take ownership of the LockFile so that it's destroyed when this + // Skeleton is destroyed. service_instance_existence_marker_file.value().TakeOwnership(); return std::make_unique(identifier, lola_service_instance_deployment, @@ -830,18 +850,6 @@ score::cpp::optional Skeleton::GetEventMetaInfo(const ElementFqId } } -bool Skeleton::IsEventControlRegistered(const ElementFqId element_fq_id) const noexcept -{ - if (control_qm_ == nullptr) - { - return false; - } - const bool found_qm = (control_qm_->event_controls_.count(element_fq_id) > 0); - - // For ASIL-B, it must be in both. For QM, control_asil_b_ is nullptr. - return found_qm && (control_asil_b_ == nullptr || control_asil_b_->event_controls_.count(element_fq_id) > 0); -} - QualityType Skeleton::GetInstanceQualityType() const { return InstanceIdentifierView{identifier_}.GetServiceInstanceDeployment().asilLevel_; @@ -913,92 +921,72 @@ void Skeleton::InitializeSharedMemoryForControl( control = memory->construct(memory->getMemoryResourceProxy()); } -Result> -Skeleton::CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept -{ - const auto& event_deployment = GetServiceElementInstanceDeployment( - lola_service_instance_deployment_, std::string(event_name)); - - const SkeletonEventProperties event_properties{event_deployment.GetNumberOfSampleSlots().value_or(1U), - event_deployment.max_subscribers_.value_or(1U), - event_deployment.enforce_max_samples_}; - - const ElementFqId event_fqn{ - GetLolaServiceId(), GetLolaEventTypeId(event_name), GetLolaInstanceId(), ServiceElementType::EVENT}; - - return std::make_unique(*this, - event_properties, - event_fqn, - SizeInfo{size, alignment}); -} - EventDataControlComposite Skeleton::CreateEventControlComposite(const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties) noexcept { - if (was_old_shm_region_reopened_) { - auto it_qm = control_qm_->event_controls_.find(element_fq_id); - if (it_qm != control_qm_->event_controls_.end()) { - - it_qm->second.data_control.RemoveAllocationsForWriting(); - - EventDataControl* asil_ctrl = nullptr; - if (control_asil_b_ != nullptr) { - auto it_asil = control_asil_b_->event_controls_.find(element_fq_id); - if (it_asil != control_asil_b_->event_controls_.end()) { - it_asil->second.data_control.RemoveAllocationsForWriting(); - asil_ctrl = &it_asil->second.data_control; - } - } - return EventDataControlComposite{&it_qm->second.data_control, asil_ctrl}; - } - } - auto control_qm = control_qm_->event_controls_.emplace(std::piecewise_construct, std::forward_as_tuple(element_fq_id), std::forward_as_tuple(element_properties.number_of_slots, element_properties.max_subscribers, element_properties.enforce_max_samples, control_qm_resource_->getMemoryResourceProxy())); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section."); EventDataControl* control_asil_result{nullptr}; if (control_asil_resource_ != nullptr) { - auto iterator = control_asil_b_->event_controls_.emplace(std::piecewise_construct, - std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(element_properties.number_of_slots, - element_properties.max_subscribers, - element_properties.enforce_max_samples, - control_asil_resource_->getMemoryResourceProxy())); + auto iterator = control_asil_b_->event_controls_.emplace( + std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(element_properties.number_of_slots, + element_properties.max_subscribers, + element_properties.enforce_max_samples, + control_asil_resource_->getMemoryResourceProxy())); + + // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares: + // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined + // within the function. + // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to + // another object that may persist after the first object has ceased to exist. + // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive. + // coverity[autosar_cpp14_m7_5_1_violation] + // coverity[autosar_cpp14_m7_5_2_violation] + // coverity[autosar_cpp14_a3_8_1_violation] control_asil_result = &iterator.first->second.data_control; } + // clang-format off + // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive. + // coverity[autosar_cpp14_m7_5_1_violation] + // coverity[autosar_cpp14_m7_5_2_violation] + // coverity[autosar_cpp14_a3_8_1_violation] return EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}; } -std::pair, EventDataControlComposite> -Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, - const SkeletonEventProperties& element_properties, - size_t sample_size, - size_t sample_alignment) noexcept +std::pair, EventDataControlComposite> +Skeleton::CreateEventDataFromOpenedSharedMemory( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept { - // 1. Check if we are REOPENING an existing region - if (was_old_shm_region_reopened_) { - // Find existing data instead of constructing - auto it = storage_->events_.find(element_fq_id); - if (it != storage_->events_.end()) { - return {it->second, CreateEventControlComposite(element_fq_id, element_properties)}; - } - } - -// 2. If it's a fresh start OR the event wasn't found, ONLY THEN construct auto* data_storage = storage_resource_->construct>( sample_size * element_properties.number_of_slots, memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy())); - storage_->events_.emplace(element_fq_id, data_storage); + auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(data_storage)); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second, + "Couldn't register/emplace event-storage in data-section."); const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; void* const event_data_raw_array = data_storage->data(); - storage_->events_metainfo_.emplace(element_fq_id, EventMetaInfo{sample_meta_info, event_data_raw_array}); + auto inserted_meta_info = storage_->events_metainfo_.emplace( + std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(sample_meta_info, event_data_raw_array)); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, + "Couldn't register/emplace event-meta-info in data-section."); return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; } @@ -1006,11 +994,33 @@ Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties, - size_t sample_size, - size_t sample_alignment) noexcept + const size_t sample_size, + const size_t sample_alignment) noexcept { - return CreateEventDataFromOpenedSharedMemory( - element_fq_id, element_properties, sample_size, sample_alignment); + if (was_old_shm_region_reopened_) + { + auto [data_storage, control_composite] = OpenEventDataFromOpenedSharedMemory(element_fq_id); + + auto& event_data_control_qm = control_composite.GetQmEventDataControl(); + auto rollback_result = event_data_control_qm.GetTransactionLogSet().RollbackSkeletonTracingTransactions( + [&event_data_control_qm](const TransactionLog::SlotIndexType slot_index) { + event_data_control_qm.DereferenceEventWithoutTransactionLogging(slot_index); + }); + if (!rollback_result.has_value()) + { + ::score::mw::log::LogWarn("lola") + << "SkeletonEvent: PrepareOffer failed: Could not rollback tracing consumer after " + "crash. Disabling tracing."; + impl::Runtime::getInstance().GetTracingRuntime()->DisableTracing(); + } + + return {data_storage, control_composite}; + } + else + { + return CreateEventDataFromOpenedSharedMemory( + element_fq_id, element_properties, sample_size, sample_alignment); + } } ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& proxy_instance_identifier, @@ -1094,11 +1104,7 @@ ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& bool Skeleton::IsProxyInAllowedConsumerList(const uid_t proxy_uid, const QualityType asil_level) const { - const auto& instance_depl_info = InstanceIdentifierView{identifier_}.GetServiceInstanceDeployment(); - const auto* lola_service_instance_deployment_ptr = - std::get_if(&instance_depl_info.bindingInfo_); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_service_instance_deployment_ptr != nullptr); - const auto& lola_service_instance_deployment = *lola_service_instance_deployment_ptr; + const auto& lola_service_instance_deployment = GetLolaServiceInstanceDeployment(identifier_); const auto& allowed_consumer = lola_service_instance_deployment.allowed_consumer_; // Check if there is an allowed consumer list for the specified quality (ASIL-B / QM) diff --git a/score/mw/com/impl/bindings/lola/skeleton.h b/score/mw/com/impl/bindings/lola/skeleton.h index f7746095c..d1ba3f6ec 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.h +++ b/score/mw/com/impl/bindings/lola/skeleton.h @@ -107,14 +107,11 @@ class Skeleton final : public SkeletonBinding return BindingType::kLoLa; }; - Result> - CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept override; - std::pair, EventDataControlComposite> RegisterGeneric( const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties, - size_t sample_size, - size_t sample_alignment) noexcept; + const size_t sample_size, + const size_t sample_alignment) noexcept; /// \brief Enables dynamic registration of Events at the Skeleton. /// \tparam SampleType The type of the event @@ -135,31 +132,6 @@ class Skeleton final : public SkeletonBinding /// \return Events meta-info, if it has been registered, null else. score::cpp::optional GetEventMetaInfo(const ElementFqId element_fq_id) const; - /// @brief Checks if an event's control block is registered. - /// @param element_fq_id The fully qualified ID of the event. - /// @return True if the control block is registered, false otherwise. - bool IsEventControlRegistered(const ElementFqId element_fq_id) const noexcept; - - const LolaServiceInstanceDeployment& GetLolaServiceInstanceDeployment() const noexcept - { - return lola_service_instance_deployment_; - } - - LolaServiceId GetLolaServiceId() const noexcept - { - return lola_service_id_; - } - - LolaEventId GetLolaEventTypeId(std::string_view event_name) const noexcept - { - return lola_service_type_deployment_.events_.at(std::string(event_name)); - } - - LolaServiceInstanceId::InstanceId GetLolaInstanceId() const noexcept - { - return lola_instance_id_; - } - QualityType GetInstanceQualityType() const; /// \brief Cleans up all allocated slots for this SkeletonEvent of any previous running instance @@ -251,7 +223,7 @@ class Skeleton final : public SkeletonBinding pid_t proxy_pid); static MethodData& GetMethodData(const memory::shared::ManagedMemoryResource& resource); - /// \brief Checks whether the Proxy which sent a notification to the Skeleton that it subscribed to a method is in + /// \brief Checks whether the Proxy which sent a notification to the Skeleton that it subscribed to a method is in /// the allowed_consumers list in the configuration. bool IsProxyInAllowedConsumerList(const uid_t proxy_uid, const QualityType asil_level) const; diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h index 3246dc10c..0ddac8bae 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event.h @@ -44,6 +44,82 @@ namespace score::mw::com::impl::lola { +namespace +{ + +template +void PrepareOfferImpl(EventType& event) +{ + const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && + (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); + if (!tracing_globally_enabled) + { + // in case tracing is globally disabled, this will never switch back to enable. Thus, we can directly disable + // all trace points for this event. This avoids any further lookups to the tracing runtime during Send() calls. + DisableAllTracePoints(event.GetTracingData()); + } + + const bool tracing_for_skeleton_event_enabled = + event.GetTracingData().enable_send || event.GetTracingData().enable_send_with_allocate; + // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in + // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code + // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in + // Ticket-188259). + if (tracing_for_skeleton_event_enabled) + { + // LCOV_EXCL_BR_STOP + event.EmplaceTransactionLogRegistrationGuard(); + event.EmplaceTypeErasedSamplePtrsGuard(); + } + + event.UpdateCurrentTimestamp(); + + // Register callbacks to be notified when event notification existence changes. + // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. + // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_QM) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_QM, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { + event.SetQmNotificationsRegistered(has_handlers); + }); + } + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_B, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { + event.SetAsilBNotificationsRegistered(has_handlers); + }); + } +} + +template +void PrepareStopOfferImpl(EventType& event) noexcept +{ + // Unregister event notification existence changed callbacks + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event.GetElementFQId()); + + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event.GetElementFQId()); + } + + // Reset the flags to indicate no handlers are registered + event.SetQmNotificationsRegistered(false); + event.SetAsilBNotificationsRegistered(false); + + event.ResetGuards(); +} + +} // namespace /// \brief Represents a binding specific instance (LoLa) of an event within a skeleton. It can be used to send events /// via Shared Memory. It will be created via a Factory Method, that will instantiate this class based on deployment @@ -110,6 +186,52 @@ class SkeletonEvent final : public SkeletonEventBinding return event_fqn_; }; + // The following methods are public but intended for use by PrepareOfferImpl helper + impl::tracing::SkeletonEventTracingData& GetTracingData() + { + return skeleton_event_tracing_data_; + } + + void EmplaceTransactionLogRegistrationGuard() + { + score::cpp::ignore = transaction_log_registration_guard_.emplace( + TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl())); + } + + void EmplaceTypeErasedSamplePtrsGuard() + { + score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data); + } + + void UpdateCurrentTimestamp() + { + current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp(); + } + + Skeleton& GetParent() + { + return parent_; + } + + void SetQmNotificationsRegistered(bool value) + { + qm_event_update_notifications_registered_.store(value); + } + + void SetAsilBNotificationsRegistered(bool value) + { + asil_b_event_update_notifications_registered_.store(value); + } + + void ResetGuards() noexcept + { + type_erased_sample_ptrs_guard_.reset(); + if (event_data_control_composite_.has_value()) + { + transaction_log_registration_guard_.reset(); + } + } + private: Skeleton& parent_; const ElementFqId event_fqn_; @@ -285,51 +407,8 @@ ResultBlank SkeletonEvent::PrepareOffer() noexcept SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_.has_value(), "Defensive programming as event_data_control_composite_ is set by Register above."); - current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp(); - const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && - (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); - if (!tracing_globally_enabled) - { - // in case tracing is globally disabled, this will never switch back to enable. Thus, we can directly disable - // all trace points for this event. This avoids any further lookups to the tracing runtime during Send() calls. - DisableAllTracePoints(skeleton_event_tracing_data_); - } - const bool tracing_for_skeleton_event_enabled = - skeleton_event_tracing_data_.enable_send || skeleton_event_tracing_data_.enable_send_with_allocate; - // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in - // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code - // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in - // Ticket-188259). - if (tracing_for_skeleton_event_enabled) - { - // LCOV_EXCL_BR_STOP - score::cpp::ignore = transaction_log_registration_guard_.emplace( - TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl())); - score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data); - } - - // Register callbacks to be notified when event notification existence changes. - // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. - // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. - if (parent_.GetInstanceQualityType() == QualityType::kASIL_QM) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_QM, event_fqn_, [this](const bool has_handlers) noexcept { - qm_event_update_notifications_registered_.store(has_handlers); - }); - } - if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_B, event_fqn_, [this](const bool has_handlers) noexcept { - asil_b_event_update_notifications_registered_.store(has_handlers); - }); - } + PrepareOfferImpl(*this); return {}; } @@ -343,27 +422,7 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] void SkeletonEvent::PrepareStopOffer() noexcept { - // Unregister event notification existence changed callbacks - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event_fqn_); - - if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event_fqn_); - } - - // Reset the flags to indicate no handlers are registered - qm_event_update_notifications_registered_.store(false); - asil_b_event_update_notifications_registered_.store(false); - - type_erased_sample_ptrs_guard_.reset(); - if (event_data_control_composite_.has_value()) - { - transaction_log_registration_guard_.reset(); - } + PrepareStopOfferImpl(*this); } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/mock_binding/skeleton.h b/score/mw/com/impl/bindings/mock_binding/skeleton.h index 613f00fff..bc75857bd 100644 --- a/score/mw/com/impl/bindings/mock_binding/skeleton.h +++ b/score/mw/com/impl/bindings/mock_binding/skeleton.h @@ -34,10 +34,6 @@ class Skeleton : public SkeletonBinding (noexcept, override, final)); MOCK_METHOD(void, PrepareStopOffer, (std::optional), (noexcept, override, final)); MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override, final)); - MOCK_METHOD(Result>, - CreateGenericEventBinding, - (std::string_view event_name, size_t size, size_t alignment), - (noexcept, override, final)); }; class SkeletonFacade : public SkeletonBinding @@ -63,12 +59,6 @@ class SkeletonFacade : public SkeletonBinding return skeleton_.GetBindingType(); } - Result> - CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept override final - { - return skeleton_.CreateGenericEventBinding(event_name, size, alignment); - } - private: Skeleton& skeleton_; }; diff --git a/score/mw/com/impl/com_error.h b/score/mw/com/impl/com_error.h index 13f3b0e0e..6195c068d 100644 --- a/score/mw/com/impl/com_error.h +++ b/score/mw/com/impl/com_error.h @@ -59,6 +59,7 @@ enum class ComErrc : score::result::ErrorCode kFindServiceHandlerFailure, kInvalidHandle, kCallQueueFull, + kServiceElementAlreadyExists, }; /** @@ -203,6 +204,9 @@ class ComErrorDomain final : public score::result::ErrorDomain case static_cast(ComErrc::kCallQueueFull): return "Call queue of service method is already full."; // coverity[autosar_cpp14_m6_4_5_violation] + case static_cast(ComErrc::kServiceElementAlreadyExists): + return "A service element (event, field, method) with the same name already exists."; + // coverity[autosar_cpp14_m6_4_5_violation] default: return "unknown future error"; } diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp index 40828bb0a..b1571fd7b 100644 --- a/score/mw/com/impl/generic_skeleton.cpp +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -4,6 +4,7 @@ * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * + * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 @@ -13,6 +14,7 @@ #include "score/mw/com/impl/generic_skeleton.h" #include "score/mw/com/impl/com_error.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" #include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/size_info.h" @@ -21,23 +23,24 @@ namespace score::mw::com::impl { -Result GenericSkeleton::Create(const InstanceSpecifier& specifier) noexcept +Result GenericSkeleton::Create( + const InstanceSpecifier& specifier, + MethodCallProcessingMode mode) noexcept { - const auto identifier_result = Runtime::getInstance().resolve(specifier); - if (identifier_result.empty()) - { - return MakeUnexpected(ComErrc::kInstanceIDCouldNotBeResolved); - } + const auto instance_identifier_result = GetInstanceIdentifier(specifier); - if (identifier_result.size() > 1) + if (!instance_identifier_result.has_value()) { - score::mw::log::LogWarn("com") << "InstanceSpecifier resolved to more than one InstanceIdentifier. Using the first one."; + score::mw::log::LogFatal("lola") << "Failed to resolve instance identifier from instance specifier"; + std::terminate(); } - return Create(identifier_result[0]); + return Create(instance_identifier_result.value(),mode); } -Result GenericSkeleton::Create(const InstanceIdentifier& identifier) noexcept +Result GenericSkeleton::Create( + const InstanceIdentifier& identifier, + MethodCallProcessingMode mode) noexcept { auto binding = SkeletonBindingFactory::Create(identifier); if (!binding) @@ -45,33 +48,33 @@ Result GenericSkeleton::Create(const InstanceIdentifier& identi return MakeUnexpected(ComErrc::kBindingFailure); } - return GenericSkeleton(identifier, std::move(binding)); + return GenericSkeleton(identifier, std::move(binding),mode); } Result GenericSkeleton::AddEvent(std::string_view name, const SizeInfo& size_info) noexcept { - if (SkeletonBaseView{*this}.IsOffered()) + auto skeleton_view = SkeletonBaseView{*this}; + if (skeleton_view.IsOffered()) { - return MakeUnexpected(ComErrc::kNotOffered); + // It is not allowed to add events after the service has been offered. + return MakeUnexpected(ComErrc::kServiceInstanceAlreadyOffered); } - auto event_binding_result = - SkeletonBaseView{*this}.GetBinding()->CreateGenericEventBinding(name, size_info.size, size_info.alignment); + auto event_binding_result = GenericSkeletonEventBindingFactory::Create(*this, name, size_info); if (!event_binding_result.has_value()) { - return MakeUnexpected(ComErrc::kNotOffered); + return MakeUnexpected(ComErrc::kBindingFailure); } - auto emplace_result = events_.emplace( + auto emplace_result = owned_events_.emplace( name, std::make_unique(*this, name, std::move(event_binding_result).value())); if (!emplace_result.second) { - return MakeUnexpected(ComErrc::kEventNotExisting); + // An event with this name has already been added. + return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); } - auto& event = *emplace_result.first->second; - SkeletonBaseView{*this}.RegisterEvent(name, event); - return &event; + return emplace_result.first->second.get(); } Result GenericSkeleton::OfferService() noexcept @@ -84,8 +87,10 @@ void GenericSkeleton::StopOfferService() noexcept SkeletonBase::StopOfferService(); } -GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding) - : SkeletonBase(std::move(binding), identifier) +GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier, + std::unique_ptr binding, + MethodCallProcessingMode mode) + : SkeletonBase(std::move(binding), identifier,mode) { } diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index f696418ac..4913b83b2 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -35,12 +35,14 @@ class GenericSkeleton : public SkeletonBase /// @brief Creates a GenericSkeleton for a given instance specifier. /// @param specifier The instance specifier. /// @return A GenericSkeleton or an error. - static Result Create(const InstanceSpecifier& specifier) noexcept; + static Result Create(const InstanceSpecifier& specifier, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; /// @brief Creates a GenericSkeleton for a given instance identifier. /// @param identifier The instance identifier. /// @return A GenericSkeleton or an error. - static Result Create(const InstanceIdentifier& identifier) noexcept; + static Result Create(const InstanceIdentifier& identifier, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; /// @brief Adds a type-erased event to the skeleton. /// @@ -59,9 +61,11 @@ class GenericSkeleton : public SkeletonBase void StopOfferService() noexcept; private: - GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding); + GenericSkeleton(const InstanceIdentifier& identifier, + std::unique_ptr binding, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent); - std::map> events_; + std::map> owned_events_; }; } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index 92c730d1f..4fb1156d7 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -12,6 +12,9 @@ ********************************************************************************/ #include "score/mw/com/impl/generic_skeleton_event.h" #include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" +#include "score/mw/com/impl/skeleton_base.h" #include @@ -23,35 +26,62 @@ GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, std::unique_ptr binding) : SkeletonEventBase(skeleton_base, event_name, std::move(binding)) { + SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, *this); + + if (binding_ != nullptr) + { + const SkeletonBaseView skeleton_base_view{skeleton_base}; + const auto& instance_identifier = skeleton_base_view.GetAssociatedInstanceIdentifier(); + const auto binding_type = binding_->GetBindingType(); + auto tracing_data = + tracing::GenerateSkeletonTracingStructFromEventConfig(instance_identifier, binding_type, event_name); + binding_->SetSkeletonEventTracingData(tracing_data); + } } Result GenericSkeletonEvent::Send(SampleAllocateePtr sample) noexcept { + if (!service_offered_flag_.IsSet()) + { + score::mw::log::LogError("lola") + << "GenericSkeletonEvent::Send failed as Event has not yet been offered or has been stop offered"; + return MakeUnexpected(ComErrc::kNotOffered); + } + + auto* const lola_sample_ptr = SampleAllocateePtrView{sample}.As>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_sample_ptr != nullptr); + + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding_ != nullptr, "Binding is not initialized!"); auto* const binding = static_cast(binding_.get()); - std::cout<<"auto* const binding = static_cast(binding_.get());"<{sample}.As(); - std::cout<<"auto* const generic_sample_ptr = SampleAllocateePtrView{sample}.As();"<Send(generic_sample_ptr->GetControlSlotIndicator()); - return result; + + const auto send_result = binding->Send(lola_sample_ptr->GetReferencedSlot()); + if (!send_result.has_value()) + { + score::mw::log::LogError("lola") << "GenericSkeletonEvent::Send failed: " << send_result.error().Message() + << ": " << send_result.error().UserMessage(); + return MakeUnexpected(ComErrc::kBindingFailure); + } + return send_result; } Result> GenericSkeletonEvent::Allocate() noexcept { - auto* binding = static_cast(binding_.get()); - auto result = binding->Allocate(); + if (!service_offered_flag_.IsSet()) + { + score::mw::log::LogError("lola") + << "GenericSkeletonEvent::Allocate failed as Event has not yet been offered or has been stop offered"; + return MakeUnexpected(ComErrc::kNotOffered); + } + auto* const binding = static_cast(binding_.get()); + auto result = binding->Allocate(); // This now returns a Result> if (!result.has_value()) { + score::mw::log::LogError("lola") << "SkeletonEvent::Allocate failed: " << result.error().Message() + << ": " << result.error().UserMessage(); + return MakeUnexpected>(ComErrc::kSampleAllocationFailure); } - - auto deallocator = [binding_ptr = binding_.get()](lola::ControlSlotCompositeIndicator indicator) { - auto* const generic_binding = static_cast(binding_ptr); - generic_binding->Deallocate(indicator); - }; - - return MakeSampleAllocateePtr( - GenericEventSamplePtr(result.value().first, result.value().second, std::move(deallocator))); + return MakeSampleAllocateePtr(std::move(result.value())); } SizeInfo GenericSkeletonEvent::GetSizeInfo() const noexcept diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h index c3a55361f..8d6c381df 100644 --- a/score/mw/com/impl/generic_skeleton_event_binding.h +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -13,6 +13,7 @@ #pragma once #include "score/mw/com/impl/skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/sample_allocatee_ptr.h" #include "score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h" #include "score/result/result.h" @@ -28,9 +29,7 @@ class GenericSkeletonEventBinding : public SkeletonEventBindingBase virtual Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; - virtual Result> Allocate() noexcept = 0; - - virtual void Deallocate(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; + virtual Result> Allocate() noexcept = 0; virtual std::pair GetSizeInfo() const noexcept = 0; }; diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 67128aa34..4bd33d440 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -443,6 +443,21 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event_binding_factory", + hdrs = ["generic_skeleton_event_binding_factory.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:skeleton_base", + "//score/mw/com/impl/plumbing:skeleton_service_element_binding_factory_impl", + ], +) + cc_library( name = "runtime", srcs = [ diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h new file mode 100644 index 000000000..438e08bc5 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/service_element_type.h" // Added for ServiceElementType +#include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" + +#include "score/result/result.h" + +#include +#include + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBindingFactory +{ + public: + static Result> Create(SkeletonBase& skeleton_base, + std::string_view event_name, + const SizeInfo& size_info) noexcept + { + const auto& instance_identifier = SkeletonBaseView{skeleton_base}.GetAssociatedInstanceIdentifier(); + return CreateSkeletonServiceElement( + instance_identifier, + skeleton_base, + event_name, + size_info); + } +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index 6878847d6..8205b0de4 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -27,56 +27,6 @@ namespace score::mw::com::impl { -/// @brief An RAII wrapper for a type-erased sample allocated by a GenericSkeletonEvent. -class GenericEventSamplePtr -{ - public: - using element_type = void; - - GenericEventSamplePtr(void* data, - lola::ControlSlotCompositeIndicator control_slot_indicator, - std::function deallocator) - : data_(data), control_slot_indicator_(control_slot_indicator), deallocator_(std::move(deallocator)) - { - } - - ~GenericEventSamplePtr() - { - if (data_ != nullptr) - { - deallocator_(control_slot_indicator_); - } - } - - GenericEventSamplePtr(const GenericEventSamplePtr&) = delete; - GenericEventSamplePtr& operator=(const GenericEventSamplePtr&) = delete; - - GenericEventSamplePtr(GenericEventSamplePtr&& other) noexcept - : data_(other.data_), - control_slot_indicator_(other.control_slot_indicator_), - deallocator_(std::move(other.deallocator_)) - { - other.data_ = nullptr; - } - - GenericEventSamplePtr& operator=(GenericEventSamplePtr&&) = default; - - void* get() const - { - return data_; - } - - lola::ControlSlotCompositeIndicator GetControlSlotIndicator() const - { - return control_slot_indicator_; - } - - private: - void* data_; - lola::ControlSlotCompositeIndicator control_slot_indicator_; - std::function deallocator_; -}; - /// \brief Pointer to a data sample allocated by the Communication Management implementation (mimics std::unique_ptr) /// /// \details We try to implement certain functionality that facades an std::unique_ptr, but some functionalities (e.g. @@ -426,9 +376,6 @@ class SampleAllocateePtr { auto visitor = score::cpp::overload( [](lola::SampleAllocateePtr& internal_ptr) noexcept -> void { internal_ptr.reset(); }, - [](GenericEventSamplePtr& internal_ptr) noexcept -> void { - GenericEventSamplePtr temp = std::move(internal_ptr); - }, [](const score::cpp::blank&) noexcept -> void {}); std::visit(visitor, internal_); } @@ -443,7 +390,6 @@ class SampleAllocateePtr { auto visitor = score::cpp::overload( [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, - [](const GenericEventSamplePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, [](const score::cpp::blank&) noexcept -> pointer { return nullptr; }); return std::visit(visitor, internal_); } @@ -454,7 +400,6 @@ class SampleAllocateePtr [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> bool { return static_cast(internal_ptr); }, - [](const GenericEventSamplePtr& internal_ptr) noexcept -> bool { return internal_ptr.get() != nullptr; }, [](const score::cpp::blank&) noexcept -> bool { return false; }); return std::visit(visitor, internal_); } @@ -465,7 +410,6 @@ class SampleAllocateePtr { auto visitor = score::cpp::overload( [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, - [](const GenericEventSamplePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, [](const score::cpp::blank&) noexcept -> pointer { std::terminate(); return nullptr; @@ -488,7 +432,7 @@ class SampleAllocateePtr template friend class SampleAllocateePtrMutableView; - std::variant, GenericEventSamplePtr> internal_; + std::variant> internal_; }; template <> diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 060852bdd..28dd05f92 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -15,6 +15,7 @@ #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/configuration/binding_service_type_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" @@ -29,6 +30,7 @@ #include #include +#include #include #include #include @@ -65,9 +67,6 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( lola_service_element_instance_deployment.max_subscribers_.value(), lola_service_element_instance_deployment.enforce_max_samples_}; } - -} // namespace detail - template // Suppress "AUTOSAR C++14 A15-5-3" rule finding. This rule states: "The std::terminate() function shall // not be called implicitly.". std::visit Throws std::bad_variant_access if @@ -77,9 +76,10 @@ template >& size_info) noexcept -> std::unique_ptr { static_assert(element_type != ServiceElementType::INVALID); @@ -88,7 +88,7 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, using ReturnType = std::unique_ptr; auto visitor = score::cpp::overload( - [identifier_view, &parent, &service_element_name]( + [identifier_view, &parent, &service_element_name, &size_info]( const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { auto* const lola_parent = dynamic_cast(SkeletonBaseView{parent}.GetBinding()); if (lola_parent == nullptr) @@ -114,8 +114,18 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, lola_service_instance_deployment.instance_id_.value().GetId(), element_type}; - return std::make_unique( - *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); + if constexpr (std::is_same_v) + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(size_info.has_value()); + return std::make_unique( + *lola_parent, skeleton_event_properties, element_fq_id, size_info.value().get()); + } + else + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(!size_info.has_value()); + return std::make_unique( + *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); + } }, [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { return nullptr; @@ -132,6 +142,39 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } +} // namespace detail + + +/// @brief Overload for typed skeletons (which do not have a SizeInfo). +template +auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name) noexcept + -> std::unique_ptr +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((!std::is_same_v), + "This overload is for typed skeletons only. Generic skeletons must provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( + identifier, parent, service_element_name, score::cpp::nullopt); +} + +/// @brief Overload for generic skeletons (which require a SizeInfo). +template +auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name, + const SizeInfo& size_info) noexcept + -> std::unique_ptr +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((std::is_same_v), + "This overload is for generic skeletons only. Typed skeletons must not provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( + identifier, + parent, + service_element_name, + std::cref(size_info)); +} + } // namespace score::mw::com::impl #endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index 67f867ea5..f4e420799 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -99,13 +99,6 @@ class SkeletonBinding /// \brief Gets the binding type of the binding virtual BindingType GetBindingType() const noexcept = 0; - /// @brief Creates a binding for a generic event. - /// @param event_name The name of the event. - /// @param size The size for the event's data. - /// @param alignment The alignment for the event's data. - /// @return A unique pointer to the created binding or an error. - virtual Result> - CreateGenericEventBinding(std::string_view event_name, size_t size, size_t alignment) noexcept = 0; }; } // namespace score::mw::com::impl From 809cecd28f5942ba796d76f051358532a4854aca Mon Sep 17 00:00:00 2001 From: Abhishek GOYAL Date: Thu, 15 Jan 2026 11:14:46 +0100 Subject: [PATCH 03/18] Remove Send Api for Generic Skeleton with void * --- score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp | 5 ----- score/mw/com/impl/bindings/lola/generic_skeleton_event.h | 2 -- score/mw/com/impl/generic_skeleton_event_binding.h | 2 -- 3 files changed, 9 deletions(-) diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index ad701b673..0d22bde6b 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -37,11 +37,6 @@ ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept return {}; } -Result GenericSkeletonEvent::Send(const void* /*data*/) noexcept -{ - return MakeUnexpected(ComErrc::kIllegalUseOfAllocate); -} - Result GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept { SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index 15c9abde4..d5b9c33a8 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -39,8 +39,6 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding const ElementFqId& event_fqn, const SizeInfo& size_info); - Result Send(const void* data) noexcept override; - Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; Result> Allocate() noexcept override; diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h index 8d6c381df..7372f91ef 100644 --- a/score/mw/com/impl/generic_skeleton_event_binding.h +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -25,8 +25,6 @@ namespace score::mw::com::impl class GenericSkeletonEventBinding : public SkeletonEventBindingBase { public: - virtual Result Send(const void* data) noexcept = 0; - virtual Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; virtual Result> Allocate() noexcept = 0; From 2e6c99db1a20a787f40f24dffdbb07182c4bd3ae Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Fri, 23 Jan 2026 03:10:27 +0200 Subject: [PATCH 04/18] Reslove GenericSkeleton review feedback, fix layering violations --- .../ipc_bridge/sample_sender_receiver.cpp | 30 +++++--- .../bindings/lola/generic_skeleton_event.cpp | 27 ++++--- .../bindings/lola/generic_skeleton_event.h | 4 +- score/mw/com/impl/bindings/lola/skeleton.cpp | 7 +- score/mw/com/impl/bindings/lola/skeleton.h | 18 +++++ score/mw/com/impl/generic_skeleton.cpp | 72 ++++++++++++------- score/mw/com/impl/generic_skeleton.h | 60 +++++++++++----- score/mw/com/impl/generic_skeleton_event.cpp | 19 ++--- .../com/impl/generic_skeleton_event_binding.h | 9 +-- ...generic_skeleton_event_binding_factory.cpp | 13 ++++ ...ton_service_element_binding_factory_impl.h | 18 +++-- score/mw/com/impl/size_info.h | 4 +- score/mw/com/impl/skeleton_base.cpp | 14 ++-- score/mw/com/impl/skeleton_base.h | 9 +-- 14 files changed, 201 insertions(+), 103 deletions(-) create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index 1445473a6..e4da16eef 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -31,6 +31,7 @@ #include #include #include +#include // Added: Required for GenericSkeleton::Create using namespace std::chrono_literals; @@ -482,7 +483,21 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec const std::chrono::milliseconds cycle_time, const std::size_t num_cycles) { - auto create_result = impl::GenericSkeleton::Create(instance_specifier); + const auto event_name = "map_api_lanes_stamped"; + + // Define the events we want to create + // Fix: Qualified SizeInfo as impl::SizeInfo + const impl::SizeInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; + + // Fix: Use vector to define events and handles + std::vector events = { + {event_name, size_info} + }; + std::vector event_handles(events.size()); + + // Fix: Use atomic Create method + auto create_result = impl::GenericSkeleton::Create(instance_specifier, events, event_handles); + if (!create_result.has_value()) { std::cerr << "Unable to construct skeleton: " << create_result.error() << ", bailing!\n"; @@ -490,15 +505,8 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec } auto& skeleton = create_result.value(); - const auto event_name = "map_api_lanes_stamped"; - const SizeInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; - auto event_result = skeleton.AddEvent(event_name, size_info); - if (!event_result.has_value()) - { - std::cerr << "Unable to add event to skeleton: " << event_result.error() << ", bailing!\n"; - return EXIT_FAILURE; - } - auto& event = *event_result.value(); + // Fix: Retrieve event using the handle + auto& event = skeleton.GetEvent(event_handles[0]); const auto offer_result = skeleton.OfferService(); if (!offer_result.has_value()) @@ -546,4 +554,4 @@ template int EventSenderReceiver::RunAsProxy GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept +Result GenericSkeletonEvent::Send(score::mw::com::impl::SampleAllocateePtr sample) noexcept { SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); + const impl::SampleAllocateePtrView view{sample}; + auto ptr = view.template As>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(nullptr != ptr); + + auto control_slot_indicator = ptr->GetReferencedSlot(); control_.value().EventReady(control_slot_indicator, ++current_timestamp_); // Only call NotifyEvent if there are any registered receive handlers for each quality level. @@ -62,7 +67,7 @@ Result GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndica return {}; } -Result> GenericSkeletonEvent::Allocate() noexcept +Result> GenericSkeletonEvent::Allocate() noexcept { if (!control_.has_value()) { @@ -82,13 +87,17 @@ Result> GenericSkeletonEvent::Allocate() noexcept if (slot.IsValidQM() || slot.IsValidAsilB()) { - auto* data_storage = data_storage_.get>(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(data_storage != nullptr); - // The at() method on EventDataStorage (which is a DynamicArray) correctly handles - // the pointer arithmetic based on its template type (std::uint8_t). - // We multiply by size_info_.size here because the underlying storage is a byte array. - void* data_ptr = &data_storage->at(static_cast(slot.GetIndex()) * size_info_.size); - return lola::SampleAllocateePtr(data_ptr, control_.value(), slot); + void* base_ptr = data_storage_.get(1); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(base_ptr != nullptr); + + // 1. Cast base_ptr to uint8_t* so we can add bytes to it. + // 2. Cast GetIndex() to uint64_t BEFORE multiplying to ensure 64-bit arithmetic. + std::uint8_t* byte_ptr = static_cast(base_ptr); + std::uint64_t offset = static_cast(slot.GetIndex()) * size_info_.size; + + void* data_ptr = byte_ptr + offset; + auto lola_ptr = lola::SampleAllocateePtr(data_ptr, control_.value(), slot); + return impl::MakeSampleAllocateePtr(std::move(lola_ptr)); } else { diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index d5b9c33a8..1cdcc8ef8 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -39,9 +39,9 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding const ElementFqId& event_fqn, const SizeInfo& size_info); - Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; + Result Send(score::mw::com::impl::SampleAllocateePtr sample) noexcept override; - Result> Allocate() noexcept override; + Result> Allocate() noexcept override; std::pair GetSizeInfo() const noexcept override; diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 8b9a0cb39..3832e2e10 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -969,9 +969,7 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( size_t sample_size, size_t sample_alignment) noexcept { - auto* data_storage = storage_resource_->construct>( - sample_size * element_properties.number_of_slots, - memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy())); + void* data_storage = storage_resource_->allocate(sample_size * element_properties.number_of_slots, sample_alignment); auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, std::forward_as_tuple(element_fq_id), @@ -980,11 +978,10 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( "Couldn't register/emplace event-storage in data-section."); const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; - void* const event_data_raw_array = data_storage->data(); auto inserted_meta_info = storage_->events_metainfo_.emplace( std::piecewise_construct, std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(sample_meta_info, event_data_raw_array)); + std::forward_as_tuple(sample_meta_info, data_storage)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); diff --git a/score/mw/com/impl/bindings/lola/skeleton.h b/score/mw/com/impl/bindings/lola/skeleton.h index d1ba3f6ec..acb77ada5 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.h +++ b/score/mw/com/impl/bindings/lola/skeleton.h @@ -107,6 +107,14 @@ class Skeleton final : public SkeletonBinding return BindingType::kLoLa; }; + /// \brief Enables dynamic registration of Generic (type-erased) Events at the Skeleton. + /// \param element_fq_id The full qualified ID of the element (event) that shall be registered. + /// \param element_properties Properties of the element (e.g. number of slots, max subscribers). + /// \param sample_size The size of a single data sample in bytes. + /// \param sample_alignment The alignment requirement of the data sample in bytes. + /// \return A pair containing: + /// - An OffsetPtr to the allocated data storage (void*). + /// - The EventDataControlComposite for managing the event's control data. std::pair, EventDataControlComposite> RegisterGeneric( const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties, @@ -175,9 +183,19 @@ class Skeleton final : public SkeletonBinding const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties); + /// \brief Creates the control structures (QM and optional ASIL-B) for an event. + /// \param element_fq_id The full qualified ID of the element. + /// \param element_properties Properties of the event. + /// \return The EventDataControlComposite containing pointers to the control structures. EventDataControlComposite CreateEventControlComposite(const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties) noexcept; + /// \brief Creates shared memory storage for a generic (type-erased) event. + /// \param element_fq_id The full qualified ID of the element. + /// \param element_properties Properties of the event. + /// \param sample_size The size of a single data sample. + /// \param sample_alignment The alignment of the data sample. + /// \return A pair containing the data storage pointer (void*) and the control composite. std::pair, EventDataControlComposite> CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties, diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp index b1571fd7b..7cb3dee8b 100644 --- a/score/mw/com/impl/generic_skeleton.cpp +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -4,7 +4,6 @@ * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * - * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 @@ -20,61 +19,82 @@ #include "score/mw/com/impl/size_info.h" #include "score/mw/com/impl/skeleton_binding.h" +#include +#include + namespace score::mw::com::impl { Result GenericSkeleton::Create( const InstanceSpecifier& specifier, + score::cpp::span events, + score::cpp::span out_handles, MethodCallProcessingMode mode) noexcept { const auto instance_identifier_result = GetInstanceIdentifier(specifier); if (!instance_identifier_result.has_value()) { - score::mw::log::LogFatal("lola") << "Failed to resolve instance identifier from instance specifier"; - std::terminate(); + score::mw::log::LogError("GenericSkeleton") << "Failed to resolve instance identifier from instance specifier"; + return MakeUnexpected(ComErrc::kInstanceIDCouldNotBeResolved); } - return Create(instance_identifier_result.value(),mode); + return Create(instance_identifier_result.value(), events, out_handles, mode); } Result GenericSkeleton::Create( const InstanceIdentifier& identifier, + score::cpp::span events, + score::cpp::span out_handles, MethodCallProcessingMode mode) noexcept { + // Ensure the output span is large enough to hold handles for all events + assert(events.size() == out_handles.size()); + auto binding = SkeletonBindingFactory::Create(identifier); if (!binding) { + + score::mw::log::LogError("GenericSkeleton") << "Failed to create SkeletonBinding for the given identifier."; return MakeUnexpected(ComErrc::kBindingFailure); } - return GenericSkeleton(identifier, std::move(binding),mode); -} + // 1. Create the Skeleton (Private Constructor) + GenericSkeleton skeleton(identifier, std::move(binding), mode); -Result GenericSkeleton::AddEvent(std::string_view name, const SizeInfo& size_info) noexcept -{ - auto skeleton_view = SkeletonBaseView{*this}; - if (skeleton_view.IsOffered()) - { - // It is not allowed to add events after the service has been offered. - return MakeUnexpected(ComErrc::kServiceInstanceAlreadyOffered); - } + // 2. Reserve space for events to avoid reallocation + skeleton.owned_events_.reserve(events.size()); - auto event_binding_result = GenericSkeletonEventBindingFactory::Create(*this, name, size_info); - if (!event_binding_result.has_value()) + // 3. Atomically create all events + for (std::size_t i = 0; i < events.size(); ++i) { - return MakeUnexpected(ComErrc::kBindingFailure); - } + const auto& info = events[i]; - auto emplace_result = owned_events_.emplace( - name, std::make_unique(*this, name, std::move(event_binding_result).value())); - if (!emplace_result.second) - { - // An event with this name has already been added. - return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); + // Create the event binding + auto event_binding_result = GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.size_info); + + if (!event_binding_result.has_value()) + { + // If any event fails to bind, the whole creation fails (Atomic) + return MakeUnexpected(ComErrc::kBindingFailure); + } + + // Store the event in the vector + skeleton.owned_events_.push_back(std::make_unique( + skeleton, info.name, std::move(event_binding_result).value())); + + // Assign the handle (index in the vector) + out_handles[i] = EventHandle{static_cast(i)}; } - return emplace_result.first->second.get(); + return skeleton; +} + +GenericSkeletonEvent& GenericSkeleton::GetEvent(EventHandle h) noexcept +{ + // Fast O(1) lookup + assert(h.index < owned_events_.size()); + return *owned_events_[h.index]; } Result GenericSkeleton::OfferService() noexcept @@ -90,7 +110,7 @@ void GenericSkeleton::StopOfferService() noexcept GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding, MethodCallProcessingMode mode) - : SkeletonBase(std::move(binding), identifier,mode) + : SkeletonBase(std::move(binding), identifier, mode) { } diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index 4913b83b2..ae5585735 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -19,39 +19,62 @@ #include "score/mw/com/impl/size_info.h" #include "score/result/result.h" +#include // Changed: Using your project's span -#include +#include #include +#include namespace score::mw::com::impl { class SkeletonBinding; +/// @brief Describes the configuration for a single event. +struct EventInfo +{ + std::string_view name; + SizeInfo size_info; +}; + +/// @brief An opaque handle to an event within a GenericSkeleton. +struct EventHandle +{ + std::uint16_t index; +}; +/// @brief A generic, type-erased skeleton implementation. +/// +/// This class allows creating and offering service instances dynamically without +/// requiring compile-time generated code. It handles the lifecycle of events +/// and the service offering process. class GenericSkeleton : public SkeletonBase { public: - /// @brief Creates a GenericSkeleton for a given instance specifier. - /// @param specifier The instance specifier. - /// @return A GenericSkeleton or an error. - static Result Create(const InstanceSpecifier& specifier, + /// @brief Creates a GenericSkeleton and all its events atomically. + /// @param id The instance identifier. + /// @param events A span of EventInfo structs describing all events to be created. + /// @param out_handles A span that will be populated with handles to the created events. + /// The caller must ensure its size is equal to events.size(). + /// @param mode The method call processing mode. + /// @return A GenericSkeleton or an error if creation fails. + static Result Create(const InstanceIdentifier& id, + score::cpp::span events, + score::cpp::span out_handles, MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; - /// @brief Creates a GenericSkeleton for a given instance identifier. - /// @param identifier The instance identifier. - /// @return A GenericSkeleton or an error. - static Result Create(const InstanceIdentifier& identifier, + /// @brief Creates a GenericSkeleton for a given instance specifier. + /// Note: You likely need to update this overload to match the new atomic creation style + /// or remove it if it is no longer supported. + static Result Create(const InstanceSpecifier& specifier, + score::cpp::span events, + score::cpp::span out_handles, MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; - /// @brief Adds a type-erased event to the skeleton. - /// - /// This must be called before OfferService(). - /// - /// @param name The name of the event. - /// @param size_info The size and alignment requirements for the event's sample data. - /// @return A reference to the created event or an error. - Result AddEvent(std::string_view name, const SizeInfo& size_info) noexcept; + /// @brief Retrieves an event using its handle. + /// @param h The handle to the event. + /// @return A reference to the GenericSkeletonEvent. + GenericSkeletonEvent& GetEvent(EventHandle h) noexcept; /// @brief Offers the service instance. /// @return A blank result, or an error if offering fails. @@ -65,7 +88,8 @@ class GenericSkeleton : public SkeletonBase std::unique_ptr binding, MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent); - std::map> owned_events_; + // Internal storage is now a vector for O(1) access via EventHandle. + std::vector> owned_events_; }; } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index 4fb1156d7..c15fae61a 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -12,7 +12,6 @@ ********************************************************************************/ #include "score/mw/com/impl/generic_skeleton_event.h" #include "score/mw/com/impl/generic_skeleton_event_binding.h" -#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" #include "score/mw/com/impl/skeleton_base.h" @@ -22,8 +21,8 @@ namespace score::mw::com::impl { GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, - const std::string_view event_name, - std::unique_ptr binding) + const std::string_view event_name, + std::unique_ptr binding) : SkeletonEventBase(skeleton_base, event_name, std::move(binding)) { SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, *this); @@ -48,13 +47,12 @@ Result GenericSkeletonEvent::Send(SampleAllocateePtr sample) return MakeUnexpected(ComErrc::kNotOffered); } - auto* const lola_sample_ptr = SampleAllocateePtrView{sample}.As>(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_sample_ptr != nullptr); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding_ != nullptr, "Binding is not initialized!"); auto* const binding = static_cast(binding_.get()); - const auto send_result = binding->Send(lola_sample_ptr->GetReferencedSlot()); + + const auto send_result = binding->Send(std::move(sample)); + if (!send_result.has_value()) { score::mw::log::LogError("lola") << "GenericSkeletonEvent::Send failed: " << send_result.error().Message() @@ -73,7 +71,10 @@ Result> GenericSkeletonEvent::Allocate() noexcept return MakeUnexpected(ComErrc::kNotOffered); } auto* const binding = static_cast(binding_.get()); - auto result = binding->Allocate(); // This now returns a Result> + + + auto result = binding->Allocate(); + if (!result.has_value()) { score::mw::log::LogError("lola") << "SkeletonEvent::Allocate failed: " << result.error().Message() @@ -81,7 +82,7 @@ Result> GenericSkeletonEvent::Allocate() noexcept return MakeUnexpected>(ComErrc::kSampleAllocationFailure); } - return MakeSampleAllocateePtr(std::move(result.value())); + return result; } SizeInfo GenericSkeletonEvent::GetSizeInfo() const noexcept diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h index 7372f91ef..cd025607a 100644 --- a/score/mw/com/impl/generic_skeleton_event_binding.h +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -13,11 +13,12 @@ #pragma once #include "score/mw/com/impl/skeleton_event_binding.h" -#include "score/mw/com/impl/bindings/lola/sample_allocatee_ptr.h" -#include "score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h" + +#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" #include "score/result/result.h" #include +#include namespace score::mw::com::impl { @@ -25,9 +26,9 @@ namespace score::mw::com::impl class GenericSkeletonEventBinding : public SkeletonEventBindingBase { public: - virtual Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; + virtual Result Send(SampleAllocateePtr sample) noexcept = 0; - virtual Result> Allocate() noexcept = 0; + virtual Result> Allocate() noexcept = 0; virtual std::pair GetSizeInfo() const noexcept = 0; }; diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp new file mode 100644 index 000000000..7be462ed4 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 28dd05f92..b0a6bf50c 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -52,7 +52,7 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( { score::mw::log::LogFatal("lola") << "Could not create SkeletonEventProperties from ServiceElementInstanceDeployment. Number of sample slots " - "was not specified in the configuration. Terminating."; + "was not specified in the configuration. Terminating."; std::terminate(); } @@ -60,7 +60,7 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( { score::mw::log::LogFatal("lola") << "Could not create SkeletonEventProperties from ServiceElementInstanceDeployment. Max subscribers was " - "not specified in the configuration. Terminating."; + "not specified in the configuration. Terminating."; std::terminate(); } return lola::SkeletonEventProperties{lola_service_element_instance_deployment.GetNumberOfSampleSlots().value(), @@ -152,8 +152,10 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, const std::string_view service_element_name) noexcept -> std::unique_ptr { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((!std::is_same_v), - "This overload is for typed skeletons only. Generic skeletons must provide a SizeInfo."); + + static_assert(!std::is_same_v, + "This overload is for typed skeletons only. Generic skeletons must provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( identifier, parent, service_element_name, score::cpp::nullopt); } @@ -166,8 +168,10 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, const SizeInfo& size_info) noexcept -> std::unique_ptr { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((std::is_same_v), - "This overload is for generic skeletons only. Typed skeletons must not provide a SizeInfo."); + + static_assert(std::is_same_v, + "This overload is for generic skeletons only. Typed skeletons must not provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( identifier, parent, @@ -177,4 +181,4 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, } // namespace score::mw::com::impl -#endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H +#endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H \ No newline at end of file diff --git a/score/mw/com/impl/size_info.h b/score/mw/com/impl/size_info.h index d69131fa4..6417f6664 100644 --- a/score/mw/com/impl/size_info.h +++ b/score/mw/com/impl/size_info.h @@ -14,7 +14,7 @@ #include -namespace score::mw::com +namespace score::mw::com::impl { /// @brief A struct to hold size and alignment information for generic type-erased data. struct SizeInfo @@ -23,4 +23,4 @@ struct SizeInfo size_t alignment; }; -} // namespace score::mw::com \ No newline at end of file +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_base.cpp b/score/mw/com/impl/skeleton_base.cpp index 280a95867..33fe4da09 100644 --- a/score/mw/com/impl/skeleton_base.cpp +++ b/score/mw/com/impl/skeleton_base.cpp @@ -85,13 +85,14 @@ SkeletonBinding::SkeletonFieldBindings GetSkeletonFieldBindingsMap(const Skeleto SkeletonBase::SkeletonBase(std::unique_ptr skeleton_binding, InstanceIdentifier instance_id, MethodCallProcessingMode) - : binding_{std::move(skeleton_binding)}, + : service_offered_flag_{}, + binding_{std::move(skeleton_binding)}, events_{}, fields_{}, methods_{}, instance_id_{std::move(instance_id)}, - skeleton_mock_{nullptr}, - service_offered_flag_{} + skeleton_mock_{nullptr} + { } @@ -123,13 +124,14 @@ void SkeletonBase::Cleanup() } SkeletonBase::SkeletonBase(SkeletonBase&& other) noexcept - : binding_{std::move(other.binding_)}, + : service_offered_flag_{std::move(other.service_offered_flag_)}, + binding_{std::move(other.binding_)}, events_{std::move(other.events_)}, fields_{std::move(other.fields_)}, methods_{std::move(other.methods_)}, instance_id_{std::move(other.instance_id_)}, - skeleton_mock_{std::move(other.skeleton_mock_)}, - service_offered_flag_{std::move(other.service_offered_flag_)} + skeleton_mock_{std::move(other.skeleton_mock_)} + { // Since the address of this skeleton has changed, we need update the address stored in each of the events and // fields belonging to the skeleton. diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index 10121c31b..11894b06f 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -86,7 +86,7 @@ class SkeletonBase * \api * \brief Offer the respective service to other applications * \return On failure, returns an error code according to the SW Component Requirements SCR-17434118 and - * SCR-566325. + * SCR-566325. */ [[nodiscard]] ResultBlank OfferService() noexcept; @@ -115,6 +115,9 @@ class SkeletonBase SkeletonBase(SkeletonBase&& other) noexcept; SkeletonBase& operator=(SkeletonBase&& other) noexcept; + + FlagOwner service_offered_flag_; + private: std::unique_ptr binding_; SkeletonEvents events_; @@ -130,8 +133,6 @@ class SkeletonBase [[nodiscard]] score::ResultBlank OfferServiceEvents() const noexcept; [[nodiscard]] score::ResultBlank OfferServiceFields() const noexcept; - - FlagOwner service_offered_flag_; }; class SkeletonBaseView @@ -240,4 +241,4 @@ score::cpp::optional GetInstanceIdentifier(const InstanceSpe } // namespace score::mw::com::impl -#endif // SCORE_MW_COM_IMPL_SKELETON_BASE_H +#endif // SCORE_MW_COM_IMPL_SKELETON_BASE_H \ No newline at end of file From 9df78410096a8e45c4ba8c4de7c08f20278fede5 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Tue, 27 Jan 2026 13:43:01 +0200 Subject: [PATCH 05/18] Required Edits for generic skeleton Interface --- .../ipc_bridge/sample_sender_receiver.cpp | 23 +- score/mw/com/impl/BUILD | 20 +- score/mw/com/impl/bindings/lola/BUILD | 25 +- .../com/impl/bindings/lola/event_meta_info.h | 6 +- .../bindings/lola/generic_proxy_event.cpp | 15 +- .../lola/generic_proxy_event_test.cpp | 4 +- .../bindings/lola/generic_skeleton_event.cpp | 54 ++-- .../bindings/lola/generic_skeleton_event.h | 92 ++----- score/mw/com/impl/bindings/lola/skeleton.cpp | 17 +- .../com/impl/bindings/lola/skeleton_event.cpp | 1 + .../com/impl/bindings/lola/skeleton_event.h | 246 +++--------------- .../bindings/lola/skeleton_event_common.cpp | 148 +++++++++++ .../bindings/lola/skeleton_event_common.h | 89 +++++++ .../lola/skeleton_event_tracing_test.cpp | 2 +- .../com/impl/bindings/lola/skeleton_test.cpp | 16 +- .../test/skeleton_event_component_test.cpp | 8 +- .../lola => }/data_type_meta_info.cpp | 2 +- .../{bindings/lola => }/data_type_meta_info.h | 8 +- score/mw/com/impl/generic_skeleton.cpp | 86 +++--- score/mw/com/impl/generic_skeleton.h | 118 +++++---- score/mw/com/impl/generic_skeleton_event.cpp | 2 +- score/mw/com/impl/generic_skeleton_event.h | 4 +- score/mw/com/impl/generic_skeleton_test.cpp | 62 +++-- score/mw/com/impl/plumbing/BUILD | 3 + .../generic_skeleton_event_binding_factory.h | 8 +- ...ton_service_element_binding_factory_impl.h | 13 +- score/mw/com/impl/size_info.h | 26 -- score/mw/com/impl/skeleton_binding.h | 2 +- 28 files changed, 606 insertions(+), 494 deletions(-) create mode 100644 score/mw/com/impl/bindings/lola/skeleton_event_common.cpp create mode 100644 score/mw/com/impl/bindings/lola/skeleton_event_common.h rename score/mw/com/impl/{bindings/lola => }/data_type_meta_info.cpp (89%) rename score/mw/com/impl/{bindings/lola => }/data_type_meta_info.h (88%) delete mode 100644 score/mw/com/impl/size_info.h diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index e4da16eef..bd2ed0fcb 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -485,18 +485,17 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec { const auto event_name = "map_api_lanes_stamped"; - // Define the events we want to create - // Fix: Qualified SizeInfo as impl::SizeInfo - const impl::SizeInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; - - // Fix: Use vector to define events and handles - std::vector events = { + const impl::DataTypeMetaInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; + + impl::GenericSkeletonCreateParams create_params; + // Use a temporary vector to construct the span + const std::vector events_vec = { {event_name, size_info} }; - std::vector event_handles(events.size()); + create_params.events = events_vec; + // create_params.fields = {}; // No fields yet - // Fix: Use atomic Create method - auto create_result = impl::GenericSkeleton::Create(instance_specifier, events, event_handles); + auto create_result = impl::GenericSkeleton::Create(instance_specifier, create_params); if (!create_result.has_value()) { @@ -505,8 +504,10 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec } auto& skeleton = create_result.value(); - // Fix: Retrieve event using the handle - auto& event = skeleton.GetEvent(event_handles[0]); + // Retrieve event using its name + auto event_it = skeleton.GetEvents().find(event_name); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_it != skeleton.GetEvents().cend(), "Event not found in GenericSkeleton"); + auto& event = event_it->second; const auto offer_result = skeleton.OfferService(); if (!offer_result.has_value()) diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 6d94b56cd..56e63827d 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -130,10 +130,12 @@ cc_library( ":instance_identifier", ":instance_specifier", ":runtime", - ":skeleton_base", - ":skeleton_binding", - ":size_info", + ":skeleton_base", # For SkeletonBase + ":skeleton_binding", # For SkeletonBinding + ":data_type_meta_info", # For DataTypeMetaInfo + ":service_element_map", "@score_baselibs//score/result", + ], ) @@ -150,9 +152,9 @@ cc_library( "//score/mw/com/impl/bindings/lola:generic_skeleton_event", ":generic_skeleton_event_binding", ":skeleton_base", - ":skeleton_event_base", - ":skeleton_event_binding", - ":size_info", + ":skeleton_event_base", # For SkeletonEventBase + ":skeleton_event_binding", # For SkeletonEventBinding + ":data_type_meta_info", # For DataTypeMetaInfo "//score/mw/com/impl/plumbing:sample_allocatee_ptr", "@score_baselibs//score/result", ], @@ -177,8 +179,8 @@ cc_library( ) cc_library( - name = "size_info", - hdrs = ["size_info.h"], + name = "data_type_meta_info", + hdrs = ["data_type_meta_info.h"], features = COMPILER_WARNING_FEATURES, tags = ["FFI"], visibility = [ @@ -459,7 +461,7 @@ cc_library( deps = [ ":binding_type", ":generic_skeleton_event_binding", - ":size_info", + ":data_type_meta_info", # For DataTypeMetaInfo "//score/mw/com/impl/configuration", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/memory/shared:i_shared_memory_resource", diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index 5ec4eecce..bf4a1c580 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -197,21 +197,11 @@ cc_library( "//score/mw/com/impl:__subpackages__", ], deps = [ - ":data_type_meta_info", + "//score/mw/com/impl:data_type_meta_info", "@score_baselibs//score/memory/shared", ], ) -cc_library( - name = "data_type_meta_info", - srcs = ["data_type_meta_info.cpp"], - hdrs = ["data_type_meta_info.h"], - features = COMPILER_WARNING_FEATURES, - tags = ["FFI"], - visibility = [ - "//score/mw/com/impl:__subpackages__", - ], -) cc_library( name = "shared_data_structures", @@ -242,8 +232,14 @@ cc_library( cc_library( name = "generic_skeleton_event", - srcs = ["generic_skeleton_event.cpp"], - hdrs = ["generic_skeleton_event.h"], + srcs = [ + "generic_skeleton_event.cpp", + "skeleton_event_common.cpp", + ], + hdrs = [ + "generic_skeleton_event.h", + "skeleton_event_common.h", + ], features = COMPILER_WARNING_FEATURES, tags = ["FFI"], visibility = [ @@ -254,7 +250,6 @@ cc_library( ":skeleton", "//score/mw/com/impl:error", "//score/mw/com/impl:generic_skeleton_event_binding", - "//score/mw/com/impl:size_info", "//score/mw/com/impl/tracing:skeleton_event_tracing", ":type_erased_sample_ptrs_guard", ], @@ -265,6 +260,7 @@ cc_library( srcs = [ "skeleton.cpp", "skeleton_event.cpp", + "skeleton_event_common.cpp", "skeleton_event_properties.cpp", "skeleton_method.cpp", "generic_skeleton_event.cpp", @@ -272,6 +268,7 @@ cc_library( hdrs = [ "skeleton.h", "skeleton_event.h", + "skeleton_event_common.h", "skeleton_event_properties.h", "skeleton_method.h", "generic_skeleton_event.h", diff --git a/score/mw/com/impl/bindings/lola/event_meta_info.h b/score/mw/com/impl/bindings/lola/event_meta_info.h index 961c12897..223dc5002 100644 --- a/score/mw/com/impl/bindings/lola/event_meta_info.h +++ b/score/mw/com/impl/bindings/lola/event_meta_info.h @@ -14,7 +14,7 @@ #define SCORE_MW_COM_IMPL_BINDINGS_LOLA_EVENT_META_INFO_H #include "score/memory/shared/offset_ptr.h" -#include "score/mw/com/impl/bindings/lola/data_type_meta_info.h" +#include "score/mw/com/impl/data_type_meta_info.h" namespace score::mw::com::impl::lola { @@ -26,7 +26,7 @@ namespace score::mw::com::impl::lola class EventMetaInfo { public: - EventMetaInfo(const DataTypeMetaInfo data_type_info, const memory::shared::OffsetPtr event_slots_raw_array) + EventMetaInfo(const impl::DataTypeMetaInfo data_type_info, const memory::shared::OffsetPtr event_slots_raw_array) : data_type_info_(data_type_info), event_slots_raw_array_(event_slots_raw_array) { } @@ -35,7 +35,7 @@ class EventMetaInfo // be private.". There are no class invariants to maintain which could be violated by directly accessing member // variables. // coverity[autosar_cpp14_m11_0_1_violation] - DataTypeMetaInfo data_type_info_; + impl::DataTypeMetaInfo data_type_info_; // coverity[autosar_cpp14_m11_0_1_violation] memory::shared::OffsetPtr event_slots_raw_array_; }; diff --git a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp index 37e303e4f..043464707 100644 --- a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp @@ -78,7 +78,7 @@ inline Result GenericProxyEvent::GetNewSamples(Callback&& receiver, std::size_t GenericProxyEvent::GetSampleSize() const noexcept { - return meta_info_.data_type_info_.size_of_; + return meta_info_.data_type_info_.size; } bool GenericProxyEvent::HasSerializedFormat() const noexcept @@ -129,8 +129,8 @@ Result GenericProxyEvent::GetNewSamplesImpl(Callback&& receiver, Tr auto& event_control = proxy_event_common_.GetEventControl(); - const std::size_t sample_size = meta_info_.data_type_info_.size_of_; - const std::uint8_t sample_alignment = meta_info_.data_type_info_.align_of_; + const std::size_t sample_size = meta_info_.data_type_info_.size; + const std::size_t sample_alignment = meta_info_.data_type_info_.alignment; const std::size_t aligned_size = memory::shared::CalculateAlignedSize(sample_size, static_cast(sample_alignment)); @@ -146,6 +146,15 @@ Result GenericProxyEvent::GetNewSamplesImpl(Callback&& receiver, Tr score::mw::log::LogFatal("lola") << "Could not calculate the event slots raw array size. Terminating."; std::terminate(); } + + std::cout << "GenericProxyEvent::GetNewSamplesImpl:" + << " sample_size=" << sample_size + << " sample_alignment=" << sample_alignment + << " aligned_size=" << aligned_size + << " max_number_of_sample_slots=" << max_number_of_sample_slots + << " expected_array_size=" << event_slots_raw_array_size.value(); + + const void* const event_slots_raw_array = meta_info_.event_slots_raw_array_.get(event_slots_raw_array_size.value()); // AMP assert that the event_slots_raw_array address is according to sample_alignment diff --git a/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp b/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp index 91d8ce425..ef82c098e 100644 --- a/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp +++ b/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp @@ -117,10 +117,10 @@ TEST_F(LolaGenericProxyEventDeathTest, OverflowWhenCalculatingRawEventsSlotsArra // Given a mocked SkeletonEvent whose metainfo stores a size which will lead to an overflow when calculating the raw // event slot array size - const auto align_of = fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.align_of_; + const auto align_of = fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.alignment; // Subtract the align of from the max size to prevent an overflow when calculating the aligned size - fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.size_of_ = + fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.size = std::numeric_limits::max() - align_of; // and given a GenericProxyEvent which has subscribed diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index dd314f851..5d02fe770 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -11,28 +11,29 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/memory/shared/pointer_arithmetic_util.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" #include "score/mw/com/impl/bindings/lola/skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/runtime.h" -#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" namespace score::mw::com::impl::lola { GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn, - const SizeInfo& size_info) - : parent_(parent), size_info_(size_info), event_properties_(event_properties), event_fqn_(event_fqn) + const DataTypeMetaInfo& size_info, + impl::tracing::SkeletonEventTracingData tracing_data) + : size_info_(size_info), event_properties_(event_properties), + common_event_logic_(parent, event_fqn, control_, current_timestamp_, tracing_data) { - } ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept { std::tie(data_storage_, control_) = - parent_.RegisterGeneric(event_fqn_, event_properties_, size_info_.size, size_info_.alignment); - PrepareOfferImpl(*this); + GetParent().RegisterGeneric(GetElementFQId(), event_properties_, size_info_.size, size_info_.alignment); + common_event_logic_.PrepareOfferCommon(); return {}; } @@ -47,21 +48,21 @@ Result GenericSkeletonEvent::Send(score::mw::com::impl::SampleAllo auto control_slot_indicator = ptr->GetReferencedSlot(); control_.value().EventReady(control_slot_indicator, ++current_timestamp_); - // Only call NotifyEvent if there are any registered receive handlers for each quality level. + // Only call NotifyEvent if there are any registered receive handlers for each quality level (QM and ASIL-B). // This avoids the expensive lock operation in the common case where no handlers are registered. // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent // handler registration, the next Send() will pick it up. - if (qm_event_update_notifications_registered_.load() && !qm_disconnect_) + if (IsQmRegistered() && !qm_disconnect_) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_QM, event_fqn_); + .NotifyEvent(QualityType::kASIL_QM, GetElementFQId()); } - if (asil_b_event_update_notifications_registered_.load() && parent_.GetInstanceQualityType() == QualityType::kASIL_B) + if (IsAsilBRegistered() && GetParent().GetInstanceQualityType() == QualityType::kASIL_B) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_B, event_fqn_); + .NotifyEvent(QualityType::kASIL_B, GetElementFQId()); } return {}; @@ -81,21 +82,29 @@ Result> GenericSkeletonEvent::All qm_disconnect_ = true; score::mw::log::LogWarn("lola") << __func__ << __LINE__ - << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_fqn_; - parent_.DisconnectQmConsumers(); + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << GetElementFQId(); + GetParent().DisconnectQmConsumers(); } if (slot.IsValidQM() || slot.IsValidAsilB()) { - void* base_ptr = data_storage_.get(1); + void* base_ptr = data_storage_.get(); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(base_ptr != nullptr); - // 1. Cast base_ptr to uint8_t* so we can add bytes to it. - // 2. Cast GetIndex() to uint64_t BEFORE multiplying to ensure 64-bit arithmetic. + const auto aligned_size = memory::shared::CalculateAlignedSize(size_info_.size, size_info_.alignment); std::uint8_t* byte_ptr = static_cast(base_ptr); - std::uint64_t offset = static_cast(slot.GetIndex()) * size_info_.size; - + //std::uint64_t offset = static_cast(slot.GetIndex()) * size_info_.size; + std::uint64_t offset = static_cast(slot.GetIndex()) * aligned_size; + void* data_ptr = byte_ptr + offset; + + std::cout << "GenericSkeletonEvent::Allocate:" + << " slot_index=" << slot.GetIndex() + << " aligned_size=" << aligned_size + << " offset=" << offset + << " base_ptr=" << base_ptr + << " data_ptr=" << data_ptr; + auto lola_ptr = lola::SampleAllocateePtr(data_ptr, control_.value(), slot); return impl::MakeSampleAllocateePtr(std::move(lola_ptr)); } @@ -118,17 +127,14 @@ std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept void GenericSkeletonEvent::PrepareStopOffer() noexcept { - PrepareStopOfferImpl(*this); + common_event_logic_.PrepareStopOfferCommon(); } BindingType GenericSkeletonEvent::GetBindingType() const noexcept { return BindingType::kLoLa; } - -void GenericSkeletonEvent::SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept -{ - tracing_data_ = tracing_data; -} + +// SetSkeletonEventTracingData is now handled by common_event_logic_ } // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index 1cdcc8ef8..d59a1b181 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -15,14 +15,9 @@ #include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/event_data_storage.h" -#include "score/mw/com/impl/size_info.h" -#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" -#include "score/memory/shared/offset_ptr.h" -#include "score/mw/com/impl/bindings/lola/event_slot_status.h" - -#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" -#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" -#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" +#include "score/mw/com/impl/data_type_meta_info.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" namespace score::mw::com::impl::lola { @@ -35,10 +30,11 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding { public: GenericSkeletonEvent(Skeleton& parent, - const SkeletonEventProperties& event_properties, + const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn, - const SizeInfo& size_info); - + const DataTypeMetaInfo& size_info, + impl::tracing::SkeletonEventTracingData tracing_data = {}); + Result Send(score::mw::com::impl::SampleAllocateePtr sample) noexcept override; Result> Allocate() noexcept override; @@ -49,7 +45,10 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding ResultBlank PrepareOffer() noexcept override; void PrepareStopOffer() noexcept override; BindingType GetBindingType() const noexcept override; - void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override; + void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override + { + common_event_logic_.GetTracingData() = tracing_data; + } std::size_t GetMaxSize() const noexcept override { @@ -57,71 +56,26 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding } private: - Skeleton& parent_; - SizeInfo size_info_; + DataTypeMetaInfo size_info_; const SkeletonEventProperties event_properties_; - const ElementFqId event_fqn_; score::cpp::optional control_{}; EventSlotStatus::EventTimeStamp current_timestamp_{0U}; score::memory::shared::OffsetPtr data_storage_{nullptr}; bool qm_disconnect_{false}; - impl::tracing::SkeletonEventTracingData tracing_data_{}; - std::atomic qm_event_update_notifications_registered_{false}; - std::atomic asil_b_event_update_notifications_registered_{false}; - std::optional transaction_log_registration_guard_{}; - std::optional type_erased_sample_ptrs_guard_{}; - - public: - // The following methods are public but intended for use by PrepareOfferImpl helper - impl::tracing::SkeletonEventTracingData& GetTracingData() - { - return tracing_data_; - } - - void EmplaceTransactionLogRegistrationGuard() - { - score::cpp::ignore = transaction_log_registration_guard_.emplace( - TransactionLogRegistrationGuard::Create(control_.value().GetQmEventDataControl())); - } - - void EmplaceTypeErasedSamplePtrsGuard() - { - score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(tracing_data_.service_element_tracing_data); - } - - void UpdateCurrentTimestamp() - { - current_timestamp_ = control_.value().GetLatestTimestamp(); - } - - Skeleton& GetParent() - { - return parent_; - } - - const ElementFqId& GetElementFQId() const - { - return event_fqn_; - } - - void SetQmNotificationsRegistered(bool value) - { - qm_event_update_notifications_registered_.store(value); - } - - void SetAsilBNotificationsRegistered(bool value) - { - asil_b_event_update_notifications_registered_.store(value); - } + + SkeletonEventCommon common_event_logic_; // Aggregated common logic + + // This method is needed by Allocate() and Send() + Skeleton& GetParent() { return common_event_logic_.GetParent(); } + const ElementFqId& GetElementFQId() const { return common_event_logic_.GetElementFQId(); } + bool IsQmRegistered() const { return common_event_logic_.IsQmRegistered(); } + bool IsAsilBRegistered() const { return common_event_logic_.IsAsilBRegistered(); } void ResetGuards() noexcept { - type_erased_sample_ptrs_guard_.reset(); - if (control_.has_value()) - { - transaction_log_registration_guard_.reset(); - } - control_.reset(); + common_event_logic_.ResetGuards(); + // Reset members specific to GenericSkeletonEvent + control_.reset(); // This was part of the original ResetGuards in GenericSkeletonEvent data_storage_ = nullptr; } }; diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 3832e2e10..75572344a 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -968,8 +968,19 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( const SkeletonEventProperties& element_properties, size_t sample_size, size_t sample_alignment) noexcept -{ - void* data_storage = storage_resource_->allocate(sample_size * element_properties.number_of_slots, sample_alignment); +{ + const auto aligned_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); + const auto total_data_size = aligned_size * element_properties.number_of_slots; + + std::cout << "Skeleton::CreateEventDataFromOpenedSharedMemory (Generic):" + << " event_fq_id=" << element_fq_id.ToString() + << " sample_size=" << sample_size + << " sample_alignment=" << sample_alignment + << " aligned_size=" << aligned_size + << " number_of_slots=" << element_properties.number_of_slots + << " total_data_size=" << total_data_size; + + void* data_storage = storage_resource_->allocate(total_data_size, sample_alignment); auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, std::forward_as_tuple(element_fq_id), @@ -977,7 +988,7 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second, "Couldn't register/emplace event-storage in data-section."); - const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; + const impl::DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; auto inserted_meta_info = storage_->events_metainfo_.emplace( std::piecewise_construct, std::forward_as_tuple(element_fq_id), diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.cpp b/score/mw/com/impl/bindings/lola/skeleton_event.cpp index 19362be40..902135396 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_event.cpp @@ -11,3 +11,4 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/mw/com/impl/bindings/lola/skeleton_event.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" // New common class diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h index 0ddac8bae..4a3ce6a01 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event.h @@ -27,99 +27,13 @@ #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_binding.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" -#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" // New common class #include "score/mw/log/logging.h" #include -#include -#include - -#include -#include -#include -#include -#include -#include - namespace score::mw::com::impl::lola { -namespace -{ - -template -void PrepareOfferImpl(EventType& event) -{ - const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && - (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); - if (!tracing_globally_enabled) - { - // in case tracing is globally disabled, this will never switch back to enable. Thus, we can directly disable - // all trace points for this event. This avoids any further lookups to the tracing runtime during Send() calls. - DisableAllTracePoints(event.GetTracingData()); - } - - const bool tracing_for_skeleton_event_enabled = - event.GetTracingData().enable_send || event.GetTracingData().enable_send_with_allocate; - // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in - // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code - // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in - // Ticket-188259). - if (tracing_for_skeleton_event_enabled) - { - // LCOV_EXCL_BR_STOP - event.EmplaceTransactionLogRegistrationGuard(); - event.EmplaceTypeErasedSamplePtrsGuard(); - } - - event.UpdateCurrentTimestamp(); - - // Register callbacks to be notified when event notification existence changes. - // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. - // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. - if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_QM) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_QM, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { - event.SetQmNotificationsRegistered(has_handlers); - }); - } - if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_B, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { - event.SetAsilBNotificationsRegistered(has_handlers); - }); - } -} - -template -void PrepareStopOfferImpl(EventType& event) noexcept -{ - // Unregister event notification existence changed callbacks - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event.GetElementFQId()); - - if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event.GetElementFQId()); - } - - // Reset the flags to indicate no handlers are registered - event.SetQmNotificationsRegistered(false); - event.SetAsilBNotificationsRegistered(false); - - event.ResetGuards(); -} - -} // namespace /// \brief Represents a binding specific instance (LoLa) of an event within a skeleton. It can be used to send events /// via Shared Memory. It will be created via a Factory Method, that will instantiate this class based on deployment @@ -139,23 +53,11 @@ class SkeletonEvent final : public SkeletonEventBinding // coverity[autosar_cpp14_a11_3_1_violation] friend class SkeletonEventAttorney; - public: - using typename SkeletonEventBinding::SendTraceCallback; - using typename SkeletonEventBindingBase::SubscribeTraceCallback; - using typename SkeletonEventBindingBase::UnsubscribeTraceCallback; + - SkeletonEvent(Skeleton& parent, - const ElementFqId event_fqn, - const std::string_view event_name, - const SkeletonEventProperties properties, - impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data = {}) noexcept; - SkeletonEvent(const SkeletonEvent&) = delete; - SkeletonEvent(SkeletonEvent&&) noexcept = delete; - SkeletonEvent& operator=(const SkeletonEvent&) & = delete; - SkeletonEvent& operator=(SkeletonEvent&&) & noexcept = delete; - - ~SkeletonEvent() override = default; + public: // Public API from SkeletonEventBinding + using typename SkeletonEventBinding::SendTraceCallback; /// \brief Sends a value by _copy_ towards a consumer. It will allocate the necessary space and then copy the value /// into Shared Memory. @@ -171,94 +73,39 @@ class SkeletonEvent final : public SkeletonEventBinding void PrepareStopOffer() noexcept override; - BindingType GetBindingType() const noexcept override - { - return BindingType::kLoLa; - } - - void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override - { - skeleton_event_tracing_data_ = tracing_data; - } - - ElementFqId GetElementFQId() const noexcept - { - return event_fqn_; - }; - - // The following methods are public but intended for use by PrepareOfferImpl helper - impl::tracing::SkeletonEventTracingData& GetTracingData() - { - return skeleton_event_tracing_data_; - } - - void EmplaceTransactionLogRegistrationGuard() - { - score::cpp::ignore = transaction_log_registration_guard_.emplace( - TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl())); - } - - void EmplaceTypeErasedSamplePtrsGuard() - { - score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data); - } - - void UpdateCurrentTimestamp() - { - current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp(); - } + BindingType GetBindingType() const noexcept override { return BindingType::kLoLa; } + void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override { common_event_logic_.GetTracingData() = tracing_data; } + ~SkeletonEvent() override = default; + SkeletonEvent(Skeleton& parent, + const ElementFqId event_fqn, + const std::string_view event_name, + const SkeletonEventProperties properties, + impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data = {}) noexcept; - Skeleton& GetParent() - { - return parent_; - } + SkeletonEvent(const SkeletonEvent&) = delete; + SkeletonEvent(SkeletonEvent&&) noexcept = delete; + SkeletonEvent& operator=(const SkeletonEvent&) & = delete; + SkeletonEvent& operator=(SkeletonEvent&&) & noexcept = delete; + + private: // Private members + const std::string_view event_name_; + const SkeletonEventProperties event_properties_; + EventDataStorage* event_data_storage_; + score::cpp::optional event_data_control_composite_; + EventSlotStatus::EventTimeStamp current_timestamp_; + bool qm_disconnect_; - void SetQmNotificationsRegistered(bool value) - { - qm_event_update_notifications_registered_.store(value); - } + SkeletonEventCommon common_event_logic_; // Aggregated common logic - void SetAsilBNotificationsRegistered(bool value) - { - asil_b_event_update_notifications_registered_.store(value); - } + Skeleton& GetParent() { return common_event_logic_.GetParent(); } + const ElementFqId& GetElementFQId() const { return common_event_logic_.GetElementFQId(); } + bool IsQmRegistered() const { return common_event_logic_.IsQmRegistered(); } + bool IsAsilBRegistered() const { return common_event_logic_.IsAsilBRegistered(); } void ResetGuards() noexcept { - type_erased_sample_ptrs_guard_.reset(); - if (event_data_control_composite_.has_value()) - { - transaction_log_registration_guard_.reset(); - } + common_event_logic_.ResetGuards(); } - - private: - Skeleton& parent_; - const ElementFqId event_fqn_; - const std::string_view event_name_; - const SkeletonEventProperties event_properties_; - EventDataStorage* event_data_storage_; - std::optional event_data_control_composite_; - EventSlotStatus::EventTimeStamp current_timestamp_; - bool qm_disconnect_; - impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data_; - - /// \brief Atomic flags indicating whether any receive handlers are currently registered for this event - /// at each quality level (QM and ASIL-B). - /// \details These flags are updated via callbacks from MessagePassingServiceInstance when handler - /// registration status changes. They allow Send() to skip the NotifyEvent() call when no - /// handlers are registered for a specific quality level, avoiding unnecessary lock overhead - /// in the main path. Uses memory_order_relaxed as the flags are optimisation hints - false - /// positives (thinking handlers exist when they don't) are harmless, and false negatives - /// (missing handlers) are prevented by the callback mechanism. - std::atomic qm_event_update_notifications_registered_{false}; - std::atomic asil_b_event_update_notifications_registered_{false}; - - /// \brief optional RAII guards for tracing transaction log registration/un-registration and cleanup of "pending" - /// type erased sample pointers which are created in PrepareOffer() and destroyed in PrepareStopoffer() - optional - /// as only needed when tracing is enabled and when they haven't been cleaned up via a call to PrepareStopoffer(). - std::optional transaction_log_registration_guard_; - std::optional type_erased_sample_ptrs_guard_; }; template @@ -266,19 +113,15 @@ SkeletonEvent::SkeletonEvent(Skeleton& parent, const ElementFqId event_fqn, const std::string_view event_name, const SkeletonEventProperties properties, - impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data) noexcept + impl::tracing::SkeletonEventTracingData tracing_data) noexcept : SkeletonEventBinding{}, - parent_{parent}, - event_fqn_{event_fqn}, event_name_{event_name}, event_properties_{properties}, event_data_storage_{nullptr}, - event_data_control_composite_{std::nullopt}, + event_data_control_composite_{score::cpp::nullopt}, current_timestamp_{1U}, qm_disconnect_{false}, - skeleton_event_tracing_data_{skeleton_event_tracing_data}, - transaction_log_registration_guard_{}, - type_erased_sample_ptrs_guard_{} + common_event_logic_(parent, event_fqn, event_data_control_composite_, current_timestamp_, tracing_data) { } @@ -329,18 +172,17 @@ ResultBlank SkeletonEvent::Send(impl::SampleAllocateePtr // This avoids the expensive lock operation in the common case where no handlers are registered. // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent // handler registration, the next Send() will pick it up. - if (qm_event_update_notifications_registered_.load() && !qm_disconnect_) + if (IsQmRegistered() && !qm_disconnect_) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_QM, event_fqn_); + .NotifyEvent(QualityType::kASIL_QM, GetElementFQId()); } - if (asil_b_event_update_notifications_registered_.load() && - parent_.GetInstanceQualityType() == QualityType::kASIL_B) + if (IsAsilBRegistered() && GetParent().GetInstanceQualityType() == QualityType::kASIL_B) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_B, event_fqn_); + .NotifyEvent(QualityType::kASIL_B, GetElementFQId()); } return {}; } @@ -367,10 +209,9 @@ Result> SkeletonEvent::Allocate if (!qm_disconnect_ && event_data_control_composite_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM()) { qm_disconnect_ = true; - score::mw::log::LogWarn("lola") - << __func__ << __LINE__ - << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_fqn_; - parent_.DisconnectQmConsumers(); + score::mw::log::LogWarn("lola") << __func__ << __LINE__ + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << GetElementFQId(); + GetParent().DisconnectQmConsumers(); } if (slot.IsValidQM() || slot.IsValidAsilB()) @@ -402,13 +243,12 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] ResultBlank SkeletonEvent::PrepareOffer() noexcept { - std::tie(event_data_storage_, event_data_control_composite_) = - parent_.Register(event_fqn_, event_properties_); + std::tie(event_data_storage_, event_data_control_composite_) = GetParent().template Register(GetElementFQId(), event_properties_); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_.has_value(), "Defensive programming as event_data_control_composite_ is set by Register above."); - PrepareOfferImpl(*this); + common_event_logic_.PrepareOfferCommon(); return {}; } @@ -422,7 +262,7 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] void SkeletonEvent::PrepareStopOffer() noexcept { - PrepareStopOfferImpl(*this); + common_event_logic_.PrepareStopOfferCommon(); } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp new file mode 100644 index 000000000..bc24fb685 --- /dev/null +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp @@ -0,0 +1,148 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" +#include "score/mw/com/impl/bindings/lola/i_runtime.h" // For GetBindingRuntime +#include "score/mw/com/impl/bindings/lola/messaging/i_message_passing_service.h" // For RegisterEventNotificationExistenceChangedCallback + +namespace score::mw::com::impl::lola +{ + +SkeletonEventCommon::SkeletonEventCommon(Skeleton& parent, + const ElementFqId& event_fqn, + score::cpp::optional& event_data_control_composite_ref, + EventSlotStatus::EventTimeStamp& current_timestamp_ref, + impl::tracing::SkeletonEventTracingData tracing_data) noexcept + : parent_{parent}, + event_fqn_{event_fqn}, + event_data_control_composite_ref_{event_data_control_composite_ref}, + current_timestamp_ref_{current_timestamp_ref}, + tracing_data_{tracing_data} +{ +} + +ResultBlank SkeletonEventCommon::PrepareOfferCommon() noexcept +{ + const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && + (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); + if (!tracing_globally_enabled) + { + DisableAllTracePoints(tracing_data_); + } + + const bool tracing_for_skeleton_event_enabled = + tracing_data_.enable_send || tracing_data_.enable_send_with_allocate; + if (tracing_for_skeleton_event_enabled) + { + EmplaceTransactionLogRegistrationGuard(); + EmplaceTypeErasedSamplePtrsGuard(); + } + + UpdateCurrentTimestamp(); + + // Register callbacks to be notified when event notification existence changes. + // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. + // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. + if (parent_.GetInstanceQualityType() == QualityType::kASIL_QM) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_QM, event_fqn_, [this](const bool has_handlers) noexcept { + SetQmNotificationsRegistered(has_handlers); + }); + } + if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_B, event_fqn_, [this](const bool has_handlers) noexcept { + SetAsilBNotificationsRegistered(has_handlers); + }); + } + return {}; +} + +void SkeletonEventCommon::PrepareStopOfferCommon() noexcept +{ + // Unregister event notification existence changed callbacks + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event_fqn_); + + if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event_fqn_); + } + + // Reset the flags to indicate no handlers are registered + SetQmNotificationsRegistered(false); + SetAsilBNotificationsRegistered(false); + + ResetGuards(); +} + +void SkeletonEventCommon::EmplaceTransactionLogRegistrationGuard() +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_ref_.has_value(), "EventDataControlComposite must be initialized."); + score::cpp::ignore = transaction_log_registration_guard_.emplace( + TransactionLogRegistrationGuard::Create(event_data_control_composite_ref_.value().GetQmEventDataControl())); +} + +void SkeletonEventCommon::EmplaceTypeErasedSamplePtrsGuard() +{ + score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(tracing_data_.service_element_tracing_data); +} + +void SkeletonEventCommon::UpdateCurrentTimestamp() +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_ref_.has_value(), "EventDataControlComposite must be initialized."); + current_timestamp_ref_ = event_data_control_composite_ref_.value().GetLatestTimestamp(); +} + +void SkeletonEventCommon::SetQmNotificationsRegistered(bool value) +{ + qm_event_update_notifications_registered_.store(value); +} + +void SkeletonEventCommon::SetAsilBNotificationsRegistered(bool value) +{ + asil_b_event_update_notifications_registered_.store(value); +} + +void SkeletonEventCommon::ResetGuards() noexcept +{ + type_erased_sample_ptrs_guard_.reset(); + if (event_data_control_composite_ref_.has_value()) + { + transaction_log_registration_guard_.reset(); + } +} + +bool SkeletonEventCommon::IsQmRegistered() const noexcept +{ + // Using memory_order_relaxed is safe here as this is an optimisation. If we miss a very recent + // handler registration, the next Send() will pick it up. + return qm_event_update_notifications_registered_.load(std::memory_order_relaxed); +} + +bool SkeletonEventCommon::IsAsilBRegistered() const noexcept +{ + // Using memory_order_relaxed is safe here as this is an optimisation. If we miss a very recent + // handler registration, the next Send() will pick it up. + return asil_b_event_update_notifications_registered_.load(std::memory_order_relaxed); +} + +} // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.h b/score/mw/com/impl/bindings/lola/skeleton_event_common.h new file mode 100644 index 000000000..d52720067 --- /dev/null +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/binding_type.h" +#include "score/mw/com/impl/bindings/lola/element_fq_id.h" +#include "score/mw/com/impl/bindings/lola/event_data_control_composite.h" +#include "score/mw/com/impl/bindings/lola/event_slot_status.h" +#include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" +#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" + +#include +#include +#include + +#include +#include +#include + +namespace score::mw::com::impl::lola +{ + +class Skeleton; // Forward declaration + +/// @brief Common implementation for LoLa skeleton events, shared between SkeletonEvent and GenericSkeletonEvent. +class SkeletonEventCommon +{ + public: + SkeletonEventCommon(Skeleton& parent, + const ElementFqId& event_fqn, + score::cpp::optional& event_data_control_composite_ref, + EventSlotStatus::EventTimeStamp& current_timestamp_ref, + impl::tracing::SkeletonEventTracingData tracing_data = {}) noexcept; + + // No copy, no move + SkeletonEventCommon(const SkeletonEventCommon&) = delete; + SkeletonEventCommon(SkeletonEventCommon&&) noexcept = delete; + SkeletonEventCommon& operator=(const SkeletonEventCommon&) & = delete; + SkeletonEventCommon& operator=(SkeletonEventCommon&&) & noexcept = delete; + + ~SkeletonEventCommon() = default; + + ResultBlank PrepareOfferCommon() noexcept; + void PrepareStopOfferCommon() noexcept; + + // Accessors for members used by PrepareOfferCommon/PrepareStopOfferCommon + impl::tracing::SkeletonEventTracingData& GetTracingData() { return tracing_data_; } + const ElementFqId& GetElementFQId() const { return event_fqn_; } + Skeleton& GetParent() { return parent_; } + + void EmplaceTransactionLogRegistrationGuard(); + void EmplaceTypeErasedSamplePtrsGuard(); + void UpdateCurrentTimestamp(); + void SetQmNotificationsRegistered(bool value); + void SetAsilBNotificationsRegistered(bool value); + void ResetGuards(); + + // Accessors for atomic flags for derived classes' Send() method + bool IsQmRegistered() const noexcept; + bool IsAsilBRegistered() const noexcept; + + private: + Skeleton& parent_; + const ElementFqId event_fqn_; + score::cpp::optional& event_data_control_composite_ref_; // Reference to the optional in derived class + EventSlotStatus::EventTimeStamp& current_timestamp_ref_; // Reference to the timestamp in derived class + impl::tracing::SkeletonEventTracingData tracing_data_; + + std::atomic qm_event_update_notifications_registered_{false}; + std::atomic asil_b_event_update_notifications_registered_{false}; + std::optional transaction_log_registration_guard_{}; + std::optional type_erased_sample_ptrs_guard_{}; +}; + +} // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp index 8e996be04..073290c3e 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp @@ -50,7 +50,7 @@ class SkeletonEventAttorney skeleton_event_.qm_disconnect_ = qm_disconnect_value; } - std::optional& GetEventDataControlComposite() + score::cpp::optional& GetEventDataControlComposite() { return skeleton_event_.event_data_control_composite_; } diff --git a/score/mw/com/impl/bindings/lola/skeleton_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_test.cpp index 336951d8b..9287fcf58 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_test.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_test.cpp @@ -1103,11 +1103,11 @@ TEST_P(SkeletonRegisterParamaterisedFixture, ValidEventMetaInfoExistAfterEventIs ASSERT_TRUE(event_foo_meta_info_ptr.has_value()); ASSERT_TRUE(event_dumb_meta_info_ptr.has_value()); // and they have the expected properties - ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.size_of_, sizeof(std::uint8_t)); - ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.align_of_, alignof(std::uint8_t)); + ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.size, sizeof(std::uint8_t)); + ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.alignment, alignof(std::uint8_t)); - ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.size_of_, sizeof(VeryComplexType)); - ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.align_of_, alignof(VeryComplexType)); + ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.size, sizeof(VeryComplexType)); + ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.alignment, alignof(VeryComplexType)); const auto GetEventSlotsArraySize = [](const std::size_t sample_size, const std::size_t sample_alignment, @@ -1117,13 +1117,13 @@ TEST_P(SkeletonRegisterParamaterisedFixture, ValidEventMetaInfoExistAfterEventIs return aligned_size * number_of_sample_slots; }; - const auto foo_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size_of_, - event_foo_meta_info_ptr->data_type_info_.align_of_, + const auto foo_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size, + event_foo_meta_info_ptr->data_type_info_.alignment, test::kDefaultEventProperties.number_of_slots); ASSERT_EQ(event_foo_meta_info_ptr->event_slots_raw_array_.get(foo_event_slots_size), foo_event_data_storage); - const auto dumb_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size_of_, - event_foo_meta_info_ptr->data_type_info_.align_of_, + const auto dumb_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size, + event_foo_meta_info_ptr->data_type_info_.alignment, test::kDefaultEventProperties.number_of_slots); ASSERT_EQ(event_dumb_meta_info_ptr->event_slots_raw_array_.get(dumb_event_slots_size), dumb_event_data_storage); diff --git a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp index fb66d5d70..982a658fd 100644 --- a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp +++ b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp @@ -56,8 +56,8 @@ class SkeletonEventAttorney /// \brief Set handler availability flags for testing purposes void SetHandlerAvailability(bool qm_available, bool asil_b_available) { - skeleton_event_.qm_event_update_notifications_registered_.store(qm_available); - skeleton_event_.asil_b_event_update_notifications_registered_.store(asil_b_available); + skeleton_event_.common_event_logic_.SetQmNotificationsRegistered(qm_available); + skeleton_event_.common_event_logic_.SetAsilBNotificationsRegistered(asil_b_available); } private: @@ -316,8 +316,8 @@ TEST_F(SkeletonEventComponentTestFixture, SkeletonWillCalculateEventMetaInfoFrom // Then the event meta info should correspond to the type of the skeleton event ASSERT_TRUE(event_meta_info.has_value()); - EXPECT_EQ(event_meta_info.value().data_type_info_.align_of_, alignof(SkeletonEventSampleType)); - EXPECT_EQ(event_meta_info.value().data_type_info_.size_of_, sizeof(SkeletonEventSampleType)); + EXPECT_EQ(event_meta_info.value().data_type_info_.alignment, alignof(SkeletonEventSampleType)); + EXPECT_EQ(event_meta_info.value().data_type_info_.size, sizeof(SkeletonEventSampleType)); } using SkeletonEventComponentDeathTest = SkeletonEventComponentTestFixture; diff --git a/score/mw/com/impl/bindings/lola/data_type_meta_info.cpp b/score/mw/com/impl/data_type_meta_info.cpp similarity index 89% rename from score/mw/com/impl/bindings/lola/data_type_meta_info.cpp rename to score/mw/com/impl/data_type_meta_info.cpp index c87ba95c0..9e0ba97c7 100644 --- a/score/mw/com/impl/bindings/lola/data_type_meta_info.cpp +++ b/score/mw/com/impl/data_type_meta_info.cpp @@ -10,4 +10,4 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#include "score/mw/com/impl/bindings/lola/data_type_meta_info.h" +#include "score/mw/com/impl/data_type_meta_info.h" diff --git a/score/mw/com/impl/bindings/lola/data_type_meta_info.h b/score/mw/com/impl/data_type_meta_info.h similarity index 88% rename from score/mw/com/impl/bindings/lola/data_type_meta_info.h rename to score/mw/com/impl/data_type_meta_info.h index 9e81895f7..63285578e 100644 --- a/score/mw/com/impl/bindings/lola/data_type_meta_info.h +++ b/score/mw/com/impl/data_type_meta_info.h @@ -16,17 +16,17 @@ #include #include -namespace score::mw::com::impl::lola +namespace score::mw::com::impl { /// \brief Meta-info of a data type exchanged via mw::com/LoLa. I.e. can be the data type of an event/filed/method arg. struct DataTypeMetaInfo { //@todo -> std::uint64_t fingerprint - std::size_t size_of_; - std::uint8_t align_of_; + std::size_t size; + std::size_t alignment; }; -} // namespace score::mw::com::impl::lola +} // namespace score::mw::com::impl #endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_DATA_TYPE_META_INFO_H diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp index 7cb3dee8b..67806ccff 100644 --- a/score/mw/com/impl/generic_skeleton.cpp +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -13,10 +13,10 @@ #include "score/mw/com/impl/generic_skeleton.h" #include "score/mw/com/impl/com_error.h" -#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" -#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" -#include "score/mw/com/impl/runtime.h" -#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" +//#include "score/mw/com/impl/plumbing/generic_skeleton_field_binding_factory.h" //not implemented yet +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" #include "score/mw/com/impl/skeleton_binding.h" #include @@ -27,8 +27,7 @@ namespace score::mw::com::impl Result GenericSkeleton::Create( const InstanceSpecifier& specifier, - score::cpp::span events, - score::cpp::span out_handles, + const GenericSkeletonCreateParams& in, MethodCallProcessingMode mode) noexcept { const auto instance_identifier_result = GetInstanceIdentifier(specifier); @@ -39,18 +38,14 @@ Result GenericSkeleton::Create( return MakeUnexpected(ComErrc::kInstanceIDCouldNotBeResolved); } - return Create(instance_identifier_result.value(), events, out_handles, mode); + return Create(instance_identifier_result.value(), in, mode); } Result GenericSkeleton::Create( const InstanceIdentifier& identifier, - score::cpp::span events, - score::cpp::span out_handles, + const GenericSkeletonCreateParams& in, MethodCallProcessingMode mode) noexcept { - // Ensure the output span is large enough to hold handles for all events - assert(events.size() == out_handles.size()); - auto binding = SkeletonBindingFactory::Create(identifier); if (!binding) { @@ -62,41 +57,74 @@ Result GenericSkeleton::Create( // 1. Create the Skeleton (Private Constructor) GenericSkeleton skeleton(identifier, std::move(binding), mode); - // 2. Reserve space for events to avoid reallocation - skeleton.owned_events_.reserve(events.size()); - // 3. Atomically create all events - for (std::size_t i = 0; i < events.size(); ++i) + for (const auto& info : in.events) // Iterate directly over in.events { - const auto& info = events[i]; - // Create the event binding - auto event_binding_result = GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.size_info); + auto event_binding_result = + GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); if (!event_binding_result.has_value()) { - // If any event fails to bind, the whole creation fails (Atomic) + // If any event fails to bind, the whole creation fails (Atomic). Error logging is typically handled within the factory. return MakeUnexpected(ComErrc::kBindingFailure); } - // Store the event in the vector - skeleton.owned_events_.push_back(std::make_unique( - skeleton, info.name, std::move(event_binding_result).value())); + // Store the event in the map + const auto emplace_result = skeleton.events_.emplace( + std::piecewise_construct, + std::forward_as_tuple(info.name), + std::forward_as_tuple(skeleton, info.name, std::move(event_binding_result).value())); - // Assign the handle (index in the vector) - out_handles[i] = EventHandle{static_cast(i)}; + if (!emplace_result.second) { + score::mw::log::LogError("GenericSkeleton") << "Failed to emplace GenericSkeletonEvent for name: " << info.name; + return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); + } } + // // 4. Atomically create all fields + // for (const auto& info : in.fields) // Iterate directly over in.fields + // { + // // Create the field binding + // auto field_binding_result = + // GenericSkeletonFieldBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); + + // if (!field_binding_result.has_value()) + // { + // // If any field fails to bind, the whole creation fails (Atomic). Error logging is typically handled within the factory. + // return MakeUnexpected(ComErrc::kBindingFailure); + // } + + // // Store the field in the map + // const auto emplace_result = skeleton.fields_.emplace( + // std::piecewise_construct, + // std::forward_as_tuple(info.name), + // std::forward_as_tuple(skeleton, info.name, std::move(field_binding_result).value(), info.initial_value_bytes)); + + // if (!emplace_result.second) { + // score::mw::log::LogError("GenericSkeleton") << "Failed to emplace GenericSkeletonField for name: " << info.name; + // return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); + // } + // } + return skeleton; } -GenericSkeletonEvent& GenericSkeleton::GetEvent(EventHandle h) noexcept +const GenericSkeleton::EventMap& GenericSkeleton::GetEvents() const noexcept { - // Fast O(1) lookup - assert(h.index < owned_events_.size()); - return *owned_events_[h.index]; + return events_; } +GenericSkeleton::EventMap& GenericSkeleton::GetEvents() noexcept +{ + return events_; +} + +// const GenericSkeleton::FieldMap& GenericSkeleton::GetFields() const noexcept +// { +// return fields_; +// } + Result GenericSkeleton::OfferService() noexcept { return SkeletonBase::OfferService(); diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index ae5585735..5fa78154d 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -13,83 +13,101 @@ #pragma once #include "score/mw/com/impl/generic_skeleton_event.h" +// #include "score/mw/com/impl/generic_skeleton_field_binding.h" // New include - commented out as field not implemented +// #include "score/mw/com/impl/generic_skeleton_field.h" // commented out as field not implemented #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/instance_specifier.h" #include "score/mw/com/impl/skeleton_base.h" -#include "score/mw/com/impl/size_info.h" - +#include "score/mw/com/impl/data_type_meta_info.h" +#include "score/mw/com/impl/service_element_map.h" #include "score/result/result.h" -#include // Changed: Using your project's span +#include #include #include #include + namespace score::mw::com::impl { -class SkeletonBinding; - -/// @brief Describes the configuration for a single event. struct EventInfo { std::string_view name; - SizeInfo size_info; + DataTypeMetaInfo data_type_meta_info; }; - -/// @brief An opaque handle to an event within a GenericSkeleton. -struct EventHandle +// struct FieldInfo // commented out as field not implemented +// { +// std::string_view name; +// DataTypeMetaInfo data_type_meta_info; +// /// @brief The initial value for the field. +// /// @note Must be non-empty. +// /// @note The data must remain valid only for the duration of the Create() call. +// /// `initial_value_bytes.size()` must be less than or equal to `size_info.size`. +// /// The bytes are in the middleware’s “generic” field representation. +// score::cpp::span initial_value_bytes; +// +// }; +struct GenericSkeletonCreateParams { - std::uint16_t index; + score::cpp::span events{}; + //score::cpp::span fields{}; }; - -/// @brief A generic, type-erased skeleton implementation. -/// -/// This class allows creating and offering service instances dynamically without -/// requiring compile-time generated code. It handles the lifecycle of events -/// and the service offering process. class GenericSkeleton : public SkeletonBase { public: - /// @brief Creates a GenericSkeleton and all its events atomically. - /// @param id The instance identifier. - /// @param events A span of EventInfo structs describing all events to be created. - /// @param out_handles A span that will be populated with handles to the created events. - /// The caller must ensure its size is equal to events.size(). - /// @param mode The method call processing mode. - /// @return A GenericSkeleton or an error if creation fails. - static Result Create(const InstanceIdentifier& id, - score::cpp::span events, - score::cpp::span out_handles, - MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; + using EventMap = ServiceElementMap; +// using FieldMap = ServiceElementMap; // commented out as field not implemented +/// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically. +/// +/// @contract +/// - Empty spans are allowed for `in.events` and/or `in.fields` +/// - Each provided name must exist in the binding deployment for this instance (events/fields respectively). +/// - All element names must be unique across all element kinds within this skeleton. +/// - For each field, `initial_value_bytes` must be non-empty and +/// `initial_value_bytes.size()` must be <= `size_info.size`. +/// - On error, no partially-created elements are left behind. +[[nodiscard]] static Result Create( + const InstanceIdentifier& identifier, + const GenericSkeletonCreateParams& in, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; + +/// @brief Same as Create(InstanceIdentifier, ...) but resolves the specifier first. +/// @param specifier The instance specifier. +/// @param in Input parameters for creation. +/// @param mode The method call processing mode. +/// @return A GenericSkeleton or an error. + [[nodiscard]] static Result Create( + const InstanceSpecifier& specifier, + const GenericSkeletonCreateParams& in, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; - /// @brief Creates a GenericSkeleton for a given instance specifier. - /// Note: You likely need to update this overload to match the new atomic creation style - /// or remove it if it is no longer supported. - static Result Create(const InstanceSpecifier& specifier, - score::cpp::span events, - score::cpp::span out_handles, - MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; +/// @brief Returns a const reference to the name-keyed map of events. +/// @note The returned reference is valid as long as the GenericSkeleton lives. +[[nodiscard]] const EventMap& GetEvents() const noexcept; - /// @brief Retrieves an event using its handle. - /// @param h The handle to the event. - /// @return A reference to the GenericSkeletonEvent. - GenericSkeletonEvent& GetEvent(EventHandle h) noexcept; +/// @brief Returns a reference to the name-keyed map of events. +/// @note The returned reference is valid as long as the GenericSkeleton lives. +[[nodiscard]] EventMap& GetEvents() noexcept; - /// @brief Offers the service instance. - /// @return A blank result, or an error if offering fails. - Result OfferService() noexcept; +/// @brief Returns a const reference to the name-keyed map of fields. +/// @note The returned reference is valid as long as the GenericSkeleton lives. +//[[nodiscard]] const FieldMap& GetFields() const noexcept; - /// @brief Stops offering the service instance. - void StopOfferService() noexcept; +/// @brief Offers the service instance. +/// @return A blank result, or an error if offering fails. +[[nodiscard]] Result OfferService() noexcept; +/// @brief Stops offering the service instance. +void StopOfferService() noexcept; private: - GenericSkeleton(const InstanceIdentifier& identifier, - std::unique_ptr binding, - MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent); + // Private constructor, only callable by static Create methods. + GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding, MethodCallProcessingMode mode); - // Internal storage is now a vector for O(1) access via EventHandle. - std::vector> owned_events_; -}; + /// @brief This map owns all GenericSkeletonEvent instances. + EventMap events_; + /// @brief This map owns all GenericSkeletonField instances. +// FieldMap fields_; // commented out as field not implemented +}; } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index c15fae61a..e0b1576ba 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -85,7 +85,7 @@ Result> GenericSkeletonEvent::Allocate() noexcept return result; } -SizeInfo GenericSkeletonEvent::GetSizeInfo() const noexcept +DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept { const auto* const binding = static_cast(binding_.get()); const auto size_info_pair = binding->GetSizeInfo(); diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h index a4a12af03..e6e592bc5 100644 --- a/score/mw/com/impl/generic_skeleton_event.h +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -14,9 +14,9 @@ #include "score/mw/com/impl/skeleton_event_base.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" -#include "score/mw/com/impl/size_info.h" #include "score/result/result.h" +#include "score/mw/com/impl/data_type_meta_info.h" // For DataTypeMetaInfo namespace score::mw::com::impl { @@ -38,7 +38,7 @@ class GenericSkeletonEvent : public SkeletonEventBase Result> Allocate() noexcept; - SizeInfo GetSizeInfo() const noexcept; + DataTypeMetaInfo GetSizeInfo() const noexcept; }; } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp index 072717d98..90c0a8500 100644 --- a/score/mw/com/impl/generic_skeleton_test.cpp +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -52,52 +52,82 @@ TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifier) { auto instance_specifier = InstanceSpecifier::Create("a/b/c").value(); auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + GenericSkeletonCreateParams create_params; EXPECT_CALL(runtime_mock_guard_.runtime_mock_, ResolveInstanceIdentifier(instance_specifier)) .WillOnce(Return(instance_identifier)); - auto skeleton_result = GenericSkeleton::Create(instance_specifier); + auto skeleton_result = GenericSkeleton::Create(instance_specifier, create_params); ASSERT_TRUE(skeleton_result.has_value()); } TEST_F(GenericSkeletonTest, CreateWithInstanceIdentifier) { auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); - auto skeleton_result = GenericSkeleton::Create(instance_identifier); + GenericSkeletonCreateParams create_params; + auto skeleton_result = GenericSkeleton::Create(instance_identifier, create_params); ASSERT_TRUE(skeleton_result.has_value()); } -TEST_F(GenericSkeletonTest, AddEvent) +TEST_F(GenericSkeletonTest, CreateWithEventsAndFields) { - auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); - const SizeInfo size_info{16, 8}; - auto event_result = skeleton.AddEvent("MyEvent", size_info); - ASSERT_TRUE(event_result.has_value()); - - // Adding the same event again fails - auto second_event_result = skeleton.AddEvent("MyEvent", size_info); - ASSERT_FALSE(second_event_result.has_value()); + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + const DataTypeMetaInfo event_size_info{16, 8}; + // const DataTypeMetaInfo field_size_info{4, 4}; // commented out as field not implemented + const std::string event_name = "MyEvent"; + // const std::string field_name = "MyField"; // commented out as field not implemented + // const std::vector initial_field_value = {1, 2, 3, 4}; // commented out as field not implemented + + // Mock event binding creation + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_guard_.factory_mock_, Create(_, event_name, event_size_info)) + .WillOnce(Return(ByMove(std::make_unique()))); + + // // Mock field binding creation // commented out as field not implemented + // EXPECT_CALL(generic_skeleton_field_binding_factory_mock_guard_.factory_mock_, Create(_, field_name, field_size_info)) + // .WillOnce(Return(ByMove(std::make_unique()))); + + GenericSkeletonCreateParams create_params; + const std::vector events_vec = {{event_name, event_size_info}}; + //const std::vector fields_vec = {{field_name, field_size_info, initial_field_value}}; + create_params.events = events_vec; + //create_params.fields = fields_vec; + + auto skeleton_result = GenericSkeleton::Create(instance_identifier, create_params); + ASSERT_TRUE(skeleton_result.has_value()); + + auto& skeleton = skeleton_result.value(); + EXPECT_EQ(skeleton.GetEvents().size(), 1); + // EXPECT_EQ(skeleton.GetFields().size(), 1); // commented out as field not implemented + EXPECT_NE(skeleton.GetEvents().find(event_name), skeleton.GetEvents().cend()); + // EXPECT_NE(skeleton.GetFields().find(field_name), skeleton.GetFields().cend()); // commented out as field not implemented } TEST_F(GenericSkeletonTest, OfferAndStopOfferService) { - auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + GenericSkeletonCreateParams create_params; // Empty params for this test + auto skeleton = GenericSkeleton::Create(instance_identifier, create_params).value(); + + // Expect PrepareOffer to be called on the binding + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::result::Blank{})); + // Expect OfferService to be called on the service discovery + EXPECT_CALL(runtime_mock_guard_.service_discovery_mock_, OfferService(instance_identifier)).WillOnce(Return(score::result::Blank{})); - EXPECT_CALL(*skeleton_binding_mock_, OfferService()).WillOnce(Return(score::result::Blank{})); auto offer_result = skeleton.OfferService(); EXPECT_TRUE(offer_result.has_value()); - EXPECT_CALL(*skeleton_binding_mock_, StopOfferService()); + EXPECT_CALL(*skeleton_binding_mock_, PrepareStopOffer(_)); + EXPECT_CALL(runtime_mock_guard_.service_discovery_mock_, StopOfferService(instance_identifier)); skeleton.StopOfferService(); } TEST_F(GenericSkeletonTest, CreateFailsIfBindingFails) { EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)).WillOnce(Return(ByMove(nullptr))); - auto skeleton_result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()); + GenericSkeletonCreateParams create_params; + auto skeleton_result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier(), create_params); ASSERT_FALSE(skeleton_result.has_value()); EXPECT_EQ(skeleton_result.error(), ComErrc::kBindingFailure); } - } // namespace } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 4bd33d440..b4645f226 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -319,6 +319,9 @@ cc_library( features = COMPILER_WARNING_FEATURES, implementation_deps = [ "skeleton_binding_factory_impl", + ], + visibility = [ + "//score/mw/com/impl:__subpackages__", ], tags = ["FFI"], deps = [ diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h index 438e08bc5..23b384abc 100644 --- a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h @@ -15,8 +15,8 @@ #include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/skeleton_base.h" -#include "score/mw/com/impl/size_info.h" -#include "score/mw/com/impl/service_element_type.h" // Added for ServiceElementType +#include "score/mw/com/impl/data_type_meta_info.h" +#include "score/mw/com/impl/service_element_type.h" #include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" #include "score/result/result.h" @@ -31,8 +31,8 @@ class GenericSkeletonEventBindingFactory { public: static Result> Create(SkeletonBase& skeleton_base, - std::string_view event_name, - const SizeInfo& size_info) noexcept + const std::string_view event_name, + const DataTypeMetaInfo& size_info) noexcept { const auto& instance_identifier = SkeletonBaseView{skeleton_base}.GetAssociatedInstanceIdentifier(); return CreateSkeletonServiceElement( diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index b0a6bf50c..7f3253aef 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H #define SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H +#include "score/mw/com/impl/data_type_meta_info.h" #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" #include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" @@ -79,7 +80,7 @@ template >& size_info) noexcept + const score::cpp::optional>& size_info) noexcept -> std::unique_ptr { static_assert(element_type != ServiceElementType::INVALID); @@ -145,7 +146,7 @@ auto CreateSkeletonServiceElementImpl(const InstanceIdentifier& identifier, } // namespace detail -/// @brief Overload for typed skeletons (which do not have a SizeInfo). +/// @brief Overload for typed skeletons (which do not have a DataTypeMetaInfo). template auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, SkeletonBase& parent, @@ -154,23 +155,23 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, { static_assert(!std::is_same_v, - "This overload is for typed skeletons only. Generic skeletons must provide a SizeInfo."); + "This overload is for typed skeletons only. Generic skeletons must provide a DataTypeMetaInfo."); return detail::CreateSkeletonServiceElementImpl( identifier, parent, service_element_name, score::cpp::nullopt); } -/// @brief Overload for generic skeletons (which require a SizeInfo). +/// @brief Overload for generic skeletons (which require a DataTypeMetaInfo). template auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, SkeletonBase& parent, const std::string_view service_element_name, - const SizeInfo& size_info) noexcept + const DataTypeMetaInfo& size_info) noexcept -> std::unique_ptr { static_assert(std::is_same_v, - "This overload is for generic skeletons only. Typed skeletons must not provide a SizeInfo."); + "This overload is for generic skeletons only. Typed skeletons must not provide a DataTypeMetaInfo."); return detail::CreateSkeletonServiceElementImpl( identifier, diff --git a/score/mw/com/impl/size_info.h b/score/mw/com/impl/size_info.h deleted file mode 100644 index 6417f6664..000000000 --- a/score/mw/com/impl/size_info.h +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#pragma once - -#include - -namespace score::mw::com::impl -{ -/// @brief A struct to hold size and alignment information for generic type-erased data. -struct SizeInfo -{ - size_t size; - size_t alignment; -}; - -} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index f4e420799..af51c84ce 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -16,7 +16,7 @@ #include "score/memory/shared/i_shared_memory_resource.h" #include "score/result/result.h" #include "score/mw/com/impl/binding_type.h" -#include "score/mw/com/impl/size_info.h" +//#include "score/mw/com/impl/size_info.h" #include "score/mw/com/impl/service_element_type.h" #include "score/mw/com/impl/generic_skeleton_event_binding.h" From ac37c9526278ea5bd04b12bba4def0c1d0090fb1 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 28 Jan 2026 08:43:37 +0200 Subject: [PATCH 06/18] Update the signature of ResetGuards func and Reusing DataTypeMetaInfo in proxy_test --- score/mw/com/impl/bindings/lola/proxy_test.cpp | 4 ++-- score/mw/com/impl/bindings/lola/skeleton_event_common.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/score/mw/com/impl/bindings/lola/proxy_test.cpp b/score/mw/com/impl/bindings/lola/proxy_test.cpp index 0aaff26fb..a47f5083b 100644 --- a/score/mw/com/impl/bindings/lola/proxy_test.cpp +++ b/score/mw/com/impl/bindings/lola/proxy_test.cpp @@ -549,8 +549,8 @@ TEST_F(ProxyGetEventMetaInfoFixture, GetEventMetaInfoWillReturnDataForEventThatW const auto event_meta_info = proxy_->GetEventMetaInfo(kDummyElementFqId); // Then the EventMetaInfo will contain the meta info of the SkeletonEvent type - EXPECT_EQ(event_meta_info.data_type_info_.size_of_, sizeof(ProxyMockedMemoryFixture::SampleType)); - EXPECT_EQ(event_meta_info.data_type_info_.align_of_, alignof(ProxyMockedMemoryFixture::SampleType)); + EXPECT_EQ(event_meta_info.data_type_info_.size, sizeof(ProxyMockedMemoryFixture::SampleType)); + EXPECT_EQ(event_meta_info.data_type_info_.alignment, alignof(ProxyMockedMemoryFixture::SampleType)); } using ProxyGetEventMetaInfoDeathTest = ProxyGetEventMetaInfoFixture; diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.h b/score/mw/com/impl/bindings/lola/skeleton_event_common.h index d52720067..843599862 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_common.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h @@ -67,7 +67,7 @@ class SkeletonEventCommon void UpdateCurrentTimestamp(); void SetQmNotificationsRegistered(bool value); void SetAsilBNotificationsRegistered(bool value); - void ResetGuards(); + void ResetGuards() noexcept; // Accessors for atomic flags for derived classes' Send() method bool IsQmRegistered() const noexcept; From 24c35eae42f035d2661df73333749002b05c5de2 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 28 Jan 2026 09:11:52 +0200 Subject: [PATCH 07/18] Remove useless comments and clean up the code --- .../example/ipc_bridge/sample_sender_receiver.cpp | 2 +- score/mw/com/impl/BUILD | 14 +++++++------- .../com/impl/bindings/lola/generic_proxy_event.cpp | 8 -------- .../impl/bindings/lola/generic_skeleton_event.cpp | 8 -------- score/mw/com/impl/bindings/lola/skeleton.cpp | 8 -------- score/mw/com/impl/bindings/lola/skeleton_event.cpp | 2 +- score/mw/com/impl/bindings/lola/skeleton_event.h | 2 +- score/mw/com/impl/skeleton_binding.h | 1 - 8 files changed, 10 insertions(+), 35 deletions(-) diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index bd2ed0fcb..8c54cd7e6 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -31,7 +31,7 @@ #include #include #include -#include // Added: Required for GenericSkeleton::Create +#include using namespace std::chrono_literals; diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 56e63827d..03873000e 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -130,9 +130,9 @@ cc_library( ":instance_identifier", ":instance_specifier", ":runtime", - ":skeleton_base", # For SkeletonBase - ":skeleton_binding", # For SkeletonBinding - ":data_type_meta_info", # For DataTypeMetaInfo + ":skeleton_base", + ":skeleton_binding", + ":data_type_meta_info", ":service_element_map", "@score_baselibs//score/result", @@ -152,9 +152,9 @@ cc_library( "//score/mw/com/impl/bindings/lola:generic_skeleton_event", ":generic_skeleton_event_binding", ":skeleton_base", - ":skeleton_event_base", # For SkeletonEventBase - ":skeleton_event_binding", # For SkeletonEventBinding - ":data_type_meta_info", # For DataTypeMetaInfo + ":skeleton_event_base", + ":skeleton_event_binding", + ":data_type_meta_info", "//score/mw/com/impl/plumbing:sample_allocatee_ptr", "@score_baselibs//score/result", ], @@ -461,7 +461,7 @@ cc_library( deps = [ ":binding_type", ":generic_skeleton_event_binding", - ":data_type_meta_info", # For DataTypeMetaInfo + ":data_type_meta_info", "//score/mw/com/impl/configuration", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/memory/shared:i_shared_memory_resource", diff --git a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp index 043464707..7097e15cb 100644 --- a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp @@ -147,14 +147,6 @@ Result GenericProxyEvent::GetNewSamplesImpl(Callback&& receiver, Tr std::terminate(); } - std::cout << "GenericProxyEvent::GetNewSamplesImpl:" - << " sample_size=" << sample_size - << " sample_alignment=" << sample_alignment - << " aligned_size=" << aligned_size - << " max_number_of_sample_slots=" << max_number_of_sample_slots - << " expected_array_size=" << event_slots_raw_array_size.value(); - - const void* const event_slots_raw_array = meta_info_.event_slots_raw_array_.get(event_slots_raw_array_size.value()); // AMP assert that the event_slots_raw_array address is according to sample_alignment diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index 5d02fe770..9db27121a 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -97,14 +97,6 @@ Result> GenericSkeletonEvent::All std::uint64_t offset = static_cast(slot.GetIndex()) * aligned_size; void* data_ptr = byte_ptr + offset; - - std::cout << "GenericSkeletonEvent::Allocate:" - << " slot_index=" << slot.GetIndex() - << " aligned_size=" << aligned_size - << " offset=" << offset - << " base_ptr=" << base_ptr - << " data_ptr=" << data_ptr; - auto lola_ptr = lola::SampleAllocateePtr(data_ptr, control_.value(), slot); return impl::MakeSampleAllocateePtr(std::move(lola_ptr)); } diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 75572344a..05c71ddbc 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -972,14 +972,6 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( const auto aligned_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); const auto total_data_size = aligned_size * element_properties.number_of_slots; - std::cout << "Skeleton::CreateEventDataFromOpenedSharedMemory (Generic):" - << " event_fq_id=" << element_fq_id.ToString() - << " sample_size=" << sample_size - << " sample_alignment=" << sample_alignment - << " aligned_size=" << aligned_size - << " number_of_slots=" << element_properties.number_of_slots - << " total_data_size=" << total_data_size; - void* data_storage = storage_resource_->allocate(total_data_size, sample_alignment); auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.cpp b/score/mw/com/impl/bindings/lola/skeleton_event.cpp index 902135396..a065c651c 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_event.cpp @@ -11,4 +11,4 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/mw/com/impl/bindings/lola/skeleton_event.h" -#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" // New common class +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h index 4a3ce6a01..95aaab35d 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event.h @@ -27,7 +27,7 @@ #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_binding.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" -#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" // New common class +#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" #include "score/mw/log/logging.h" diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index af51c84ce..c2d6830cf 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -16,7 +16,6 @@ #include "score/memory/shared/i_shared_memory_resource.h" #include "score/result/result.h" #include "score/mw/com/impl/binding_type.h" -//#include "score/mw/com/impl/size_info.h" #include "score/mw/com/impl/service_element_type.h" #include "score/mw/com/impl/generic_skeleton_event_binding.h" From 409fd3d3db36a2f37d81245a75b94d360154e3b9 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 28 Jan 2026 10:38:07 +0200 Subject: [PATCH 08/18] Add docstrings to new functions --- score/mw/com/impl/generic_skeleton.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index 5fa78154d..232a2d41b 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -53,6 +53,12 @@ struct GenericSkeletonCreateParams score::cpp::span events{}; //score::cpp::span fields{}; }; + +/// @brief Represents a type-erased, runtime-configurable skeleton for a service instance. +/// +/// A `GenericSkeleton` is created at runtime based on configuration data. It manages +/// a collection of `GenericSkeletonEvent` and `GenericSkeletonField` instances. + class GenericSkeleton : public SkeletonBase { public: From 69faf2f47c7bdebdef5ccb5a3b2396bced8bb7e7 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Sun, 1 Feb 2026 23:50:13 +0200 Subject: [PATCH 09/18] Integrate SampleAllocateePtr without void specilization --- score/mw/com/impl/bindings/lola/skeleton.cpp | 21 ++- score/mw/com/impl/bindings/mock_binding/BUILD | 12 ++ .../mock_binding/sample_allocatee_ptr.cpp | 13 ++ .../mock_binding/sample_allocatee_ptr.h | 36 +++++ .../bindings/mock_binding/skeleton_event.h | 8 +- score/mw/com/impl/plumbing/BUILD | 4 +- .../com/impl/plumbing/sample_allocatee_ptr.h | 152 ++++-------------- .../plumbing/sample_allocatee_ptr_test.cpp | 38 +++-- .../com/impl/tracing/skeleton_event_tracing.h | 5 +- 9 files changed, 136 insertions(+), 153 deletions(-) create mode 100644 score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp create mode 100644 score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 05c71ddbc..6a6d78190 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -972,23 +972,36 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( const auto aligned_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); const auto total_data_size = aligned_size * element_properties.number_of_slots; - void* data_storage = storage_resource_->allocate(total_data_size, sample_alignment); + // 1. Construct the Vector Object (Typed Proxy needs this) + auto* vector_ptr = storage_resource_->construct>( + total_data_size, + memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy()) + ); + + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(vector_ptr != nullptr, + "Failed to construct Generic Vector in Shared Memory"); + + // 2. Register the VECTOR OBJECT in the Data Map (for Typed Proxy) + void* vector_obj_void = static_cast(vector_ptr); auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(data_storage)); + std::forward_as_tuple(vector_obj_void)); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second, "Couldn't register/emplace event-storage in data-section."); + // 3. Get the RAW DATA POINTER (The actual buffer inside the vector) + void* raw_data_ptr = static_cast(vector_ptr->data()); const impl::DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; auto inserted_meta_info = storage_->events_metainfo_.emplace( std::piecewise_construct, std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(sample_meta_info, data_storage)); + std::forward_as_tuple(sample_meta_info, raw_data_ptr)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; + return {raw_data_ptr, CreateEventControlComposite(element_fq_id, element_properties)}; } std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( diff --git a/score/mw/com/impl/bindings/mock_binding/BUILD b/score/mw/com/impl/bindings/mock_binding/BUILD index 926b5df59..70714832d 100644 --- a/score/mw/com/impl/bindings/mock_binding/BUILD +++ b/score/mw/com/impl/bindings/mock_binding/BUILD @@ -65,3 +65,15 @@ cc_library( "@score_baselibs//score/language/futurecpp", ], ) + + +cc_library( + name = "sample_allocatee_ptr", + hdrs = ["sample_allocatee_ptr.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/mw/com/impl/plumbing:__pkg__"], + deps = [ + "@score_baselibs//score/language/futurecpp", + ], +) \ No newline at end of file diff --git a/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp new file mode 100644 index 000000000..37464c942 --- /dev/null +++ b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" diff --git a/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h new file mode 100644 index 000000000..63c26004f --- /dev/null +++ b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H +#define SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H + +#include +#include + +namespace score::mw::com::impl::mock_binding +{ + +template +using CustomDeleter = score::cpp::callback; + +/// \brief SampleAllocateePtr used for the mock binding. +/// +/// The SampleAllocateePtr is an alias for a unique_ptr with a custom deleter. +/// This matches the logic used in SamplePtr to support 'void' types safely. +/// +/// @tparam SampleType The data type managed by this pointer. +template +using SampleAllocateePtr = std::unique_ptr>; + +} // namespace score::mw::com::impl::mock_binding + +#endif // SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H \ No newline at end of file diff --git a/score/mw/com/impl/bindings/mock_binding/skeleton_event.h b/score/mw/com/impl/bindings/mock_binding/skeleton_event.h index 7bf6e5e74..e6ac6df1e 100644 --- a/score/mw/com/impl/bindings/mock_binding/skeleton_event.h +++ b/score/mw/com/impl/bindings/mock_binding/skeleton_event.h @@ -43,10 +43,10 @@ class SkeletonEvent : public SkeletonEventBinding (noexcept, override)); MOCK_METHOD(ResultBlank, Send, - (SampleAllocateePtr sample, + (score::mw::com::impl::SampleAllocateePtr sample, score::cpp::optional::SendTraceCallback>), (noexcept, override)); - MOCK_METHOD(Result>, Allocate, (), (noexcept, override)); + MOCK_METHOD(Result>, Allocate, (), (noexcept, override)); MOCK_METHOD(ResultBlank, PrepareOffer, (), (noexcept, override)); MOCK_METHOD(void, PrepareStopOffer, (), (noexcept, override)); MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override)); @@ -73,12 +73,12 @@ class SkeletonEventFacade : public SkeletonEventBinding return skeleton_event_.Send(value, std::move(callback)); }; ResultBlank Send( - SampleAllocateePtr sample, + score::mw::com::impl::SampleAllocateePtr sample, score::cpp::optional::SendTraceCallback> callback) noexcept override { return skeleton_event_.Send(std::move(sample), std::move(callback)); } - Result> Allocate() noexcept override + Result> Allocate() noexcept override { return skeleton_event_.Allocate(); }; diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index b4645f226..9f2f32858 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -541,7 +541,9 @@ cc_library( features = COMPILER_WARNING_FEATURES, tags = ["FFI"], visibility = ["//score/mw/com/impl:__subpackages__"], - deps = ["//score/mw/com/impl/bindings/lola:event"], + deps = ["//score/mw/com/impl/bindings/lola:event", + "//score/mw/com/impl/bindings/mock_binding:sample_allocatee_ptr", + ], ) cc_library( diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index 8205b0de4..8c418bd38 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -14,6 +14,7 @@ #define SCORE_MW_COM_IMPL_PLUMBING_SAMPLE_ALLOCATEE_PTR_H #include "score/mw/com/impl/bindings/lola/sample_allocatee_ptr.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" #include #include @@ -23,6 +24,7 @@ #include #include #include +#include namespace score::mw::com::impl { @@ -94,16 +96,17 @@ class SampleAllocateePtr // coverity[autosar_cpp14_a15_5_3_violation : FALSE] explicit operator bool() const noexcept; - /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will - /// terminate. - // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) - // coverity[autosar_cpp14_a15_5_3_violation : FALSE] - typename std::add_lvalue_reference::type operator*() const noexcept(noexcept(*std::declval())); - - /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will - /// terminate. - // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) - // coverity[autosar_cpp14_a15_5_3_violation : FALSE] + // ------------------------------------------------------------------------- + // [CRITICAL UPDATE] Declarations MUST be templates to support SFINAE + // This allows disabling these operators when SampleType is void. + // ------------------------------------------------------------------------- + + /// \brief operator* - Enabled via SFINAE only if SampleType is NOT void + template ::value, int>::type = 0> + typename std::add_lvalue_reference::type operator*() const noexcept; + + /// \brief operator-> - Enabled via SFINAE only if SampleType is NOT void + template ::value, int>::type = 0> pointer operator->() const noexcept; private: @@ -133,8 +136,8 @@ class SampleAllocateePtr // coverity[autosar_cpp14_a11_3_1_violation] friend class SampleAllocateePtrMutableView; - // We don't use the pimpl idiom because it would require dynamic memory allocation (that we want to avoid) - std::variant, std::unique_ptr> internal_; + // Stores either the LoLa pointer or the Mock Binding pointer (which handles void safely) + std::variant, mock_binding::SampleAllocateePtr> internal_; }; template @@ -172,8 +175,8 @@ void SampleAllocateePtr::reset() noexcept internal_ptr.reset(); }, // coverity[autosar_cpp14_a7_1_7_violation] - [](std::unique_ptr& internal_ptr) noexcept -> void { - internal_ptr.reset(nullptr); + [](mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> void { + internal_ptr.reset(nullptr); }, // coverity[autosar_cpp14_a7_1_7_violation] [](const score::cpp::blank&) noexcept -> void {}); @@ -208,7 +211,7 @@ auto SampleAllocateePtr::Get() const noexcept -> pointer // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return internal_ptr.get(); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -236,7 +239,7 @@ SampleAllocateePtr::operator bool() const noexcept // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> bool { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> bool { return static_cast(internal_ptr); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -248,8 +251,8 @@ SampleAllocateePtr::operator bool() const noexcept } template -typename std::add_lvalue_reference::type SampleAllocateePtr::operator*() const - noexcept(noexcept(*std::declval())) +template ::value, int>::type> +typename std::add_lvalue_reference::type SampleAllocateePtr::operator*() const noexcept { using ReturnType = typename std::add_lvalue_reference::type; @@ -268,7 +271,7 @@ typename std::add_lvalue_reference::type SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return *internal_ptr; }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -280,6 +283,7 @@ typename std::add_lvalue_reference::type SampleAllocateePtr +template ::value, int>::type> auto SampleAllocateePtr::operator->() const noexcept -> pointer { using ReturnType = pointer; @@ -299,7 +303,7 @@ auto SampleAllocateePtr::operator->() const noexcept -> pointer // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return internal_ptr.get(); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -329,7 +333,7 @@ bool operator!=(const SampleAllocateePtr& lhs, const SampleAllocateePtr& template void swap(SampleAllocateePtr& lhs, SampleAllocateePtr& rhs) noexcept { - lhs.swap(rhs); + lhs.Swap(rhs); } /// \brief Helper function to create a SampleAllocateePtr within the middleware (not to be used by the user) @@ -339,108 +343,6 @@ auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr{std::move(ptr)}; } -/// \brief Template specialization of SampleAllocateePtr for void. -template <> -class SampleAllocateePtr -{ - public: - using pointer = void*; - using element_type = void; - - constexpr SampleAllocateePtr() noexcept : SampleAllocateePtr(score::cpp::blank{}) {} - constexpr explicit SampleAllocateePtr(std::nullptr_t) noexcept : SampleAllocateePtr() {} - - SampleAllocateePtr(const SampleAllocateePtr&) = delete; - SampleAllocateePtr& operator=(const SampleAllocateePtr&) & = delete; - - SampleAllocateePtr(SampleAllocateePtr&& other) noexcept : SampleAllocateePtr() - { - this->Swap(other); - } - - SampleAllocateePtr& operator=(SampleAllocateePtr&& other) & noexcept - { - this->Swap(other); - return *this; - } - - SampleAllocateePtr& operator=(std::nullptr_t) noexcept - { - reset(); - return *this; - } - - ~SampleAllocateePtr() noexcept = default; - - void reset() noexcept - { - auto visitor = score::cpp::overload( - [](lola::SampleAllocateePtr& internal_ptr) noexcept -> void { internal_ptr.reset(); }, - [](const score::cpp::blank&) noexcept -> void {}); - std::visit(visitor, internal_); - } - - void Swap(SampleAllocateePtr& other) noexcept - { - using std::swap; - swap(internal_, other.internal_); - } - - pointer Get() const noexcept - { - auto visitor = score::cpp::overload( - [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, - [](const score::cpp::blank&) noexcept -> pointer { return nullptr; }); - return std::visit(visitor, internal_); - } - - explicit operator bool() const noexcept - { - auto visitor = score::cpp::overload( - [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> bool { - return static_cast(internal_ptr); - }, - [](const score::cpp::blank&) noexcept -> bool { return false; }); - return std::visit(visitor, internal_); - } - - // operator* is intentionally omitted for void specialization. - - pointer operator->() const noexcept - { - auto visitor = score::cpp::overload( - [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, - [](const score::cpp::blank&) noexcept -> pointer { - std::terminate(); - return nullptr; - }); - return std::visit(visitor, internal_); - } - - private: - template - constexpr explicit SampleAllocateePtr(T ptr) : internal_{std::move(ptr)} - { - } - - template - friend auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr; - - template - friend class SampleAllocateePtrView; - - template - friend class SampleAllocateePtrMutableView; - - std::variant> internal_; -}; - -template <> -inline void swap(SampleAllocateePtr& lhs, SampleAllocateePtr& rhs) noexcept -{ - lhs.Swap(rhs); -} - /// \brief SampleAllocateePtr is user facing, in order to interact with its internals we provide a view towards it template class SampleAllocateePtrView @@ -456,7 +358,7 @@ class SampleAllocateePtrView return std::get_if(&ptr_.internal_); } - const std::variant, std::unique_ptr>& + const std::variant, mock_binding::SampleAllocateePtr>& GetUnderlyingVariant() const noexcept { return ptr_.internal_; @@ -473,7 +375,7 @@ class SampleAllocateePtrMutableView public: explicit SampleAllocateePtrMutableView(SampleAllocateePtr& ptr) : ptr_{ptr} {} - std::variant, std::unique_ptr>& + std::variant, mock_binding::SampleAllocateePtr>& GetUnderlyingVariant() noexcept { // Suppress "AUTOSAR C++14 A9-3-1", The rule states: "Member functions shall not return non-const “raw” pointers diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp index 87b0190bc..3897446b4 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp @@ -15,6 +15,8 @@ #include "score/mw/com/impl/bindings/lola/control_slot_types.h" #include "score/mw/com/impl/bindings/lola/test_doubles/fake_memory_resource.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" + #include #include @@ -54,7 +56,10 @@ class SampleAllocateePtrFixture : public ::testing::Test lola::SlotIndexType event_data_slot_index_{std::numeric_limits::max()}; lola::SampleAllocateePtr lola_allocatee_ptr_{&value_, event_data_ctrl_, {}}; SampleAllocateePtr valid_unit_{MakeSampleAllocateePtr(std::move(lola_allocatee_ptr_))}; - SampleAllocateePtr unit_with_unique_ptr_{MakeSampleAllocateePtr(std::make_unique(42))}; + + + SampleAllocateePtr unit_with_unique_ptr_{ + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(42)))}; }; TEST_F(SampleAllocateePtrFixture, ConstructFromNullptr) @@ -198,14 +203,6 @@ TEST(SampleAllocateePtrTest, InterfaceMatchesRequirements) // Move assignment operator static_assert(std::is_move_assignable_v>, "Should be move assignable"); - // Dereferences the stored pointer (operator->) - static_assert(std::is_member_function_pointer_v::operator->)>, - "Should contain operator->"); - - // Dereferences the stored pointer (operator*) - static_assert(std::is_member_function_pointer_v::operator*)>, - "Should contain operator*"); - // Checks if the stored pointer is null static_assert(std::is_member_function_pointer_v::operator bool)>, "Should contain operator bool"); @@ -235,7 +232,8 @@ TEST(SampleAllocateePtrTest, NullLolaSampleAllocateePtrConvertsToFalse) TEST(SampleAllocateePtrTest, NullUniquePtrConvertsToFalse) { // Given a unique_ptr which holds a nullptr - std::unique_ptr null_unique_ptr{nullptr}; + + mock_binding::SampleAllocateePtr null_unique_ptr{nullptr}; // When creating an impl::SampleAllocateePtr from the unique_ptr auto ptr = MakeSampleAllocateePtr(std::move(null_unique_ptr)); @@ -266,7 +264,8 @@ TEST_F(SampleAllocateePtrFixture, ValidLolaSampleAllocateePtrConvertsToTrue) TEST_F(SampleAllocateePtrFixture, ValidUniquePtrConvertsToTrue) { // Given a valid unique_ptr - auto valid_unique_ptr = std::make_unique(10); + + mock_binding::SampleAllocateePtr valid_unique_ptr(new std::uint8_t(10)); EXPECT_TRUE(valid_unique_ptr); // When creating an impl::SampleAllocateePtr from the unique_ptr @@ -361,7 +360,7 @@ TEST_F(SampleAllocateePtrFixture, CanResetUnderlyingPointerUsingUniquePtr) // Given a SampleAllocateePtr with an underlying unique_ptr bool is_destructed{false}; SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(std::make_unique(is_destructed))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed)))}; // When calling Reset unit_with_unique_ptr.reset(); @@ -406,7 +405,8 @@ TEST_F(SampleAllocateePtrFixture, CanDereferenceUsingArrowUsingUniquePtr) std::uint8_t bar{}; }; - auto value = std::make_unique(); + + auto value = mock_binding::SampleAllocateePtr(new Foo()); value->bar = 42; const auto unit = MakeSampleAllocateePtr(std::move(value)); @@ -416,11 +416,13 @@ TEST_F(SampleAllocateePtrFixture, CanDereferenceUsingArrowUsingUniquePtr) TEST_F(SampleAllocateePtrFixture, CanWrapUniquePtr) { // Given a SampleAllocateePtr with an underlying unique_ptr - const auto ptr = MakeSampleAllocateePtr(std::make_unique()); + + const auto ptr = MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t())); const auto unit = SampleAllocateePtrView{ptr}; // When trying to read its underlying implementation - const auto* underlying_impl = unit.As>(); + + const auto* underlying_impl = unit.As>(); // This is possible and we can interact with it ASSERT_NE(underlying_impl, nullptr); @@ -430,7 +432,8 @@ TEST_F(SampleAllocateePtrFixture, CanCompareTwoUnequalPtrs) { // Given a valid_unit and a second SampleAllocateePtr pointing to a different value std::uint8_t value{0x43}; - SampleAllocateePtr unit2{MakeSampleAllocateePtr(std::make_unique(value))}; + + SampleAllocateePtr unit2{MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(value)))}; // When testing equality // Then the pointers are considered unequal @@ -479,8 +482,9 @@ TEST(SampleAllocateePtrTest, UnderlyingUniquePtrIsFreedOnDestruction) bool is_destructed{false}; { // Given a SampleAllocateePtr with an underlying unique_ptr + SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(std::make_unique(is_destructed))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed)))}; // The underlying object will not be freed while the SampleAllocateePtr has not been destructed EXPECT_FALSE(is_destructed); diff --git a/score/mw/com/impl/tracing/skeleton_event_tracing.h b/score/mw/com/impl/tracing/skeleton_event_tracing.h index 87f0bf091..c4b21fa2b 100644 --- a/score/mw/com/impl/tracing/skeleton_event_tracing.h +++ b/score/mw/com/impl/tracing/skeleton_event_tracing.h @@ -19,6 +19,7 @@ #include "score/mw/com/impl/bindings/lola/sample_ptr.h" #include "score/mw/com/impl/bindings/lola/transaction_log_set.h" #include "score/mw/com/impl/bindings/mock_binding/sample_ptr.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" #include "score/mw/com/impl/skeleton_event_binding.h" @@ -85,7 +86,7 @@ TracingData ExtractBindingTracingData(const impl::SampleAllocateePtr // Here we can't use a raw pointer / reference since we're using score::cpp::overload, and the function is not // replaceing the managed object, so this should be a const reference. // coverity[autosar_cpp14_a8_4_12_violation] - [](const std::unique_ptr& ptr) -> TracingData { + [](const mock_binding::SampleAllocateePtr& ptr) -> TracingData { return {0U, {ptr.get(), sizeof(SampleType)}}; }, [](const score::cpp::blank&) noexcept -> TracingData { @@ -126,7 +127,7 @@ TypeErasedSamplePtr CreateTypeErasedSamplePtr(impl::SampleAllocateePtr& ptr) -> TypeErasedSamplePtr { + [](mock_binding::SampleAllocateePtr& ptr) -> TypeErasedSamplePtr { impl::tracing::TypeErasedSamplePtr type_erased_sample_ptr{std::make_unique(*ptr)}; return type_erased_sample_ptr; }, From c77993a9ac6a83e7c24d0f4966cde7a7fc3371c6 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Mon, 2 Feb 2026 21:37:47 +0200 Subject: [PATCH 10/18] Fix blank dereference crash in SampleAllocateePtr and missing mock deleters in tests --- .../impl/plumbing/sample_allocatee_ptr_test.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp index 3897446b4..bb35a4af4 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp @@ -59,7 +59,7 @@ class SampleAllocateePtrFixture : public ::testing::Test SampleAllocateePtr unit_with_unique_ptr_{ - MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(42)))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(42), [](std::uint8_t* p) { delete p; } ))}; }; TEST_F(SampleAllocateePtrFixture, ConstructFromNullptr) @@ -265,7 +265,7 @@ TEST_F(SampleAllocateePtrFixture, ValidUniquePtrConvertsToTrue) { // Given a valid unique_ptr - mock_binding::SampleAllocateePtr valid_unique_ptr(new std::uint8_t(10)); + mock_binding::SampleAllocateePtr valid_unique_ptr(new std::uint8_t(10),[](std::uint8_t* p) { delete p; } ); EXPECT_TRUE(valid_unique_ptr); // When creating an impl::SampleAllocateePtr from the unique_ptr @@ -360,7 +360,7 @@ TEST_F(SampleAllocateePtrFixture, CanResetUnderlyingPointerUsingUniquePtr) // Given a SampleAllocateePtr with an underlying unique_ptr bool is_destructed{false}; SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed)))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed),[](ObjectDestructionNotifier* p) { delete p; }))}; // When calling Reset unit_with_unique_ptr.reset(); @@ -406,7 +406,7 @@ TEST_F(SampleAllocateePtrFixture, CanDereferenceUsingArrowUsingUniquePtr) }; - auto value = mock_binding::SampleAllocateePtr(new Foo()); + auto value = mock_binding::SampleAllocateePtr(new Foo(),[](Foo* p) { delete p; }); value->bar = 42; const auto unit = MakeSampleAllocateePtr(std::move(value)); @@ -417,7 +417,7 @@ TEST_F(SampleAllocateePtrFixture, CanWrapUniquePtr) { // Given a SampleAllocateePtr with an underlying unique_ptr - const auto ptr = MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t())); + const auto ptr = MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(),[](std::uint8_t* p) { delete p; })); const auto unit = SampleAllocateePtrView{ptr}; // When trying to read its underlying implementation @@ -433,7 +433,7 @@ TEST_F(SampleAllocateePtrFixture, CanCompareTwoUnequalPtrs) // Given a valid_unit and a second SampleAllocateePtr pointing to a different value std::uint8_t value{0x43}; - SampleAllocateePtr unit2{MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(value)))}; + SampleAllocateePtr unit2{MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(value),[](std::uint8_t* p) { delete p; }))}; // When testing equality // Then the pointers are considered unequal @@ -484,7 +484,7 @@ TEST(SampleAllocateePtrTest, UnderlyingUniquePtrIsFreedOnDestruction) // Given a SampleAllocateePtr with an underlying unique_ptr SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed)))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed),[](ObjectDestructionNotifier* p) { delete p; }))}; // The underlying object will not be freed while the SampleAllocateePtr has not been destructed EXPECT_FALSE(is_destructed); @@ -527,4 +527,4 @@ TEST_F(SampleAllocateePtrFixture, UnderlyingLolaPtrIsFreedOnDestruction) } } // namespace -} // namespace score::mw::com::impl +} // namespace score::mw::com::impl \ No newline at end of file From f26e10a68f8a0ab4e161bb068221f66d7326f5e6 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Sun, 8 Feb 2026 17:40:15 +0200 Subject: [PATCH 11/18] Refactor the Generic Skeleton Event implementation to address all remaining feedback review points and introduce GTest-based unit tests for generic_skeleton_event --- .../ipc_bridge/sample_sender_receiver.cpp | 6 +- score/mw/com/impl/BUILD | 50 ++++ .../bindings/lola/generic_skeleton_event.cpp | 41 +-- .../bindings/lola/generic_skeleton_event.h | 25 +- score/mw/com/impl/bindings/lola/skeleton.cpp | 32 +- .../com/impl/bindings/lola/skeleton_event.h | 92 +++--- .../bindings/lola/skeleton_event_common.cpp | 23 +- .../bindings/lola/skeleton_event_common.h | 57 ++-- .../test/skeleton_event_component_test.cpp | 4 +- score/mw/com/impl/bindings/mock_binding/BUILD | 16 + .../mock_binding/generic_skeleton_event.cpp | 13 + .../mock_binding/generic_skeleton_event.h | 48 +++ score/mw/com/impl/generic_skeleton.cpp | 68 ++++- score/mw/com/impl/generic_skeleton.h | 8 +- score/mw/com/impl/generic_skeleton_event.cpp | 14 +- score/mw/com/impl/generic_skeleton_event.h | 9 +- .../com/impl/generic_skeleton_event_test.cpp | 225 ++++++++++++++ score/mw/com/impl/generic_skeleton_test.cpp | 275 ++++++++++++++---- ...i_generic_skeleton_event_binding_factory.h | 37 +++ score/mw/com/impl/plumbing/BUILD | 43 ++- .../generic_skeleton_event_binding_factory.h | 33 ++- ...ic_skeleton_event_binding_factory_impl.cpp | 31 ++ ...eric_skeleton_event_binding_factory_impl.h | 32 ++ ...ic_skeleton_event_binding_factory_mock.cpp | 13 + ...eric_skeleton_event_binding_factory_mock.h | 32 ++ .../com/impl/plumbing/sample_allocatee_ptr.h | 11 +- ...ton_service_element_binding_factory_impl.h | 107 +++---- score/mw/com/impl/skeleton_base.cpp | 14 +- score/mw/com/impl/skeleton_base.h | 10 +- .../dummy_instance_identifier_builder.cpp | 10 + 30 files changed, 1086 insertions(+), 293 deletions(-) create mode 100644 score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp create mode 100644 score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h create mode 100644 score/mw/com/impl/generic_skeleton_event_test.cpp create mode 100644 score/mw/com/impl/i_generic_skeleton_event_binding_factory.h create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp create mode 100644 score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index 8c54cd7e6..1251a7cd4 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -507,7 +507,7 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec // Retrieve event using its name auto event_it = skeleton.GetEvents().find(event_name); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_it != skeleton.GetEvents().cend(), "Event not found in GenericSkeleton"); - auto& event = event_it->second; + auto* event_ptr = event_it->second; const auto offer_result = skeleton.OfferService(); if (!offer_result.has_value()) @@ -519,7 +519,7 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec for (std::size_t cycle = 0U; cycle < num_cycles || num_cycles == 0U; ++cycle) { - auto sample_result = PrepareMapLaneSample(event, cycle); + auto sample_result = PrepareMapLaneSample(*event_ptr, cycle); if (!sample_result.has_value()) { std::cerr << "No sample received. Exiting.\n"; @@ -529,7 +529,7 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec { std::lock_guard lock{event_sending_mutex_}; - event.Send(std::move(sample)); + event_ptr->Send(std::move(sample)); event_published_ = true; } std::this_thread::sleep_for(cycle_time); diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 03873000e..dd2a659f5 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -750,6 +750,20 @@ cc_library( ], ) +cc_library( + name = "i_generic_skeleton_event_binding_factory", + hdrs = ["i_generic_skeleton_event_binding_factory.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + ":generic_skeleton_event_binding", + ":skeleton_base", + ], +) + cc_library( name = "event_receive_handler", srcs = ["event_receive_handler.cpp"], @@ -1059,6 +1073,40 @@ cc_gtest_unit_test( ], ) +cc_gtest_unit_test( + name = "generic_skeleton_test", + srcs = ["generic_skeleton_test.cpp"], + deps = [ + ":generic_skeleton", + ":runtime_mock", + ":service_discovery_mock", + "//score/mw/com/impl/bindings/mock_binding:generic_skeleton_event", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory_mock", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory", + ":service_discovery_client_mock", + "//score/mw/com/impl/test:dummy_instance_identifier_builder", + "//score/mw/com/impl/test:binding_factory_resources", + "//score/mw/com/impl/test:runtime_mock_guard", + ], +) + +cc_gtest_unit_test( + name = "generic_skeleton_event_test", + srcs = ["generic_skeleton_event_test.cpp", + "service_discovery_client_mock.h"], + deps = [ + ":generic_skeleton", + ":generic_skeleton_event", + ":service_discovery_mock", + "//score/mw/com/impl/bindings/mock_binding:generic_skeleton_event", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory_mock", + "//score/mw/com/impl/test:binding_factory_resources", + "//score/mw/com/impl/test:dummy_instance_identifier_builder", + "//score/mw/com/impl/test:runtime_mock_guard", + ], +) + cc_gtest_unit_test( name = "traits_test", srcs = [ @@ -1144,6 +1192,8 @@ cc_unit_test_suites_for_host_and_qnx( cc_unit_tests = [ ":flag_owner_test", ":find_service_handle_test", + ":generic_skeleton_test", + ":generic_skeleton_event_test", ":generic_proxy_test", ":proxy_field_test", ":runtime_test", diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index 9db27121a..9690618e3 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -20,20 +20,25 @@ namespace score::mw::com::impl::lola { GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, - const SkeletonEventProperties& event_properties, - const ElementFqId& event_fqn, - const DataTypeMetaInfo& size_info, - impl::tracing::SkeletonEventTracingData tracing_data) - : size_info_(size_info), event_properties_(event_properties), - common_event_logic_(parent, event_fqn, control_, current_timestamp_, tracing_data) + const ElementFqId& event_fqn, + std::string_view event_name, + const SkeletonEventProperties& event_properties, + const DataTypeMetaInfo& size_info, + impl::tracing::SkeletonEventTracingData tracing_data) + : size_info_(size_info), + event_properties_(event_properties), + event_shared_impl_(parent, event_fqn, control_, current_timestamp_, tracing_data) { + // The factory passes the name, but we don't store it in this class currently. + // Casting to void suppresses "unused parameter" compiler warnings. + static_cast(event_name); } ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept { std::tie(data_storage_, control_) = - GetParent().RegisterGeneric(GetElementFQId(), event_properties_, size_info_.size, size_info_.alignment); - common_event_logic_.PrepareOfferCommon(); + event_shared_impl_.GetParent().RegisterGeneric(event_shared_impl_.GetElementFQId(), event_properties_, size_info_.size, size_info_.alignment); + event_shared_impl_.PrepareOfferCommon(); return {}; } @@ -52,17 +57,19 @@ Result GenericSkeletonEvent::Send(score::mw::com::impl::SampleAllo // This avoids the expensive lock operation in the common case where no handlers are registered. // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent // handler registration, the next Send() will pick it up. - if (IsQmRegistered() && !qm_disconnect_) + + + if (event_shared_impl_.IsQmNotificationsRegistered() && !qm_disconnect_) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_QM, GetElementFQId()); + .NotifyEvent(QualityType::kASIL_QM, event_shared_impl_.GetElementFQId()); } - if (IsAsilBRegistered() && GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + if (event_shared_impl_.IsAsilBNotificationsRegistered() && event_shared_impl_.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_B, GetElementFQId()); + .NotifyEvent(QualityType::kASIL_B, event_shared_impl_.GetElementFQId()); } return {}; @@ -82,8 +89,8 @@ Result> GenericSkeletonEvent::All qm_disconnect_ = true; score::mw::log::LogWarn("lola") << __func__ << __LINE__ - << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << GetElementFQId(); - GetParent().DisconnectQmConsumers(); + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_shared_impl_.GetElementFQId(); + event_shared_impl_.GetParent().DisconnectQmConsumers(); } if (slot.IsValidQM() || slot.IsValidAsilB()) @@ -93,7 +100,6 @@ Result> GenericSkeletonEvent::All const auto aligned_size = memory::shared::CalculateAlignedSize(size_info_.size, size_info_.alignment); std::uint8_t* byte_ptr = static_cast(base_ptr); - //std::uint64_t offset = static_cast(slot.GetIndex()) * size_info_.size; std::uint64_t offset = static_cast(slot.GetIndex()) * aligned_size; void* data_ptr = byte_ptr + offset; @@ -119,7 +125,9 @@ std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept void GenericSkeletonEvent::PrepareStopOffer() noexcept { - common_event_logic_.PrepareStopOfferCommon(); + event_shared_impl_.PrepareStopOfferCommon(); + control_.reset(); + data_storage_ = nullptr; } BindingType GenericSkeletonEvent::GetBindingType() const noexcept @@ -127,6 +135,5 @@ BindingType GenericSkeletonEvent::GetBindingType() const noexcept return BindingType::kLoLa; } -// SetSkeletonEventTracingData is now handled by common_event_logic_ } // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index d59a1b181..120574fc3 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -19,6 +19,8 @@ #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" +#include + namespace score::mw::com::impl::lola { @@ -29,9 +31,11 @@ class Skeleton; class GenericSkeletonEvent : public GenericSkeletonEventBinding { public: + GenericSkeletonEvent(Skeleton& parent, - const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn, + std::string_view event_name, + const SkeletonEventProperties& event_properties, const DataTypeMetaInfo& size_info, impl::tracing::SkeletonEventTracingData tracing_data = {}); @@ -47,7 +51,7 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding BindingType GetBindingType() const noexcept override; void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override { - common_event_logic_.GetTracingData() = tracing_data; + event_shared_impl_.GetTracingData() = tracing_data; } std::size_t GetMaxSize() const noexcept override @@ -59,25 +63,12 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding DataTypeMetaInfo size_info_; const SkeletonEventProperties event_properties_; score::cpp::optional control_{}; - EventSlotStatus::EventTimeStamp current_timestamp_{0U}; + EventSlotStatus::EventTimeStamp current_timestamp_{1U}; score::memory::shared::OffsetPtr data_storage_{nullptr}; bool qm_disconnect_{false}; - SkeletonEventCommon common_event_logic_; // Aggregated common logic + SkeletonEventCommon event_shared_impl_; - // This method is needed by Allocate() and Send() - Skeleton& GetParent() { return common_event_logic_.GetParent(); } - const ElementFqId& GetElementFQId() const { return common_event_logic_.GetElementFQId(); } - bool IsQmRegistered() const { return common_event_logic_.IsQmRegistered(); } - bool IsAsilBRegistered() const { return common_event_logic_.IsAsilBRegistered(); } - - void ResetGuards() noexcept - { - common_event_logic_.ResetGuards(); - // Reset members specific to GenericSkeletonEvent - control_.reset(); // This was part of the original ResetGuards in GenericSkeletonEvent - data_storage_ = nullptr; - } }; } // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 6a6d78190..ed52b1e3c 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -112,22 +112,6 @@ ServiceDataStorage* GetServiceDataStorageSkeletonSide(const memory::shared::Mana return service_data_storage; } -std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( - const LolaServiceInstanceId::InstanceId lola_instance_id, - const IPartialRestartPathBuilder& partial_restart_path_builder) -{ - auto service_instance_existence_marker_file_path = - partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); - - // The instance existence marker file can be opened in the case that another skeleton of the same service currently - // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try - // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can - // exclusively flock the file. - bool take_ownership{false}; - return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), - take_ownership); -} - enum class ShmObjectType : std::uint8_t { kControl_QM = 0x00, @@ -160,6 +144,22 @@ bool CreatePartialRestartDirectory(const score::filesystem::Filesystem& filesyst return true; } +std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( + const LolaServiceInstanceId::InstanceId lola_instance_id, + const IPartialRestartPathBuilder& partial_restart_path_builder) +{ + auto service_instance_existence_marker_file_path = + partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); + + // The instance existence marker file can be opened in the case that another skeleton of the same service currently + // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try + // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can + // exclusively flock the file. + bool take_ownership{false}; + return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), + take_ownership); +} + std::optional CreateOrOpenServiceInstanceUsageMarkerFile( const LolaServiceInstanceId::InstanceId lola_instance_id, const IPartialRestartPathBuilder& partial_restart_path_builder) diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h index 95aaab35d..7f4efa235 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event.h @@ -27,11 +27,22 @@ #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_binding.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" #include "score/mw/log/logging.h" #include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace score::mw::com::impl::lola { @@ -53,11 +64,23 @@ class SkeletonEvent final : public SkeletonEventBinding // coverity[autosar_cpp14_a11_3_1_violation] friend class SkeletonEventAttorney; - + public: + using typename SkeletonEventBinding::SendTraceCallback; + using typename SkeletonEventBindingBase::SubscribeTraceCallback; + using typename SkeletonEventBindingBase::UnsubscribeTraceCallback; + + SkeletonEvent(Skeleton& parent, + const ElementFqId event_fqn, + const std::string_view event_name, + const SkeletonEventProperties properties, + impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data = {}) noexcept; + SkeletonEvent(const SkeletonEvent&) = delete; + SkeletonEvent(SkeletonEvent&&) noexcept = delete; + SkeletonEvent& operator=(const SkeletonEvent&) & = delete; + SkeletonEvent& operator=(SkeletonEvent&&) & noexcept = delete; - public: // Public API from SkeletonEventBinding - using typename SkeletonEventBinding::SendTraceCallback; + ~SkeletonEvent() override = default; /// \brief Sends a value by _copy_ towards a consumer. It will allocate the necessary space and then copy the value /// into Shared Memory. @@ -73,21 +96,18 @@ class SkeletonEvent final : public SkeletonEventBinding void PrepareStopOffer() noexcept override; - BindingType GetBindingType() const noexcept override { return BindingType::kLoLa; } - void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override { common_event_logic_.GetTracingData() = tracing_data; } - ~SkeletonEvent() override = default; - SkeletonEvent(Skeleton& parent, - const ElementFqId event_fqn, - const std::string_view event_name, - const SkeletonEventProperties properties, - impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data = {}) noexcept; + BindingType GetBindingType() const noexcept override + { + return BindingType::kLoLa; + } - SkeletonEvent(const SkeletonEvent&) = delete; - SkeletonEvent(SkeletonEvent&&) noexcept = delete; - SkeletonEvent& operator=(const SkeletonEvent&) & = delete; - SkeletonEvent& operator=(SkeletonEvent&&) & noexcept = delete; - - private: // Private members + void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override + { + event_shared_impl_.GetTracingData() = tracing_data; + } + + + private: const std::string_view event_name_; const SkeletonEventProperties event_properties_; EventDataStorage* event_data_storage_; @@ -95,17 +115,8 @@ class SkeletonEvent final : public SkeletonEventBinding EventSlotStatus::EventTimeStamp current_timestamp_; bool qm_disconnect_; - SkeletonEventCommon common_event_logic_; // Aggregated common logic + SkeletonEventCommon event_shared_impl_; - Skeleton& GetParent() { return common_event_logic_.GetParent(); } - const ElementFqId& GetElementFQId() const { return common_event_logic_.GetElementFQId(); } - bool IsQmRegistered() const { return common_event_logic_.IsQmRegistered(); } - bool IsAsilBRegistered() const { return common_event_logic_.IsAsilBRegistered(); } - - void ResetGuards() noexcept - { - common_event_logic_.ResetGuards(); - } }; template @@ -113,7 +124,7 @@ SkeletonEvent::SkeletonEvent(Skeleton& parent, const ElementFqId event_fqn, const std::string_view event_name, const SkeletonEventProperties properties, - impl::tracing::SkeletonEventTracingData tracing_data) noexcept + impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data) noexcept : SkeletonEventBinding{}, event_name_{event_name}, event_properties_{properties}, @@ -121,7 +132,7 @@ SkeletonEvent::SkeletonEvent(Skeleton& parent, event_data_control_composite_{score::cpp::nullopt}, current_timestamp_{1U}, qm_disconnect_{false}, - common_event_logic_(parent, event_fqn, event_data_control_composite_, current_timestamp_, tracing_data) + event_shared_impl_(parent, event_fqn, event_data_control_composite_, current_timestamp_, skeleton_event_tracing_data) { } @@ -172,17 +183,17 @@ ResultBlank SkeletonEvent::Send(impl::SampleAllocateePtr // This avoids the expensive lock operation in the common case where no handlers are registered. // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent // handler registration, the next Send() will pick it up. - if (IsQmRegistered() && !qm_disconnect_) + if (event_shared_impl_.IsQmNotificationsRegistered() && !qm_disconnect_) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_QM, GetElementFQId()); + .NotifyEvent(QualityType::kASIL_QM, event_shared_impl_.GetElementFQId()); } - if (IsAsilBRegistered() && GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + if (event_shared_impl_.IsAsilBNotificationsRegistered() && event_shared_impl_.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) { GetBindingRuntime(BindingType::kLoLa) .GetLolaMessaging() - .NotifyEvent(QualityType::kASIL_B, GetElementFQId()); + .NotifyEvent(QualityType::kASIL_B, event_shared_impl_.GetElementFQId()); } return {}; } @@ -209,9 +220,10 @@ Result> SkeletonEvent::Allocate if (!qm_disconnect_ && event_data_control_composite_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM()) { qm_disconnect_ = true; - score::mw::log::LogWarn("lola") << __func__ << __LINE__ - << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << GetElementFQId(); - GetParent().DisconnectQmConsumers(); + score::mw::log::LogWarn("lola") + << __func__ << __LINE__ + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_shared_impl_.GetElementFQId(); + event_shared_impl_.GetParent().DisconnectQmConsumers(); } if (slot.IsValidQM() || slot.IsValidAsilB()) @@ -243,12 +255,14 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] ResultBlank SkeletonEvent::PrepareOffer() noexcept { - std::tie(event_data_storage_, event_data_control_composite_) = GetParent().template Register(GetElementFQId(), event_properties_); + + std::tie(event_data_storage_, event_data_control_composite_) = + event_shared_impl_.GetParent().template Register(event_shared_impl_.GetElementFQId(), event_properties_); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_.has_value(), "Defensive programming as event_data_control_composite_ is set by Register above."); - common_event_logic_.PrepareOfferCommon(); + event_shared_impl_.PrepareOfferCommon(); return {}; } @@ -262,7 +276,7 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] void SkeletonEvent::PrepareStopOffer() noexcept { - common_event_logic_.PrepareStopOfferCommon(); + event_shared_impl_.PrepareStopOfferCommon(); } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp index bc24fb685..e8979aada 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp @@ -13,6 +13,7 @@ #include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" #include "score/mw/com/impl/bindings/lola/i_runtime.h" // For GetBindingRuntime #include "score/mw/com/impl/bindings/lola/messaging/i_message_passing_service.h" // For RegisterEventNotificationExistenceChangedCallback +#include "score/mw/com/impl/bindings/lola/skeleton.h" namespace score::mw::com::impl::lola { @@ -30,7 +31,7 @@ SkeletonEventCommon::SkeletonEventCommon(Skeleton& parent, { } -ResultBlank SkeletonEventCommon::PrepareOfferCommon() noexcept +void SkeletonEventCommon::PrepareOfferCommon() noexcept { const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); @@ -41,6 +42,10 @@ ResultBlank SkeletonEventCommon::PrepareOfferCommon() noexcept const bool tracing_for_skeleton_event_enabled = tracing_data_.enable_send || tracing_data_.enable_send_with_allocate; + // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in + // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code + // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in + // Ticket-188259). if (tracing_for_skeleton_event_enabled) { EmplaceTransactionLogRegistrationGuard(); @@ -70,7 +75,7 @@ ResultBlank SkeletonEventCommon::PrepareOfferCommon() noexcept SetAsilBNotificationsRegistered(has_handlers); }); } - return {}; + } void SkeletonEventCommon::PrepareStopOfferCommon() noexcept @@ -131,18 +136,16 @@ void SkeletonEventCommon::ResetGuards() noexcept } } -bool SkeletonEventCommon::IsQmRegistered() const noexcept +bool SkeletonEventCommon::IsQmNotificationsRegistered() const noexcept { - // Using memory_order_relaxed is safe here as this is an optimisation. If we miss a very recent - // handler registration, the next Send() will pick it up. - return qm_event_update_notifications_registered_.load(std::memory_order_relaxed); + + return qm_event_update_notifications_registered_.load(); } -bool SkeletonEventCommon::IsAsilBRegistered() const noexcept +bool SkeletonEventCommon::IsAsilBNotificationsRegistered() const noexcept { - // Using memory_order_relaxed is safe here as this is an optimisation. If we miss a very recent - // handler registration, the next Send() will pick it up. - return asil_b_event_update_notifications_registered_.load(std::memory_order_relaxed); + + return asil_b_event_update_notifications_registered_.load(); } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.h b/score/mw/com/impl/bindings/lola/skeleton_event_common.h index 843599862..0772b03f1 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_common.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h @@ -12,11 +12,8 @@ ********************************************************************************/ #pragma once -#include "score/mw/com/impl/binding_type.h" + #include "score/mw/com/impl/bindings/lola/element_fq_id.h" -#include "score/mw/com/impl/bindings/lola/event_data_control_composite.h" -#include "score/mw/com/impl/bindings/lola/event_slot_status.h" -#include "score/mw/com/impl/bindings/lola/skeleton.h" #include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" #include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" #include "score/mw/com/impl/runtime.h" @@ -34,11 +31,21 @@ namespace score::mw::com::impl::lola { -class Skeleton; // Forward declaration +template +class SkeletonEventAttorney; + +class Skeleton; /// @brief Common implementation for LoLa skeleton events, shared between SkeletonEvent and GenericSkeletonEvent. class SkeletonEventCommon { + // Grant friendship to allow access to private helpers + // The "SkeletonEventAttorney" class is a helper, which sets the internal state of "SkeletonEventCommon" accessing + // private members and used for testing purposes only. + + template + friend class SkeletonEventAttorney; + public: SkeletonEventCommon(Skeleton& parent, const ElementFqId& event_fqn, @@ -46,7 +53,7 @@ class SkeletonEventCommon EventSlotStatus::EventTimeStamp& current_timestamp_ref, impl::tracing::SkeletonEventTracingData tracing_data = {}) noexcept; - // No copy, no move + SkeletonEventCommon(const SkeletonEventCommon&) = delete; SkeletonEventCommon(SkeletonEventCommon&&) noexcept = delete; SkeletonEventCommon& operator=(const SkeletonEventCommon&) & = delete; @@ -54,7 +61,7 @@ class SkeletonEventCommon ~SkeletonEventCommon() = default; - ResultBlank PrepareOfferCommon() noexcept; + void PrepareOfferCommon() noexcept; void PrepareStopOfferCommon() noexcept; // Accessors for members used by PrepareOfferCommon/PrepareStopOfferCommon @@ -62,16 +69,10 @@ class SkeletonEventCommon const ElementFqId& GetElementFQId() const { return event_fqn_; } Skeleton& GetParent() { return parent_; } - void EmplaceTransactionLogRegistrationGuard(); - void EmplaceTypeErasedSamplePtrsGuard(); - void UpdateCurrentTimestamp(); - void SetQmNotificationsRegistered(bool value); - void SetAsilBNotificationsRegistered(bool value); - void ResetGuards() noexcept; - // Accessors for atomic flags for derived classes' Send() method - bool IsQmRegistered() const noexcept; - bool IsAsilBRegistered() const noexcept; + bool IsQmNotificationsRegistered() const noexcept; + bool IsAsilBNotificationsRegistered() const noexcept; + private: Skeleton& parent_; @@ -79,11 +80,31 @@ class SkeletonEventCommon score::cpp::optional& event_data_control_composite_ref_; // Reference to the optional in derived class EventSlotStatus::EventTimeStamp& current_timestamp_ref_; // Reference to the timestamp in derived class impl::tracing::SkeletonEventTracingData tracing_data_; - + + /// \brief Atomic flags indicating whether any receive handlers are currently registered for this event + /// at each quality level (QM and ASIL-B). + /// \details These flags are updated via callbacks from MessagePassingServiceInstance when handler + /// registration status changes. They allow Send() to skip the NotifyEvent() call when no + /// handlers are registered for a specific quality level, avoiding unnecessary lock overhead + /// in the main path. Uses memory_order_relaxed as the flags are optimisation hints - false + /// positives (thinking handlers exist when they don't) are harmless, and false negatives + /// (missing handlers) are prevented by the callback mechanism. std::atomic qm_event_update_notifications_registered_{false}; std::atomic asil_b_event_update_notifications_registered_{false}; - std::optional transaction_log_registration_guard_{}; + + /// \brief optional RAII guards for tracing transaction log registration/un-registration and cleanup of "pending" + /// type erased sample pointers which are created in PrepareOfferCommon() and destroyed in PrepareStopOfferCommon() - optional + /// as only needed when tracing is enabled and when they haven't been cleaned up via a call to PrepareStopOfferCommon(). + std::optional transaction_log_registration_guard_{}; std::optional type_erased_sample_ptrs_guard_{}; + + void EmplaceTransactionLogRegistrationGuard(); + void EmplaceTypeErasedSamplePtrsGuard(); + void UpdateCurrentTimestamp(); + void SetQmNotificationsRegistered(bool value); + void SetAsilBNotificationsRegistered(bool value); + void ResetGuards() noexcept; + }; } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp index 982a658fd..a5d49ca06 100644 --- a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp +++ b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp @@ -56,8 +56,8 @@ class SkeletonEventAttorney /// \brief Set handler availability flags for testing purposes void SetHandlerAvailability(bool qm_available, bool asil_b_available) { - skeleton_event_.common_event_logic_.SetQmNotificationsRegistered(qm_available); - skeleton_event_.common_event_logic_.SetAsilBNotificationsRegistered(asil_b_available); + skeleton_event_.event_shared_impl_.SetQmNotificationsRegistered(qm_available); + skeleton_event_.event_shared_impl_.SetAsilBNotificationsRegistered(asil_b_available); } private: diff --git a/score/mw/com/impl/bindings/mock_binding/BUILD b/score/mw/com/impl/bindings/mock_binding/BUILD index 70714832d..a1eb362cc 100644 --- a/score/mw/com/impl/bindings/mock_binding/BUILD +++ b/score/mw/com/impl/bindings/mock_binding/BUILD @@ -18,6 +18,7 @@ cc_library( testonly = True, srcs = [ "generic_proxy_event.cpp", + "generic_skeleton_event.cpp", "proxy.cpp", "proxy_event.cpp", "proxy_method.cpp", @@ -27,6 +28,7 @@ cc_library( ], hdrs = [ "generic_proxy_event.h", + "generic_skeleton_event.h", "proxy.h", "proxy_event.h", "proxy_method.h", @@ -41,6 +43,7 @@ cc_library( ], deps = [ ":sample_ptr", + "//score/mw/com/impl:generic_skeleton_event_binding", "//score/mw/com/impl:generic_proxy_event_binding", "//score/mw/com/impl:proxy_binding", "//score/mw/com/impl:proxy_event_binding", @@ -54,6 +57,19 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event", + testonly = True, + srcs = ["generic_skeleton_event.cpp"], + hdrs = ["generic_skeleton_event.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//visibility:public"], + deps = [ + "//score/mw/com/impl:generic_skeleton_event_binding", + "@googletest//:gtest", + ], +) + cc_library( name = "sample_ptr", srcs = ["sample_ptr.cpp"], diff --git a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp new file mode 100644 index 000000000..89b8190a0 --- /dev/null +++ b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h" \ No newline at end of file diff --git a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h new file mode 100644 index 000000000..e7ba1f37b --- /dev/null +++ b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H +#define SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" + +#include + +namespace score::mw::com::impl::mock_binding +{ + +class GenericSkeletonEvent : public GenericSkeletonEventBinding +{ + public: + // Use explicit 'score::mw::com::impl::SampleAllocateePtr' to avoid ambiguity + // with the 'mock_binding::SampleAllocateePtr' alias (which is a unique_ptr). + + MOCK_METHOD(Result, Send, + (score::mw::com::impl::SampleAllocateePtr), + (noexcept, override)); + + MOCK_METHOD(Result>, Allocate, + (), + (noexcept, override)); + + MOCK_METHOD((std::pair), GetSizeInfo, (), (const, noexcept, override)); + MOCK_METHOD(ResultBlank, PrepareOffer, (), (noexcept, override)); + MOCK_METHOD(void, PrepareStopOffer, (), (noexcept, override)); + MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override)); + MOCK_METHOD(void, SetSkeletonEventTracingData, (impl::tracing::SkeletonEventTracingData), (noexcept, override)); + MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override)); +}; + +} // namespace score::mw::com::impl::mock_binding + +#endif // SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp index 67806ccff..79b0719b3 100644 --- a/score/mw/com/impl/generic_skeleton.cpp +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -19,12 +19,39 @@ #include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" #include "score/mw/com/impl/skeleton_binding.h" +#include + #include #include namespace score::mw::com::impl { +namespace +{ +// Helper to fetch the stable event name from the Configuration +std::string_view GetEventName(const InstanceIdentifier& identifier, std::string_view search_name) +{ + const auto& service_type_deployment = InstanceIdentifierView{identifier}.GetServiceTypeDeployment(); + + auto visitor = score::cpp::overload( + [&](const LolaServiceTypeDeployment& deployment) -> std::string_view { + const auto it = deployment.events_.find(std::string{search_name}); + if (it != deployment.events_.end()) + { + return it->first; // Return the stable address of the Key from the Config Map + } + return {}; + }, + [](const score::cpp::blank&) noexcept -> std::string_view { + return {}; + } + ); + + return std::visit(visitor, service_type_deployment.binding_info_); +} +} // namespace + Result GenericSkeleton::Create( const InstanceSpecifier& specifier, const GenericSkeletonCreateParams& in, @@ -57,9 +84,25 @@ Result GenericSkeleton::Create( // 1. Create the Skeleton (Private Constructor) GenericSkeleton skeleton(identifier, std::move(binding), mode); - // 3. Atomically create all events + // 2. Atomically create all events for (const auto& info : in.events) // Iterate directly over in.events { + // Check for duplicates before creating the binding to prevent unnecessary work and potential memory issues. + if (skeleton.events_.find(info.name) != skeleton.events_.cend()) + { + score::mw::log::LogError("GenericSkeleton") << "Duplicate event name provided: " << info.name; + return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); + } + + // 1. Fetch the STABLE Name from Configuration + std::string_view stable_name = GetEventName(identifier, info.name); + + if (stable_name.empty()) + { + score::mw::log::LogError("GenericSkeleton") << "Event name not found in configuration: " << info.name; + return MakeUnexpected(ComErrc::kBindingFailure); + } + // Create the event binding auto event_binding_result = GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); @@ -70,16 +113,16 @@ Result GenericSkeleton::Create( return MakeUnexpected(ComErrc::kBindingFailure); } - // Store the event in the map - const auto emplace_result = skeleton.events_.emplace( - std::piecewise_construct, - std::forward_as_tuple(info.name), - std::forward_as_tuple(skeleton, info.name, std::move(event_binding_result).value())); + // 2. Create object in Vector (Ownership & Stability) + // Pass stable_name to constructor + skeleton.owned_events_.push_back(std::make_unique( + skeleton, stable_name, std::move(event_binding_result).value())); - if (!emplace_result.second) { - score::mw::log::LogError("GenericSkeleton") << "Failed to emplace GenericSkeletonEvent for name: " << info.name; - return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); - } + // 3. Get Pointer to the stable object + auto* event_ptr = skeleton.owned_events_.back().get(); + + // 4. Store Pointer in Map using the stable_name retrieved from config + skeleton.events_.emplace(stable_name, event_ptr); } // // 4. Atomically create all fields @@ -115,11 +158,6 @@ const GenericSkeleton::EventMap& GenericSkeleton::GetEvents() const noexcept return events_; } -GenericSkeleton::EventMap& GenericSkeleton::GetEvents() noexcept -{ - return events_; -} - // const GenericSkeleton::FieldMap& GenericSkeleton::GetFields() const noexcept // { // return fields_; diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index 232a2d41b..5aead0651 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -26,7 +26,7 @@ #include #include #include - +#include namespace score::mw::com::impl { @@ -62,7 +62,7 @@ struct GenericSkeletonCreateParams class GenericSkeleton : public SkeletonBase { public: - using EventMap = ServiceElementMap; + using EventMap = ServiceElementMap; // using FieldMap = ServiceElementMap; // commented out as field not implemented /// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically. /// @@ -92,9 +92,6 @@ class GenericSkeleton : public SkeletonBase /// @note The returned reference is valid as long as the GenericSkeleton lives. [[nodiscard]] const EventMap& GetEvents() const noexcept; -/// @brief Returns a reference to the name-keyed map of events. -/// @note The returned reference is valid as long as the GenericSkeleton lives. -[[nodiscard]] EventMap& GetEvents() noexcept; /// @brief Returns a const reference to the name-keyed map of fields. /// @note The returned reference is valid as long as the GenericSkeleton lives. @@ -110,6 +107,7 @@ void StopOfferService() noexcept; // Private constructor, only callable by static Create methods. GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding, MethodCallProcessingMode mode); + std::vector> owned_events_; /// @brief This map owns all GenericSkeletonEvent instances. EventMap events_; diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index e0b1576ba..59d93a4d8 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -14,6 +14,7 @@ #include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" #include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/com_error.h" #include @@ -31,10 +32,14 @@ GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, { const SkeletonBaseView skeleton_base_view{skeleton_base}; const auto& instance_identifier = skeleton_base_view.GetAssociatedInstanceIdentifier(); - const auto binding_type = binding_->GetBindingType(); - auto tracing_data = - tracing::GenerateSkeletonTracingStructFromEventConfig(instance_identifier, binding_type, event_name); - binding_->SetSkeletonEventTracingData(tracing_data); + auto* const binding_ptr = static_cast(binding_.get()); + if (binding_ptr) + { + const auto binding_type = binding_ptr->GetBindingType(); + auto tracing_data = + tracing::GenerateSkeletonTracingStructFromEventConfig(instance_identifier, binding_type, event_name); + binding_ptr->SetSkeletonEventTracingData(tracing_data); + } } } @@ -88,6 +93,7 @@ Result> GenericSkeletonEvent::Allocate() noexcept DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept { const auto* const binding = static_cast(binding_.get()); + if (!binding) return {}; const auto size_info_pair = binding->GetSizeInfo(); return {size_info_pair.first, size_info_pair.second}; } diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h index e6e592bc5..c20d0d953 100644 --- a/score/mw/com/impl/generic_skeleton_event.h +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -14,16 +14,16 @@ #include "score/mw/com/impl/skeleton_event_base.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" - #include "score/result/result.h" -#include "score/mw/com/impl/data_type_meta_info.h" // For DataTypeMetaInfo +#include "score/mw/com/impl/data_type_meta_info.h" + +#include namespace score::mw::com::impl { class GenericSkeletonEventBinding; - class GenericSkeletonEvent : public SkeletonEventBase { public: @@ -31,13 +31,10 @@ class GenericSkeletonEvent : public SkeletonEventBase const std::string_view event_name, std::unique_ptr binding); - Result Send(SampleAllocateePtr sample) noexcept; - Result> Allocate() noexcept; - DataTypeMetaInfo GetSizeInfo() const noexcept; }; diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp new file mode 100644 index 000000000..aaa01a924 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event_test.cpp @@ -0,0 +1,225 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/generic_skeleton_event.h" + +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h" +#include "score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h" +#include "score/mw/com/impl/bindings/mock_binding/skeleton.h" +#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" + +#include "score/mw/com/impl/test/binding_factory_resources.h" +#include "score/mw/com/impl/i_binding_runtime.h" +#include "score/mw/com/impl/service_discovery_client_mock.h" +#include "score/mw/com/impl/test/runtime_mock_guard.h" +#include "score/mw/com/impl/runtime_mock.h" +#include "score/mw/com/impl/service_discovery_mock.h" +#include "score/mw/com/impl/com_error.h" + +#include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" +#include +#include + +#include +#include + +namespace score::mw::com::impl { +namespace { + +using ::testing::_; +using ::testing::ByMove; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::StrictMock; +using ::testing::NiceMock; +using ::testing::Invoke; +using ::testing::Field; +using ::testing::AllOf; + +// --- Helper Mocks --- +class IBindingRuntimeMock : public IBindingRuntime { +public: + MOCK_METHOD(IServiceDiscoveryClient&, GetServiceDiscoveryClient, (), (noexcept, override)); + MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override)); + MOCK_METHOD(tracing::IBindingTracingRuntime*, GetTracingRuntime, (), (noexcept, override)); +}; + +class GenericSkeletonEventTest : public ::testing::Test { +public: + GenericSkeletonEventTest() { + GenericSkeletonEventBindingFactory::mock_ = &generic_event_binding_factory_mock_; + + ON_CALL(runtime_mock_guard_.runtime_mock_, GetBindingRuntime(BindingType::kLoLa)) + .WillByDefault(Return(&binding_runtime_mock_)); + + ON_CALL(runtime_mock_guard_.runtime_mock_, GetServiceDiscovery()) + .WillByDefault(ReturnRef(service_discovery_mock_)); + + ON_CALL(binding_runtime_mock_, GetBindingType()) + .WillByDefault(Return(BindingType::kLoLa)); + + ON_CALL(binding_runtime_mock_, GetServiceDiscoveryClient()) + .WillByDefault(ReturnRef(service_discovery_client_mock_)); + + ON_CALL(service_discovery_mock_, OfferService(_)).WillByDefault(Return(score::Blank{})); + + ON_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) + .WillByDefault(Invoke([this](const auto&) { + auto mock = std::make_unique>(); + this->skeleton_binding_mock_ = mock.get(); + ON_CALL(*mock, PrepareOffer(_, _, _)).WillByDefault(Return(score::Blank{})); + return mock; + })); + } + + ~GenericSkeletonEventTest() override { + GenericSkeletonEventBindingFactory::mock_ = nullptr; + } + + // Mocks + NiceMock generic_event_binding_factory_mock_; + RuntimeMockGuard runtime_mock_guard_{}; + NiceMock binding_runtime_mock_{}; + NiceMock service_discovery_mock_{}; + NiceMock service_discovery_client_mock_{}; + SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{}; + + // Pointers and Helpers + mock_binding::Skeleton* skeleton_binding_mock_{nullptr}; + DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_; +}; + +TEST_F(GenericSkeletonEventTest, AllocateBeforeOfferReturnsError) +{ + RecordProperty("Description", "Checks that calling Allocate() before OfferService() returns kNotOffered."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton created with one event "test_event" + const DataTypeMetaInfo size_info{16, 8}; + const std::string event_name = "test_event"; + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, size_info}); + create_params.events = events; + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::make_unique>()))); + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + + // And given the event instance + auto& skeleton = skeleton_result.value(); + const auto& events_map = skeleton.GetEvents(); + auto it = events_map.find(event_name); + ASSERT_NE(it, events_map.cend()); + auto* event = it->second; + + // When calling Allocate() before OfferService() + auto alloc_result = event->Allocate(); + + // Then it fails with kNotOffered + ASSERT_FALSE(alloc_result.has_value()); + EXPECT_EQ(alloc_result.error(), ComErrc::kNotOffered); +} + +TEST_F(GenericSkeletonEventTest, SendBeforeOfferReturnsError) +{ + RecordProperty("Description", "Checks that calling Send() before OfferService() returns kNotOffered."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton created with one event "test_event" + const std::string event_name = "test_event"; + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, {16, 8}}); + create_params.events = events; + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::make_unique>()))); + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + + auto& skeleton = skeleton_result.value(); + auto* event = skeleton.GetEvents().find(event_name)->second; + + // And a valid sample to send + mock_binding::SampleAllocateePtr dummy_sample{nullptr, [](void*){}}; + + // When calling Send() before OfferService() + auto send_result = event->Send(MakeSampleAllocateePtr(std::move(dummy_sample))); + + // Then it fails with kNotOffered + ASSERT_FALSE(send_result.has_value()); + EXPECT_EQ(send_result.error(), ComErrc::kNotOffered); +} + +TEST_F(GenericSkeletonEventTest, AllocateAndSendDispatchesToBindingAfterOffer) +{ + RecordProperty("Description", "Checks that Allocate and Send dispatch to the binding when the service is offered."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton configured with an event binding mock + const std::string event_name = "test_event"; + auto mock_event_binding = std::make_unique>(); + auto* mock_event_binding_ptr = mock_event_binding.get(); + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::move(mock_event_binding)))); + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, {16, 8}}); + create_params.events = events; + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + auto& skeleton = skeleton_result.value(); + auto* event = skeleton.GetEvents().find(event_name)->second; + + // And Given the service is Offered + EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + + // When calling Allocate() + mock_binding::SampleAllocateePtr dummy_alloc{nullptr, [](void*){}}; + EXPECT_CALL(*mock_event_binding_ptr, Allocate()) + .WillOnce(Return(ByMove(MakeSampleAllocateePtr(std::move(dummy_alloc))))); + + auto alloc_result = event->Allocate(); + ASSERT_TRUE(alloc_result.has_value()); + + // And When calling Send() with the allocated sample + EXPECT_CALL(*mock_event_binding_ptr, Send(_)).WillOnce(Return(score::Blank{})); + + auto send_result = event->Send(std::move(alloc_result.value())); + + // Then both operations succeed + ASSERT_TRUE(send_result.has_value()); +} + +} // namespace +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp index 90c0a8500..f08df5c0a 100644 --- a/score/mw/com/impl/generic_skeleton_test.cpp +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -12,122 +12,269 @@ ********************************************************************************/ #include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h" #include "score/mw/com/impl/bindings/mock_binding/skeleton.h" +#include "score/mw/com/impl/i_binding_runtime.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h" #include "score/mw/com/impl/runtime_mock.h" +#include "score/mw/com/impl/service_discovery_client_mock.h" +#include "score/mw/com/impl/service_discovery_mock.h" +#include "score/mw/com/impl/test/binding_factory_resources.h" #include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" #include "score/mw/com/impl/test/runtime_mock_guard.h" -#include "score/mw/com/impl/test/skeleton_binding_factory_mock_guard.h" +#include "score/mw/com/impl/com_error.h" #include #include +#include +#include +#include + namespace score::mw::com::impl { namespace { using ::testing::_; +using ::testing::AllOf; using ::testing::ByMove; +using ::testing::Field; +using ::testing::Invoke; +using ::testing::NiceMock; using ::testing::Return; +using ::testing::ReturnRef; + +// --- Helper Mocks --- +class IBindingRuntimeMock : public IBindingRuntime +{ + public: + MOCK_METHOD(IServiceDiscoveryClient&, GetServiceDiscoveryClient, (), (noexcept, override)); + MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override)); + MOCK_METHOD(tracing::IBindingTracingRuntime*, GetTracingRuntime, (), (noexcept, override)); +}; class GenericSkeletonTest : public ::testing::Test { public: GenericSkeletonTest() { - auto skeleton_binding_mock = std::make_unique(); - skeleton_binding_mock_ = skeleton_binding_mock.get(); + GenericSkeletonEventBindingFactory::mock_ = &generic_skeleton_event_binding_factory_mock_; + + ON_CALL(runtime_mock_guard_.runtime_mock_, GetBindingRuntime(BindingType::kLoLa)) + .WillByDefault(Return(&binding_runtime_mock_)); + ON_CALL(runtime_mock_guard_.runtime_mock_, GetServiceDiscovery()) + .WillByDefault(ReturnRef(service_discovery_mock_)); + ON_CALL(binding_runtime_mock_, GetBindingType()).WillByDefault(Return(BindingType::kLoLa)); + ON_CALL(binding_runtime_mock_, GetServiceDiscoveryClient()) + .WillByDefault(ReturnRef(service_discovery_client_mock_)); ON_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) - .WillByDefault(Return(ByMove(std::move(skeleton_binding_mock)))); + .WillByDefault(Invoke([this](const auto&) { + auto mock = std::make_unique>(); + this->skeleton_binding_mock_ = mock.get(); + ON_CALL(*mock, PrepareOffer(_, _, _)).WillByDefault(Return(score::Blank{})); + return mock; + })); + } + + ~GenericSkeletonTest() override + { + GenericSkeletonEventBindingFactory::mock_ = nullptr; } RuntimeMockGuard runtime_mock_guard_{}; SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{}; + NiceMock generic_skeleton_event_binding_factory_mock_{}; + + NiceMock binding_runtime_mock_{}; + NiceMock service_discovery_mock_{}; + NiceMock service_discovery_client_mock_{}; + mock_binding::Skeleton* skeleton_binding_mock_{nullptr}; + DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_{}; }; -TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifier) + +TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifierResolvesIdentifier) +{ + RecordProperty("Description", "Checks that GenericSkeleton resolves the InstanceSpecifier."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a valid string specifier + auto instance_specifier = InstanceSpecifier::Create(std::string("path/to/my/service")).value(); + auto expected_identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + + // Expect the Runtime to be asked to resolve it + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, resolve(instance_specifier)) + .WillOnce(Return(std::vector{expected_identifier})); + + // When creating the skeleton + GenericSkeletonCreateParams params; + auto result = GenericSkeleton::Create(instance_specifier, params); + + // Then creation succeeds + ASSERT_TRUE(result.has_value()); +} + + +TEST_F(GenericSkeletonTest, CreateWithEventsInitializesEventBindings) +{ + RecordProperty("Description", "Checks that GenericSkeleton creates bindings for configured events."); + RecordProperty("TestType", "Requirements-based test"); + + // Given configuration for one event + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + const DataTypeMetaInfo meta_info{16, 8}; + + std::vector event_storage; + event_storage.push_back({event_name, meta_info}); + + GenericSkeletonCreateParams params; + params.events = event_storage; + + // Expect the Event Factory to be called + auto MetaMatcher = AllOf( + Field(&DataTypeMetaInfo::size, meta_info.size), + Field(&DataTypeMetaInfo::alignment, meta_info.alignment) + ); + + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, MetaMatcher)) + .WillOnce(Return(ByMove(std::make_unique>()))); + + // When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // Then the skeleton contains the event + ASSERT_TRUE(result.has_value()); + const auto& events = result.value().GetEvents(); + ASSERT_EQ(events.size(), 1); + + EXPECT_NE(events.find(event_name), events.cend()); +} + + +TEST_F(GenericSkeletonTest, CreateWithDuplicateEventNamesFails) { - auto instance_specifier = InstanceSpecifier::Create("a/b/c").value(); - auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); - GenericSkeletonCreateParams create_params; + RecordProperty("Description", "Checks that creating a skeleton with duplicate event names returns an error."); + RecordProperty("TestType", "Requirements-based test"); + + // Given an identifier and configuration with duplicate event names + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + + std::vector event_storage; + event_storage.push_back({event_name, {1, 1}}); + event_storage.push_back({event_name, {2, 2}}); // Duplicate + + GenericSkeletonCreateParams params; + params.events = event_storage; - EXPECT_CALL(runtime_mock_guard_.runtime_mock_, ResolveInstanceIdentifier(instance_specifier)) - .WillOnce(Return(instance_identifier)); + // Expecting at least one attempt to create an event binding + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, _)) + .WillRepeatedly(Return(ByMove(std::make_unique>()))); - auto skeleton_result = GenericSkeleton::Create(instance_specifier, create_params); - ASSERT_TRUE(skeleton_result.has_value()); + // When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // Then creation fails with kServiceElementAlreadyExists + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kServiceElementAlreadyExists); } -TEST_F(GenericSkeletonTest, CreateWithInstanceIdentifier) + +TEST_F(GenericSkeletonTest, CreateFailsIfMainBindingCannotBeCreated) { - auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); - GenericSkeletonCreateParams create_params; - auto skeleton_result = GenericSkeleton::Create(instance_identifier, create_params); - ASSERT_TRUE(skeleton_result.has_value()); + RecordProperty("Description", "Checks that creation fails if the main SkeletonBinding factory returns null."); + RecordProperty("TestType", "Requirements-based test"); + + // Given the binding factory returns nullptr + EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) + .WillOnce(Return(ByMove(nullptr))); + + GenericSkeletonCreateParams params; + + // When creating the skeleton + auto result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(), params); + + // Then creation fails with kBindingFailure + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); } -TEST_F(GenericSkeletonTest, CreateWithEventsAndFields) + +TEST_F(GenericSkeletonTest, OfferServicePropagatesToBindingAndDiscovery) { - auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); - const DataTypeMetaInfo event_size_info{16, 8}; - // const DataTypeMetaInfo field_size_info{4, 4}; // commented out as field not implemented - const std::string event_name = "MyEvent"; - // const std::string field_name = "MyField"; // commented out as field not implemented - // const std::vector initial_field_value = {1, 2, 3, 4}; // commented out as field not implemented - - // Mock event binding creation - EXPECT_CALL(generic_skeleton_event_binding_factory_mock_guard_.factory_mock_, Create(_, event_name, event_size_info)) - .WillOnce(Return(ByMove(std::make_unique()))); - - // // Mock field binding creation // commented out as field not implemented - // EXPECT_CALL(generic_skeleton_field_binding_factory_mock_guard_.factory_mock_, Create(_, field_name, field_size_info)) - // .WillOnce(Return(ByMove(std::make_unique()))); - - GenericSkeletonCreateParams create_params; - const std::vector events_vec = {{event_name, event_size_info}}; - //const std::vector fields_vec = {{field_name, field_size_info, initial_field_value}}; - create_params.events = events_vec; - //create_params.fields = fields_vec; - - auto skeleton_result = GenericSkeleton::Create(instance_identifier, create_params); - ASSERT_TRUE(skeleton_result.has_value()); - - auto& skeleton = skeleton_result.value(); - EXPECT_EQ(skeleton.GetEvents().size(), 1); - // EXPECT_EQ(skeleton.GetFields().size(), 1); // commented out as field not implemented - EXPECT_NE(skeleton.GetEvents().find(event_name), skeleton.GetEvents().cend()); - // EXPECT_NE(skeleton.GetFields().find(field_name), skeleton.GetFields().cend()); // commented out as field not implemented + RecordProperty("Description", "Checks that OfferService calls PrepareOffer on the binding and notifies ServiceDiscovery."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); + + // Expecting OfferService to trigger binding and discovery + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::Blank{})); + EXPECT_CALL(service_discovery_mock_, OfferService(identifier)).WillOnce(Return(score::Blank{})); + + // When offering service + auto result = skeleton.OfferService(); + + // Then it succeeds + ASSERT_TRUE(result.has_value()); } -TEST_F(GenericSkeletonTest, OfferAndStopOfferService) + +TEST_F(GenericSkeletonTest, StopOfferServicePropagatesToBindingAndDiscovery) { - auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); - GenericSkeletonCreateParams create_params; // Empty params for this test - auto skeleton = GenericSkeleton::Create(instance_identifier, create_params).value(); + RecordProperty("Description", "Checks that StopOfferService calls PrepareStopOffer and notifies ServiceDiscovery."); + RecordProperty("TestType", "Requirements-based test"); - // Expect PrepareOffer to be called on the binding - EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::result::Blank{})); - // Expect OfferService to be called on the service discovery - EXPECT_CALL(runtime_mock_guard_.service_discovery_mock_, OfferService(instance_identifier)).WillOnce(Return(score::result::Blank{})); + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); - auto offer_result = skeleton.OfferService(); - EXPECT_TRUE(offer_result.has_value()); + // And given the service is already Offered + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::Blank{})); + EXPECT_CALL(service_discovery_mock_, OfferService(identifier)).WillOnce(Return(score::Blank{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + // Expecting StopOffer to trigger binding and discovery EXPECT_CALL(*skeleton_binding_mock_, PrepareStopOffer(_)); - EXPECT_CALL(runtime_mock_guard_.service_discovery_mock_, StopOfferService(instance_identifier)); + EXPECT_CALL(service_discovery_mock_, StopOfferService(identifier)); + + // When stopping offer skeleton.StopOfferService(); + + // Then (Verified by mock expectations) } -TEST_F(GenericSkeletonTest, CreateFailsIfBindingFails) + +TEST_F(GenericSkeletonTest, OfferServiceReturnsErrorIfBindingFails) { - EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)).WillOnce(Return(ByMove(nullptr))); - GenericSkeletonCreateParams create_params; - auto skeleton_result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier(), create_params); - ASSERT_FALSE(skeleton_result.has_value()); - EXPECT_EQ(skeleton_result.error(), ComErrc::kBindingFailure); + RecordProperty("Description", "Checks that OfferService returns an error if the binding's PrepareOffer fails."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); + + // Expecting Binding to fail + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)) + .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); + + // Expecting ServiceDiscovery NOT to be called + EXPECT_CALL(service_discovery_mock_, OfferService(_)).Times(0); + + // When offering service + auto result = skeleton.OfferService(); + + // Then it fails with kBindingFailure + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); } + } // namespace } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h b/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h new file mode 100644 index 000000000..1bb3edffb --- /dev/null +++ b/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#define SCORE_MW_COM_IMPL_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/data_type_meta_info.h" +#include +#include + +namespace score::mw::com::impl { + +class IGenericSkeletonEventBindingFactory { +public: + virtual ~IGenericSkeletonEventBindingFactory() noexcept = default; + + // Changed SizeInfo -> DataTypeMetaInfo + virtual score::Result> Create( + SkeletonBase&, + std::string_view, + const DataTypeMetaInfo&) noexcept = 0; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 9f2f32858..df4244d2f 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -446,21 +446,54 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event_binding_factory_impl", + srcs = ["generic_skeleton_event_binding_factory_impl.cpp"], + hdrs = ["generic_skeleton_event_binding_factory_impl.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = ["//score/mw/com/impl/bindings/lola"], + tags = ["FFI"], # Increased visibility as it's now in score::mw::com::impl namespace + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", + ":skeleton_service_element_binding_factory_impl", + ], +) + cc_library( name = "generic_skeleton_event_binding_factory", - hdrs = ["generic_skeleton_event_binding_factory.h"], + srcs = ["generic_skeleton_event_binding_factory.cpp"], + hdrs = ["generic_skeleton_event_binding_factory.h", + "skeleton_service_element_binding_factory_impl.h"], features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":generic_skeleton_event_binding_factory_impl", + ], tags = ["FFI"], visibility = [ "//score/mw/com/impl:__subpackages__", ], - deps = [ - "//score/mw/com/impl:generic_skeleton_event_binding", - "//score/mw/com/impl:skeleton_base", - "//score/mw/com/impl/plumbing:skeleton_service_element_binding_factory_impl", + deps = [ # Removed unnecessary dependency + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", ], ) +cc_library( + name = "generic_skeleton_event_binding_factory_mock", + testonly = True, + srcs = ["generic_skeleton_event_binding_factory_mock.cpp"], + hdrs = ["generic_skeleton_event_binding_factory_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//visibility:public", # platform_only + ], + deps = [ + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", + "@googletest//:gtest", + ], +) cc_library( name = "runtime", srcs = [ diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h index 23b384abc..4158a7795 100644 --- a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h @@ -13,36 +13,57 @@ #ifndef SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H #define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" #include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/skeleton_base.h" #include "score/mw/com/impl/data_type_meta_info.h" #include "score/mw/com/impl/service_element_type.h" #include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/result/result.h" #include #include + namespace score::mw::com::impl { - + class GenericSkeletonEventBindingFactory { public: - static Result> Create(SkeletonBase& skeleton_base, - const std::string_view event_name, - const DataTypeMetaInfo& size_info) noexcept + // C++17 inline static allows defining this variable directly in the header. + // This serves as the "hook" for your unit tests. + inline static IGenericSkeletonEventBindingFactory* mock_ = nullptr; + + // This static method allows your Source Code (generic_skeleton.cpp) + // to call GenericSkeletonEventBindingFactory::Create(...) directly. + static Result> Create( + SkeletonBase& skeleton_base, + std::string_view event_name, + const DataTypeMetaInfo& meta_info) noexcept { + // A. If a Mock is registered (during Unit Tests), use it. + if (mock_ != nullptr) + { + // Pass meta_info to mock + return mock_->Create(skeleton_base, event_name, meta_info); + } + + // B. Otherwise (in Production), use the Real Implementation. const auto& instance_identifier = SkeletonBaseView{skeleton_base}.GetAssociatedInstanceIdentifier(); - return CreateSkeletonServiceElement( + + + return CreateGenericSkeletonServiceElement( instance_identifier, skeleton_base, event_name, - size_info); + meta_info); } }; + } // namespace score::mw::com::impl #endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp new file mode 100644 index 000000000..885bcdf63 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/service_element_type.h" +#include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" +#include "score/mw/com/impl/skeleton_base.h" + +// Updated signature to use DataTypeMetaInfo +score::Result> +score::mw::com::impl::GenericSkeletonEventBindingFactoryImpl::Create( + SkeletonBase& parent, + std::string_view event_name, + const DataTypeMetaInfo& meta_info) noexcept +{ + const auto& instance_identifier = SkeletonBaseView{parent}.GetAssociatedInstanceIdentifier(); + + + return CreateGenericSkeletonServiceElement( + instance_identifier, parent, event_name, meta_info); +} \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h new file mode 100644 index 000000000..885999f6a --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H + +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/data_type_meta_info.h" + +namespace score::mw::com::impl { + +class GenericSkeletonEventBindingFactoryImpl : public IGenericSkeletonEventBindingFactory { +public: + + score::Result> Create( + SkeletonBase&, + std::string_view, + const DataTypeMetaInfo&) noexcept override; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp new file mode 100644 index 000000000..34e868cee --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h" \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h new file mode 100644 index 000000000..1bcbd90ea --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H + +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/data_type_meta_info.h" + +#include + +namespace score::mw::com::impl { + +class GenericSkeletonEventBindingFactoryMock : public IGenericSkeletonEventBindingFactory { +public: + MOCK_METHOD(score::Result>, Create, + (SkeletonBase&, std::string_view, const DataTypeMetaInfo&), + (noexcept, override)); +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index 8c418bd38..b30f265f6 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -102,10 +102,18 @@ class SampleAllocateePtr // ------------------------------------------------------------------------- /// \brief operator* - Enabled via SFINAE only if SampleType is NOT void + /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will + /// terminate. + // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) + // coverity[autosar_cpp14_a15_5_3_violation : FALSE] template ::value, int>::type = 0> typename std::add_lvalue_reference::type operator*() const noexcept; /// \brief operator-> - Enabled via SFINAE only if SampleType is NOT void + /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will + /// terminate. + // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) + // coverity[autosar_cpp14_a15_5_3_violation : FALSE] template ::value, int>::type = 0> pointer operator->() const noexcept; @@ -135,7 +143,8 @@ class SampleAllocateePtr template // coverity[autosar_cpp14_a11_3_1_violation] friend class SampleAllocateePtrMutableView; - + + // We don't use the pimpl idiom because it would require dynamic memory allocation (that we want to avoid) // Stores either the LoLa pointer or the Mock Binding pointer (which handles void safely) std::variant, mock_binding::SampleAllocateePtr> internal_; }; diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 7f3253aef..6e0cf19c6 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -13,16 +13,15 @@ #ifndef SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H #define SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H -#include "score/mw/com/impl/data_type_meta_info.h" #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" -#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/configuration/binding_service_type_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" #include "score/mw/com/impl/configuration/service_instance_deployment.h" #include "score/mw/com/impl/configuration/someip_service_instance_deployment.h" #include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/data_type_meta_info.h" #include "score/mw/log/logging.h" @@ -31,7 +30,6 @@ #include #include -#include #include #include #include @@ -68,6 +66,9 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( lola_service_element_instance_deployment.max_subscribers_.value(), lola_service_element_instance_deployment.enforce_max_samples_}; } + +} // namespace detail + template // Suppress "AUTOSAR C++14 A15-5-3" rule finding. This rule states: "The std::terminate() function shall // not be called implicitly.". std::visit Throws std::bad_variant_access if @@ -77,10 +78,9 @@ template >& size_info) noexcept + const std::string_view service_element_name) noexcept -> std::unique_ptr { static_assert(element_type != ServiceElementType::INVALID); @@ -89,7 +89,7 @@ auto CreateSkeletonServiceElementImpl(const InstanceIdentifier& identifier, using ReturnType = std::unique_ptr; auto visitor = score::cpp::overload( - [identifier_view, &parent, &service_element_name, &size_info]( + [identifier_view, &parent, &service_element_name]( const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { auto* const lola_parent = dynamic_cast(SkeletonBaseView{parent}.GetBinding()); if (lola_parent == nullptr) @@ -115,18 +115,8 @@ auto CreateSkeletonServiceElementImpl(const InstanceIdentifier& identifier, lola_service_instance_deployment.instance_id_.value().GetId(), element_type}; - if constexpr (std::is_same_v) - { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(size_info.has_value()); - return std::make_unique( - *lola_parent, skeleton_event_properties, element_fq_id, size_info.value().get()); - } - else - { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(!size_info.has_value()); - return std::make_unique( - *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); - } + return std::make_unique( + *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); }, [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { return nullptr; @@ -143,41 +133,60 @@ auto CreateSkeletonServiceElementImpl(const InstanceIdentifier& identifier, return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } -} // namespace detail - - /// @brief Overload for typed skeletons (which do not have a DataTypeMetaInfo). template -auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, - SkeletonBase& parent, - const std::string_view service_element_name) noexcept +// coverity[autosar_cpp14_a15_5_3_violation : FALSE] +auto CreateGenericSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name, + const DataTypeMetaInfo& meta_info) noexcept -> std::unique_ptr { - - static_assert(!std::is_same_v, - "This overload is for typed skeletons only. Generic skeletons must provide a DataTypeMetaInfo."); - - return detail::CreateSkeletonServiceElementImpl( - identifier, parent, service_element_name, score::cpp::nullopt); -} + static_assert(element_type != ServiceElementType::INVALID); -/// @brief Overload for generic skeletons (which require a DataTypeMetaInfo). -template -auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, - SkeletonBase& parent, - const std::string_view service_element_name, - const DataTypeMetaInfo& size_info) noexcept - -> std::unique_ptr -{ - - static_assert(std::is_same_v, - "This overload is for generic skeletons only. Typed skeletons must not provide a DataTypeMetaInfo."); - - return detail::CreateSkeletonServiceElementImpl( - identifier, - parent, - service_element_name, - std::cref(size_info)); + const InstanceIdentifierView identifier_view{identifier}; + + using ReturnType = std::unique_ptr; + auto visitor = score::cpp::overload( + [identifier_view, &parent, &service_element_name, &meta_info]( + const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { + auto* const lola_parent = dynamic_cast(SkeletonBaseView{parent}.GetBinding()); + if (lola_parent == nullptr) + { + score::mw::log::LogFatal("lola") << "Skeleton service element could not be created because parent " + "skeleton binding is a nullptr."; + return nullptr; + } + + const auto& service_instance_deployment = identifier_view.GetServiceInstanceDeployment(); + const auto& lola_service_instance_deployment = + GetServiceInstanceDeploymentBinding(service_instance_deployment); + + const auto& lola_service_element_instance_deployment = GetServiceElementInstanceDeployment( + lola_service_instance_deployment, std::string{service_element_name}); + const auto skeleton_event_properties = + detail::GetSkeletonEventProperties(lola_service_element_instance_deployment); + + const auto lola_service_element_id = + GetServiceElementId(lola_service_type_deployment, std::string{service_element_name}); + const lola::ElementFqId element_fq_id{lola_service_type_deployment.service_id_, + lola_service_element_id, + lola_service_instance_deployment.instance_id_.value().GetId(), + element_type}; + + // Generic Constructor Call (Matches your updated GenericSkeletonEvent class) + // Order: Parent, FqId, Name, Properties, MetaInfo + return std::make_unique( + *lola_parent, element_fq_id, service_element_name, skeleton_event_properties, meta_info); + }, + [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { + return nullptr; + }, + [](const score::cpp::blank&) noexcept -> ReturnType { + return nullptr; + }); + + return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/skeleton_base.cpp b/score/mw/com/impl/skeleton_base.cpp index 33fe4da09..280a95867 100644 --- a/score/mw/com/impl/skeleton_base.cpp +++ b/score/mw/com/impl/skeleton_base.cpp @@ -85,14 +85,13 @@ SkeletonBinding::SkeletonFieldBindings GetSkeletonFieldBindingsMap(const Skeleto SkeletonBase::SkeletonBase(std::unique_ptr skeleton_binding, InstanceIdentifier instance_id, MethodCallProcessingMode) - : service_offered_flag_{}, - binding_{std::move(skeleton_binding)}, + : binding_{std::move(skeleton_binding)}, events_{}, fields_{}, methods_{}, instance_id_{std::move(instance_id)}, - skeleton_mock_{nullptr} - + skeleton_mock_{nullptr}, + service_offered_flag_{} { } @@ -124,14 +123,13 @@ void SkeletonBase::Cleanup() } SkeletonBase::SkeletonBase(SkeletonBase&& other) noexcept - : service_offered_flag_{std::move(other.service_offered_flag_)}, - binding_{std::move(other.binding_)}, + : binding_{std::move(other.binding_)}, events_{std::move(other.events_)}, fields_{std::move(other.fields_)}, methods_{std::move(other.methods_)}, instance_id_{std::move(other.instance_id_)}, - skeleton_mock_{std::move(other.skeleton_mock_)} - + skeleton_mock_{std::move(other.skeleton_mock_)}, + service_offered_flag_{std::move(other.service_offered_flag_)} { // Since the address of this skeleton has changed, we need update the address stored in each of the events and // fields belonging to the skeleton. diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index 11894b06f..f9a970ec3 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -115,9 +115,6 @@ class SkeletonBase SkeletonBase(SkeletonBase&& other) noexcept; SkeletonBase& operator=(SkeletonBase&& other) noexcept; - - FlagOwner service_offered_flag_; - private: std::unique_ptr binding_; SkeletonEvents events_; @@ -133,6 +130,8 @@ class SkeletonBase [[nodiscard]] score::ResultBlank OfferServiceEvents() const noexcept; [[nodiscard]] score::ResultBlank OfferServiceFields() const noexcept; + + FlagOwner service_offered_flag_; }; class SkeletonBaseView @@ -150,11 +149,6 @@ class SkeletonBaseView return skeleton_base_.binding_.get(); } - bool IsOffered() const - { - return skeleton_base_.service_offered_flag_.IsSet(); - } - void RegisterEvent(const std::string_view event_name, SkeletonEventBase& event) { const auto result = skeleton_base_.events_.emplace(event_name, event); diff --git a/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp b/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp index 144360efb..1366b30d8 100644 --- a/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp +++ b/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp @@ -58,6 +58,16 @@ InstanceIdentifier DummyInstanceIdentifierBuilder::CreateValidLolaInstanceIdenti service_instance_deployment_.instance_id_ = LolaServiceInstanceId{0x42}; service_instance_deployment_.allowed_consumer_ = {{QualityType::kASIL_QM, {42}}}; service_instance_deployment_.events_ = events; + + // The GenericSkeleton needs the event names to be present in the Type Deployment + // to perform the stable string lookup. We sync it here. + service_type_deployment_.events_.clear(); + for (const auto& event_pair : events) { + // Add the event name to the type deployment map. + // We assume default construction of the value (EventId) is sufficient for this mock. + service_type_deployment_.events_[event_pair.first] = {}; + } + type_deployment_.binding_info_ = service_type_deployment_; instance_deployment_ = std::make_unique( type_, service_instance_deployment_, QualityType::kASIL_QM, instance_specifier_); From 537931a6a6a4ea1653febd393759ee9d4caad901 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 11 Feb 2026 00:32:25 +0200 Subject: [PATCH 12/18] refine the Generic Skeleton Interface and adding Readme for generic skeleton --- score/mw/com/design/skeleton_proxy/README.md | 6 +- .../skeleton_proxy/generic_skeleton/README.md | 147 ++++++++++++++ .../generic_skeleton_model.puml | 183 ++++++++++++++++++ .../ipc_bridge/sample_sender_receiver.cpp | 7 +- .../bindings/lola/generic_skeleton_event.cpp | 13 +- .../bindings/lola/generic_skeleton_event.h | 4 +- score/mw/com/impl/generic_skeleton.cpp | 69 ++----- score/mw/com/impl/generic_skeleton.h | 3 +- .../com/impl/generic_skeleton_event_test.cpp | 9 +- ...ton_service_element_binding_factory_impl.h | 10 +- 10 files changed, 376 insertions(+), 75 deletions(-) create mode 100644 score/mw/com/design/skeleton_proxy/generic_skeleton/README.md create mode 100644 score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml diff --git a/score/mw/com/design/skeleton_proxy/README.md b/score/mw/com/design/skeleton_proxy/README.md index 1ef4c8335..d8958e391 100644 --- a/score/mw/com/design/skeleton_proxy/README.md +++ b/score/mw/com/design/skeleton_proxy/README.md @@ -4,8 +4,8 @@ The following structural view shows, how the separation of generic/binding independent part of a proxy/skeleton from its flexible/variable technical binding implementation is achieved. **Note**: It does **only** reflect the common use -case of strongly typed proxies. The special case of "generic proxies" is described in -[design extension for generic proxies](generic_proxy/README.md#) to not bloat this class diagram even more: +case of strongly typed proxies and skeletons. The special case of "generic proxies" and "generic skeletons" are described in +[design extension for generic proxies](generic_proxy/README.md#) and [design extension for generic skeletons](generic_skeleton/README.md#) to not bloat this class diagram even more: @@ -225,6 +225,8 @@ aggregates an object of type `lola::ProxyEventCommon`, to which it dispatches al calls, it has to implement to fulfill its interface `ProxyEventBindingBase`. The reason for this architectural decision is described in the [design extension for generic proxies](./generic_proxy/README.md) +Similarly, on the skeleton side, `lola::SkeletonEvent` aggregates an object of type `lola::SkeletonEventCommon`. This class encapsulates all `SampleTyp`e agnostic logic (such as interaction with `lola::Skeleton` for offering services, timestamp management, and notification handling). This allows both strongly typed skeletons and generic skeletons to share the same core implementation logic. + ### Proxy auto-reconnect functionality According to our requirements (namely requirement `SCR-29682823`), we need to support a functionality, which is known as diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md new file mode 100644 index 000000000..1f0abc5f7 --- /dev/null +++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md @@ -0,0 +1,147 @@ +# Support for Generic Skeletons + +## Introduction + +Generic skeletons are skeletons, which are not generated based on compile-time info about types used within the service +interface. Instead, they are skeletons, which can be instantiated at runtime and be connected to any service consuming +instance (proxy) just based on deployment information. + +Since such generic skeletons are loosely typed (don't have the strong type info of the services event/field/service-method +data-types), they provide a different C++ API opposed to normal (strongly typed) skeletons. + +The use cases for such restricted generic skeletons are typically for dynamically providing "raw data" in terms of blobs +from service providers. Interpreting the blob data semantically is obviously hard to accomplish. +If this is needed (normal use case) one should use the normal/strongly typed skeletons. + + +## LoLa supported feature set for Generic Skeletons + +Since `LoLa`s support for fields is restricted to only the event-functionality of fields, we will in a 1st step **only** +implement event specific functionality for the `Generic Skeleton`. + +This comprises the following items: +* `GenericSkeleton` class: We will provide a class `GenericSkeleton`, but opposed to the concept (linked above) **not** in + the `ara::com`, but in the `mw::com` namespace. +* This `GenericSkeleton` class will (in relation to the concept) **only** provide a public method `GetEvents()`, which + returns a `GenericSkeleton::EventMap` (which is a stripped down `std::map`) +* this map returned by `GetEvents()` will only contain all the events mentioned in the service deployment. Later (when + our fields get extended with get/set method functionality). +* This `GenericSkeleton` will be based on the signatures of our current `LoLa` typed skeleton class: + * base skeleton interface + * skeleton event interface + +There will be **no** changes done on `LoLa` configuration approach. This means the `LoLa` configuration (deployment of +service instances) is processed once at startup. The deployment information of a service instance, to which a +`GenericSkeleton` shall offer, needs to be provided in the `LoLa` configuration at application startup. This means, +that any deployment update affecting a `LoLa` application needs an application restart to become effective. + +## Architecture enhancements for GenericSkeleton support + +The main addition to the architecture for the interaction between a service providing instance (skeleton) and a service +consuming instance (proxy) is, that there now is some additional meta-data needed in the `lola::ServiceDataStorage`, +which resides in the shared-memory object containing the service instance specific data. This `lola::ServiceDataStorage` +is already described in the design details of our data layout in the shared memory +here + +Before introduction of `GenericSkeleton` support, the skeleton side constructed in the shared-memory object for **Data** an +instance of `lola::ServiceDataStorage`, which is a map between an event-identifier and a slot-vector of event-data for +the given event-identifier. +When strongly typed skeletons offer to this shared-memory object for data, they exactly know the type (size/layout) of +this slot-vector, since the event datatype is known to them at compile-time. So they are able to: +* cast the slot-vector of event-data, which is held within the map as a `OffsetPtr` to the correctly typed vector + (`score::memory::shared::Vector`) +* access therefore the vector elements as `SampleType` + +`GenericSkeleton`s don't know the exact type (size/layout) at compile time. However, provided with runtime meta-info (size/alignment), they must allocate and describe the slot-vector in shared memory so that consumers (Generic Proxies) can find and interpret it. So this +information has now additionally provided by the skeleton. So we extend `lola::ServiceDataStorage` with an additional +meta-data section: + +I.e. beside the given map for the event data slots a second map is provided, which gives the meta-info for each event +provided by the service: +`score::memory::shared::Map events_metainfo_` + +### Meta-Info + +The `lola::EventMetaInfo` is displayed in the class diagram for shared-mem data. +It contains meta-info of the events data type in the form of `lola::DataTypeMetaInfo` and an `OffsetPtr` to the +location, where the event-slot vector is stored. With this combined information a `GenericProxy`, which just knows the +deployment info (essentially the `ElementFqId` of the event) is able to access events in the event-slot vector in its raw +form (as a byte array). + +The `lola::DataTypeMetaInfo` contains the following members: +* `fingerprint_` : unique fingerprint for the data type. This is a preparation/placeholder for an upcoming feature, + which will allow doing a runtime check at the proxy side during connection to the service provider, whether provider + and consumer side are based on the same C++ interface definition. +* `size_of_` : the result of `sizeof()` operator on the data type (in case of event/field: `SampleType`) +* `align_of_` : the result of `alignof()` operator on the data type (in case of event/field: `SampleType`) + +### initialization of `lola::EventMetaInfo` + +The map containing the meta-information gets initialized by the skeleton instance together with the setup/creation of +the vectors containing the sample slots via `RegisterGeneric` (or the internal `CreateEventDataFromOpenedSharedMemory` helper). +```cpp +std::pair,EventDataControlComposite> +lola::Skeleton::RegisterGeneric(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + const size_t sample_size, + const size_t sample_alignment) +``` +This happens during initial offering-phase of a service instance, before potential consumers/proxies are able to access +the shared-memory object. `GenericSkeleton`s will utilize this mechanism to register their events. + +## GenericSkeleton class + +As shown in the class diagram, below, the `mw::com::impl::GenericSkeleton` is **not** a generated (from some interface +specification) class (see Sys-Req-13525992), derived from +`impl::SkeletonBase` (like `DummySkeleton` example) + +### Creation and Configuration +Runtime instantiation is handled via the static `GenericSkeleton::Create` method, which accepts `GenericSkeletonCreateParams`. This structure allows users to define the service elements dynamically without compile-time type information. + +```cpp +struct EventInfo { + std::string_view name; + DataTypeMetaInfo data_type_meta_info; // contains size and alignment +}; + +struct GenericSkeletonCreateParams { + score::cpp::span events{}; +}; +``` + +The `GenericSkeleton`contains one member `events_`, which is a map of +`impl::GenericSkeletonEvent` in the form of `mw::com::EventMap`. + +GENERIC_SKELETON_MODEL + +Classes drawn with yellow background are extensions of the class diagram to support `GenericSkeleton` functionality beside +"normal" skeletons. +While there is a new `impl::GenericSkeleton` in the binding **independent** area, we currently do not need to reflect it in +the binding specific part as `impl::SkeletonBinding` (and therefore `lola::Skeleton` implementing `impl::SkeletonBinding`) +doesn't need any extensions for `GenericSkeleton` functionality. +On the level below the skeleton (skeleton-event level) this looks different. In the binding independent part we introduced the +non-templated class `impl::GenericSkeletonEvent` beside the existing class template `impl::SkeletonEvent`. +Both derive from `impl::SkeletonEventBase`, which contains all the `SampleType` agnostic/independent signatures/ +implementations a skeleton-event needs to have. +The methods `Send()` and `Allocate()` are `SampleType` specific (via their arguments, which depend on the `SampleType`). +Therefore, they aren't contained in `impl::SkeletonEventBase` but specifically implemented in `impl::GenericSkeletonEvent` and +`impl::SkeletonEvent`. + +We have a similar setup on the binding side: `impl::GenericSkeletonEventBinding` has been introduced beside +`impl::SkeletonEventBinding` as abstract classes/interface definitions for "normal", respectively generic +skeleton-event bindings. +Their common abstract base class `impl::SkeletonEventBindingBase` aggregates all interfaces, which are `SampleType` +agnostic and are common to both skeleton-event bindings. + +On `LoLa` implementation level for the skeleton-event binding, we added `lola::GenericSkeletonEvent` beside +`lola::SkeletonEvent`. +To factor out common code/implementations for all signatures, which are `SampleType` agnostic and common to both +`lola::GenericSkeletonEvent` and `lola::SkeletonEvent` +the class `lola::SkeletonEventCommon` has been +introduced, which implements this common code. I.e. instances of `lola::GenericSkeletonEvent` and `lola::SkeletonEvent` +create such an instance and dispatch the corresponding methods to it. We decided to go for this composite/dispatch +approach instead of introducing a common base class to `lola::GenericSkeletonEvent` and `lola::SkeletonEvent`, +which provides/implements those signatures as this would have meant, that `lola::GenericSkeletonEvent` and +`lola::SkeletonEvent` had multi-inheritance (from this base class and their corresponding interface)! +Even if this hadn't been problematic (in terms of potential diamond pattern), we would have to explicitly argue/analyse +it due to the `ASIL-B` nature of our code base and the general avoidance pattern of multi-inheritance. diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml new file mode 100644 index 000000000..8ce3b5637 --- /dev/null +++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml @@ -0,0 +1,183 @@ +@startuml generic_skeleton_model +title "Generic Skeleton Extension" + +class "score::mw::com::impl::HandleType" { + -indentifier_: InstanceIdentifier + +operator==(const HandleType& other): bool + +operator<(const HandleType& other): bool + +GetInstanceId(): InstanceIdentifier& + +GetServiceInstanceDeployment(): ServiceInstanceDeployment& +} + +class "SkeletonBindingFactory" { + +{static} Create(InstanceIdentifier identifier): std::unique_ptr +} + +abstract class "score::mw::com::impl::SkeletonBase" +{ + -skeleton_binding_ : std::unique_ptr + -identifier_ : InstanceIdentifier + +GetAssociatedInstanceIdentifier() : const InstanceIdentifier& + +OfferService(): ResultBlank + +StopOfferService(): void + .. + Notes: + SkeletonBase is not copyable but moveable +} + +abstract class "SkeletonBinding" { + +{abstract} PrepareOffer() = 0: ResultBlank + +{abstract} PrepareStopOffer() = 0: void +} + +class "mw::com::impl::GenericSkeleton" #yellow { + using EventMap = ServiceElementMap + .. + +GenericSkeleton(const InstanceIdentifier&, std::unique_ptr, MethodCallProcessingMode) + +Create(const InstanceIdentifier&, const GenericSkeletonCreateParams&, MethodCallProcessingMode): Result + +Create(const InstanceSpecifier&, const GenericSkeletonCreateParams&, MethodCallProcessingMode): Result + +events_ : EventMap + +OfferService(): ResultBlank + +StopOfferService(): void +} + +class "lola::Skeleton" { + +Skeleton(const InstanceIdentifier&, MethodCallProcessingMode) + +PrepareOffer(): ResultBlank + +PrepareStopOffer(): void + +Register(ElementFqId event_fqn, const SkeletonEventProperties& event_properties, std::size_t size, std::size_t alignment): std::pair, EventDataControlComposite> + +RegisterGeneric(ElementFqId event_fqn, const SkeletonEventProperties& event_properties, std::size_t size, std::size_t alignment): std::pair, EventDataControlComposite> + +DisconnectQmConsumers(): void + +GetInstanceQualityType(): QualityType + +GetSourcePid(): pid_t +} + +class "mw::com::impl::SkeletonEvent" { + +Send(const SampleType&): ResultBlank + +Allocate(): Result> +} + +abstract class "SkeletonEventBindingBase" { + +{abstract} PrepareOffer() = 0: ResultBlank + +{abstract} PrepareStopOffer() = 0: void + +{abstract} GetBindingType() = 0: BindingType + +{abstract} SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) = 0: void +} + +abstract class "SkeletonEventBinding" +{ + +{abstract} Send(const SampleType&) = 0: ResultBlank + +{abstract} Allocate() = 0: Result> +} + +class "SampleAllocateePtr" { + +Get(): SampleType* + +operator->(): SampleType* + +operator*(): SampleType& +} + +class "lola::SkeletonEvent" { + +SkeletonEvent(lola::Skeleton& parent, const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn) + +Send(const SampleType&) : ResultBlank + +Allocate(): Result> + -event_shared_impl_ : lola::SkeletonEventCommon +} + +class "GenericSkeletonEventBindingFactory" { + +{static} Create(SkeletonBase& parent, std::string_view event_name, const DataTypeMetaInfo& size_info): Result> +} + +class "ServiceElementMap" { + using key_type = score::cpp::stringview + using mapped_type = GenericSkeletonEvent + using value_type = std::pair + using const_iterator = LegacyBidirectionalIterator to const value_type + .. + +cbegin() : const_iterator + +cend() : const_iterator + +find() : const_iterator + +size() : std::size_t + +empty() : bool +} + +abstract class "mw::com::impl::SkeletonEventBase" #yellow { + +SkeletonEventBase(SkeletonBase&, const std::string_view, std::unique_ptr) + +OfferService(): ResultBlank + +StopOfferService(): void +} + +class "mw::com::impl::GenericSkeletonEvent" #yellow { + +GenericSkeletonEvent(SkeletonBase&, const std::string_view, std::unique_ptr) + +Send(SampleAllocateePtr sample): ResultBlank + +Allocate(): Result> + +GetSizeInfo() const : DataTypeMetaInfo +} + +abstract class "GenericSkeletonEventBinding" #yellow { + +{abstract} Send(SampleAllocateePtr sample) = 0: ResultBlank + +{abstract} Allocate() = 0: Result> + +{abstract} GetSizeInfo() const = 0: std::pair +} + +class "lola::SkeletonEventCommon" #yellow { + -parent_: lola::Skeleton& + -element_fq_id_: ElementFqId + -control_: score::cpp::optional& + -current_timestamp_: EventSlotStatus::EventTimeStamp& + +SkeletonEventCommon(lola::Skeleton&, const ElementFqId&, score::cpp::optional&, EventSlotStatus::EventTimeStamp&, impl::tracing::SkeletonEventTracingData) + +PrepareOfferCommon(): void + +PrepareStopOfferCommon(): void + +GetParent(): lola::Skeleton& + +GetElementFQId(): const ElementFqId& + +IsQmNotificationsRegistered(): bool + +IsAsilBNotificationsRegistered(): bool + +GetTracingData(): impl::tracing::SkeletonEventTracingData& +} + +class "lola::GenericSkeletonEvent" #yellow { + +GenericSkeletonEvent(lola::Skeleton& parent, const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn, const DataTypeMetaInfo& size_info, impl::tracing::SkeletonEventTracingData tracing_data) + +Send(score::mw::com::impl::SampleAllocateePtr sample): ResultBlank + +Allocate(): Result> + +GetSizeInfo() const : std::pair + +PrepareOffer(): ResultBlank + +PrepareStopOffer(): void + +GetBindingType(): BindingType + +SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data): void + +GetMaxSize() const : std::size_t + -event_shared_impl_ : lola::SkeletonEventCommon +} + +class "DummySkeleton" <> { + +DummySkeleton(const InstanceIdentifier&, MethodCallProcessingMode) + +DummyEvent : events::DummyEvent +} + +' Relationships +"score::mw::com::impl::SkeletonBase" *--> "SkeletonBinding" +"score::mw::com::impl::SkeletonBase" <|-- "DummySkeleton" +"score::mw::com::impl::SkeletonBase" <|-- "mw::com::impl::GenericSkeleton" +"mw::com::impl::GenericSkeleton" *-- "ServiceElementMap" +"SkeletonBindingFactory" ..> "SkeletonBinding" : creates +"SkeletonBinding" <|-- "lola::Skeleton" + +"mw::com::impl::SkeletonEventBase" <|-- "mw::com::impl::SkeletonEvent" +"mw::com::impl::SkeletonEventBase" <|-- "mw::com::impl::GenericSkeletonEvent" + +"SkeletonEventBindingBase" <|-- "SkeletonEventBinding" +"SkeletonEventBindingBase" <|-- "GenericSkeletonEventBinding" + +"SkeletonEventBinding" <|-- "lola::SkeletonEvent" +"GenericSkeletonEventBinding" <|-- "lola::GenericSkeletonEvent" + +"mw::com::impl::GenericSkeletonEvent" ..> "GenericSkeletonEventBindingFactory" : uses +"mw::com::impl::SkeletonEvent" ..> "GenericSkeletonEventBindingFactory" : uses + +"lola::SkeletonEvent" *-- "lola::SkeletonEventCommon" : event_shared_impl_ +"lola::GenericSkeletonEvent" *-- "lola::SkeletonEventCommon" : event_shared_impl_ + +"lola::SkeletonEventCommon" o-- "1" "lola::Skeleton" + +"DummySkeleton" *--> "mw::com::impl::SkeletonEvent" : "0..n" +"mw::com::impl::GenericSkeleton" *--> "mw::com::impl::GenericSkeletonEvent" : "0..n" + +@enduml \ No newline at end of file diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index 1251a7cd4..f1f87b965 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -507,7 +507,8 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec // Retrieve event using its name auto event_it = skeleton.GetEvents().find(event_name); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_it != skeleton.GetEvents().cend(), "Event not found in GenericSkeleton"); - auto* event_ptr = event_it->second; + + auto& event = const_cast(event_it->second); const auto offer_result = skeleton.OfferService(); if (!offer_result.has_value()) @@ -519,7 +520,7 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec for (std::size_t cycle = 0U; cycle < num_cycles || num_cycles == 0U; ++cycle) { - auto sample_result = PrepareMapLaneSample(*event_ptr, cycle); + auto sample_result = PrepareMapLaneSample(event, cycle); if (!sample_result.has_value()) { std::cerr << "No sample received. Exiting.\n"; @@ -529,7 +530,7 @@ int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpec { std::lock_guard lock{event_sending_mutex_}; - event_ptr->Send(std::move(sample)); + event.Send(std::move(sample)); event_published_ = true; } std::this_thread::sleep_for(cycle_time); diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index 9690618e3..a8594d910 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -20,18 +20,15 @@ namespace score::mw::com::impl::lola { GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, - const ElementFqId& event_fqn, - std::string_view event_name, - const SkeletonEventProperties& event_properties, - const DataTypeMetaInfo& size_info, - impl::tracing::SkeletonEventTracingData tracing_data) + const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, + const DataTypeMetaInfo& size_info, + impl::tracing::SkeletonEventTracingData tracing_data) : size_info_(size_info), event_properties_(event_properties), event_shared_impl_(parent, event_fqn, control_, current_timestamp_, tracing_data) { - // The factory passes the name, but we don't store it in this class currently. - // Casting to void suppresses "unused parameter" compiler warnings. - static_cast(event_name); + } ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index 120574fc3..bab500692 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -19,7 +19,6 @@ #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_common.h" -#include namespace score::mw::com::impl::lola { @@ -33,9 +32,8 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding public: GenericSkeletonEvent(Skeleton& parent, - const ElementFqId& event_fqn, - std::string_view event_name, const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, const DataTypeMetaInfo& size_info, impl::tracing::SkeletonEventTracingData tracing_data = {}); diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp index 79b0719b3..6d7df56b0 100644 --- a/score/mw/com/impl/generic_skeleton.cpp +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -14,15 +14,16 @@ #include "score/mw/com/impl/com_error.h" #include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" -//#include "score/mw/com/impl/plumbing/generic_skeleton_field_binding_factory.h" //not implemented yet #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" #include "score/mw/com/impl/skeleton_binding.h" +#include "score/mw/com/impl/configuration/lola_service_type_deployment.h" #include #include #include +#include namespace score::mw::com::impl { @@ -84,10 +85,10 @@ Result GenericSkeleton::Create( // 1. Create the Skeleton (Private Constructor) GenericSkeleton skeleton(identifier, std::move(binding), mode); - // 2. Atomically create all events - for (const auto& info : in.events) // Iterate directly over in.events + // 2. Create events directly in the map + for (const auto& info : in.events) { - // Check for duplicates before creating the binding to prevent unnecessary work and potential memory issues. + // Check for duplicates if (skeleton.events_.find(info.name) != skeleton.events_.cend()) { score::mw::log::LogError("GenericSkeleton") << "Duplicate event name provided: " << info.name; @@ -103,53 +104,30 @@ Result GenericSkeleton::Create( return MakeUnexpected(ComErrc::kBindingFailure); } - // Create the event binding - auto event_binding_result = - GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); + auto event_binding_result = GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); if (!event_binding_result.has_value()) { - // If any event fails to bind, the whole creation fails (Atomic). Error logging is typically handled within the factory. return MakeUnexpected(ComErrc::kBindingFailure); } - // 2. Create object in Vector (Ownership & Stability) - // Pass stable_name to constructor - skeleton.owned_events_.push_back(std::make_unique( - skeleton, stable_name, std::move(event_binding_result).value())); - - // 3. Get Pointer to the stable object - auto* event_ptr = skeleton.owned_events_.back().get(); - - // 4. Store Pointer in Map using the stable_name retrieved from config - skeleton.events_.emplace(stable_name, event_ptr); + const auto emplace_result = skeleton.events_.emplace( + std::piecewise_construct, + std::forward_as_tuple(stable_name), + std::forward_as_tuple( + skeleton, + stable_name, + std::move(event_binding_result).value() + ) + ); + + if (!emplace_result.second) + { + score::mw::log::LogError("GenericSkeleton") << "Failed to emplace event in map: " << info.name; + return MakeUnexpected(ComErrc::kBindingFailure); + } } - // // 4. Atomically create all fields - // for (const auto& info : in.fields) // Iterate directly over in.fields - // { - // // Create the field binding - // auto field_binding_result = - // GenericSkeletonFieldBindingFactory::Create(skeleton, info.name, info.data_type_meta_info); - - // if (!field_binding_result.has_value()) - // { - // // If any field fails to bind, the whole creation fails (Atomic). Error logging is typically handled within the factory. - // return MakeUnexpected(ComErrc::kBindingFailure); - // } - - // // Store the field in the map - // const auto emplace_result = skeleton.fields_.emplace( - // std::piecewise_construct, - // std::forward_as_tuple(info.name), - // std::forward_as_tuple(skeleton, info.name, std::move(field_binding_result).value(), info.initial_value_bytes)); - - // if (!emplace_result.second) { - // score::mw::log::LogError("GenericSkeleton") << "Failed to emplace GenericSkeletonField for name: " << info.name; - // return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); - // } - // } - return skeleton; } @@ -158,11 +136,6 @@ const GenericSkeleton::EventMap& GenericSkeleton::GetEvents() const noexcept return events_; } -// const GenericSkeleton::FieldMap& GenericSkeleton::GetFields() const noexcept -// { -// return fields_; -// } - Result GenericSkeleton::OfferService() noexcept { return SkeletonBase::OfferService(); diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h index 5aead0651..33bad87c7 100644 --- a/score/mw/com/impl/generic_skeleton.h +++ b/score/mw/com/impl/generic_skeleton.h @@ -62,7 +62,7 @@ struct GenericSkeletonCreateParams class GenericSkeleton : public SkeletonBase { public: - using EventMap = ServiceElementMap; + using EventMap = ServiceElementMap; // using FieldMap = ServiceElementMap; // commented out as field not implemented /// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically. /// @@ -107,7 +107,6 @@ void StopOfferService() noexcept; // Private constructor, only callable by static Create methods. GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding, MethodCallProcessingMode mode); - std::vector> owned_events_; /// @brief This map owns all GenericSkeletonEvent instances. EventMap events_; diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp index aaa01a924..4ed3bf374 100644 --- a/score/mw/com/impl/generic_skeleton_event_test.cpp +++ b/score/mw/com/impl/generic_skeleton_event_test.cpp @@ -128,7 +128,8 @@ TEST_F(GenericSkeletonEventTest, AllocateBeforeOfferReturnsError) const auto& events_map = skeleton.GetEvents(); auto it = events_map.find(event_name); ASSERT_NE(it, events_map.cend()); - auto* event = it->second; + + auto* event = const_cast(&it->second); // When calling Allocate() before OfferService() auto alloc_result = event->Allocate(); @@ -161,7 +162,8 @@ TEST_F(GenericSkeletonEventTest, SendBeforeOfferReturnsError) ASSERT_TRUE(skeleton_result.has_value()); auto& skeleton = skeleton_result.value(); - auto* event = skeleton.GetEvents().find(event_name)->second; + + auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); // And a valid sample to send mock_binding::SampleAllocateePtr dummy_sample{nullptr, [](void*){}}; @@ -198,7 +200,8 @@ TEST_F(GenericSkeletonEventTest, AllocateAndSendDispatchesToBindingAfterOffer) ); ASSERT_TRUE(skeleton_result.has_value()); auto& skeleton = skeleton_result.value(); - auto* event = skeleton.GetEvents().find(event_name)->second; + + auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); // And Given the service is Offered EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{})); diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 6e0cf19c6..0f493e82f 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -51,7 +51,7 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( { score::mw::log::LogFatal("lola") << "Could not create SkeletonEventProperties from ServiceElementInstanceDeployment. Number of sample slots " - "was not specified in the configuration. Terminating."; + "was not specified in the configuration. Terminating."; std::terminate(); } @@ -59,7 +59,7 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( { score::mw::log::LogFatal("lola") << "Could not create SkeletonEventProperties from ServiceElementInstanceDeployment. Max subscribers was " - "not specified in the configuration. Terminating."; + "not specified in the configuration. Terminating."; std::terminate(); } return lola::SkeletonEventProperties{lola_service_element_instance_deployment.GetNumberOfSampleSlots().value(), @@ -95,7 +95,7 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, if (lola_parent == nullptr) { score::mw::log::LogFatal("lola") << "Skeleton service element could not be created because parent " - "skeleton binding is a nullptr."; + "skeleton binding is a nullptr."; return nullptr; } @@ -174,10 +174,8 @@ auto CreateGenericSkeletonServiceElement(const InstanceIdentifier& identifier, lola_service_instance_deployment.instance_id_.value().GetId(), element_type}; - // Generic Constructor Call (Matches your updated GenericSkeletonEvent class) - // Order: Parent, FqId, Name, Properties, MetaInfo return std::make_unique( - *lola_parent, element_fq_id, service_element_name, skeleton_event_properties, meta_info); + *lola_parent, skeleton_event_properties, element_fq_id, meta_info, tracing::SkeletonEventTracingData{} ); }, [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { return nullptr; From c42fe16e5452fe6ebccb738f9e3082fcea4d8015 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Mon, 16 Feb 2026 11:19:23 +0200 Subject: [PATCH 13/18] Update GenericSkeleton Readme file & Update data block handling --- .../skeleton_proxy/generic_skeleton/README.md | 64 +++++++++++++++---- score/mw/com/impl/bindings/lola/proxy_event.h | 33 ++++++++-- score/mw/com/impl/bindings/lola/skeleton.cpp | 10 ++- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md index 1f0abc5f7..216ab330a 100644 --- a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md +++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md @@ -37,12 +37,6 @@ that any deployment update affecting a `LoLa` application needs an application r ## Architecture enhancements for GenericSkeleton support -The main addition to the architecture for the interaction between a service providing instance (skeleton) and a service -consuming instance (proxy) is, that there now is some additional meta-data needed in the `lola::ServiceDataStorage`, -which resides in the shared-memory object containing the service instance specific data. This `lola::ServiceDataStorage` -is already described in the design details of our data layout in the shared memory -here - Before introduction of `GenericSkeleton` support, the skeleton side constructed in the shared-memory object for **Data** an instance of `lola::ServiceDataStorage`, which is a map between an event-identifier and a slot-vector of event-data for the given event-identifier. @@ -53,12 +47,7 @@ this slot-vector, since the event datatype is known to them at compile-time. So * access therefore the vector elements as `SampleType` `GenericSkeleton`s don't know the exact type (size/layout) at compile time. However, provided with runtime meta-info (size/alignment), they must allocate and describe the slot-vector in shared memory so that consumers (Generic Proxies) can find and interpret it. So this -information has now additionally provided by the skeleton. So we extend `lola::ServiceDataStorage` with an additional -meta-data section: - -I.e. beside the given map for the event data slots a second map is provided, which gives the meta-info for each event -provided by the service: -`score::memory::shared::Map events_metainfo_` +information has now additionally provided by the skeleton. ### Meta-Info @@ -96,7 +85,9 @@ specification) class (see Sys-Req-13525992), derived from `impl::SkeletonBase` (like `DummySkeleton` example) ### Creation and Configuration -Runtime instantiation is handled via the static `GenericSkeleton::Create` method, which accepts `GenericSkeletonCreateParams`. This structure allows users to define the service elements dynamically without compile-time type information. +Unlike strongly typed skeletons, a `GenericSkeleton` is instantiated dynamically at runtime. Because it lacks compile-time type information, the user must provide the necessary meta-information (such as memory size and alignment) for each service element (events and fields) during the creation phase. + +This configuration is passed via the `GenericSkeletonCreateParams` struct, which accepts spans of element configurations. ```cpp struct EventInfo { @@ -108,6 +99,53 @@ struct GenericSkeletonCreateParams { score::cpp::span events{}; }; ``` +#### Adding Events +To add an event to a Generic Skeleton, you must populate an `EventInfo` struct. The `EventInfo` requires: +1. **`name`**: The exact string name of the event as defined in the deployment configuration (e.g., `mw_com_config.json`). +2. **`data_type_meta_info`**: A `DataTypeMetaInfo` struct containing the `sizeof()` and `alignof()` the payload data type. This is crucial for the middleware to allocate appropriately sized shared memory slots. + +These `EventInfo` structs are then collected into a span and passed to the `GenericSkeleton::Create` method. + +**Example: Adding an Event to a Generic Skeleton** + +```cpp +#include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/data_type_meta_info.h" +#include + +// 1. Define the event name and calculate its memory requirements +const auto event_name = "map_api_lanes_stamped"; +const score::mw::com::impl::DataTypeMetaInfo size_info{ + sizeof(MapApiLanesStamped), + alignof(MapApiLanesStamped) +}; + +// 2. Populate the EventInfo struct +const std::vector events_vec = { + {event_name, size_info} +}; + +// 3. Assign the events to the creation parameters +score::mw::com::impl::GenericSkeletonCreateParams create_params; +create_params.events = events_vec; + +// 4. Create the Generic Skeleton +auto create_result = score::mw::com::impl::GenericSkeleton::Create( + instance_specifier, + create_params +); + +if (create_result.has_value()) { + auto& skeleton = create_result.value(); + + // 5. Retrieve the event by name to use it + auto event_it = skeleton.GetEvents().find(event_name); + if (event_it != skeleton.GetEvents().cend()) { + auto& generic_event = event_it->second; + // Proceed with skeleton.OfferService() and generic_event.Send(...) + } +} +``` The `GenericSkeleton`contains one member `events_`, which is a map of `impl::GenericSkeletonEvent` in the form of `mw::com::EventMap`. diff --git a/score/mw/com/impl/bindings/lola/proxy_event.h b/score/mw/com/impl/bindings/lola/proxy_event.h index 1be45fa0f..a82a66e00 100644 --- a/score/mw/com/impl/bindings/lola/proxy_event.h +++ b/score/mw/com/impl/bindings/lola/proxy_event.h @@ -20,6 +20,9 @@ #include "score/mw/com/impl/subscription_state.h" #include "score/mw/com/impl/tracing/i_tracing_runtime.h" +#include "score/memory/shared/pointer_arithmetic_util.h" +#include "score/language/safecpp/safe_math/safe_math.h" + #include "score/result/result.h" #include "score/mw/log/logging.h" @@ -64,7 +67,8 @@ class ProxyEvent final : public ProxyEventBinding ProxyEvent(Proxy& parent, const ElementFqId element_fq_id, const std::string_view event_name) : ProxyEventBinding{}, proxy_event_common_{parent, element_fq_id, event_name}, - samples_{parent.GetEventDataStorage(element_fq_id)} + samples_{parent.GetEventDataStorage(element_fq_id)}, + meta_info_{parent.GetEventMetaInfo(element_fq_id)} { } @@ -127,6 +131,7 @@ class ProxyEvent final : public ProxyEventBinding ProxyEventCommon proxy_event_common_; const EventDataStorage& samples_; + const EventMetaInfo& meta_info_; }; template @@ -187,14 +192,34 @@ inline Result ProxyEvent::GetNewSamplesImpl(Callback&& SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(transaction_log_index.has_value(), "GetNewSamplesImpl should only be called after a TransactionLog has been registered."); + // Retrieve Meta Info to support Generic Skeleton (Runtime Alignment) + const std::size_t sample_size = meta_info_.data_type_info_.size; + const std::size_t sample_alignment = meta_info_.data_type_info_.alignment; + + // Calculate stride manually to handle runtime alignment requirements + const std::size_t aligned_stride = + memory::shared::CalculateAlignedSize(sample_size, sample_alignment); + + // Get Base Pointer as VOID* then UINT8* for byte-level arithmetic + const void* base_ptr_void = static_cast(samples_.data()); + const std::uint8_t* base_ptr_u8 = static_cast(base_ptr_void); + for (auto slot_indicator_it = slot_indicators.begin; slot_indicator_it != slot_indicators.end; ++slot_indicator_it) { - const SampleType& sample_data{samples_.at(static_cast(slot_indicator_it->GetIndex()))}; + // Calculate Offset: Index * Stride + const std::size_t offset = safe_math::Multiply(aligned_stride, static_cast(slot_indicator_it->GetIndex())).value(); + + // Get Slot Address as VOID* + const void* slot_address_void = static_cast(base_ptr_u8 + offset); + + // Safe STATIC_CAST to SampleType* (Allowed because we originated from a correctly aligned void*) + const SampleType* sample_data_ptr = static_cast(slot_address_void); + const EventSlotStatus event_slot_status{slot_indicator_it->GetSlot().load()}; const EventSlotStatus::EventTimeStamp sample_timestamp{event_slot_status.GetTimeStamp()}; SamplePtr sample{ - &sample_data, event_control.data_control, *slot_indicator_it, transaction_log_index.value()}; + sample_data_ptr, event_control.data_control, *slot_indicator_it, transaction_log_index.value()}; auto guard = std::move(*tracker.TakeGuard()); auto sample_binding_independent = this->MakeSamplePtr(std::move(sample), std::move(guard)); @@ -217,4 +242,4 @@ inline Result ProxyEvent::GetNewSamplesImpl(Callback&& } // namespace score::mw::com::impl::lola -#endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_PROXY_EVENT_H +#endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_PROXY_EVENT_H \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index ed52b1e3c..141e87b11 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -969,13 +969,19 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( size_t sample_size, size_t sample_alignment) noexcept { + // Calculate the total size required, respecting alignment padding between elements const auto aligned_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); const auto total_data_size = aligned_size * element_properties.number_of_slots; - // 1. Construct the Vector Object (Typed Proxy needs this) + // We pass 'sample_alignment' to the Allocator. Even though T is uint8_t (align 1), + // the allocator will force the underlying memory block to start at an address + // aligned to 'sample_alignment'. auto* vector_ptr = storage_resource_->construct>( total_data_size, - memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy()) + memory::shared::PolymorphicOffsetPtrAllocator( + storage_resource_->getMemoryResourceProxy(), + sample_alignment + ) ); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(vector_ptr != nullptr, From a9c4bfca3057358e2542cb3eb5f7dfc3f1cf2007 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 25 Feb 2026 05:03:24 +0200 Subject: [PATCH 14/18] Update Readme for GS and shared memory alignment using std::max_align_t --- .../skeleton_proxy/generic_skeleton/README.md | 207 +++++------------- .../bindings/lola/generic_skeleton_event.cpp | 14 +- score/mw/com/impl/bindings/lola/proxy_event.h | 31 +-- score/mw/com/impl/bindings/lola/skeleton.cpp | 48 ++-- 4 files changed, 85 insertions(+), 215 deletions(-) diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md index 216ab330a..195ec2929 100644 --- a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md +++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md @@ -1,185 +1,86 @@ -# Support for Generic Skeletons +# Generic Skeleton -## Introduction +A `GenericSkeleton` is a provider-side (skeleton) object that can be configured at runtime to offer any service. Unlike standard, code-generated skeletons, it is not tied to a specific service interface at compile-time. -Generic skeletons are skeletons, which are not generated based on compile-time info about types used within the service -interface. Instead, they are skeletons, which can be instantiated at runtime and be connected to any service consuming -instance (proxy) just based on deployment information. +It operates on raw data , making it ideal for applications that need to handle data without understanding its structure, such as: +* **Gateways/Bridges**: Forwarding data between different communication protocols. -Since such generic skeletons are loosely typed (don't have the strong type info of the services event/field/service-method -data-types), they provide a different C++ API opposed to normal (strongly typed) skeletons. +## How to Use a Generic Skeleton -The use cases for such restricted generic skeletons are typically for dynamically providing "raw data" in terms of blobs -from service providers. Interpreting the blob data semantically is obviously hard to accomplish. -If this is needed (normal use case) one should use the normal/strongly typed skeletons. +Using a `GenericSkeleton` requires two steps: defining the service in the deployment configuration and creating the skeleton instance with its runtime configuration. +### 1. Prerequisite: Deployment Configuration -## LoLa supported feature set for Generic Skeletons +The service instance must be defined in the `mw_com_config.json` file, just like any other service. -Since `LoLa`s support for fields is restricted to only the event-functionality of fields, we will in a 1st step **only** -implement event specific functionality for the `Generic Skeleton`. +### 2. Creating and Using the Skeleton -This comprises the following items: -* `GenericSkeleton` class: We will provide a class `GenericSkeleton`, but opposed to the concept (linked above) **not** in - the `ara::com`, but in the `mw::com` namespace. -* This `GenericSkeleton` class will (in relation to the concept) **only** provide a public method `GetEvents()`, which - returns a `GenericSkeleton::EventMap` (which is a stripped down `std::map`) -* this map returned by `GetEvents()` will only contain all the events mentioned in the service deployment. Later (when - our fields get extended with get/set method functionality). -* This `GenericSkeleton` will be based on the signatures of our current `LoLa` typed skeleton class: - * base skeleton interface - * skeleton event interface +A `GenericSkeleton` is created by providing it with metadata about the service elements (e.g., events, fields) it will offer. This metadata, including memory size and alignment for each element, is passed via the `GenericSkeletonCreateParams` struct. -There will be **no** changes done on `LoLa` configuration approach. This means the `LoLa` configuration (deployment of -service instances) is processed once at startup. The deployment information of a service instance, to which a -`GenericSkeleton` shall offer, needs to be provided in the `LoLa` configuration at application startup. This means, -that any deployment update affecting a `LoLa` application needs an application restart to become effective. +The following example demonstrates how to create a generic skeleton that provides a single event. -## Architecture enhancements for GenericSkeleton support - -Before introduction of `GenericSkeleton` support, the skeleton side constructed in the shared-memory object for **Data** an -instance of `lola::ServiceDataStorage`, which is a map between an event-identifier and a slot-vector of event-data for -the given event-identifier. -When strongly typed skeletons offer to this shared-memory object for data, they exactly know the type (size/layout) of -this slot-vector, since the event datatype is known to them at compile-time. So they are able to: -* cast the slot-vector of event-data, which is held within the map as a `OffsetPtr` to the correctly typed vector - (`score::memory::shared::Vector`) -* access therefore the vector elements as `SampleType` - -`GenericSkeleton`s don't know the exact type (size/layout) at compile time. However, provided with runtime meta-info (size/alignment), they must allocate and describe the slot-vector in shared memory so that consumers (Generic Proxies) can find and interpret it. So this -information has now additionally provided by the skeleton. - -### Meta-Info - -The `lola::EventMetaInfo` is displayed in the class diagram for shared-mem data. -It contains meta-info of the events data type in the form of `lola::DataTypeMetaInfo` and an `OffsetPtr` to the -location, where the event-slot vector is stored. With this combined information a `GenericProxy`, which just knows the -deployment info (essentially the `ElementFqId` of the event) is able to access events in the event-slot vector in its raw -form (as a byte array). - -The `lola::DataTypeMetaInfo` contains the following members: -* `fingerprint_` : unique fingerprint for the data type. This is a preparation/placeholder for an upcoming feature, - which will allow doing a runtime check at the proxy side during connection to the service provider, whether provider - and consumer side are based on the same C++ interface definition. -* `size_of_` : the result of `sizeof()` operator on the data type (in case of event/field: `SampleType`) -* `align_of_` : the result of `alignof()` operator on the data type (in case of event/field: `SampleType`) - -### initialization of `lola::EventMetaInfo` - -The map containing the meta-information gets initialized by the skeleton instance together with the setup/creation of -the vectors containing the sample slots via `RegisterGeneric` (or the internal `CreateEventDataFromOpenedSharedMemory` helper). -```cpp -std::pair,EventDataControlComposite> -lola::Skeleton::RegisterGeneric(const ElementFqId element_fq_id, - const SkeletonEventProperties& element_properties, - const size_t sample_size, - const size_t sample_alignment) -``` -This happens during initial offering-phase of a service instance, before potential consumers/proxies are able to access -the shared-memory object. `GenericSkeleton`s will utilize this mechanism to register their events. - -## GenericSkeleton class - -As shown in the class diagram, below, the `mw::com::impl::GenericSkeleton` is **not** a generated (from some interface -specification) class (see Sys-Req-13525992), derived from -`impl::SkeletonBase` (like `DummySkeleton` example) - -### Creation and Configuration -Unlike strongly typed skeletons, a `GenericSkeleton` is instantiated dynamically at runtime. Because it lacks compile-time type information, the user must provide the necessary meta-information (such as memory size and alignment) for each service element (events and fields) during the creation phase. - -This configuration is passed via the `GenericSkeletonCreateParams` struct, which accepts spans of element configurations. - -```cpp -struct EventInfo { - std::string_view name; - DataTypeMetaInfo data_type_meta_info; // contains size and alignment -}; - -struct GenericSkeletonCreateParams { - score::cpp::span events{}; -}; -``` -#### Adding Events -To add an event to a Generic Skeleton, you must populate an `EventInfo` struct. The `EventInfo` requires: -1. **`name`**: The exact string name of the event as defined in the deployment configuration (e.g., `mw_com_config.json`). -2. **`data_type_meta_info`**: A `DataTypeMetaInfo` struct containing the `sizeof()` and `alignof()` the payload data type. This is crucial for the middleware to allocate appropriately sized shared memory slots. - -These `EventInfo` structs are then collected into a span and passed to the `GenericSkeleton::Create` method. - -**Example: Adding an Event to a Generic Skeleton** +**Example: Creating a Generic Skeleton with an Event** ```cpp #include "score/mw/com/impl/generic_skeleton.h" #include "score/mw/com/impl/data_type_meta_info.h" #include -// 1. Define the event name and calculate its memory requirements +// The service instance specifier, as defined in mw_com_config.json +// const score::mw::com::InstanceSpecifier instance_specifier = ...; + +// 1. Define the metadata for the event. +// The name must match the service definition. const auto event_name = "map_api_lanes_stamped"; -const score::mw::com::impl::DataTypeMetaInfo size_info{ +// The middleware needs the size and alignment for memory allocation. +const score::mw::com::impl::DataTypeMetaInfo event_meta_info{ sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped) }; -// 2. Populate the EventInfo struct -const std::vector events_vec = { - {event_name, size_info} +// 2. Collect all event definitions. +const std::vector events = { + {event_name, event_meta_info} }; -// 3. Assign the events to the creation parameters +// 3. Populate the creation parameters. +// Similar spans can be provided for fields and methods if they were supported. score::mw::com::impl::GenericSkeletonCreateParams create_params; -create_params.events = events_vec; +create_params.events = events; -// 4. Create the Generic Skeleton +// 4. Create the Generic Skeleton instance. auto create_result = score::mw::com::impl::GenericSkeleton::Create( instance_specifier, create_params ); -if (create_result.has_value()) { - auto& skeleton = create_result.value(); - - // 5. Retrieve the event by name to use it - auto event_it = skeleton.GetEvents().find(event_name); - if (event_it != skeleton.GetEvents().cend()) { - auto& generic_event = event_it->second; - // Proceed with skeleton.OfferService() and generic_event.Send(...) +if (!create_result.has_value()) { + // Handle creation error + return; +} +auto& skeleton = create_result.value(); + +// 5. Offer the service. +skeleton.OfferService(); + +// 6. Retrieve the event by name to send data. +auto event_it = skeleton.GetEvents().find(event_name); +if (event_it != skeleton.GetEvents().cend()) { + // Get a non-const reference to the event to call non-const methods. + auto& generic_event = const_cast(event_it->second); + + // Allocate a sample from the middleware. The size is known from the metadata + auto allocate_result = generic_event.Allocate(); + if (allocate_result.has_value()) { + auto sample = std::move(allocate_result.value()); + + // Get the raw pointer from the smart pointer for populating the data. + auto* typed_sample = static_cast(sample.Get()); + + // ... populate the typed_sample ... + + // Send the sample. + generic_event.Send(std::move(sample)); } } -``` - -The `GenericSkeleton`contains one member `events_`, which is a map of -`impl::GenericSkeletonEvent` in the form of `mw::com::EventMap`. - -GENERIC_SKELETON_MODEL - -Classes drawn with yellow background are extensions of the class diagram to support `GenericSkeleton` functionality beside -"normal" skeletons. -While there is a new `impl::GenericSkeleton` in the binding **independent** area, we currently do not need to reflect it in -the binding specific part as `impl::SkeletonBinding` (and therefore `lola::Skeleton` implementing `impl::SkeletonBinding`) -doesn't need any extensions for `GenericSkeleton` functionality. -On the level below the skeleton (skeleton-event level) this looks different. In the binding independent part we introduced the -non-templated class `impl::GenericSkeletonEvent` beside the existing class template `impl::SkeletonEvent`. -Both derive from `impl::SkeletonEventBase`, which contains all the `SampleType` agnostic/independent signatures/ -implementations a skeleton-event needs to have. -The methods `Send()` and `Allocate()` are `SampleType` specific (via their arguments, which depend on the `SampleType`). -Therefore, they aren't contained in `impl::SkeletonEventBase` but specifically implemented in `impl::GenericSkeletonEvent` and -`impl::SkeletonEvent`. - -We have a similar setup on the binding side: `impl::GenericSkeletonEventBinding` has been introduced beside -`impl::SkeletonEventBinding` as abstract classes/interface definitions for "normal", respectively generic -skeleton-event bindings. -Their common abstract base class `impl::SkeletonEventBindingBase` aggregates all interfaces, which are `SampleType` -agnostic and are common to both skeleton-event bindings. - -On `LoLa` implementation level for the skeleton-event binding, we added `lola::GenericSkeletonEvent` beside -`lola::SkeletonEvent`. -To factor out common code/implementations for all signatures, which are `SampleType` agnostic and common to both -`lola::GenericSkeletonEvent` and `lola::SkeletonEvent` -the class `lola::SkeletonEventCommon` has been -introduced, which implements this common code. I.e. instances of `lola::GenericSkeletonEvent` and `lola::SkeletonEvent` -create such an instance and dispatch the corresponding methods to it. We decided to go for this composite/dispatch -approach instead of introducing a common base class to `lola::GenericSkeletonEvent` and `lola::SkeletonEvent`, -which provides/implements those signatures as this would have meant, that `lola::GenericSkeletonEvent` and -`lola::SkeletonEvent` had multi-inheritance (from this base class and their corresponding interface)! -Even if this hadn't been problematic (in terms of potential diamond pattern), we would have to explicitly argue/analyse -it due to the `ASIL-B` nature of our code base and the general avoidance pattern of multi-inheritance. +``` \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index a8594d910..fe84bb776 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -92,14 +92,18 @@ Result> GenericSkeletonEvent::All if (slot.IsValidQM() || slot.IsValidAsilB()) { - void* base_ptr = data_storage_.get(); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(base_ptr != nullptr); + // Get the actual CONTAINER object (using the max_align_t type we allocated it with!) + using StorageType = lola::EventDataStorage; + StorageType* storage_ptr = data_storage_.get(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(storage_ptr != nullptr); - const auto aligned_size = memory::shared::CalculateAlignedSize(size_info_.size, size_info_.alignment); - std::uint8_t* byte_ptr = static_cast(base_ptr); - std::uint64_t offset = static_cast(slot.GetIndex()) * aligned_size; + std::uint8_t* byte_ptr = reinterpret_cast(storage_ptr->data()); + // Calculate the exact slot spacing based on alignment padding + const auto aligned_size = memory::shared::CalculateAlignedSize(size_info_.size, size_info_.alignment); + std::size_t offset = static_cast(slot.GetIndex()) * aligned_size; void* data_ptr = byte_ptr + offset; + auto lola_ptr = lola::SampleAllocateePtr(data_ptr, control_.value(), slot); return impl::MakeSampleAllocateePtr(std::move(lola_ptr)); } diff --git a/score/mw/com/impl/bindings/lola/proxy_event.h b/score/mw/com/impl/bindings/lola/proxy_event.h index a82a66e00..d22bdf9af 100644 --- a/score/mw/com/impl/bindings/lola/proxy_event.h +++ b/score/mw/com/impl/bindings/lola/proxy_event.h @@ -20,9 +20,6 @@ #include "score/mw/com/impl/subscription_state.h" #include "score/mw/com/impl/tracing/i_tracing_runtime.h" -#include "score/memory/shared/pointer_arithmetic_util.h" -#include "score/language/safecpp/safe_math/safe_math.h" - #include "score/result/result.h" #include "score/mw/log/logging.h" @@ -67,8 +64,7 @@ class ProxyEvent final : public ProxyEventBinding ProxyEvent(Proxy& parent, const ElementFqId element_fq_id, const std::string_view event_name) : ProxyEventBinding{}, proxy_event_common_{parent, element_fq_id, event_name}, - samples_{parent.GetEventDataStorage(element_fq_id)}, - meta_info_{parent.GetEventMetaInfo(element_fq_id)} + samples_{parent.GetEventDataStorage(element_fq_id)} { } @@ -131,7 +127,6 @@ class ProxyEvent final : public ProxyEventBinding ProxyEventCommon proxy_event_common_; const EventDataStorage& samples_; - const EventMetaInfo& meta_info_; }; template @@ -192,34 +187,14 @@ inline Result ProxyEvent::GetNewSamplesImpl(Callback&& SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(transaction_log_index.has_value(), "GetNewSamplesImpl should only be called after a TransactionLog has been registered."); - // Retrieve Meta Info to support Generic Skeleton (Runtime Alignment) - const std::size_t sample_size = meta_info_.data_type_info_.size; - const std::size_t sample_alignment = meta_info_.data_type_info_.alignment; - - // Calculate stride manually to handle runtime alignment requirements - const std::size_t aligned_stride = - memory::shared::CalculateAlignedSize(sample_size, sample_alignment); - - // Get Base Pointer as VOID* then UINT8* for byte-level arithmetic - const void* base_ptr_void = static_cast(samples_.data()); - const std::uint8_t* base_ptr_u8 = static_cast(base_ptr_void); - for (auto slot_indicator_it = slot_indicators.begin; slot_indicator_it != slot_indicators.end; ++slot_indicator_it) { - // Calculate Offset: Index * Stride - const std::size_t offset = safe_math::Multiply(aligned_stride, static_cast(slot_indicator_it->GetIndex())).value(); - - // Get Slot Address as VOID* - const void* slot_address_void = static_cast(base_ptr_u8 + offset); - - // Safe STATIC_CAST to SampleType* (Allowed because we originated from a correctly aligned void*) - const SampleType* sample_data_ptr = static_cast(slot_address_void); - + const SampleType& sample_data{samples_.at(static_cast(slot_indicator_it->GetIndex()))}; const EventSlotStatus event_slot_status{slot_indicator_it->GetSlot().load()}; const EventSlotStatus::EventTimeStamp sample_timestamp{event_slot_status.GetTimeStamp()}; SamplePtr sample{ - sample_data_ptr, event_control.data_control, *slot_indicator_it, transaction_log_index.value()}; + &sample_data, event_control.data_control, *slot_indicator_it, transaction_log_index.value()}; auto guard = std::move(*tracker.TakeGuard()); auto sample_binding_independent = this->MakeSamplePtr(std::move(sample), std::move(guard)); diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 141e87b11..f9c8e87e8 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -968,48 +968,38 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( const SkeletonEventProperties& element_properties, size_t sample_size, size_t sample_alignment) noexcept -{ - // Calculate the total size required, respecting alignment padding between elements - const auto aligned_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); - const auto total_data_size = aligned_size * element_properties.number_of_slots; - - // We pass 'sample_alignment' to the Allocator. Even though T is uint8_t (align 1), - // the allocator will force the underlying memory block to start at an address - // aligned to 'sample_alignment'. - auto* vector_ptr = storage_resource_->construct>( - total_data_size, - memory::shared::PolymorphicOffsetPtrAllocator( - storage_resource_->getMemoryResourceProxy(), - sample_alignment - ) - ); - - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(vector_ptr != nullptr, - "Failed to construct Generic Vector in Shared Memory"); - - // 2. Register the VECTOR OBJECT in the Data Map (for Typed Proxy) - void* vector_obj_void = static_cast(vector_ptr); +{ + // Calculate the aligned size for a single sample to ensure proper padding between slots + const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); + const auto total_data_size_bytes = aligned_sample_size * element_properties.number_of_slots; + + // Convert total bytes to the number of std::max_align_t elements needed (round up) + const size_t num_max_align_elements = + (total_data_size_bytes + sizeof(std::max_align_t) - 1) / sizeof(std::max_align_t); + + auto* data_storage = storage_resource_->construct>( + num_max_align_elements, + memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy())); auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(vector_obj_void)); - + std::forward_as_tuple(data_storage)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second, "Couldn't register/emplace event-storage in data-section."); - // 3. Get the RAW DATA POINTER (The actual buffer inside the vector) - void* raw_data_ptr = static_cast(vector_ptr->data()); - const impl::DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; + + const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; + void* const event_data_raw_array = data_storage->data(); + auto inserted_meta_info = storage_->events_metainfo_.emplace( std::piecewise_construct, std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(sample_meta_info, raw_data_ptr)); + std::forward_as_tuple(sample_meta_info, event_data_raw_array)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - return {raw_data_ptr, CreateEventControlComposite(element_fq_id, element_properties)}; + return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; } - std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties, From 6150c15a3af968e72e23be1f287fe355f84940ef Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 25 Feb 2026 13:23:09 +0200 Subject: [PATCH 15/18] Guard against over-aligned types & Update TestCases --- score/mw/com/impl/bindings/lola/skeleton.cpp | 12 ++ .../com/impl/generic_skeleton_event_test.cpp | 125 ++++++++++++++++++ score/mw/com/impl/generic_skeleton_test.cpp | 48 +++++++ 3 files changed, 185 insertions(+) diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index f9c8e87e8..343621fdc 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -969,6 +969,18 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( size_t sample_size, size_t sample_alignment) noexcept { + + // Guard against over-aligned types (Short-term solution protection) + if (sample_alignment > alignof(std::max_align_t)) + { + score::mw::log::LogFatal("Skeleton") + << "Requested sample alignment (" << sample_alignment + << ") exceeds max_align_t (" << alignof(std::max_align_t) + << "). Safe shared memory layout cannot be guaranteed."; + + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(sample_alignment <= alignof(std::max_align_t),"Requested sample alignment exceeds maximum supported alignment."); + } + // Calculate the aligned size for a single sample to ensure proper padding between slots const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); const auto total_data_size_bytes = aligned_sample_size * element_properties.number_of_slots; diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp index 4ed3bf374..a283fa168 100644 --- a/score/mw/com/impl/generic_skeleton_event_test.cpp +++ b/score/mw/com/impl/generic_skeleton_event_test.cpp @@ -224,5 +224,130 @@ TEST_F(GenericSkeletonEventTest, AllocateAndSendDispatchesToBindingAfterOffer) ASSERT_TRUE(send_result.has_value()); } +TEST_F(GenericSkeletonEventTest, AllocateReturnsErrorWhenBindingFails) +{ + RecordProperty("Description", "Checks that Allocate returns kSampleAllocationFailure if the binding allocation fails."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton configured with an event binding mock + const std::string event_name = "test_event"; + auto mock_event_binding = std::make_unique>(); + auto* mock_event_binding_ptr = mock_event_binding.get(); + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::move(mock_event_binding)))); + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, {16, 8}}); + create_params.events = events; + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + auto& skeleton = skeleton_result.value(); + auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); + + // And Given the service is Offered + EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + + // Expect the binding to fail allocation + EXPECT_CALL(*mock_event_binding_ptr, Allocate()) + .WillOnce(Return(MakeUnexpected(ComErrc::kSampleAllocationFailure))); + + // When calling Allocate() + auto alloc_result = event->Allocate(); + + // Then it fails with kSampleAllocationFailure + ASSERT_FALSE(alloc_result.has_value()); + EXPECT_EQ(alloc_result.error(), ComErrc::kSampleAllocationFailure); +} + + +TEST_F(GenericSkeletonEventTest, SendReturnsErrorWhenBindingFails) +{ + RecordProperty("Description", "Checks that Send returns kBindingFailure if the binding send fails."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton configured with an event binding mock + const std::string event_name = "test_event"; + auto mock_event_binding = std::make_unique>(); + auto* mock_event_binding_ptr = mock_event_binding.get(); + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::move(mock_event_binding)))); + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, {16, 8}}); + create_params.events = events; + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + auto& skeleton = skeleton_result.value(); + auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); + + // And Given the service is Offered + EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + + // Expect the binding to fail sending + EXPECT_CALL(*mock_event_binding_ptr, Send(_)) + .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); + + // When calling Send() with a dummy sample + mock_binding::SampleAllocateePtr dummy_alloc{nullptr, [](void*){}}; + auto send_result = event->Send(MakeSampleAllocateePtr(std::move(dummy_alloc))); + + // Then it fails with kBindingFailure + ASSERT_FALSE(send_result.has_value()); + EXPECT_EQ(send_result.error(), ComErrc::kBindingFailure); +} + +TEST_F(GenericSkeletonEventTest, GetSizeInfoDispatchesToBinding) +{ + RecordProperty("Description", "Checks that GetSizeInfo returns the correct DataTypeMetaInfo from the binding."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton configured with an event binding mock + const std::string event_name = "test_event"; + auto mock_event_binding = std::make_unique>(); + auto* mock_event_binding_ptr = mock_event_binding.get(); + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::move(mock_event_binding)))); + + GenericSkeletonCreateParams create_params; + std::vector events; + events.push_back({event_name, {16, 8}}); // Original creation info + create_params.events = events; + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), + create_params + ); + ASSERT_TRUE(skeleton_result.has_value()); + auto& skeleton = skeleton_result.value(); + auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); + + // Expect the binding to return specific size info + std::pair expected_size_info{32, 16}; + EXPECT_CALL(*mock_event_binding_ptr, GetSizeInfo()) + .WillOnce(Return(expected_size_info)); + + // When calling GetSizeInfo + auto result_info = event->GetSizeInfo(); + + // Then it matches the binding's return values + EXPECT_EQ(result_info.size, expected_size_info.first); + EXPECT_EQ(result_info.alignment, expected_size_info.second); +} + } // namespace } // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp index f08df5c0a..e39305bc9 100644 --- a/score/mw/com/impl/generic_skeleton_test.cpp +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -119,6 +119,54 @@ TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifierResolvesIdentifier) ASSERT_TRUE(result.has_value()); } +TEST_F(GenericSkeletonTest, CreateWithUnresolvedInstanceSpecifierFails) +{ + RecordProperty("Description", "Checks that GenericSkeleton returns kInstanceIDCouldNotBeResolved when InstanceSpecifier cannot be resolved."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a valid string specifier + auto instance_specifier = InstanceSpecifier::Create(std::string("path/to/unknown/service")).value(); + + // Expect the Runtime to attempt to resolve it, but simulate failure by returning an empty vector + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, resolve(instance_specifier)) + .WillOnce(Return(std::vector{})); + + // When creating the skeleton + GenericSkeletonCreateParams params; + auto result = GenericSkeleton::Create(instance_specifier, params); + + // Then creation fails with the expected error + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kInstanceIDCouldNotBeResolved); +} + +TEST_F(GenericSkeletonTest, CreateFailsIfEventBindingCannotBeCreated) +{ + RecordProperty("Description", "Checks that creation fails if the GenericSkeletonEventBindingFactory returns an error for any event."); + RecordProperty("TestType", "Requirements-based test"); + + // 1. Given an identifier and configuration with one valid event + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + + std::vector event_storage; + event_storage.push_back({event_name, {16, 8}}); + + GenericSkeletonCreateParams params; + params.events = event_storage; + + // 2. Expect the Event Binding Factory to be called, but force it to FAIL + // We simulate an internal failure by returning MakeUnexpected + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); + + // 3. When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // 4. Then creation fails and correctly propagates the kBindingFailure error + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); +} TEST_F(GenericSkeletonTest, CreateWithEventsInitializesEventBindings) { From fbdb6cffef96eda5f09c7ec1a85f858112d598b5 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 25 Feb 2026 17:22:39 +0200 Subject: [PATCH 16/18] Adding Testcase to validate correct allocation --- score/mw/com/impl/bindings/lola/skeleton.cpp | 4 +-- .../bindings/lola/skeleton_event_test.cpp | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 343621fdc..91aececb7 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -980,7 +980,7 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(sample_alignment <= alignof(std::max_align_t),"Requested sample alignment exceeds maximum supported alignment."); } - + // Calculate the aligned size for a single sample to ensure proper padding between slots const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment); const auto total_data_size_bytes = aligned_sample_size * element_properties.number_of_slots; @@ -1010,7 +1010,7 @@ Skeleton::CreateEventDataFromOpenedSharedMemory( SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; + return {score::memory::shared::OffsetPtr(data_storage), CreateEventControlComposite(element_fq_id, element_properties)}; } std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( const ElementFqId element_fq_id, diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp index cf5b2a257..68d53fbe7 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp @@ -133,6 +133,41 @@ TEST_F(SkeletonEventAllocateFixture, SkeletonEventWithNotMaxSamplesEnforcementAl EXPECT_EQ(allocate_result.error(), ComErrc::kBindingFailure); } +TEST_F(SkeletonEventAllocateFixture, AllocateReturnsUniquePointersForMultipleCalls) +{ + RecordProperty("Description", "Checks that multiple calls to Allocate() return unique memory pointers."); + RecordProperty("TestType", "Unit Test"); + + const bool enforce_max_samples{true}; + const size_t num_allocations = 3; + ASSERT_LE(num_allocations, max_samples_); + + // Given an offered event + InitialiseSkeletonEvent(fake_element_fq_id_, fake_event_name_, max_samples_, max_subscribers_, enforce_max_samples); + skeleton_event_->PrepareOffer(); + + // When allocating multiple samples without sending them + std::vector> allocated_pointers; + std::vector raw_pointers; + + for (size_t i = 0; i < num_allocations; ++i) + { + auto alloc_result = skeleton_event_->Allocate(); + ASSERT_TRUE(alloc_result.has_value()) << "Allocation " << i << " failed"; + + // Store the raw pointer to check for uniqueness + raw_pointers.push_back(alloc_result.value().Get()); + + // Keep the SampleAllocateePtr alive to keep the slot busy + allocated_pointers.push_back(std::move(alloc_result.value())); + } + + // Then all allocated raw pointers should be unique + std::sort(raw_pointers.begin(), raw_pointers.end()); + auto it = std::unique(raw_pointers.begin(), raw_pointers.end()); + EXPECT_EQ(it, raw_pointers.end()) << "Duplicate memory addresses were allocated."; +} + using SkeletonEventPrepareOfferFixture = SkeletonEventFixture; TEST_F(SkeletonEventPrepareOfferFixture, SubscriptionsAcceptedIfMaxSamplesCanBeProvided) { From 45c117a31a2c4df1436ba0c894ae1f511d493d01 Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Wed, 25 Feb 2026 23:50:12 +0200 Subject: [PATCH 17/18] fix:shared_memory_storage_application --- .../shared_memory_storage_application.cpp | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp b/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp index 03555307f..8b900cb8a 100644 --- a/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp +++ b/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp @@ -37,6 +37,37 @@ #include #include + +namespace score::mw::com::impl::lola +{ + // The Attorney uses the friend declaration to access private SkeletonEvent members + template + class SkeletonEventAttorney + { + public: + static ElementFqId GetElementFQId(const SkeletonEvent& event) noexcept + { + // Access the private event_shared_impl_ and call its public GetElementFQId() + return event.event_shared_impl_.GetElementFQId(); + } + }; +} // namespace score::mw::com::impl::lola + +// Overload for SkeletonEvent (uses Attorney to bypass private access) +template +score::mw::com::impl::lola::ElementFqId ExtractId(const score::mw::com::impl::lola::SkeletonEvent* binding) +{ + return score::mw::com::impl::lola::SkeletonEventAttorney::GetElementFQId(*binding); +} + +// Overload for ProxyEvent (method is still public) +template +score::mw::com::impl::lola::ElementFqId ExtractId(const BindingType* binding) +{ + return binding->GetElementFQId(); +} + + namespace { @@ -57,7 +88,7 @@ score::cpp::optional GetElementFqId(Ser { return {}; } - return map_api_lanes_stamped_binding->GetElementFQId(); + return ExtractId(map_api_lanes_stamped_binding); } template From bdf5d83d0f857b01a2006d9a64a1ace618b897da Mon Sep 17 00:00:00 2001 From: essam SROUR Date: Thu, 26 Feb 2026 14:27:30 +0200 Subject: [PATCH 18/18] Fix Alignment --- score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs b/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs index bd89e2369..1d90a1f0e 100644 --- a/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs +++ b/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs @@ -68,7 +68,7 @@ struct AllocationVariant { _index: u8, } -#[repr(C)] +#[repr(C, align(16))] pub struct SampleAllocateePtr { _internal: AllocationVariant, }