Skip to content

Commit b7c543a

Browse files
authored
Merge pull request #439 from SebSparrowHawk/ssp_generic_skeleton_receive_handler
GenericSkeleton supports ReceiveHandler registration notification
2 parents 6fd6307 + 807f945 commit b7c543a

14 files changed

Lines changed: 426 additions & 141 deletions

score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,24 @@ class "mw::com::impl::GenericSkeletonEvent" #yellow {
121121
+Send(SampleAllocateePtr<void> sample): Result<void>
122122
+Allocate(): Result<SampleAllocateePtr<void>>
123123
+GetSizeInfo() const : DataTypeMetaInfo
124+
+SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)> callback): Result<void>
125+
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
124126
}
125127

126128
abstract class "GenericSkeletonEventBinding" #yellow {
127129
+{abstract} Send(SampleAllocateePtr<void> sample) = 0: Result<void>
128130
+{abstract} Allocate() = 0: Result<SampleAllocateePtr<void>>
129131
+{abstract} GetSizeInfo() const = 0: std::pair<size_t, size_t>
132+
+{abstract} SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)> callback) = 0: Result<void>
133+
+{abstract} UnsetReceiveHandlerRegistrationChangedHandler() = 0: Result<void>
130134
}
131135

132136
class "lola::SkeletonEventCommon" #yellow {
133137
-parent_: lola::Skeleton&
134138
-element_fq_id_: ElementFqId
135139
-control_: score::cpp::optional<EventDataControlComposite>&
136140
-current_timestamp_: EventSlotStatus::EventTimeStamp&
141+
-receive_handler_registration_changed_callback_: std::optional<lola::IMessagePassingService::HandlerStatusChangeCallback>
137142
+SkeletonEventCommon(lola::Skeleton&, const ElementFqId&, score::cpp::optional<EventDataControlComposite>&, EventSlotStatus::EventTimeStamp&, impl::tracing::SkeletonEventTracingData)
138143
+PrepareOfferCommon(): void
139144
+PrepareStopOfferCommon(): void
@@ -142,6 +147,8 @@ class "lola::SkeletonEventCommon" #yellow {
142147
+IsQmNotificationsRegistered(): bool
143148
+IsAsilBNotificationsRegistered(): bool
144149
+GetTracingData(): impl::tracing::SkeletonEventTracingData&
150+
+SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)>): Result<void>
151+
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
145152
}
146153

147154
class "lola::GenericSkeletonEvent" #yellow {
@@ -154,6 +161,8 @@ class "lola::GenericSkeletonEvent" #yellow {
154161
+GetBindingType(): BindingType
155162
+SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data): void
156163
+GetMaxSize() const : std::size_t
164+
+SetReceiveHandlerNotificationCallback(score::cpp::callback<void(bool)>): Result<void>
165+
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
157166
-skeleton_event_common_ : lola::SkeletonEventCommon
158167
}
159168

score/mw/com/impl/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ cc_library(
214214
"//score/mw/com/impl/plumbing:__pkg__",
215215
],
216216
deps = [
217+
":receive_handler_registration_changed_handler",
217218
":skeleton_event_binding",
218219
"@score_baselibs//score/result",
219220
],
@@ -842,6 +843,18 @@ cc_library(
842843
deps = ["@score_baselibs//score/language/futurecpp"],
843844
)
844845

846+
cc_library(
847+
name = "receive_handler_registration_changed_handler",
848+
srcs = ["receive_handler_registration_changed_handler.cpp"],
849+
hdrs = ["receive_handler_registration_changed_handler.h"],
850+
features = COMPILER_WARNING_FEATURES,
851+
tags = ["FFI"],
852+
visibility = [
853+
"//score/mw/com:__subpackages__",
854+
],
855+
deps = ["@score_baselibs//score/language/futurecpp"],
856+
)
857+
845858
cc_library(
846859
name = "subscription_state_change_handler",
847860
srcs = ["subscription_state_change_handler.cpp"],

score/mw/com/impl/bindings/lola/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,16 @@ cc_unit_test(
11151115
],
11161116
)
11171117

1118+
cc_unit_test(
1119+
name = "skeleton_event_common_test",
1120+
srcs = ["skeleton_event_common_test.cpp"],
1121+
features = COMPILER_WARNING_FEATURES,
1122+
deps = [
1123+
":skeleton",
1124+
"//score/mw/com/impl/bindings/lola/test:skeleton_event_test_resources",
1125+
],
1126+
)
1127+
11181128
cc_unit_test(
11191129
name = "proxy_test",
11201130
srcs = ["proxy_test.cpp"],

score/mw/com/impl/bindings/lola/generic_skeleton_event.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding
6161
return size_info_.size;
6262
}
6363

64+
/// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last
65+
/// event-notification has been unregistered.
66+
/// \detail This extension has been added to GenericSkeletonEvent only (not "typed" SkeletonEvent),
67+
/// because we are only using it so far in the gateway use case, where the gateway uses only
68+
/// GenericProxy/GenericSkeleton and not typed proxies/skeletons.
69+
Result<void> SetReceiveHandlerRegistrationChangedHandler(
70+
ReceiveHandlerRegistrationChangedCallback callback) noexcept override
71+
{
72+
return skeleton_event_common_.SetReceiveHandlerRegistrationChangedHandler(std::move(callback));
73+
}
74+
75+
/// \brief Unset the callback for ReceiveHandler registration change notifications.
76+
Result<void> UnsetReceiveHandlerRegistrationChangedHandler() noexcept override
77+
{
78+
return skeleton_event_common_.UnsetReceiveHandlerRegistrationChangedHandler();
79+
}
80+
6481
private:
6582
DataTypeMetaInfo size_info_;
6683
std::uint8_t* event_data_storage_;

score/mw/com/impl/bindings/lola/skeleton_event_common.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h"
2525
#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h"
2626
#include "score/mw/com/impl/configuration/quality_type.h"
27+
#include "score/mw/com/impl/generic_skeleton_event_binding.h"
2728
#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
2829
#include "score/mw/com/impl/runtime.h"
2930
#include "score/mw/com/impl/skeleton_event_binding.h"
@@ -56,6 +57,8 @@ class SkeletonEventCommon
5657
// private members and used for testing purposes only.
5758
friend class SkeletonEventAttorney<SampleType>;
5859

60+
using ReceiveHandlerRegistrationChangedCallback = lola::IMessagePassingService::HandlerStatusChangeCallback;
61+
5962
public:
6063
SkeletonEventCommon(Skeleton& parent,
6164
const std::string_view event_name,
@@ -113,6 +116,31 @@ class SkeletonEventCommon
113116
return consumer_control_local_view_qm_.value();
114117
}
115118

119+
/// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last
120+
/// event-notification has been unregistered.
121+
/// \detail This extension has been added to GenericSkeletonEvent only (not "typed" SkeletonEvent), because we
122+
/// are only using it so far in the gateway use case, where the gateway use only GenericProxy/GenericSkeleton and
123+
/// not typed proxies/skeletons.
124+
/// \attention The callback must be set before OfferService() has been called in order to avoid any race
125+
/// conditions between setting the callback and it being called.
126+
Result<void> SetReceiveHandlerRegistrationChangedHandler(
127+
ReceiveHandlerRegistrationChangedCallback callback) noexcept
128+
{
129+
static_assert(std::is_same_v<decltype(callback), ReceiveHandlerRegistrationChangedCallback>,
130+
"Callback type mismatch between GenericSkeletonEvent and lola::GenericSkeletonEvent");
131+
receive_handler_registration_changed_callback_ = std::move(callback);
132+
return {};
133+
}
134+
135+
/// \brief Unset the callback for Receive Handler registration change notifications
136+
/// \attention The callback must not be unset until after StopOffer() has been called to avoid any
137+
/// race conditions between unsetting the callback and it being called.
138+
Result<void> UnsetReceiveHandlerRegistrationChangedHandler()
139+
{
140+
receive_handler_registration_changed_callback_.reset();
141+
return {};
142+
}
143+
116144
private:
117145
Skeleton& parent_;
118146
std::string_view event_name_;
@@ -146,6 +174,7 @@ class SkeletonEventCommon
146174
/// PrepareStopOfferCommon().
147175
std::optional<TransactionLogRegistrationGuard> transaction_log_registration_guard_{};
148176
std::optional<tracing::TypeErasedSamplePtrsGuard> type_erased_sample_ptrs_guard_{};
177+
std::optional<ReceiveHandlerRegistrationChangedCallback> receive_handler_registration_changed_callback_;
149178

150179
void EmplaceTransactionLogRegistrationGuard(TransactionLogSet& transaction_log_set);
151180
void EmplaceTypeErasedSamplePtrsGuard();
@@ -213,11 +242,18 @@ void SkeletonEventCommon<SampleType>::PrepareOfferCommon(EventControl& event_con
213242
// Register callbacks to be notified when event notification existence changes.
214243
// This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered.
215244
// Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access.
245+
// If a callback for receive handler registration changes has been set, like it is done for the gateway
246+
// use case, it will also be called.
216247
GetBindingRuntime<lola::IRuntime>(BindingType::kLoLa)
217248
.GetLolaMessaging()
218249
.RegisterEventNotificationExistenceChangedCallback(
219250
QualityType::kASIL_QM, element_fq_id_, [this](const bool has_handlers) noexcept {
220251
SetQmNotificationsRegistered(has_handlers);
252+
if (receive_handler_registration_changed_callback_.has_value())
253+
{
254+
const bool qm_registered = qm_event_update_notifications_registered_.load();
255+
receive_handler_registration_changed_callback_.value()(qm_registered);
256+
}
221257
});
222258

223259
if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
@@ -227,6 +263,11 @@ void SkeletonEventCommon<SampleType>::PrepareOfferCommon(EventControl& event_con
227263
.RegisterEventNotificationExistenceChangedCallback(
228264
QualityType::kASIL_B, element_fq_id_, [this](const bool has_handlers) noexcept {
229265
SetAsilBNotificationsRegistered(has_handlers);
266+
if (receive_handler_registration_changed_callback_.has_value())
267+
{
268+
const bool asil_b_registered = asil_b_event_update_notifications_registered_.load();
269+
receive_handler_registration_changed_callback_.value()(asil_b_registered);
270+
}
230271
});
231272
}
232273
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
14+
#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h"
15+
#include "score/mw/com/impl/bindings/lola/test/skeleton_event_test_resources.h"
16+
17+
#include <gmock/gmock.h>
18+
#include <gtest/gtest.h>
19+
20+
namespace score::mw::com::impl::lola
21+
{
22+
namespace
23+
{
24+
25+
using ::testing::_;
26+
using ::testing::AnyNumber;
27+
using ::testing::Return;
28+
using ::testing::ReturnRef;
29+
30+
class SkeletonEventCommonFixture : public SkeletonEventFixture
31+
{
32+
public:
33+
SkeletonEventCommonFixture()
34+
{
35+
ON_CALL(runtime_mock_, GetServiceDiscovery()).WillByDefault(ReturnRef(service_discovery_mock_));
36+
}
37+
38+
void InitialiseSkeletonEventCommon(const ElementFqId element_fq_id,
39+
const std::string& service_element_name,
40+
const std::size_t max_samples,
41+
const std::uint8_t max_subscribers,
42+
const bool enforce_max_samples,
43+
const QualityType quality_type,
44+
impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data)
45+
{
46+
// We defer initialisation of the Skeleton to InitialiseSkeletonEventCommon to allow test fixtures to set any
47+
// mocked expectations before creating the skeleton.
48+
InitialiseSkeleton(GetValidInstanceIdentifierForEventCommon(quality_type));
49+
50+
SkeletonBinding::SkeletonEventBindings events{};
51+
SkeletonBinding::SkeletonFieldBindings fields{};
52+
std::optional<SkeletonBinding::RegisterShmObjectTraceCallback> register_shm_object_trace_callback{};
53+
54+
std::ignore = skeleton_->PrepareOffer(events, fields, std::move(register_shm_object_trace_callback));
55+
56+
skeleton_event_ = std::make_unique<SkeletonEvent<test::TestSampleType>>(
57+
*skeleton_,
58+
element_fq_id,
59+
service_element_name,
60+
SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples},
61+
skeleton_event_tracing_data);
62+
63+
// Call PrepareOffer on the skeleton event to trigger Skeleton::Register, which populates
64+
// the event_controls_ maps in ServiceDataControl for both QM and (if ASIL-B) ASIL-B.
65+
std::ignore = skeleton_event_->PrepareOffer();
66+
67+
skeleton_event_common_ =
68+
std::make_unique<score::mw::com::impl::lola::SkeletonEventCommon<test::TestSampleType>>(
69+
*skeleton_,
70+
event_name_,
71+
SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples},
72+
element_fq_id,
73+
skeleton_event_tracing_data);
74+
}
75+
76+
protected:
77+
std::string_view event_name_{"test_event"};
78+
std::unique_ptr<SkeletonEventCommon<test::TestSampleType>> skeleton_event_common_;
79+
80+
InstanceIdentifier GetValidInstanceIdentifierForEventCommon(QualityType quality_type) const
81+
{
82+
if (quality_type == QualityType::kASIL_B)
83+
{
84+
return make_InstanceIdentifier(valid_asil_instance_deployment_, valid_type_deployment_);
85+
}
86+
else
87+
{
88+
return make_InstanceIdentifier(valid_qm_instance_deployment_, valid_type_deployment_);
89+
}
90+
}
91+
92+
private:
93+
ServiceInstanceDeployment valid_qm_instance_deployment_{make_ServiceIdentifierType(service_type_name_),
94+
binding_info_,
95+
QualityType::kASIL_QM,
96+
instance_specifier_};
97+
};
98+
99+
TEST_F(SkeletonEventCommonFixture, RegisterEventNotificationCallbacksForAsilBTriggersMessagePassingRegistration)
100+
{
101+
const bool enforce_max_samples{true};
102+
InitialiseSkeletonEventCommon(fake_element_fq_id_,
103+
fake_event_name_,
104+
max_samples_,
105+
max_subscribers_,
106+
enforce_max_samples,
107+
QualityType::kASIL_B,
108+
{});
109+
110+
auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM);
111+
auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B);
112+
ASSERT_NE(event_control_qm, nullptr);
113+
ASSERT_NE(event_control_asil_b, nullptr);
114+
115+
// Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL
116+
// level and element ID
117+
EXPECT_CALL(message_passing_mock_,
118+
RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_, _))
119+
.Times(1);
120+
EXPECT_CALL(message_passing_mock_,
121+
RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_, _))
122+
.Times(1);
123+
// ... when PrepareOfferCommon is called
124+
skeleton_event_common_->PrepareOfferCommon(*event_control_qm, event_control_asil_b);
125+
}
126+
127+
TEST_F(SkeletonEventCommonFixture, UnregisterEventNotificationCallbacksForAsilBTriggersMessagePassingUnregistration)
128+
{
129+
const bool enforce_max_samples{true};
130+
InitialiseSkeletonEventCommon(fake_element_fq_id_,
131+
fake_event_name_,
132+
max_samples_,
133+
max_subscribers_,
134+
enforce_max_samples,
135+
QualityType::kASIL_B,
136+
{});
137+
138+
auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM);
139+
auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B);
140+
ASSERT_NE(event_control_qm, nullptr);
141+
ASSERT_NE(event_control_asil_b, nullptr);
142+
143+
// Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL
144+
// level and element ID
145+
EXPECT_CALL(message_passing_mock_,
146+
UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_))
147+
.Times(1);
148+
EXPECT_CALL(message_passing_mock_,
149+
UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_))
150+
.Times(1);
151+
// ... when PrepareStopOfferCommon is called
152+
skeleton_event_common_->PrepareStopOfferCommon();
153+
}
154+
155+
} // namespace
156+
157+
} // namespace score::mw::com::impl::lola

score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding
3939
MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override));
4040
MOCK_METHOD(void, SetSkeletonEventTracingData, (impl::tracing::SkeletonEventTracingData), (noexcept, override));
4141
MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override));
42+
MOCK_METHOD(Result<void>,
43+
SetReceiveHandlerRegistrationChangedHandler,
44+
(ReceiveHandlerRegistrationChangedCallback),
45+
(noexcept, override));
46+
MOCK_METHOD(Result<void>, UnsetReceiveHandlerRegistrationChangedHandler, (), (noexcept, override));
4247
};
4348

4449
} // namespace score::mw::com::impl::mock_binding

score/mw/com/impl/generic_skeleton_event.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,20 @@ DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept
110110
return {size_info_pair.first, size_info_pair.second};
111111
}
112112

113+
Result<void> GenericSkeletonEvent::SetReceiveHandlerRegistrationChangedHandler(
114+
ReceiveHandlerRegistrationChangedCallback callback) noexcept
115+
{
116+
auto* const binding = dynamic_cast<GenericSkeletonEventBinding*>(binding_.get());
117+
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding != nullptr, "Cast to GenericSkeletonEventBinding failed");
118+
119+
return binding->SetReceiveHandlerRegistrationChangedHandler(std::move(callback));
120+
}
121+
122+
Result<void> GenericSkeletonEvent::UnsetReceiveHandlerRegistrationChangedHandler() noexcept
123+
{
124+
auto* const binding = dynamic_cast<GenericSkeletonEventBinding*>(binding_.get());
125+
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding != nullptr, "Cast to GenericSkeletonEventBinding failed");
126+
127+
return binding->UnsetReceiveHandlerRegistrationChangedHandler();
128+
}
113129
} // namespace score::mw::com::impl

0 commit comments

Comments
 (0)