Skip to content

Commit 1c271e6

Browse files
authored
Add PluginInitializerService plugin (#910)
* [PluginInitService] Initial commit * [PluginInitService] now make it go in parallel * [PluginInitService] improved queue logging * [PluginInitService] more fixes * [PluginInitService] Final changes after testing * [PluginInitService] More final changes :) * [PluginInitService] gcc fixes * [PluginInitService] update * [PluginInitService] fixed some typos * [PluginInitializerService] forgot one explicit * [PluginInitService] Update copyrights * [ThunderNanoservices] add RDK management credit to Notice file * [PluginInitService] first set of fixes after review * [PluginInitService] Improve startup handling * [PluginInitService] More changes * [PluginInitService] more changes * [PluginInitService] more fixes * [PluginInitService] And more fixes * [PLuginInitService] and even more changes * [PluginInitService] update * [PluginInitService] it builds again... * [PluginInitService] code complete beforen internal review * [PluginInitService] Fixes after selfreview * [PluginInitService] first run again * [PluginInitService] adding activationresult job * [PluginInitService] fix typos * [PluginInitService] okay most complex (failing preconditions) case seems to work gain * [PluginInitService] fix typo * [PluginInitService] add some useful comment * [PluginInitService] update some comments * [PluginInitService] blocked bool does no longer need to be atomic * [PluginInitService] make it log queue info morem consistent (as the cost of an if sometimes :) ) * [PluginInitSerice] fix abba and cancelall (not complete) * [PluginInitService] initial decoupling notifications * [PluginInitService] First few tests look promosing * [PlugInitService] add some usefull coments * [PluginInitService] more comments and small fixes * [PluginInitService] Ordering * [PluginInitService] small fixes * [PluginInitServiece] add some useful comment
1 parent 283209b commit 1c271e6

12 files changed

+1795
-0
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ option(PLUGIN_WEBSERVER "Include WebServer plugin" OFF)
6464
option(PLUGIN_WEBSHELL "Include WebShell plugin" OFF)
6565
option(PLUGIN_WIFICONTROL "Include WifiControl plugin" OFF)
6666
option(PLUGIN_FILETRANSFER "Include FileTransfer plugin" OFF)
67+
option(PLUGIN_PLUGININITIALIZERSERVICE "Include PluginInitializerService plugin" OFF)
68+
6769

6870
if(ENABLE_STRICT_COMPILER_SETTINGS)
6971
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
@@ -268,5 +270,9 @@ if(PLUGIN_FILETRANSFER)
268270
add_subdirectory(FileTransfer)
269271
endif()
270272

273+
if(PLUGIN_PLUGININITIALIZERSERVICE)
274+
add_subdirectory(PluginInitializerService)
275+
endif()
276+
271277
add_subdirectory(examples)
272278
add_subdirectory(tests)

NOTICE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Licensed under the BSD-2 license
2323
Copyright (C) 2014 Igalia S.L.
2424
Licensed under the BSD-2 license
2525

26+
Copyright 2025 RDK Management
27+
Licensed under the Apache License, V2.0
28+
2629
Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
2730
Licensed under the BSD-2 license
2831

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# If not stated otherwise in this file or this component's license file the
2+
# following copyright and licenses apply:
3+
#
4+
# Copyright 2025 RDK Management
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
project(PluginInitializerService)
19+
20+
cmake_minimum_required(VERSION 3.15)
21+
22+
find_package(Thunder)
23+
24+
project_version(1.0.0)
25+
26+
set(MODULE_NAME ${NAMESPACE}${PROJECT_NAME})
27+
28+
message("Setup ${MODULE_NAME} v${PROJECT_VERSION}")
29+
30+
set(PLUGIN_PLUGININITIALIZERSERVICE_STARTMODE "Activated" CACHE STRING "Automatically start PluginInitializerService plugin")
31+
set(PLUGIN_PLUGININITIALIZERSERVICE_MAXPARALLEL "2" CACHE STRING "Maximum number of plugins that can be started in parallel")
32+
set(PLUGIN_PLUGININITIALIZERSERVICE_DEFAULTMAXRETRIES "10" CACHE STRING "Default maximum number of retries used starting a plugin")
33+
set(PLUGIN_PLUGININITIALIZERSERVICE_DEFAULTDELAY "500" CACHE STRING "Default delay between retries starting a plugin (ms)")
34+
35+
if(BUILD_REFERENCE)
36+
add_definitions(-DBUILD_REFERENCE=${BUILD_REFERENCE})
37+
endif()
38+
39+
find_package(${NAMESPACE}Plugins REQUIRED)
40+
find_package(${NAMESPACE}Messaging REQUIRED)
41+
find_package(CompileSettingsDebug CONFIG REQUIRED)
42+
43+
add_library(${MODULE_NAME} SHARED
44+
PluginInitializerService.cpp
45+
Module.cpp
46+
)
47+
48+
set_target_properties(${MODULE_NAME} PROPERTIES
49+
CXX_STANDARD 11
50+
CXX_STANDARD_REQUIRED YES)
51+
52+
target_link_libraries(${MODULE_NAME}
53+
PRIVATE
54+
CompileSettingsDebug::CompileSettingsDebug
55+
${NAMESPACE}Plugins::${NAMESPACE}Plugins
56+
${NAMESPACE}Messaging::${NAMESPACE}Messaging)
57+
58+
target_include_directories(${MODULE_NAME}
59+
PRIVATE
60+
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>)
61+
62+
install(TARGETS ${MODULE_NAME}
63+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/${STORAGE_DIRECTORY}/plugins COMPONENT ${NAMESPACE}_Runtime)
64+
65+
write_config()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* If not stated otherwise in this file or this component's LICENSE file the
3+
* following copyright and licenses apply:
4+
*
5+
* Copyright 2025 RDK Management
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
#include "Module.h"
21+
22+
MODULE_NAME_DECLARATION(BUILD_REFERENCE)

PluginInitializerService/Module.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* If not stated otherwise in this file or this component's LICENSE file the
3+
* following copyright and licenses apply:
4+
*
5+
* Copyright 2025 RDK Management
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#ifndef MODULE_NAME
23+
#define MODULE_NAME Plugin_PluginInitializerService
24+
#endif
25+
26+
#include <core/core.h>
27+
#include <plugins/plugins.h>
28+
#include <messaging/messaging.h>
29+
30+
#undef EXTERNAL
31+
#define EXTERNAL
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
startmode = "@PLUGIN_PLUGININITIALIZERSERVICE_STARTMODE@"
2+
3+
configuration = JSON()
4+
if "@PLUGIN_PLUGININITIALIZERSERVICE_MAXPARALLEL@":
5+
configuration.add("maxparallel", "@PLUGIN_PLUGININITIALIZERSERVICE_MAXPARALLEL@")
6+
7+
configuration.add("maxretries", "@PLUGIN_PLUGININITIALIZERSERVICE_DEFAULTMAXRETRIES@")
8+
configuration.add("delay", "@PLUGIN_PLUGININITIALIZERSERVICE_DEFAULTDELAY@")
9+
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* If not stated otherwise in this file or this component's LICENSE file the
3+
* following copyright and licenses apply:
4+
*
5+
* Copyright 2025 RDK Management
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
#include "PluginInitializerService.h"
21+
22+
namespace Thunder {
23+
namespace Plugin {
24+
25+
namespace {
26+
27+
static Metadata<PluginInitializerService>metadata(
28+
// Version
29+
1, 0, 0,
30+
// Preconditions
31+
{},
32+
// Terminations
33+
{},
34+
// Controls
35+
{}
36+
);
37+
}
38+
39+
const string PluginInitializerService::Initialize(PluginHost::IShell* service) {
40+
string message;
41+
42+
ASSERT(service != nullptr);
43+
ASSERT(_service == nullptr);
44+
45+
Config config;
46+
config.FromString(service->ConfigLine());
47+
const Exchange::Controller::IMetadata* metadata = service->QueryInterfaceByCallsign<Exchange::Controller::IMetadata>(_T(""));
48+
ASSERT(metadata != nullptr);
49+
50+
Exchange::Controller::IMetadata::Data::BuildInfo buildinfo{};
51+
VARIABLE_IS_NOT_USED Core::hresult result = metadata->BuildInfo(buildinfo);
52+
metadata->Release();
53+
metadata = nullptr;
54+
ASSERT(result == Core::ERROR_NONE);
55+
56+
if (config.MaxParallel.IsSet() == true) {
57+
_maxparallel = config.MaxParallel.Value();
58+
if ((_maxparallel == 0) || (_maxparallel > (buildinfo.ThreadPoolCount-1))) {
59+
message = _T("maxparallel configured incorrectly");
60+
}
61+
} else {
62+
_maxparallel = ((buildinfo.ThreadPoolCount / 2) > 0 ? (buildinfo.ThreadPoolCount / 2) : 1 );
63+
}
64+
_maxretries = config.MaxRetries.Value();
65+
_delay = config.Delay.Value();
66+
67+
if (message.empty() == true) {
68+
_service = service;
69+
_service->AddRef();
70+
}
71+
72+
// note we will not register for the plugin state notifications here but only do that when it actually needed later on, and more importantly also stop listening when there are no more plugins to start
73+
// (this to make sure hat even if this plugin is not deactivated when there are no more plugins to start it will not give any unnecessary overhead like constantly being notified on plugin state transitions)
74+
75+
return (message);
76+
}
77+
78+
void PluginInitializerService::Deinitialize(PluginHost::IShell* service VARIABLE_IS_NOT_USED)
79+
{
80+
ASSERT((_service == nullptr) || (_service == service));
81+
82+
CancelAll();
83+
84+
if (_service != nullptr) {
85+
_service->Release();
86+
_service = nullptr;
87+
}
88+
}
89+
90+
string PluginInitializerService::Information() const {
91+
return (string());
92+
}
93+
94+
// note we will not specifically handle the connection from the client and the plugin being closed after we stored the callback.
95+
// worst case: the connection being closed without abort called but in that case we will call the callback on a dead proxy and then
96+
// release it, so no leaks (no need to go through the trouble to handle the dangling proxies here)
97+
Core::hresult PluginInitializerService::Activate(const string& callsign, const Core::OptionalType<uint8_t>& maxnumberretries, const Core::OptionalType<uint16_t>& delay, IPluginAsyncStateControl::IActivationCallback* const cb)
98+
{
99+
TRACE(Trace::Information, (_T("Plugin Activate request received for plugin [%s]"), callsign.c_str()));
100+
101+
Core::hresult result = Core::ERROR_NONE;
102+
103+
PluginHost::IShell* requestedpluginShell = _service->QueryInterfaceByCallsign<PluginHost::IShell>(callsign);
104+
105+
if (requestedpluginShell != nullptr) {
106+
PluginHost::IShell::state state = requestedpluginShell->State();
107+
if ((state == PluginHost::IShell::DEACTIVATED) ||
108+
(state == PluginHost::IShell::DEACTIVATION) ||
109+
(state == PluginHost::IShell::ACTIVATION) || // this and the PRECONDITION are rather special cases, These plugin were already request to activate somewhere else, as we cannot just send a success notification (they might fail to initialize) we'll take them into account, if it was the result of a previous PluginInitializerService we'll find out anyway.
110+
(state == PluginHost::IShell::PRECONDITION) ) // note PRECONDITION can be both reached during deactivation and activation, for the purpose here it does not matter, we only have to monitor if the activation succeeds and report back or monitor deinitialization failure and retry
111+
{
112+
TRACE(Trace::Information, (_T("Plugin Activate request received for plugin [%s] in state [%s]"), callsign.c_str(), Core::EnumerateType<PluginHost::IShell::state>(state).Data()));
113+
if (NewPluginStarter(requestedpluginShell
114+
, (maxnumberretries.IsSet() == true ? maxnumberretries.Value() : _maxretries)
115+
, (delay.IsSet() == true ? delay.Value() : _delay)
116+
, cb) == true) {
117+
TRACE(Trace::DetailedInfo, (_T("Plugin start entry created for plugin [%s]"), callsign.c_str()));
118+
} else {
119+
TRACE(Trace::Warning, (_T("Plugin start entry not created for plugin [%s], there was already a pending request for this plugin"), callsign.c_str()));
120+
result = Core::ERROR_INPROGRESS;
121+
}
122+
} else if ((state == PluginHost::IShell::ACTIVATED) ||
123+
(state == PluginHost::IShell::HIBERNATED)) {
124+
TRACE(Trace::Warning, (_T("Plugin Activate received for plugin [%s] that was already active, state [%s]"), callsign.c_str(), Core::EnumerateType<PluginHost::IShell::state>(state).Data()));
125+
126+
if (cb != nullptr)
127+
{
128+
TRACE(Trace::DetailedInfo, (_T("Result callback success called for plugin [%s]"), callsign.c_str()));
129+
cb->Finished(callsign, Exchange::IPluginAsyncStateControl::IActivationCallback::state::SUCCESS, 0);
130+
}
131+
} else { // DESTROYED || UNAVAILABLE
132+
TRACE(Trace::Error, (_T("Could not start activating plugin [%s] as it is in an illegal state [%s]"), callsign.c_str(), Core::EnumerateType<PluginHost::IShell::state>(state).Data()));
133+
result = Core::ERROR_ILLEGAL_STATE;
134+
}
135+
136+
requestedpluginShell->Release();
137+
requestedpluginShell = nullptr;
138+
139+
} else {
140+
TRACE(Trace::Error, (_T("Could not start activating plugin [%s] as it is unknown"), callsign.c_str()));
141+
result = Core::ERROR_NOT_EXIST;
142+
}
143+
144+
return result;
145+
}
146+
147+
Core::hresult PluginInitializerService::AbortActivate(const string& callsign)
148+
{
149+
TRACE(Trace::Information, (_T("Plugin Abort Activate request received for plugin [%s]"), callsign.c_str()));
150+
151+
Core::hresult result = Core::ERROR_NONE;
152+
153+
if (CancelPluginStarter(callsign) == true) {
154+
TRACE(Trace::DetailedInfo, (_T("Plugin Activate request was canceled for plugin [%s]"), callsign.c_str()));
155+
} else {
156+
// note this is not necessarily an error, the abort request could just have crossed the successful activation (or failure to do so for that matter) so it was just removed from the list
157+
TRACE(Trace::Warning, (_T("Plugin Abort Activate request: plugin was not in activation list [%s]"), callsign.c_str()));
158+
result = Core::ERROR_NOT_EXIST;
159+
}
160+
161+
return result;
162+
}
163+
164+
} // Plugin
165+
} // Thunder

0 commit comments

Comments
 (0)