From 98926e952ba45476a3ba3df7903a1ceb4d2b718c Mon Sep 17 00:00:00 2001 From: Toby Cormack Date: Thu, 10 Oct 2024 17:14:14 -0400 Subject: [PATCH 1/3] serviceReferenceFromService RFC V1 --- text/0015-Service-Reference-Retrieval.md | 233 +++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 text/0015-Service-Reference-Retrieval.md diff --git a/text/0015-Service-Reference-Retrieval.md b/text/0015-Service-Reference-Retrieval.md new file mode 100644 index 0000000..7feed5a --- /dev/null +++ b/text/0015-Service-Reference-Retrieval.md @@ -0,0 +1,233 @@ +- Start Date: 2024-10-10 +- CppMicroServices PR: https://github.com/CppMicroServices/CppMicroServices/pull/1044 + +# Add an free API in that can retrieve a ServiceObject's ServiceReference from itself + +## Summary + +Currently there is no API in the cppmicroservices namespace that can retrieve a ServiceReference from the ServiceObject itself. +This document discusses need for such API in the particular use case of declarative services (DS) and its possible design/solution. + +## Motivation + +Developers using DS rely on DS's SCR (service component runtime) to inject references of services statically at construction or dynamically using the bind and unbind methods. These ServiceObjects can then be used by the client. However, clients often use properties in the Service to store metadata relevant to that service. Those properties, however, are not available to the ServiceObject itself, only its reference. + +Given this it seems appropriate to have a new method that the client can use to translate a ServiceObject into its ServiceReference. + +## Requirements + +This API must: + +1. take in a ServiceObject\ and return the ServiceReference\ for that service +2. take in a ServiceObject\ and return the ServiceReference\ for that service where T is any class interface +3. not expose internal ServiceObject implementation details to the user + +## Detailed design + +ServiceReferenceFromService API will be declared in the file framework/include/cppmicroservices/ServiceReference.h in the namespace cppmicroservices:: as below: + +```c++ + +/** +* \ingroup MicroServices +* \ingroup gr_servicereference +* +* A method to retrieve a ServiceObject's original ServiceReference +* +*/ +US_Framework_EXPORT ServiceReferenceU ServiceReferenceFromService(std::shared_ptr const& s); + +/** +* \ingroup MicroServices +* \ingroup gr_servicereference +* +* A method to retrieve a ServiceObject's original ServiceReference +* +* @tparam T The class type of the ServiceObject +* @tparam U The class type of the ServiceReference. Defaults to T if not specified. +*/ +template +ServiceReference +ServiceReferenceFromService(std::shared_ptr const& s) +{ + return ServiceReference(ServiceReferenceFromService(std::static_pointer_cast(s))); +} + +``` + +This API will take the ServiceObject and first verify that it was a ServiceObject created by the CppMicroServices framework. + +If it is valid, it will return the original ServiceReference for that ServiceObject. + +A typical usage workflow could be as below, in the static constructor of a service ServiceImpl which depends on a service of type ServiceInterface2. + +```c++ +ServiceImpl::ServiceImpl(std::shared_ptr const& dep) { + // get the reference + ServiceReference depRef = cppmicroservices::ServiceReferenceFromService(dep); + // get a property from the ServiceReference's metadata + auto someProp = cppmicroservices::any_cast(retSRef.GetProperty("someProp")); + // use that property + doSomethingWith(someProp); +} + +``` + +## Implementation + +In order to solve this problem, we have to somehow embed metadata into the ServiceObject about its ServiceReference in a way that is inaccessible directly by clients. + +One solution that we have found is to embed a custom deleter into the std::shared_ptr\ and retrieve that using the std::get_deleter functionality. + +This objects that would allow this can be seen below: + +```c++ + +/* @brief Private helper struct used to facilitate the shared_ptr aliasing constructor + * in BundleContext::GetService method. The aliasing constructor helps automate + * the call to UngetService method. + * + * Service consumers can simply call GetService to obtain a shared_ptr to the + * service object and not worry about calling UngetService when they are done. + * The UngetService is called when all instances of the returned shared_ptr object + * go out of scope. + */ +template +struct ServiceHolder +{ + bool singletonService; + std::weak_ptr const b; + ServiceReferenceBase const sref; + std::shared_ptr const service; + InterfaceMapConstPtr const interfaceMap; + + ServiceHolder(ServiceHolder&) = default; + ServiceHolder(ServiceHolder&&) noexcept = default; + ServiceHolder& operator=(ServiceHolder&) = delete; + ServiceHolder& operator=(ServiceHolder&&) noexcept = delete; + + ServiceHolder(std::shared_ptr const& b, + ServiceReferenceBase const& sr, + std::shared_ptr s, + InterfaceMapConstPtr im) + : singletonService(s ? true : false) + , b(b) + , sref(sr) + , service(std::move(s)) + , interfaceMap(std::move(im)) + { + } + + ~ServiceHolder() + { + try + { + singletonService ? destroySingleton() : destroyPrototype(); + } + catch (...) + { + // Make sure that we don't crash if the shared_ptr service object outlives + // the BundlePrivate or CoreBundleContext objects. + if (!b.expired()) + { + DIAG_LOG(*b.lock()->coreCtx->sink) + << "UngetService threw an exception. " << util::GetLastExceptionStr(); + } + // don't throw exceptions from the destructor. For an explanation, see: + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md + // Following this rule means that a FrameworkEvent isn't an option here + // since it contains an exception object which clients could throw. + } + } + + private: + void + destroySingleton() + { + sref.d.Load()->UngetService(b.lock(), true); + } + + void + destroyPrototype() + { + auto bundle = b.lock(); + if (sref) + { + bool isPrototypeScope + = sref.GetProperty(Constants::SERVICE_SCOPE).ToString() == Constants::SCOPE_PROTOTYPE; + + if (isPrototypeScope) + { + sref.d.Load()->UngetPrototypeService(bundle, interfaceMap); + } + else + { + sref.d.Load()->UngetService(bundle, true); + } + } + } +}; + +/* @brief Private helper struct used to facilitate the retrieval of a serviceReference from + * a serviceObject. + * + * Service consumers can pass a service to the public API ServiceReferenceFromService. + * This method can use the std::get_deleter method to retrieve this object and through + * it the original serviceReference. + */ +class CustomServiceDeleter +{ + public: + CustomServiceDeleter(ServiceHolder* sh) : sHolder(sh) {} + + void + operator()(ServiceHolder* sh) + { + delete sh; + } + + [[nodiscard]] ServiceReferenceBase + getServiceRef() const + { + return sHolder->sref; + } + + private: + ServiceHolder const* const sHolder; +}; + +ServiceReferenceU +ServiceReferenceFromService(std::shared_ptr const& s) +{ + auto deleter = std::get_deleter(s); + if (!deleter) + { + throw std::runtime_error("The input is not a CppMicroServices managed ServiceObject"); + } + return deleter->getServiceRef(); +} +``` + +The new way that ServiceHolder objects would be constructed can be seen below: + +```c++ +// For a Singleton object (from framework/src/Bundle/BundleContext.cpp) +auto serviceHolder = new ServiceHolder(b, reference, reference.d.Load()->GetService(b.get()), nullptr); +std::shared_ptr> h(serviceHolder, CustomServiceDeleter { serviceHolder }); +return std::shared_ptr(h, h->service.get()); + +// For a prototype object (from framework/src/service/ServiceObjects.cpp) +auto sh = new ServiceHolder { bundle_, d->m_reference, nullptr, result }; +std::shared_ptr> h(sh, CustomServiceDeleter { sh }); +return InterfaceMapConstPtr(h, h->interfaceMap.get()); +``` + +## How we teach this + +If the proposal is accepted, the CppMicroServices doxygen guide will be modified to reflect the new functionality. +Most clients will not need this functionality. Configurations can be injected into DS services using ConfigAdmin. + +## Drawbacks + +- We are using custom deleters in a way that they were not intended to be used. +- We are writing the code to delete the memory manually rather than allowing std::shared_ptr to do it for us. From 9c6ca17eee64b528ed86ce9a9cee2474f7c0c947 Mon Sep 17 00:00:00 2001 From: Toby Cormack Date: Fri, 11 Oct 2024 09:12:54 -0400 Subject: [PATCH 2/3] update for comments --- text/0015-Service-Reference-Retrieval.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/text/0015-Service-Reference-Retrieval.md b/text/0015-Service-Reference-Retrieval.md index 7feed5a..d8360c7 100644 --- a/text/0015-Service-Reference-Retrieval.md +++ b/text/0015-Service-Reference-Retrieval.md @@ -10,7 +10,7 @@ This document discusses need for such API in the particular use case of declarat ## Motivation -Developers using DS rely on DS's SCR (service component runtime) to inject references of services statically at construction or dynamically using the bind and unbind methods. These ServiceObjects can then be used by the client. However, clients often use properties in the Service to store metadata relevant to that service. Those properties, however, are not available to the ServiceObject itself, only its reference. +Developers using DS rely on DS's SCR (service component runtime) to inject references of services statically at construction or dynamically using the bind and unbind methods. These ServiceObjects can then be used by the client. However, clients often use properties in the Service to store metadata relevant to that service. Those properties, however, are not available to the ServiceObject itself, only via its ServiceReference. Given this it seems appropriate to have a new method that the client can use to translate a ServiceObject into its ServiceReference. @@ -64,7 +64,7 @@ A typical usage workflow could be as below, in the static constructor of a servi ```c++ ServiceImpl::ServiceImpl(std::shared_ptr const& dep) { // get the reference - ServiceReference depRef = cppmicroservices::ServiceReferenceFromService(dep); + ServiceReference depRef = cppmicroservices::ServiceReferenceFromService(dep); // get a property from the ServiceReference's metadata auto someProp = cppmicroservices::any_cast(retSRef.GetProperty("someProp")); // use that property @@ -79,7 +79,7 @@ In order to solve this problem, we have to somehow embed metadata into the std::shared_ptr\ and retrieve that using the std::get_deleter functionality. -This objects that would allow this can be seen below: +The objects that would allow this can be seen below: ```c++ @@ -230,4 +230,3 @@ Most clients will not need this functionality. Configurations can be injected in ## Drawbacks - We are using custom deleters in a way that they were not intended to be used. -- We are writing the code to delete the memory manually rather than allowing std::shared_ptr to do it for us. From 364a440c9e623866367c02725caa4c1e54975460 Mon Sep 17 00:00:00 2001 From: Toby Cormack Date: Fri, 11 Oct 2024 14:48:35 -0400 Subject: [PATCH 3/3] address jeff's comments: --- text/0015-Service-Reference-Retrieval.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/text/0015-Service-Reference-Retrieval.md b/text/0015-Service-Reference-Retrieval.md index d8360c7..fcbf68d 100644 --- a/text/0015-Service-Reference-Retrieval.md +++ b/text/0015-Service-Reference-Retrieval.md @@ -1,26 +1,25 @@ - Start Date: 2024-10-10 - CppMicroServices PR: https://github.com/CppMicroServices/CppMicroServices/pull/1044 -# Add an free API in that can retrieve a ServiceObject's ServiceReference from itself +# Add an free API in that can retrieve a ServiceReference from a std::shared_ptr\ given to the client by the framework ## Summary -Currently there is no API in the cppmicroservices namespace that can retrieve a ServiceReference from the ServiceObject itself. +Currently there is no API in the cppmicroservices namespace that can retrieve a ServiceReference from the std::shared_ptr\ itself. This document discusses need for such API in the particular use case of declarative services (DS) and its possible design/solution. ## Motivation -Developers using DS rely on DS's SCR (service component runtime) to inject references of services statically at construction or dynamically using the bind and unbind methods. These ServiceObjects can then be used by the client. However, clients often use properties in the Service to store metadata relevant to that service. Those properties, however, are not available to the ServiceObject itself, only via its ServiceReference. +Developers using DS rely on DS's SCR (service component runtime) to inject services statically at construction or dynamically using the bind and unbind methods. These std::shared_ptr\s can then be used by the client. However, clients often use properties in the Service to store metadata relevant to that service. Those properties, however, are not available to the std::shared_ptr\ itself, only via its ServiceReference. -Given this it seems appropriate to have a new method that the client can use to translate a ServiceObject into its ServiceReference. +Given this it seems appropriate to have a new method that the client can use to translate a std::shared_ptr\ into its ServiceReference. ## Requirements This API must: -1. take in a ServiceObject\ and return the ServiceReference\ for that service -2. take in a ServiceObject\ and return the ServiceReference\ for that service where T is any class interface -3. not expose internal ServiceObject implementation details to the user +1. take in a ServiceObject\ and return the ServiceReference\ for that service where T is any class interface +2. not expose internal implementation details to the user ## Detailed design @@ -55,9 +54,9 @@ ServiceReferenceFromService(std::shared_ptr const& s) ``` -This API will take the ServiceObject and first verify that it was a ServiceObject created by the CppMicroServices framework. +This API will take the std::shared_ptr\ and first verify that it was a std::shared_ptr\ created by the CppMicroServices framework. -If it is valid, it will return the original ServiceReference for that ServiceObject. +If it is valid, it will return the original ServiceReference for that std::shared_ptr\. A typical usage workflow could be as below, in the static constructor of a service ServiceImpl which depends on a service of type ServiceInterface2. @@ -75,7 +74,7 @@ ServiceImpl::ServiceImpl(std::shared_ptr const& dep) { ## Implementation -In order to solve this problem, we have to somehow embed metadata into the ServiceObject about its ServiceReference in a way that is inaccessible directly by clients. +In order to solve this problem, we have to somehow embed metadata into the std::shared_ptr\ about its ServiceReference in a way that is inaccessible directly by clients. One solution that we have found is to embed a custom deleter into the std::shared_ptr\ and retrieve that using the std::get_deleter functionality.