Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions score/mw/com/impl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ cc_library(
"//score/mw/com:__subpackages__",
],
deps = [
":flag_owner",
":generic_proxy_event",
":handle_type",
":proxy_base",
Expand Down Expand Up @@ -428,6 +429,7 @@ cc_library(
],
deps = [
":error",
":flag_owner",
":proxy_binding",
":proxy_event_binding",
":sample_reference_tracker",
Expand Down
1 change: 1 addition & 0 deletions score/mw/com/impl/bindings/lola/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ cc_library(
":subscription_state_machine",
":transaction_log_id",
":transaction_log_rollback_executor",
"//score/mw/com/impl:find_service_handle",
"//score/mw/com/impl:generic_proxy_event_binding",
"//score/mw/com/impl:instance_identifier",
"//score/mw/com/impl:instance_specifier",
Expand Down
10 changes: 10 additions & 0 deletions score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit should be squashed with the previous one since tests should be merged alongside the changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both commits would be merged at once anyway since this is one PR,
but if you are fine with all the things in one commit then i will do it like this,
usually ppl ask for separate commit for tests in the past. since you are reviewing it i will squash as you asked.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commits should be split up into small but complete parts. E.g. in this PR, you probably should have had one commit which adds PrepareUnsubscribe / FinalizeUnsubscribe and a second commit which adds the checks to ~Proxy() which modifies a lot of tests. (and then another commit to add checks to ~Skeleton etc).

Splitting the pr up serves a few purposes: 1) It makes it easier to review and also if we see that the pr is too big, makes it easier to split it up. 2) In the future, it makes it easier to understand a full change by checking the commit without having to try to find the pr that merged the change (this is also why these commits like "Address review comments" are not good). 3) If we ever want to revert a change, then you can just revert a single commit. If the change and tests are in multiple commits, you would have to revert all of them (and if you don't realise there are multiple commits then you might end up manually reverting all the tests). E.G. It's possible in the future that we prefer not to have the check in ~Proxy(). With all the changes together as they are now, we can't revert the change at all because we still need the unsubscribe change.

Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class LolaGenericProxyEventFixture : public LolaProxyEventResources
return *this;
}

// ProxyEventCommon no longer Unsubscribes on destruction; release state-machine state explicitly.
void TearDown() override
{
if (generic_proxy_event_ != nullptr)
{
generic_proxy_event_->Unsubscribe();
}
ProxyMockedMemoryFixture::TearDown();
}

std::unique_ptr<GenericProxyEvent> generic_proxy_event_{nullptr};
};
TEST_F(LolaGenericProxyEventFixture, CanConstructAGenericProxyEvent)
Expand Down
123 changes: 68 additions & 55 deletions score/mw/com/impl/bindings/lola/proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,50 +263,6 @@ ServiceDataStorage& GetServiceDataStorage(const memory::shared::ManagedMemoryRes

} // namespace detail_proxy

class FindServiceGuard final
{
public:
// Suppress "AUTOSAR C++14 A15-5-3" rule findings. This rule states: "The std::terminate() function shall not be
// called implicitly". std::terminate() is implicitly called from 'find_service_handle_result.value()' in case
// find_service_handle_result doesn't have a value but as we check before with 'has_value()' so no way for throwing
// std::bad_optional_access which leds to std::terminate().
// coverity[autosar_cpp14_a15_5_3_violation : FALSE]
FindServiceGuard(FindServiceHandler<HandleType> find_service_handler,
EnrichedInstanceIdentifier enriched_instance_identifier)
: service_availability_change_handle_{nullptr}
{
auto& service_discovery = impl::Runtime::getInstance().GetServiceDiscovery();
const auto find_service_handle_result = service_discovery.StartFindService(
std::move(find_service_handler), std::move(enriched_instance_identifier));
if (!find_service_handle_result.has_value())
{
score::mw::log::LogFatal("lola")
<< "StartFindService failed with error" << find_service_handle_result.error() << ". Terminating.";
std::terminate();
}
service_availability_change_handle_ = std::make_unique<FindServiceHandle>(find_service_handle_result.value());
}

~FindServiceGuard() noexcept
{
auto& service_discovery = impl::Runtime::getInstance().GetServiceDiscovery();
const auto stop_find_service_result = service_discovery.StopFindService(*service_availability_change_handle_);
if (!stop_find_service_result.has_value())
{
score::mw::log::LogError("lola")
<< "StopFindService failed with error" << stop_find_service_result.error() << ". Ignoring error.";
}
}

FindServiceGuard(const FindServiceGuard&) = delete;
FindServiceGuard& operator=(const FindServiceGuard&) = delete;
FindServiceGuard(FindServiceGuard&& other) = delete;
FindServiceGuard& operator=(FindServiceGuard&& other) = delete;

private:
std::unique_ptr<FindServiceHandle> service_availability_change_handle_;
};

std::atomic<ProxyInstanceIdentifier::ProxyInstanceCounter> Proxy::current_proxy_instance_counter_{0U};

ElementFqId Proxy::EventNameToElementFqIdConverter::Convert(const std::string_view event_name) const noexcept
Expand Down Expand Up @@ -430,20 +386,20 @@ Proxy::Proxy(std::shared_ptr<memory::shared::ManagedMemoryResource> control,
offered_state_machine_{},
are_proxy_methods_setup_{false},
filesystem_{filesystem},
find_service_guard_{std::make_unique<FindServiceGuard>(
[this](ServiceHandleContainer<HandleType> service_handle_container, FindServiceHandle) {
// Suppress Autosar C++14 A8-5-3 states that auto variables shall not be initialized using braced
// initialization. This is a false positive, we don't use auto here
// coverity[autosar_cpp14_a8_5_3_violation : FALSE]
std::lock_guard lock{proxy_event_registration_mutex_};
is_service_instance_available_ = !service_handle_container.empty();
ServiceAvailabilityChangeHandler(is_service_instance_available_);
},
EnrichedInstanceIdentifier{handle_})}
find_service_handle_{}
{
StartProxyAutoReconnect();
}

Proxy::~Proxy() = default;
Proxy::~Proxy()
{
if (!prepare_unsubscribe_called_ || !finalize_unsubscribe_called_)
{
score::mw::log::LogFatal("lola")
<< "Proxy destroyed without prior PrepareUnsubscribe + FinalizeUnsubscribe. Terminating.";
std::terminate();
}
}

void Proxy::ServiceAvailabilityChangeHandler(const bool is_service_available)
{
Expand Down Expand Up @@ -929,4 +885,61 @@ void Proxy::RegisterMethod(const UniqueMethodIdentifier method_id, ProxyMethod&
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(was_inserted, "Method IDs must be unique!");
}

void Proxy::PrepareUnsubscribe()
{
StopProxyAutoReconnect();
prepare_unsubscribe_called_ = true;
}

void Proxy::FinalizeUnsubscribe()
{
{
std::lock_guard lock{proxy_event_registration_mutex_};
event_bindings_.clear();
is_service_instance_available_ = false;
}
{
std::lock_guard lock{proxy_method_registration_mutex_};
proxy_methods_.clear();
}
finalize_unsubscribe_called_ = true;
}

void Proxy::StartProxyAutoReconnect()
{
auto& service_discovery = impl::Runtime::getInstance().GetServiceDiscovery();
const auto find_service_handle_result = service_discovery.StartFindService(
[this](ServiceHandleContainer<HandleType> service_handle_container, FindServiceHandle) {
std::lock_guard lock{proxy_event_registration_mutex_};
is_service_instance_available_ = !service_handle_container.empty();
ServiceAvailabilityChangeHandler(is_service_instance_available_);
},
EnrichedInstanceIdentifier{handle_});
if (!find_service_handle_result.has_value())
{
score::mw::log::LogFatal("lola")
<< "StartFindService failed with error" << find_service_handle_result.error() << ". Terminating.";
std::terminate();
}
find_service_handle_ = find_service_handle_result.value();
}

void Proxy::StopProxyAutoReconnect()
{
if (!find_service_handle_.has_value())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an assertion? If the guard fails to be created then we terminate in StartProxyAutoReconnect.

{
return;
}
// StopFindService waits for any in-flight handler to finish; must run before we take
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the point of the second part of this comment. The handler is run in a separate thread so I wouldn't expect a deadlock, but anyway, we have no reason to lock proxy_event_registration_mutex_ here?

// proxy_event_registration_mutex_ since the handler also takes it.
auto& service_discovery = impl::Runtime::getInstance().GetServiceDiscovery();
const auto stop_find_service_result = service_discovery.StopFindService(*find_service_handle_);
if (!stop_find_service_result.has_value())
{
score::mw::log::LogError("lola")
<< "StopFindService failed with error" << stop_find_service_result.error() << ". Ignoring error.";
}
find_service_handle_.reset();
}

} // namespace score::mw::com::impl::lola
18 changes: 14 additions & 4 deletions score/mw/com/impl/bindings/lola/proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "score/mw/com/impl/configuration/lola_service_instance_id.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/find_service_handle.h"
#include "score/mw/com/impl/handle_type.h"
#include "score/mw/com/impl/proxy_binding.h"
#include "score/mw/com/impl/proxy_event_binding_base.h"
Expand Down Expand Up @@ -62,7 +63,6 @@ namespace score::mw::com::impl::lola
{

class IShmPathBuilder;
class FindServiceGuard;

namespace detail_proxy
{
Expand Down Expand Up @@ -200,9 +200,15 @@ class Proxy : public ProxyBinding

void RegisterMethod(const UniqueMethodIdentifier method_id, ProxyMethod& proxy_method) noexcept;

void PrepareUnsubscribe() override;
void FinalizeUnsubscribe() override;

private:
static std::atomic<ProxyInstanceIdentifier::ProxyInstanceCounter> current_proxy_instance_counter_;

void StartProxyAutoReconnect();
void StopProxyAutoReconnect();

void ServiceAvailabilityChangeHandler(const bool is_service_available);
void InitializeSharedMemoryForMethods(
memory::shared::ManagedMemoryResource& memory_resource,
Expand Down Expand Up @@ -261,9 +267,13 @@ class Proxy : public ProxyBinding

score::filesystem::Filesystem filesystem_;

// We make find_service_guard_ the last member variable since it registers a handler which accesses member variables
// of this class, so they should be initialised first.
std::unique_ptr<FindServiceGuard> find_service_guard_;
/// Handle returned by ServiceDiscovery::StartFindService. Held so we can call StopFindService later from
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Handle returned by ServiceDiscovery::StartFindService. Held so we can call StopFindService later from
/// Handle returned by ServiceDiscovery::StartFindService which is called for proxy auto reconnect. Held so we can call StopFindService later from

/// StopProxyAutoReconnect().
std::optional<FindServiceHandle> find_service_handle_;

/// Set by PrepareUnsubscribe / FinalizeUnsubscribe respectively. ~Proxy terminates unless both were called.
bool prepare_unsubscribe_called_{false};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialise them in the constructor

bool finalize_unsubscribe_called_{false};
};

template <typename EventSampleType>
Expand Down
5 changes: 0 additions & 5 deletions score/mw/com/impl/bindings/lola/proxy_event_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ ProxyEventCommon::ProxyEventCommon(Proxy& parent, const ElementFqId element_fq_i
{
}

ProxyEventCommon::~ProxyEventCommon()
{
Unsubscribe();
}

Result<void> ProxyEventCommon::Subscribe(const std::size_t max_sample_count)
{
std::stringstream sstream{};
Expand Down
2 changes: 1 addition & 1 deletion score/mw/com/impl/bindings/lola/proxy_event_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ProxyEventCommon final

public:
ProxyEventCommon(Proxy& parent, const ElementFqId element_fq_id, const std::string_view event_name);
~ProxyEventCommon();
~ProxyEventCommon() = default;

ProxyEventCommon(const ProxyEventCommon&) = delete;
ProxyEventCommon(ProxyEventCommon&&) noexcept = delete;
Expand Down
13 changes: 12 additions & 1 deletion score/mw/com/impl/bindings/lola/proxy_event_common_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ class LolaProxyEventCommonFixture : public ProxyMockedMemoryFixture
UnregisterEventNotification(QualityType::kASIL_QM, kElementFqId, my_handler_no, kDummyPid));
}

// ProxyEventCommon no longer Unsubscribes on destruction; release state-machine state explicitly.
void TearDown() override
{
if (proxy_event_ != nullptr)
{
proxy_event_->Unsubscribe();
}
ProxyMockedMemoryFixture::TearDown();
}

protected:
::testing::MockFunction<void()> event_handler_{};
std::unique_ptr<T> proxy_event_{nullptr};
Expand Down Expand Up @@ -280,7 +290,8 @@ TYPED_TEST(LolaProxyEventCommonFixture, DestroyingTheProxyEventWillUnregisterEve
// Then the receive handler should not be unregistered
EXPECT_FALSE(handler_unregistered);

// and when the ProxyEvent is destroyed
// and when the ProxyEvent is unsubscribed and destroyed
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really done when destroying the event? Or just when unsubscribing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this test name does not fit well now,
now destroying the proxy_event_ does not implicitly call Unsub() anymore,
we already have UnsubscribingWillUnregisterEventHandler test, shall i just remove this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it should be removed since it's testing the raii semantics which no longer exist.

this->proxy_event_->Unsubscribe();
this->proxy_event_.reset();

// Then the receive handler should be unregistered
Expand Down
10 changes: 10 additions & 0 deletions score/mw/com/impl/bindings/lola/proxy_event_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ class LolaProxyEventFixture : public LolaProxyEventResources
return test_proxy_event_->GetNewSamples(std::move(receiver), guard_factory);
}

// ProxyEventCommon no longer Unsubscribes on destruction; release state-machine state explicitly.
void TearDown() override
{
if (test_proxy_event_ != nullptr)
{
test_proxy_event_->Unsubscribe();
}
ProxyMockedMemoryFixture::TearDown();
}

std::unique_ptr<ProxyEventType> test_proxy_event_{nullptr};
std::unique_ptr<SampleReferenceTracker> sample_reference_tracker_{};
};
Expand Down
13 changes: 13 additions & 0 deletions score/mw/com/impl/bindings/lola/skeleton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ auto Skeleton::PrepareOffer(SkeletonEventBindings& events,
method_subscription_registration_guard_asil_b_.emplace(std::move(asil_b_registration_result).value());
}

prepare_offer_called_ = true;
return {};
}

Expand All @@ -370,6 +371,8 @@ auto Skeleton::PrepareOffer(SkeletonEventBindings& events,
auto Skeleton::PrepareStopOffer(std::optional<UnregisterShmObjectTraceCallback> unregister_shm_object_callback) noexcept
-> void
{
prepare_stop_offer_called_ = true;

if (unregister_shm_object_callback.has_value())
{
// Suppress "AUTOSAR C++14 A15-4-2" rule finding. This rule states: "I a function is declared to be
Expand Down Expand Up @@ -443,6 +446,16 @@ auto Skeleton::PrepareStopOffer(std::optional<UnregisterShmObjectTraceCallback>
memory_manager_.Reset();
}

Skeleton::~Skeleton() noexcept
{
if (prepare_offer_called_ && !prepare_stop_offer_called_)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm, this starts to become a bit problematic IMO because we start adding logic here which is reliant on the logic of the layer above (i.e. SkeletonBase only calls PrepareStopOffer if the service was offered). But then on proxy side, unsubscribe is always called. We should discuss this.

{
score::mw::log::LogFatal("lola")
<< "Skeleton was offered but destroyed without prior PrepareStopOffer. Terminating.";
std::terminate();
}
}

QualityType Skeleton::GetInstanceQualityType() const
{
return quality_type_;
Expand Down
8 changes: 7 additions & 1 deletion score/mw/com/impl/bindings/lola/skeleton.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class Skeleton final : public SkeletonBinding
std::unique_ptr<score::memory::shared::FlockMutexAndLock<score::memory::shared::ExclusiveFlockMutex>>
service_instance_existence_flock_mutex_and_lock);

~Skeleton() noexcept override = default;
~Skeleton() noexcept override;

Skeleton(const Skeleton&) = delete;
Skeleton& operator=(const Skeleton&) = delete;
Expand Down Expand Up @@ -240,6 +240,12 @@ class Skeleton final : public SkeletonBinding
/// This scope will be manually expired in PrepareStopOffer which will prevent any Proxy from subscribing to
/// this method.
safecpp::Scope<> on_service_method_subscribed_handler_scope_;

/// Set by PrepareOffer / PrepareStopOffer respectively. ~Skeleton terminates if PrepareOffer was called but
/// PrepareStopOffer was not — i.e. the skeleton registered method handlers and is being destroyed without
/// unregistering them.
bool prepare_offer_called_{false};
bool prepare_stop_offer_called_{false};
};

template <typename SampleType>
Expand Down
Loading
Loading