ChimeraTK-ApplicationCore 04.08.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) {
88 logger(Logger::Severity::debug, "DataLossCounter") << "Data loss in variable " << name;
90}
91
92/**********************************************************************************************************************/
93
95 size_t counter = getInstance()._dataLossCounter.load(std::memory_order_relaxed);
96 while(!getInstance()._dataLossCounter.compare_exchange_weak(
97 counter, 0, std::memory_order_release, std::memory_order_relaxed)) {
98 }
99 return counter;
100}
101
102/**********************************************************************************************************************/
103
106 throw ChimeraTK::logic_error("Application::initialise() was already called before.");
107 }
108
109 // call postConstruct on all Modules
110 for(auto& module : getSubmoduleListRecursive()) {
111 module->postConstruct();
112 }
113
114 _cm.finalise();
115
116 _initialiseCalled = true;
117}
118
119/**********************************************************************************************************************/
120
121void Application::optimiseUnmappedVariables(const std::set<std::string>& names) {
122 if(!_initialiseCalled) {
123 throw ChimeraTK::logic_error(
124 "Application::initialise() must be called before Application::optimiseUnmappedVariables().");
125 }
126
128}
129
130/**********************************************************************************************************************/
131
133 assert(!_applicationName.empty());
134
135 if(!getPVManager()) {
136 throw ChimeraTK::logic_error("Application::run() was called without an instance of ChimeraTK::PVManager.");
137 }
138
139 if(_testableMode.isEnabled()) {
141 throw ChimeraTK::logic_error(
142 "Testable mode enabled but Application::run() called directly. Call TestFacility::runApplication() instead.");
143 }
144 }
145
146 if(_runCalled) {
147 throw ChimeraTK::logic_error("Application::run() has already been called before.");
148 }
149 _runCalled = true;
150
151 // realise the PV connections
152 _cm.connect();
153
154 // set all initial version numbers in the modules to the same value
155 for(auto& module : getSubmoduleListRecursive()) {
156 if(module->getModuleType() != ModuleType::ApplicationModule) {
157 continue;
158 }
159 module->setCurrentVersionNumber(getStartVersion());
160 }
161
162 // prepare the modules
163 for(auto& module : getSubmoduleListRecursive()) {
164 module->prepare();
165 }
166
167 // Switch life-cycle state to run
169
170 // start the necessary threads for the FanOuts etc.
171 for(auto& internalModule : _internalModuleList) {
172 internalModule->activate();
173 }
174
175 // start the threads for the modules
176 for(auto& module : getSubmoduleListRecursive()) {
177 module->run();
178 }
179
180 // When in testable mode, wait for all modules to report that they have reached the testable mode.
181 // We have to start all module threads first because some modules might only send the initial
182 // values in their main loop, and following modules need them to enter testable mode.
183
184 // just a small helper lambda to avoid code repetition
185 auto waitForTestableMode = [](EntityOwner* module) {
186 while(!module->hasReachedTestableMode()) {
187 Application::getInstance().getTestableMode().unlock("releaseForReachTestableMode");
188 usleep(100);
189 // Note: This is executed inside the test thread (by TestFacility::runApplication()), so we need the exclusive
190 // lock here.
191 Application::getInstance().getTestableMode().lock("acquireForReachTestableMode", false);
192 }
193 };
194
195 if(Application::getInstance().getTestableMode().isEnabled()) {
196 for(auto& internalModule : _internalModuleList) {
197 waitForTestableMode(internalModule.get());
198 }
199
200 for(auto& module : getSubmoduleListRecursive()) {
201 waitForTestableMode(module);
202 }
203 }
204
205 // Launch circular dependency detector thread
206 _circularDependencyDetector.startDetectBlockedModules();
207}
208
209/**********************************************************************************************************************/
210
212 // switch life-cycle state
214
215 // first allow to run the application threads again, if we are in testable
216 // mode
217 if(_testableMode.isEnabled() && _testableMode.testLock()) {
218 _testableMode.unlock("shutdown");
219 }
220
221 // deactivate the FanOuts first, since they have running threads inside
222 // accessing the modules etc. (note: the modules are members of the
223 // Application implementation and thus get destroyed after this destructor)
224 for(auto& internalModule : _internalModuleList) {
225 internalModule->deactivate();
226 }
227
228 // shutdown all DeviceManagers, otherwise application modules might hang if still waiting for initial values from
229 // devices
230 for(auto& pair : _deviceManagerMap) {
231 pair.second->terminate();
232 }
233
234 // next deactivate the modules, as they have running threads inside as well
235 for(auto& module : getSubmoduleListRecursive()) {
236 module->terminate();
237 }
238
239 _circularDependencyDetector.terminate();
240
241 // Since the destructor of the Application may come too late, we will de-init the Python system here
242 getPythonModuleManager().deinit();
243
244 ApplicationBase::shutdown();
245}
246
247/**********************************************************************************************************************/
248
249/**********************************************************************************************************************/
250
252 assert(!_applicationName.empty());
253
254 XMLGenerator generator{*this};
255 generator.run();
256 generator.save(_applicationName + ".xml");
257}
258
259/**********************************************************************************************************************/
260
262 assert(!_applicationName.empty());
263 this->getModel().writeGraphViz(_applicationName + ".dot");
264}
265
266/**********************************************************************************************************************/
267
269 return dynamic_cast<Application&>(ApplicationBase::getInstance());
270}
271
272/**********************************************************************************************************************/
273
274boost::shared_ptr<DeviceManager> Application::getDeviceManager(const std::string& aliasOrCDD) {
275 if(_deviceManagerMap.find(aliasOrCDD) == _deviceManagerMap.end()) {
276 // Add initialisation handler below, since we also need to add it if the DeviceModule already exists
277 _deviceManagerMap[aliasOrCDD] = boost::make_shared<DeviceManager>(&Application::getInstance(), aliasOrCDD);
278 }
279 return _deviceManagerMap.at(aliasOrCDD);
280}
281
282/**********************************************************************************************************************/
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.
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
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:156