Skip to content

Commit 73bba2f

Browse files
RobertLauferElektrobittcormackMWjeffdiclemente
authored
Remove deadlock in config notification (CppMicroServices#1040) (CppMicroServices#1174)
* added test to verify that update call from inside modified no longer causes deadlock * memory leaks... no idea why they are happening but 99% sure this is a false positive * fix false leak * respond to jeff comments * respond to conor 's comment --------- Co-authored-by: tcormackMW <113473781+tcormackMW@users.noreply.github.com> Co-authored-by: Toby Cormack <tcormack@mathworks.com> Co-authored-by: Jeff DiClemente <jeffdiclemente@users.noreply.github.com>
1 parent 651af45 commit 73bba2f

16 files changed

Lines changed: 428 additions & 70 deletions

compendium/DeclarativeServices/src/manager/ConfigurationNotifier.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ namespace cppmicroservices
185185
cppmicroservices::service::cm::ConfigurationEventType type,
186186
std::shared_ptr<cppmicroservices::AnyMap> properties)
187187
{
188-
// lock held throughout to guarantee ordering for calls to this function
189-
std::lock_guard<std::mutex> l(notificationOrderingLock);
190188
ConfigChangeNotification notification
191189
= ConfigChangeNotification(pid, std::move(properties), std::move(type));
192190
std::vector<Listener> callbacks;

compendium/DeclarativeServices/src/manager/ConfigurationNotifier.hpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,9 @@ namespace cppmicroservices
102102
void NotifyAllListeners(std::string const& pid,
103103
cppmicroservices::service::cm::ConfigurationEventType type,
104104
std::shared_ptr<cppmicroservices::AnyMap> properties);
105-
106105
std::shared_ptr<ComponentFactoryImpl> GetComponentFactory();
107106
void LogInvalidDynamicTargetInProperties(cppmicroservices::AnyMap const& properties,
108-
std::shared_ptr<ComponentConfigurationImpl> mgr) const noexcept;
109-
107+
std::shared_ptr<ComponentConfigurationImpl> mgr) const noexcept;
110108
private:
111109
using TokenMap = std::unordered_map<ListenerTokenId, Listener>;
112110

@@ -119,9 +117,8 @@ namespace cppmicroservices
119117

120118
std::shared_ptr<cppmicroservices::logservice::LogService> logger;
121119
std::shared_ptr<ComponentFactoryImpl> componentFactory;
122-
std::mutex notificationOrderingLock;
123-
};
120+
};
124121

125122
} // namespace scrimpl
126123
} // namespace cppmicroservices
127-
#endif //__CPPMICROSERVICES_SCRIMPL_CONFIGURATIONNOTIFIER_HPP__
124+
#endif // __CPPMICROSERVICES_SCRIMPL_CONFIGURATIONNOTIFIER_HPP__

compendium/DeclarativeServices/test/TestUtils.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,43 @@ namespace test
275275
#endif
276276
}
277277

278+
AsyncWorkServiceThreadPool::AsyncWorkServiceThreadPool(int nThreads) : cppmicroservices::async::AsyncWorkService(), threadpool(std::make_shared<boost::asio::thread_pool>(nThreads))
279+
{
280+
}
281+
282+
AsyncWorkServiceThreadPool::~AsyncWorkServiceThreadPool()
283+
{
284+
try
285+
{
286+
if (threadpool)
287+
{
288+
try
289+
{
290+
threadpool->join();
291+
}
292+
catch (...)
293+
{
294+
//
295+
}
296+
}
297+
}
298+
catch (...)
299+
{
300+
//
301+
}
302+
}
303+
304+
void
305+
AsyncWorkServiceThreadPool::post(std::packaged_task<void()>&& task)
306+
{
307+
using Sig = void();
308+
using Result = boost::asio::async_result<decltype(task), Sig>;
309+
using Handler = typename Result::completion_handler_type;
310+
311+
Handler handler{std::move(task)};
312+
Result result(handler);
313+
314+
boost::asio::post(threadpool->get_executor(), [handler = std::move(handler)]() mutable { handler(); });
315+
}
316+
278317
} // namespace test

compendium/DeclarativeServices/test/TestUtils.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
#include <cppmicroservices/Bundle.h>
2727
#include <cppmicroservices/BundleContext.h>
2828
#include <cppmicroservices/ServiceReference.h>
29-
29+
#include "boost/asio/async_result.hpp"
30+
#include "boost/asio/packaged_task.hpp"
31+
#include "boost/asio/post.hpp"
32+
#include "boost/asio/thread_pool.hpp"
33+
#include <cppmicroservices/asyncworkservice/AsyncWorkService.hpp>
3034
#include <random>
3135
#include <string>
3236

@@ -103,6 +107,20 @@ namespace test
103107
*/
104108
bool isBundleLoadedInThisProcess(std::string bundleName);
105109

110+
class AsyncWorkServiceThreadPool : public cppmicroservices::async::AsyncWorkService
111+
{
112+
public:
113+
AsyncWorkServiceThreadPool(int nThreads);
114+
115+
~AsyncWorkServiceThreadPool() override;
116+
117+
void
118+
post(std::packaged_task<void()>&& task) override;
119+
120+
private:
121+
std::shared_ptr<boost::asio::thread_pool> threadpool;
122+
};
123+
106124
} // namespace test
107125

108126
#endif /* TestUtils_hpp */

compendium/DeclarativeServices/test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ set(_test_bundles
206206
TestBundleDSCA07
207207
TestBundleDSCA08
208208
TestBundleDSCA09
209+
TestBundleDSCA10
209210
TestBundleDSCA12
210211
TestBundleDSCA16
211212
TestBundleDSCA20

compendium/DeclarativeServices/test/gtest/ImportTestBundles.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA06)
3737
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA07)
3838
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA08)
3939
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA09)
40+
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA10)
4041
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA12)
4142
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA16)
4243
CPPMICROSERVICES_INITIALIZE_STATIC_BUNDLE(TestBundleDSCA20)

compendium/DeclarativeServices/test/gtest/TestAsyncWorkService.cpp

Lines changed: 54 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@
3434
#include "Mocks.hpp"
3535
#include "TestFixture.hpp"
3636
#include "TestInterfaces/Interfaces.hpp"
37-
#include "boost/asio/async_result.hpp"
38-
#include "boost/asio/packaged_task.hpp"
39-
#include "boost/asio/post.hpp"
40-
#include "boost/asio/thread_pool.hpp"
37+
4138
#include "cppmicroservices/servicecomponent/ComponentConstants.hpp"
4239
#include "cppmicroservices/servicecomponent/runtime/ServiceComponentRuntime.hpp"
4340

@@ -80,53 +77,6 @@ namespace test
8077
cppmicroservices::Framework framework;
8178
};
8279

83-
class AsyncWorkServiceThreadPool : public cppmicroservices::async::AsyncWorkService
84-
{
85-
public:
86-
AsyncWorkServiceThreadPool(int nThreads) : cppmicroservices::async::AsyncWorkService()
87-
{
88-
threadpool = std::make_shared<boost::asio::thread_pool>(nThreads);
89-
}
90-
91-
~AsyncWorkServiceThreadPool() override
92-
{
93-
try
94-
{
95-
if (threadpool)
96-
{
97-
try
98-
{
99-
threadpool->join();
100-
}
101-
catch (...)
102-
{
103-
//
104-
}
105-
}
106-
}
107-
catch (...)
108-
{
109-
//
110-
}
111-
}
112-
113-
void
114-
post(std::packaged_task<void()>&& task) override
115-
{
116-
using Sig = void();
117-
using Result = boost::asio::async_result<decltype(task), Sig>;
118-
using Handler = typename Result::completion_handler_type;
119-
120-
Handler handler(std::forward<decltype(task)>(task));
121-
Result result(handler);
122-
123-
boost::asio::post(threadpool->get_executor(), [handler = std::move(handler)]() mutable { handler(); });
124-
}
125-
126-
private:
127-
std::shared_ptr<boost::asio::thread_pool> threadpool;
128-
};
129-
13080
TEST_F(tGenericDSSuite, TestAsyncWorkServiceWithoutUserService)
13181
{
13282
std::shared_ptr<cppmicroservices::scrimpl::SCRLogger> logger
@@ -304,7 +254,7 @@ namespace test
304254

305255
auto reg = ctx.RegisterService<cppmicroservices::async::AsyncWorkService>(param);
306256

307-
for (const auto& bundleName : bundlesToInstall)
257+
for (auto const& bundleName : bundlesToInstall)
308258
{
309259
installedBundles.emplace_back(::test::InstallAndStartBundle(ctx, bundleName));
310260
}
@@ -343,7 +293,7 @@ namespace test
343293
auto configInstance = configuration->GetPid();
344294

345295
cppmicroservices::AnyMap props({
346-
{"uniqueProp", std::string("instance1")}
296+
{ "uniqueProp", std::string("instance1") }
347297
});
348298

349299
auto fut = configuration->Update(props);
@@ -391,7 +341,7 @@ namespace test
391341
auto configInstance = configuration->GetPid();
392342

393343
cppmicroservices::AnyMap props({
394-
{"uniqueProp", std::string("instance1")}
344+
{ "uniqueProp", std::string("instance1") }
395345
});
396346

397347
auto fut = configuration->Update(props);
@@ -409,14 +359,14 @@ namespace test
409359
auto configInstance = configuration->GetPid();
410360

411361
cppmicroservices::AnyMap props({
412-
{"uniqueProp", std::string("instance1")}
362+
{ "uniqueProp", std::string("instance1") }
413363
});
414364

415365
auto fut = configuration->SafeUpdate(props);
416366
fut->get();
417367

418368
cppmicroservices::AnyMap props1({
419-
{"uniqueProp", std::string("instance2")}
369+
{ "uniqueProp", std::string("instance2") }
420370
});
421371
auto fut1 = configuration->SafeUpdateIfDifferent(props1);
422372
fut1.second->get();
@@ -457,7 +407,7 @@ namespace test
457407
auto configInstance = configuration->GetPid();
458408

459409
cppmicroservices::AnyMap props({
460-
{"uniqueProp", std::string("instance1")}
410+
{ "uniqueProp", std::string("instance1") }
461411
});
462412

463413
auto fut = configuration->SafeUpdate(props);
@@ -471,4 +421,51 @@ namespace test
471421
fut.get();
472422
}
473423

424+
/**
425+
* Verify that a service's configuration can be updated from with a 'Modified' callback without
426+
* deadlocking the framework trying to get the lock in configNotifier
427+
*/
428+
TEST_F(TestAsyncWorkServiceEndToEnd, testUpdateConfigFromWithinModifiedCallback)
429+
{
430+
auto const& param = std::make_shared<AsyncWorkServiceThreadPool>(10);
431+
auto context = framework.GetBundleContext();
432+
433+
// ASYNCWORKSERVICE
434+
auto reg = context.RegisterService<cppmicroservices::async::AsyncWorkService>(param);
435+
US_UNUSED(reg);
436+
437+
// CA
438+
::test::InstallAndStartConfigAdmin(context);
439+
440+
// CA Service
441+
auto CARef = context.GetServiceReference<cppmicroservices::service::cm::ConfigurationAdmin>();
442+
auto configAdminService = context.GetService<cppmicroservices::service::cm::ConfigurationAdmin>(CARef);
443+
ASSERT_TRUE(configAdminService) << "GetService failed for ConfigurationAdmin";
444+
445+
// Start bundle
446+
std::string componentName = "sample::ServiceComponentCA10";
447+
448+
auto testBundle = ::test::InstallAndStartBundle(context, "TestBundleDSCA10");
449+
::test::InstallAndStartBundle(context, "TestBundleDSCA03");
450+
451+
auto configObject = configAdminService->GetConfiguration(componentName);
452+
453+
auto props = configObject->GetProperties();
454+
auto configObject2 = configAdminService->GetConfiguration("sample::ServiceComponentCA02");
455+
456+
props["uniqueProp"] = std::string("UNIQUE");
457+
auto fut = configObject->Update(props);
458+
fut.get();
459+
fut = configObject2->Update(props);
460+
fut.get();
461+
props["configID"] = std::string("sample::ServiceComponentCA03");
462+
fut = configObject->Update(props);
463+
fut.get();
464+
465+
// GetService to make component active
466+
auto serviceRef = context.GetServiceReference<test::CAInterface>();
467+
auto service = context.GetService<test::CAInterface>(serviceRef);
468+
ASSERT_TRUE(service) << "GetService failed for CAInterface";
469+
}
470+
474471
}; // namespace test

compendium/DeclarativeServices/test/gtest/TestFixture.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ namespace test
109109
test::InstallLib(context, "TestBundleDSCA07");
110110
test::InstallLib(context, "TestBundleDSCA08");
111111
test::InstallLib(context, "TestBundleDSCA09");
112+
test::InstallLib(context, "TestBundleDSCA10");
112113
test::InstallLib(context, "TestBundleDSCA12");
113114
test::InstallLib(context, "TestBundleDSCA16");
114115
test::InstallLib(context, "TestBundleDSCA20");

compendium/DeclarativeServices/test/gtest/TestUpdateConfiguration.cpp

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
#include "TestFixture.hpp"
2424
#include "gtest/gtest.h"
25-
2625
#include "cppmicroservices/Constants.h"
2726
#include "cppmicroservices/ServiceObjects.h"
2827

@@ -50,7 +49,7 @@ namespace test
5049
// starting the bundle which defines the service.
5150
auto configuration = configAdminService->GetConfiguration("sample::ServiceComponentCA02");
5251
configuration->UpdateIfDifferent(std::unordered_map<std::string, cppmicroservices::Any> {
53-
{"foo", true}
52+
{ "foo", true }
5453
});
5554

5655
// Install and start the bundle which has the service
@@ -95,7 +94,7 @@ namespace test
9594
ready.wait();
9695
auto configuration = configAdminService->GetConfiguration("sample::ServiceComponentCA02");
9796
configuration->UpdateIfDifferent(std::unordered_map<std::string, cppmicroservices::Any> {
98-
{"foo", true}
97+
{ "foo", true }
9998
});
10099
});
101100

@@ -149,7 +148,7 @@ namespace test
149148

150149
configuration
151150
->UpdateIfDifferent(std::unordered_map<std::string, cppmicroservices::Any> {
152-
{"foo", true}
151+
{ "foo", true }
153152
})
154153
.second.get();
155154

@@ -245,6 +244,50 @@ namespace test
245244

246245
testBundle.Stop();
247246
}
247+
248+
/*
249+
* Verify that Factory Components will not be registered even if an incorrect Configuration matching exactly Factory
250+
* Configuration PID is initialized.
251+
*/
252+
TEST_F(tServiceComponent, testErroneousConfigurationInjection)
253+
{
254+
std::string const configName = "my.dependency.pid";
255+
256+
// Install and start the bundle containing the configuration object and the
257+
// service which is dependent on the configuration object.
258+
std::string componentName = "sample::ServiceComponentCA28";
259+
cppmicroservices::Bundle testBundle = StartTestBundle("TestBundleDSCA28");
260+
ASSERT_TRUE(testBundle);
261+
262+
auto configAdminService = GetInstance<cppmicroservices::service::cm::ConfigurationAdmin>();
263+
ASSERT_TRUE(configAdminService) << "GetService failed for ConfigurationAdmin.";
264+
265+
// GetService to make component active
266+
auto invalidService = GetInstance<test::CAInterface>();
267+
ASSERT_FALSE(invalidService) << "Factory component itself should not be registered";
268+
269+
auto uniqueVal0 = 1;
270+
auto configuration0 = configAdminService->GetConfiguration(configName);
271+
cppmicroservices::AnyMap props0 { cppmicroservices::AnyMap::UNORDERED_MAP_CASEINSENSITIVE_KEYS,
272+
{ { "uniqueKey", uniqueVal0 } } };
273+
274+
auto fut0 = configuration0->Update(props0);
275+
ASSERT_FALSE(GetInstance<test::CAInterface>())
276+
<< "Factory component should not be registered even with config matching exactly (with no ~)";
277+
278+
auto uniqueVal1 = 6;
279+
auto configuration1 = configAdminService->GetConfiguration(configName + "~1");
280+
cppmicroservices::AnyMap props1 { cppmicroservices::AnyMap::UNORDERED_MAP_CASEINSENSITIVE_KEYS,
281+
{ { "uniqueKey", uniqueVal1 } } };
282+
283+
auto fut1 = configuration1->Update(props1);
284+
fut1.wait();
285+
auto service0 = GetInstance<test::CAInterface>();
286+
ASSERT_TRUE(service0) << "Factory instance should be created when a '~' configuration is added";
287+
ASSERT_TRUE(service0->GetProperties().find("uniqueKey")->second == 6);
288+
289+
testBundle.Stop();
290+
}
248291
/*
249292
* Tests that if a configuration object is defined in the manifest.json file
250293
* and the same configuration object is also defined programmatically before the service

0 commit comments

Comments
 (0)