Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declarative Services doesn't seem to handle circular references correctly. #678

Open
jeffdiclemente opened this issue May 25, 2022 · 0 comments · May be fixed by #917
Open

Declarative Services doesn't seem to handle circular references correctly. #678

jeffdiclemente opened this issue May 25, 2022 · 0 comments · May be fixed by #917
Assignees
Labels

Comments

@jeffdiclemente
Copy link
Member

Given the following from the DS OSGi spec:

112.3.11 Circular References
It is possible for a set of component descriptions to create a circular dependency. For example, if component A references a service provided by component B and component B references a service provided by component A then a component configuration of one component cannot be satisfied without accessing a partially activated component instance of the other component. SCR must ensure that a component instance is never accessible to another component instance or as a service until it has been fully activated, that is it has returned from its activate method if it has one.

Circular references must be detected by SCR when it attempts to satisfy component configurations and SCR must fail to satisfy the references involved in the cycle and log an error message with the Log Service, if present. However, if one of the references in the cycle has optional cardinality SCR must break the cycle. The reference with the optional cardinality can be satisfied and bound to zero target services. Therefore the cycle is broken and the other references may be satisfied.

Consider the following test case:

#include <chrono>

#include <gtest/gtest.h>

#include "../src/manager/ComponentConfigurationImpl.hpp"
#include "../src/manager/ReferenceManager.hpp"
#include "../src/manager/SingletonComponentConfiguration.hpp"

#include "cppmicroservices/Framework.h"
#include "cppmicroservices/FrameworkFactory.h"
#include "cppmicroservices/FrameworkEvent.h"

#include "Mocks.hpp"
#include "TestUtils.hpp"

namespace scr = cppmicroservices::service::component::runtime;

namespace cppmicroservices {
namespace scrimpl {

TEST(TestCircuarReference, circularReferenceTest) {

  auto mockMetadata = std::make_shared<metadata::ComponentMetadata>();
  auto mockRegistry = std::make_shared<MockComponentRegistry>();
  auto fakeLogger = std::make_shared<FakeLogger>();
  

  // service component A requires a reference to a service from service component B and B
  // requires a reference to a service from service component A.
  // In this case, neither A nor B will be satisfied and an error should be
  // logged about detecting a circular dependency.
  // NOTE: Optional cardinality on either of these references will break the cycle and allow
  // the service components to be satisfied.
  auto componentAMetadata = std::make_shared<metadata::ComponentMetadata>();
  auto componentBMetadata = std::make_shared<metadata::ComponentMetadata>();

  componentAMetadata->implClassName = us_service_interface_iid<dummy::Reference1>();
  componentBMetadata->implClassName = us_service_interface_iid<dummy::Reference2>();

  metadata::ReferenceMetadata referenceA;
  referenceA.interfaceName = us_service_interface_iid<dummy::Reference1>();
  referenceA.name = std::move(std::string{"ref"});
  metadata::ReferenceMetadata referenceB;
  referenceB.interfaceName = us_service_interface_iid<dummy::Reference2>();
  referenceB.name = std::move(std::string{"ref"});

  componentAMetadata->refsMetadata.emplace_back(referenceA);
  componentBMetadata->refsMetadata.emplace_back(referenceB);

  auto framework = cppmicroservices::FrameworkFactory().NewFramework();
  framework.Start();
  auto context = framework.GetBundleContext();

  auto asyncWorkSvc = std::make_shared<SCRAsyncWorkService>(context, fakeLogger);
  auto configNotifier = std::make_shared<ConfigurationNotifier>(context, fakeLogger, asyncWorkSvc);
  auto managers =
    std::make_shared<std::vector<std::shared_ptr<ComponentManager>>>();

  auto componentA = std::make_shared<SingletonComponentConfigurationImpl>(componentAMetadata, framework, mockRegistry, fakeLogger, configNotifier, managers);
  auto componentB = std::make_shared<SingletonComponentConfigurationImpl>(componentBMetadata, framework, mockRegistry, fakeLogger, configNotifier, managers);

  componentA->Initialize();
  componentA->Register();

  componentB->Initialize(); 
  componentB->Register();

  ASSERT_EQ(cppmicroservices::service::component::runtime::dto::ComponentState::UNSATISFIED_REFERENCE,
      componentA->GetState()->GetValue());
  ASSERT_EQ(cppmicroservices::service::component::runtime::dto::ComponentState::UNSATISFIED_REFERENCE,
      componentB->GetState()->GetValue());

  framework.Stop();
  framework.WaitForStop(std::chrono::milliseconds::zero());
}

// TODO: TEST: SCR must ensure that a component instance or a service is never accessible until it has been
//       "fully activated", meaning the service component's Activate method has returned, if present.

}
}

neither componentA nor componentB should be satisfied.

Additionally a manual code inspection indicates that DS exposes the service before the service component has finished activation.

@tcormackMW tcormackMW self-assigned this Jul 21, 2023
@jeffdiclemente jeffdiclemente linked a pull request Sep 12, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants