ChimeraTK-ApplicationCore  04.01.00
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 
21 using namespace ChimeraTK;
22 
23 /*********************************************************************************************************************/
24 
25 Application::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 
81 void Application::registerThread(const std::string& name) {
82  getInstance()._testableMode.setThreadName(name);
83 }
84 
85 /*********************************************************************************************************************/
86 
87 void 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 
107  if(_initialiseCalled) {
108  throw ChimeraTK::logic_error("Application::initialise() was already called before.");
109  }
110 
111  if(!getPVManager()) {
112  throw ChimeraTK::logic_error("Application::initialise() was called without an instance of ChimeraTK::PVManager.");
113  }
114 
116  _cm.finalise();
117 
118  _initialiseCalled = true;
119 }
120 
121 /*********************************************************************************************************************/
122 
123 void Application::optimiseUnmappedVariables(const std::set<std::string>& names) {
124  if(!_initialiseCalled) {
125  throw ChimeraTK::logic_error(
126  "Application::initialise() must be called before Application::optimiseUnmappedVariables().");
127  }
128 
130 }
131 
132 /*********************************************************************************************************************/
133 
135  assert(!_applicationName.empty());
136 
137  if(_testableMode.isEnabled()) {
139  throw ChimeraTK::logic_error(
140  "Testable mode enabled but Application::run() called directly. Call TestFacility::runApplication() instead.");
141  }
142  }
143 
144  if(_runCalled) {
145  throw ChimeraTK::logic_error("Application::run() has already been called before.");
146  }
147  _runCalled = true;
148 
149  // realise the PV connections
150  _cm.connect();
151 
152  // set all initial version numbers in the modules to the same value
153  for(auto& module : getSubmoduleListRecursive()) {
154  if(module->getModuleType() != ModuleType::ApplicationModule) {
155  continue;
156  }
157  module->setCurrentVersionNumber(getStartVersion());
158  }
159 
160  // prepare the modules
161  for(auto& module : getSubmoduleListRecursive()) {
162  module->prepare();
163  }
164 
165  // Switch life-cycle state to run
167 
168  // start the necessary threads for the FanOuts etc.
169  for(auto& internalModule : _internalModuleList) {
170  internalModule->activate();
171  }
172 
173  // start the threads for the modules
174  for(auto& module : getSubmoduleListRecursive()) {
175  module->run();
176  }
177 
178  // When in testable mode, wait for all modules to report that they have reached the testable mode.
179  // We have to start all module threads first because some modules might only send the initial
180  // values in their main loop, and following modules need them to enter testable mode.
181 
182  // just a small helper lambda to avoid code repetition
183  auto waitForTestableMode = [](EntityOwner* module) {
184  while(!module->hasReachedTestableMode()) {
185  Application::getInstance().getTestableMode().unlock("releaseForReachTestableMode");
186  usleep(100);
187  Application::getInstance().getTestableMode().lock("acquireForReachTestableMode");
188  }
189  };
190 
191  if(Application::getInstance().getTestableMode().isEnabled()) {
192  for(auto& internalModule : _internalModuleList) {
193  waitForTestableMode(internalModule.get());
194  }
195 
196  for(auto& module : getSubmoduleListRecursive()) {
197  waitForTestableMode(module);
198  }
199  }
200 
201  // Launch circular dependency detector thread
202  _circularDependencyDetector.startDetectBlockedModules();
203 }
204 
205 /*********************************************************************************************************************/
206 
208  // switch life-cycle state
210 
211  // first allow to run the application threads again, if we are in testable
212  // mode
213  if(_testableMode.isEnabled() && _testableMode.testLock()) {
214  _testableMode.unlock("shutdown");
215  }
216 
217  // deactivate the FanOuts first, since they have running threads inside
218  // accessing the modules etc. (note: the modules are members of the
219  // Application implementation and thus get destroyed after this destructor)
220  for(auto& internalModule : _internalModuleList) {
221  internalModule->deactivate();
222  }
223 
224  // shutdown all DeviceManagers, otherwise application modules might hang if still waiting for initial values from
225  // devices
226  for(auto& pair : _deviceManagerMap) {
227  pair.second->terminate();
228  }
229 
230  // next deactivate the modules, as they have running threads inside as well
231  for(auto& module : getSubmoduleListRecursive()) {
232  module->terminate();
233  }
234 
235  _circularDependencyDetector.terminate();
236 
237  ApplicationBase::shutdown();
238 }
239 
240 /*********************************************************************************************************************/
241 
242 /*********************************************************************************************************************/
243 
245  assert(!_applicationName.empty());
246 
247  XMLGenerator generator{*this};
248  generator.run();
249  generator.save(_applicationName + ".xml");
250 }
251 
252 /*********************************************************************************************************************/
253 
255  assert(!_applicationName.empty());
256  this->getModel().writeGraphViz(_applicationName + ".dot");
257 }
258 
259 /*********************************************************************************************************************/
260 
262  return dynamic_cast<Application&>(ApplicationBase::getInstance());
263 }
264 
265 /*********************************************************************************************************************/
266 
267 boost::shared_ptr<DeviceManager> Application::getDeviceManager(const std::string& aliasOrCDD) {
268  if(_deviceManagerMap.find(aliasOrCDD) == _deviceManagerMap.end()) {
269  // Add initialisation handler below, since we also need to add it if the DeviceModule already exists
270  _deviceManagerMap[aliasOrCDD] = boost::make_shared<DeviceManager>(&Application::getInstance(), aliasOrCDD);
271  }
272  return _deviceManagerMap.at(aliasOrCDD);
273 }
274 
275 /*********************************************************************************************************************/
ChimeraTK::Application::enableTestableMode
void enableTestableMode()
Enable the testable mode.
Definition: Application.cc:74
ChimeraTK::Application::_deviceManagerMap
std::map< std::string, boost::shared_ptr< DeviceManager > > _deviceManagerMap
Map of DeviceManagers.
Definition: Application.h:221
ChimeraTK::Application::_defaultConfigReader
ConfigReader * _defaultConfigReader
Definition: Application.h:287
ChimeraTK::Application::registerThread
static void registerThread(const std::string &name)
Register the thread in the application system and give it a name.
Definition: Application.cc:81
ChimeraTK::Logger::Severity::debug
@ debug
ChimeraTK::LifeCycleState::run
@ run
Actual run phase with full multi threading.
ChimeraTK::Application::optimiseUnmappedVariables
void optimiseUnmappedVariables(const std::set< std::string > &names) override
Definition: Application.cc:123
ChimeraTK::Application::_initialiseCalled
bool _initialiseCalled
Flag whether initialise() has been called already, to make sure it doesn't get called twice.
Definition: Application.h:228
ConfigReader.h
ChimeraTK::EntityOwner::ModuleType::ApplicationModule
@ ApplicationModule
ChimeraTK::Application::shutdown
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
Definition: Application.cc:207
ChimeraTK::Application::_dataLossCounter
std::atomic< size_t > _dataLossCounter
Counter for how many write() operations have overwritten unread data.
Definition: Application.h:249
ChimeraTK::Application::_debugDataLoss
bool _debugDataLoss
Flag whether to debug data loss (as counted with the data loss counter).
Definition: Application.h:252
ChimeraTK::Application::_lifeCycleState
std::atomic< LifeCycleState > _lifeCycleState
Life-cycle state of the application.
Definition: Application.h:255
ChimeraTK::LifeCycleState::shutdown
@ shutdown
The application is in the process of shutting down.
ChimeraTK::Application::_model
Model::RootProxy _model
The model of the application.
Definition: Application.h:212
ChimeraTK::ModuleGroup
Definition: ModuleGroup.h:16
ChimeraTK::Application::getModel
Model::RootProxy getModel()
Return the root of the application model.
Definition: Application.h:78
ChimeraTK::Application::incrementDataLossCounter
static void incrementDataLossCounter(const std::string &name)
Increment counter for how many write() operations have overwritten unread data.
Definition: Application.cc:87
Utilities.h
ChimeraTK::Application::generateXML
void generateXML()
Instead of running the application, just initialise it and output the published variables to an XML f...
Definition: Application.cc:244
ChimeraTK::EntityOwner
Base class for owners of other EntityOwners (e.g.
Definition: EntityOwner.h:38
ChimeraTK::Application::_internalModuleList
std::list< boost::shared_ptr< InternalModule > > _internalModuleList
List of InternalModules.
Definition: Application.h:218
pybind11::module
module_ module
Definition: PyModuleGroup.h:12
ChimeraTK::Model::RootProxy::writeGraphViz
void writeGraphViz(const std::string &filename, Args... args) const
Implementations of RootProxy.
Definition: Model.h:1787
ChimeraTK::Application::initialise
void initialise() override
Definition: Application.cc:106
ChimeraTK::XMLGenerator
Generate XML representation of variables.
Definition: XMLGeneratorVisitor.h:23
ChimeraTK::Application::run
void run() override
Execute the module.
Definition: Application.cc:134
ChimeraTK::Utilities::checkName
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:87
ChimeraTK::Application::_circularDependencyDetector
detail::CircularDependencyDetector _circularDependencyDetector
Definition: Application.h:240
ChimeraTK::Application::getInstance
static Application & getInstance()
Obtain instance of the application.
Definition: Application.cc:261
ChimeraTK::logger
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition: Logger.h:124
ChimeraTK::Application::generateDOT
void generateDOT()
Instead of running the application, just initialise it and output the published variables to a DOT fi...
Definition: Application.cc:254
XMLGeneratorVisitor.h
ChimeraTK::Application::getDeviceManager
boost::shared_ptr< DeviceManager > getDeviceManager(const std::string &aliasOrCDD)
Return the DeviceManager for the given alias name or CDD.
Definition: Application.cc:267
ChimeraTK::ConnectionMaker::finalise
void finalise()
Finalise the model and register all PVs with the control system adapter.
Definition: ConnectionMaker.cc:327
ChimeraTK::Model::ModuleGroupProxy
Definition: Model.h:274
ChimeraTK::Application::_configReader
std::shared_ptr< ConfigReader > _configReader
Manager for Python-based ApplicationModules.
Definition: Application.h:286
Application.h
CircularDependencyDetector.h
ChimeraTK::ConnectionMaker::connect
void connect()
Realise connections.
Definition: ConnectionMaker.cc:373
DeviceManager.h
ConnectionMaker.h
ChimeraTK::Application::_testableMode
detail::TestableMode _testableMode
Definition: Application.h:241
ChimeraTK::ModuleGroup::Application
friend class Application
Definition: ModuleGroup.h:47
ChimeraTK::Application::_testFacilityRunApplicationCalled
bool _testFacilityRunApplicationCalled
Flag which is set by the TestFacility in runApplication() at the beginning.
Definition: Application.h:225
ChimeraTK::LifeCycleState::initialisation
@ initialisation
Initialisation phase including ApplicationModule::prepare().
ChimeraTK::Application::_runCalled
bool _runCalled
Flag whether run() has been called already, to make sure it doesn't get called twice.
Definition: Application.h:231
ChimeraTK::Application::_cm
ConnectionMaker _cm
Helper class to create connections.
Definition: Application.h:215
ChimeraTK::Application::_enableDebugMakeConnections
bool _enableDebugMakeConnections
Flag if debug output is enabled for creation of the variable connections.
Definition: Application.h:234
ChimeraTK::Model::RootProxy
Proxy representing the root of the application model.
Definition: Model.h:210
ChimeraTK::XMLGenerator::run
void run()
Definition: XMLGeneratorVisitor.cc:32
ChimeraTK::ConnectionMaker::optimiseUnmappedVariables
void optimiseUnmappedVariables(const std::set< std::string > &names)
Execute the optimisation request from the control system adapter (remove unused variables)
Definition: ConnectionMaker.cc:894
ChimeraTK::Application::getAndResetDataLossCounter
static size_t getAndResetDataLossCounter()
Return the current value of the data loss counter and (atomically) reset it to 0.
Definition: Application.cc:96
ChimeraTK::Application::getStartVersion
VersionNumber getStartVersion() const
Return the start version.
Definition: Application.h:175
ChimeraTK::NetworkVisitor::setDebugConnections
void setDebugConnections(bool enable)
Definition: ConnectionMaker.h:21
ChimeraTK
InvalidityTracer application module.
Definition: spec_dataValidityPropagation.dox:2
ChimeraTK::EntityOwner::getSubmoduleListRecursive
std::list< Module * > getSubmoduleListRecursive() const
Obtain the list of submodules associated with this instance and any submodules.
Definition: EntityOwner.cc:83
ChimeraTK::Application::~Application
~Application() override
Definition: Application.cc:64
ChimeraTK::Application::getTestableMode
detail::TestableMode & getTestableMode()
Get the TestableMode control object of this application.
Definition: Application.h:117
ChimeraTK::Application
Definition: Application.h:48