ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
Application.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#include "Application.h"
4
6#include "ConfigReader.h"
7#include "ConnectionMaker.h"
8#include "DeviceManager.h"
9#include "Utilities.h"
10#include "XMLGeneratorVisitor.h"
11
12#include <ChimeraTK/BackendFactory.h>
13
14#include <boost/fusion/container/map.hpp>
15
16#include <exception>
17#include <fstream>
18#include <string>
19#include <thread>
20
21using namespace ChimeraTK;
22
23/**********************************************************************************************************************/
24
25Application::Application(const std::string& name) : ApplicationBase(name), ModuleGroup(nullptr, name) {
26 // Create the model and its root.
28
29 // Make sure the ModuleGroup base class has the model, too.
30 ModuleGroup::_model = Model::ModuleGroupProxy(_model);
31
32 // check if the application name has been set
33 if(_applicationName.empty()) {
35 throw ChimeraTK::logic_error("Error: An instance of Application must have its applicationName set.");
36 }
37 // check if application name contains illegal characters
38 if(!Utilities::checkName(name, false)) {
40 throw ChimeraTK::logic_error(
41 "Error: The application name may only contain alphanumeric characters and underscores.");
42 }
43
44#pragma GCC diagnostic push
45#pragma GCC diagnostic ignored "-Wdeprecated"
46 _configReader = std::make_shared<ConfigReader>(this, "/", name + "-config.xml");
47#pragma GCC diagnostic pop
49
50 // Create Python modules
51#ifdef CHIMERATK_APPLICATION_CORE_WITH_PYTHON
52 try {
53 _pythonModuleManager.createModules(*this);
54 }
55 catch(ChimeraTK::logic_error&) {
57 std::rethrow_exception(std::current_exception());
58 }
59#endif
60}
61
62/**********************************************************************************************************************/
63
65 if(_lifeCycleState == LifeCycleState::initialisation && !_hasBeenShutdown) {
66 // likely an exception has been thrown in the initialisation phase, in which case we better call shutdown to prevent
67 // ApplicationBase from complaining and hiding the exception
68 ApplicationBase::shutdown();
69 }
70}
71
72/**********************************************************************************************************************/
73
75 assert(not _initialiseCalled);
76 _testableMode.enable();
77}
78
79/**********************************************************************************************************************/
80
81void Application::registerThread(const std::string& name) {
82 getInstance()._testableMode.setThreadName(name);
83}
84
85/**********************************************************************************************************************/
86
87void Application::incrementDataLossCounter(const std::string& name) {
89 logger(Logger::Severity::debug, "DataLossCounter") << "Data loss in variable " << name;
90 }
92}
93
94/**********************************************************************************************************************/
95
97 size_t counter = getInstance()._dataLossCounter.load(std::memory_order_relaxed);
98 while(!getInstance()._dataLossCounter.compare_exchange_weak(
99 counter, 0, std::memory_order_release, std::memory_order_relaxed)) {
100 }
101 return counter;
102}
103
104/**********************************************************************************************************************/
105
108 throw ChimeraTK::logic_error("Application::initialise() was already called before.");
109 }
110
111 // call postConstruct on all Modules
112 for(auto& module : getSubmoduleListRecursive()) {
113 module->postConstruct();
114 }
115
117 _cm.finalise();
118
119 _initialiseCalled = true;
120}
121
122/**********************************************************************************************************************/
123
124void Application::optimiseUnmappedVariables(const std::set<std::string>& names) {
125 if(!_initialiseCalled) {
126 throw ChimeraTK::logic_error(
127 "Application::initialise() must be called before Application::optimiseUnmappedVariables().");
128 }
129
131}
132
133/**********************************************************************************************************************/
134
136 assert(!_applicationName.empty());
137
138 if(!getPVManager()) {
139 throw ChimeraTK::logic_error("Application::run() was called without an instance of ChimeraTK::PVManager.");
140 }
141
142 if(_testableMode.isEnabled()) {
144 throw ChimeraTK::logic_error(
145 "Testable mode enabled but Application::run() called directly. Call TestFacility::runApplication() instead.");
146 }
147 }
148
149 if(_runCalled) {
150 throw ChimeraTK::logic_error("Application::run() has already been called before.");
151 }
152 _runCalled = true;
153
154 // realise the PV connections
155 _cm.connect();
156
157 // set all initial version numbers in the modules to the same value
158 for(auto& module : getSubmoduleListRecursive()) {
159 if(module->getModuleType() != ModuleType::ApplicationModule) {
160 continue;
161 }
162 module->setCurrentVersionNumber(getStartVersion());
163 }
164
165 // prepare the modules
166 for(auto& module : getSubmoduleListRecursive()) {
167 module->prepare();
168 }
169
170 // Switch life-cycle state to run
172
173 // start the necessary threads for the FanOuts etc.
174 for(auto& internalModule : _internalModuleList) {
175 internalModule->activate();
176 }
177
178 // start the threads for the modules
179 for(auto& module : getSubmoduleListRecursive()) {
180 module->run();
181 }
182
183 // When in testable mode, wait for all modules to report that they have reached the testable mode.
184 // We have to start all module threads first because some modules might only send the initial
185 // values in their main loop, and following modules need them to enter testable mode.
186
187 // just a small helper lambda to avoid code repetition
188 auto waitForTestableMode = [](EntityOwner* module) {
189 while(!module->hasReachedTestableMode()) {
190 Application::getInstance().getTestableMode().unlock("releaseForReachTestableMode");
191 usleep(100);
192 // Note: This is executed inside the test thread (by TestFacility::runApplication()), so we need the exclusive
193 // lock here.
194 Application::getInstance().getTestableMode().lock("acquireForReachTestableMode", false);
195 }
196 };
197
198 if(Application::getInstance().getTestableMode().isEnabled()) {
199 for(auto& internalModule : _internalModuleList) {
200 waitForTestableMode(internalModule.get());
201 }
202
203 for(auto& module : getSubmoduleListRecursive()) {
204 waitForTestableMode(module);
205 }
206 }
207
208 // Launch circular dependency detector thread
209 _circularDependencyDetector.startDetectBlockedModules();
210}
211
212/**********************************************************************************************************************/
213
215 // switch life-cycle state
217
218 // first allow to run the application threads again, if we are in testable
219 // mode
220 if(_testableMode.isEnabled() && _testableMode.testLock()) {
221 _testableMode.unlock("shutdown");
222 }
223
224 // deactivate the FanOuts first, since they have running threads inside
225 // accessing the modules etc. (note: the modules are members of the
226 // Application implementation and thus get destroyed after this destructor)
227 for(auto& internalModule : _internalModuleList) {
228 internalModule->deactivate();
229 }
230
231 // shutdown all DeviceManagers, otherwise application modules might hang if still waiting for initial values from
232 // devices
233 for(auto& pair : _deviceManagerMap) {
234 pair.second->terminate();
235 }
236
237 // next deactivate the modules, as they have running threads inside as well
238 for(auto& module : getSubmoduleListRecursive()) {
239 module->terminate();
240 }
241
242 _circularDependencyDetector.terminate();
243
244 // Since the destructor of the Application may come too late, we will de-init the Python system here
245 getPythonModuleManager().deinit();
246
247 ApplicationBase::shutdown();
248}
249
250/**********************************************************************************************************************/
251
252/**********************************************************************************************************************/
253
255 assert(!_applicationName.empty());
256
257 XMLGenerator generator{*this};
258 generator.run();
259 generator.save(_applicationName + ".xml");
260}
261
262/**********************************************************************************************************************/
263
265 assert(!_applicationName.empty());
266 this->getModel().writeGraphViz(_applicationName + ".dot");
267}
268
269/**********************************************************************************************************************/
270
272 return dynamic_cast<Application&>(ApplicationBase::getInstance());
273}
274
275/**********************************************************************************************************************/
276
277boost::shared_ptr<DeviceManager> Application::getDeviceManager(const std::string& aliasOrCDD) {
278 if(_deviceManagerMap.find(aliasOrCDD) == _deviceManagerMap.end()) {
279 // Add initialisation handler below, since we also need to add it if the DeviceModule already exists
280 _deviceManagerMap[aliasOrCDD] = boost::make_shared<DeviceManager>(&Application::getInstance(), aliasOrCDD);
281 }
282 return _deviceManagerMap.at(aliasOrCDD);
283}
284
285/**********************************************************************************************************************/
void generateXML()
Instead of running the application, just initialise it and output the published variables to an XML f...
Model::RootProxy getModel()
Return the root of the application model.
Definition Application.h:75
std::list< boost::shared_ptr< InternalModule > > _internalModuleList
List of InternalModules.
bool _initialiseCalled
Flag whether initialise() has been called already, to make sure it doesn't get called twice.
void enableTestableMode()
Enable the testable mode.
std::atomic< LifeCycleState > _lifeCycleState
Life-cycle state of the application.
bool _testFacilityRunApplicationCalled
Flag which is set by the TestFacility in runApplication() at the beginning.
ConfigReader * _defaultConfigReader
Application(const std::string &name)
The constructor takes the application name as an argument.
void run() override
Execute the module.
void initialise() override
detail::TestableMode & getTestableMode()
Get the TestableMode control object of this application.
static void registerThread(const std::string &name)
Register the thread in the application system and give it a name.
bool _runCalled
Flag whether run() has been called already, to make sure it doesn't get called twice.
std::map< std::string, boost::shared_ptr< DeviceManager > > _deviceManagerMap
Map of DeviceManagers.
boost::shared_ptr< DeviceManager > getDeviceManager(const std::string &aliasOrCDD)
Return the DeviceManager for the given alias name or CDD.
std::shared_ptr< ConfigReader > _configReader
Manager for Python-based ApplicationModules.
void optimiseUnmappedVariables(const std::set< std::string > &names) override
detail::TestableMode _testableMode
static Application & getInstance()
Obtain instance of the application.
void generateDOT()
Instead of running the application, just initialise it and output the published variables to a DOT fi...
ConnectionMaker _cm
Helper class to create connections.
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
bool _enableDebugMakeConnections
Flag if debug output is enabled for creation of the variable connections.
bool _debugDataLoss
Flag whether to debug data loss (as counted with the data loss counter).
detail::CircularDependencyDetector _circularDependencyDetector
static size_t getAndResetDataLossCounter()
Return the current value of the data loss counter and (atomically) reset it to 0.
std::atomic< size_t > _dataLossCounter
Counter for how many write() operations have overwritten unread data.
static void incrementDataLossCounter(const std::string &name)
Increment counter for how many write() operations have overwritten unread data.
Model::RootProxy _model
The model of the application.
const T & get(std::string variableName) const
Get value for given configuration variable.
void finalise()
Finalise the model and register all PVs with the control system adapter.
void connect()
Realise connections.
void optimiseUnmappedVariables(const std::set< std::string > &names)
Execute the optimisation request from the control system adapter (remove unused variables)
Base class for owners of other EntityOwners (e.g.
Definition EntityOwner.h:38
std::list< Module * > getSubmoduleListRecursive() const
Obtain the list of submodules associated with this instance and any submodules.
Proxy representing the root of the application model.
Definition Model.h:210
void writeGraphViz(const std::string &filename, Args... args) const
Implementations of RootProxy.
Definition Model.h:1789
void setDebugConnections(bool enable)
Generate XML representation of variables.
bool checkName(const std::string &name, bool allowDotsAndSlashes)
Check given name for characters which are not allowed in variable or module names.
Definition Utilities.cc:88
InvalidityTracer application module.
@ initialisation
Initialisation phase including ApplicationModule::prepare().
@ shutdown
The application is in the process of shutting down.
@ run
Actual run phase with full multi threading.
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:124