ChimeraTK-ApplicationCore  04.01.00
ApplicationModule.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 
4 #include "ApplicationModule.h"
5 
6 #include "Application.h"
8 #include "ModuleGroup.h"
9 
10 #include <ChimeraTK/Utilities.h>
11 
12 #include <iterator>
13 #include <list>
14 
15 namespace ChimeraTK {
16 
17  /*********************************************************************************************************************/
18 
19  ApplicationModule::ApplicationModule(ModuleGroup* owner, const std::string& name, const std::string& description,
20  const std::unordered_set<std::string>& tags)
21  : VariableGroup(owner, name, description, tags) {
22  if(!owner) {
23  throw ChimeraTK::logic_error("ApplicationModule owner cannot be nullptr");
24  }
25 
26  if(owner->getModel().isValid()) {
27  _model = owner->getModel().add(*this);
29  // Model::VariableGroupProxy(_model);
30  }
31  }
32 
33  /*********************************************************************************************************************/
34 
36  assert(!_moduleThread.joinable()); // if the thread is already running, moving is no longer allowed!
37 
38  // Keep the model as is (except from updating the pointers to the C++ objects). To do so, we have to hide it from
39  // unregisterModule() which is executed in Module::operator=(), because it would destroy the model.
40  Model::ApplicationModuleProxy model = std::move(other._model);
41  other._model = {};
42 
43  VariableGroup::operator=(std::move(other));
44 
45  if(model.isValid()) {
46  model.informMove(*this);
47  }
48  _model = model;
49 
50  return *this;
51  }
52 
53  /*********************************************************************************************************************/
54 
56  // start the module thread
57  assert(!_moduleThread.joinable());
58  _moduleThread = boost::thread(&ApplicationModule::mainLoopWrapper, this);
59  }
60 
61  /*********************************************************************************************************************/
62 
64  if(_moduleThread.joinable()) {
65  _moduleThread.interrupt();
66  // try joining the thread
67  while(!_moduleThread.try_join_for(boost::chrono::milliseconds(10))) {
68  // if thread is not yet joined, send interrupt() to all variables.
69  for(auto& var : getAccessorListRecursive()) {
70  auto el{var.getAppAccessorNoType().getHighLevelImplElement()};
71  el->interrupt();
72  }
73  // it may not suffice to send interrupt() once, as the exception might get
74  // overwritten in the queue, thus we repeat this until the thread was
75  // joined.
76  }
77  }
78  assert(!_moduleThread.joinable());
79  }
80 
81  /*********************************************************************************************************************/
82 
84  assert(!_moduleThread.joinable());
85  }
86 
87  /*********************************************************************************************************************/
88 
89  void ApplicationModule::setCurrentVersionNumber(VersionNumber versionNumber) {
90  if(versionNumber > _currentVersionNumber) {
91  _currentVersionNumber = versionNumber;
92  }
93  }
94 
95  /*********************************************************************************************************************/
96 
98  Application::registerThread("AM_" + className()); // + getName());
99 
100  // Acquire testable mode lock, so from this point on we are running only one user thread concurrently
101  Application::getInstance().getTestableMode().lock("start");
102 
103  // Read all variables once to obtain the initial values from the devices and from the control system persistency
104  // layer. This is done in two steps, first for all poll-type variables and then for all push-types, because
105  // poll-type reads might trigger distribution of values to push-type variables via a ConsumingFanOut.
106  for(auto& variable : getAccessorListRecursive()) {
107  if(variable.getDirection().dir != VariableDirection::consuming) {
108  continue;
109  }
110  if(variable.getMode() == UpdateMode::poll) {
111  assert(!variable.getAppAccessorNoType().getHighLevelImplElement()->getAccessModeFlags().has(
112  AccessMode::wait_for_new_data));
113  Application::getInstance().getTestableMode().unlock("Initial value read for poll-type " + variable.getName());
114  Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
115  variable.getAppAccessorNoType().read();
116  Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
117  if(not Application::getInstance().getTestableMode().testLock()) {
118  // The lock may have already been acquired if the above read() goes to a ConsumingFanOut, which sends out
119  // the data to a slave decorated by a TestableModeAccessorDecorator. Hence we here must acquire the lock only
120  // if we do not have it.
121  Application::getInstance().getTestableMode().lock("Initial value read for poll-type " + variable.getName());
122  }
123  }
124  }
125  for(auto& variable : getAccessorListRecursive()) {
126  if(variable.getDirection().dir != VariableDirection::consuming) {
127  continue;
128  }
129  if(variable.getMode() == UpdateMode::push) {
130  Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
131  Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
132  // Will internally release and lock during the read, hence surround with lock/unlock
133  Application::getInstance().getTestableMode().lock("Initial value read for push-type " + variable.getName());
134  variable.getAppAccessorNoType().read();
135  Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
136  Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
137  Application::getInstance().getTestableMode().lock("Initial value read for push-type " + variable.getName());
138  }
139  }
140 
141  // We are holding the testable mode lock, so we are sure the mechanism will work now.
142  _testableModeReached = true;
143 
144  // enter the main loop
145  mainLoop();
146  Application::getInstance().getTestableMode().unlock("terminate");
147  }
148 
149  /*********************************************************************************************************************/
150 
153  }
154 
156  assert(_dataFaultCounter > 0);
158  }
159 
160  /*********************************************************************************************************************/
161 
162  std::list<EntityOwner*> ApplicationModule::getInputModulesRecursively(std::list<EntityOwner*> startList) {
163  if(_recursionStopper.recursionDetected()) {
164  return startList;
165  }
166 
167  // If this module is already in the list we found a circular dependency.
168  // Remember this for the next time the recursive scan calls this function
169  if(std::count(startList.begin(), startList.end(), this)) {
170  _recursionStopper.setRecursionDetected();
171  }
172 
173  // Whether a circular dependency has been detected or not, we must loop all inputs and add this module to the list
174  // so the calling code sees the second instance and can also detect the circle. The reason why we have to scan all
175  // inputs even if a circle is detected is this:
176  // * A single input starts the scan by adding it's owning module. At this point not all inputs if that module are in
177  // the circular network.
178  // * When a circle is detected, it might only be one of multiple entangled circled. If we would break the recursion
179  // and not scan all the
180  // inputs this is sufficient to identify that the particular input is in a circle. But at this point we have to
181  // tell in which network it is and have to scan the complete network to calculate the correct hash value.
182 
183  startList.push_back(this); // first we add this module to the start list. We will call all inputs with it.
184  std::list<EntityOwner*> returnList{
185  startList}; // prepare the return list. Deltas from the inputs will be added to it.
186  for(auto& accessor : this->getAccessorListRecursive()) {
187  // not consumed from network -> not an input, just continue
188  if(accessor.getDirection().dir != VariableDirection::consuming) {
189  continue;
190  }
191 
192  // find the feeder in the network
193  auto proxy = accessor.getModel().visit(Model::returnApplicationModule, Model::keepApplicationModules,
194  Model::keepPvAccess, Model::adjacentInSearch, Model::returnFirstHit(Model::ApplicationModuleProxy{}));
195  if(!proxy.isValid()) {
196  continue;
197  }
198  auto& feedingModule = proxy.getApplicationModule();
199 
200  auto thisInputsRecursiveModuleList = feedingModule.getInputModulesRecursively(startList);
201  // only add the modules that were added by the recursive search to the output list
202  assert(startList.size() <= thisInputsRecursiveModuleList.size());
203  auto copyStartIter = thisInputsRecursiveModuleList.begin();
204  std::advance(copyStartIter, startList.size());
205 
206  returnList.insert(returnList.end(), copyStartIter, thisInputsRecursiveModuleList.end());
207  }
208  return returnList;
209  }
210 
211  /*********************************************************************************************************************/
212 
214  return _circularNetworkHash;
215  }
216 
217  /*********************************************************************************************************************/
218 
219  void ApplicationModule::setCircularNetworkHash(size_t circularNetworkHash) {
220  if(_circularNetworkHash != 0 && _circularNetworkHash != circularNetworkHash) {
221  throw ChimeraTK::logic_error(
222  "Error: setCircularNetworkHash() called with different values for EntityOwner \"" + _name + "\" ");
223  }
224  if(Application::getInstance().getLifeCycleState() != LifeCycleState::initialisation) {
225  throw ChimeraTK::logic_error("Error: setCircularNetworkHash() called after initialisation.");
226  }
227  _circularNetworkHash = circularNetworkHash;
228  }
229 
230  /*********************************************************************************************************************/
231 
232  DataValidity ApplicationModule::getDataValidity() const {
233  if(_dataFaultCounter == 0) {
234  return DataValidity::ok;
235  }
236  if(_circularNetworkHash != 0) {
237  // In a circular dependency network, internal inputs are ignored.
238  // If all external inputs (including the ones from this module) are OK, the
239  // data validity is set to OK.
240  if(Application::getInstance()._circularNetworkInvalidityCounters[_circularNetworkHash] == 0) {
241  return DataValidity::ok;
242  }
243  }
244  // not a circular network or invalidity counter not 0 -> keep the faulty flag
245  return DataValidity::faulty;
246  }
247 
248  /********************************************************************************************************************/
249 
252  if(_model.isValid()) {
253  auto* vg = dynamic_cast<VariableGroup*>(module);
254  if(!vg) {
255  // during destruction unregisterModule is called from the base class destructor where the dynamic_cast already
256  // fails.
257  return;
258  }
259  _model.remove(*vg);
260  }
261  }
262 
263  /*********************************************************************************************************************/
264 
265 } /* namespace ChimeraTK */
ChimeraTK::EntityOwner::getAccessorListRecursive
std::list< VariableNetworkNode > getAccessorListRecursive() const
Obtain the list of accessors/variables associated with this instance and any submodules.
Definition: EntityOwner.cc:69
ChimeraTK::Model::ApplicationModuleProxy::remove
void remove(VariableGroup &module)
Definition: Model.cc:234
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::Model::ApplicationModuleProxy
Definition: Model.h:300
ChimeraTK::UpdateMode::poll
@ poll
ChimeraTK::ApplicationModule::_dataFaultCounter
std::atomic< size_t > _dataFaultCounter
Number of inputs which report DataValidity::faulty.
Definition: ApplicationModule.h:111
ChimeraTK::ModuleGroup
Definition: ModuleGroup.h:16
pybind11::module
module_ module
Definition: PyModuleGroup.h:12
ChimeraTK::ApplicationModule::_currentVersionNumber
VersionNumber _currentVersionNumber
Version number of last push-type read operation - will be passed on to any write operations.
Definition: ApplicationModule.h:105
ModuleGroup.h
ChimeraTK::VariableDirection::consuming
@ consuming
Definition: Flags.h:18
ChimeraTK::ApplicationModule::setCurrentVersionNumber
void setCurrentVersionNumber(VersionNumber versionNumber) override
Set the current version number.
Definition: ApplicationModule.cc:89
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::ApplicationModule::terminate
void terminate() override
Terminate the module.
Definition: ApplicationModule.cc:63
ChimeraTK::ApplicationModule::_recursionStopper
detail::CircularDependencyDetectionRecursionStopper _recursionStopper
Helper needed to stop the recursion when detecting circular dependency networks.
Definition: ApplicationModule.h:123
ChimeraTK::UpdateMode::push
@ push
ChimeraTK::ApplicationModule::setCircularNetworkHash
void setCircularNetworkHash(size_t circularNetworkHash)
Set the ID of the circular dependency network.
Definition: ApplicationModule.cc:219
ChimeraTK::Model::ModuleGroupProxy::add
ModuleGroupProxy add(ModuleGroup &module)
Definition: Model.cc:164
ChimeraTK::ApplicationModule
Definition: ApplicationModule.h:24
ChimeraTK::VariableGroup
Definition: VariableGroup.h:22
ChimeraTK::ApplicationModule::operator=
ApplicationModule & operator=(ApplicationModule &&other) noexcept
Move assignment.
Definition: ApplicationModule.cc:35
ChimeraTK::ApplicationModule::mainLoop
virtual void mainLoop()=0
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
ChimeraTK::VariableGroup::operator=
VariableGroup & operator=(VariableGroup &&other) noexcept
Move assignment.
Definition: VariableGroup.cc:38
ChimeraTK::ApplicationModule::decrementDataFaultCounter
void decrementDataFaultCounter() override
Decrement the fault counter and set the data validity flag to ok if the counter has reached 0.
Definition: ApplicationModule.cc:155
Application.h
CircularDependencyDetector.h
ChimeraTK::VariableGroup::unregisterModule
void unregisterModule(Module *module) override
Unregister another module as a sub-module.
Definition: VariableGroup.cc:62
ChimeraTK::ModuleGroup::getModel
ChimeraTK::Model::ModuleGroupProxy getModel()
Return the application model proxy representing this module.
Definition: ModuleGroup.h:40
ChimeraTK::ApplicationModule::unregisterModule
void unregisterModule(Module *module) override
Unregister another module as a sub-module.
Definition: ApplicationModule.cc:250
ChimeraTK::Model::Proxy::isValid
bool isValid() const
Check if the model is valid.
Definition: Model.cc:31
ChimeraTK::ApplicationModule::_model
ChimeraTK::Model::ApplicationModuleProxy _model
Definition: ApplicationModule.h:133
ChimeraTK::VariableGroup::_model
ChimeraTK::Model::VariableGroupProxy _model
Definition: VariableGroup.h:58
ChimeraTK::ApplicationModule::run
void run() override
Execute the module.
Definition: ApplicationModule.cc:55
ChimeraTK::ApplicationModule::_moduleThread
boost::thread _moduleThread
The thread executing mainLoop()
Definition: ApplicationModule.h:101
ChimeraTK::LifeCycleState::initialisation
@ initialisation
Initialisation phase including ApplicationModule::prepare().
ChimeraTK::ApplicationModule::_circularNetworkHash
size_t _circularNetworkHash
Unique ID for the circular dependency network.
Definition: ApplicationModule.h:118
ChimeraTK::ApplicationModule::~ApplicationModule
~ApplicationModule() override
Destructor.
Definition: ApplicationModule.cc:83
ChimeraTK::ApplicationModule::getDataValidity
DataValidity getDataValidity() const override
Return the data validity flag.
Definition: ApplicationModule.cc:232
ApplicationModule.h
ChimeraTK::EntityOwner::_name
std::string _name
The name of this instance.
Definition: EntityOwner.h:171
ChimeraTK::ApplicationModule::incrementDataFaultCounter
void incrementDataFaultCounter() override
Set the data validity flag to fault and increment the fault counter.
Definition: ApplicationModule.cc:151
ChimeraTK::ApplicationModule::ApplicationModule
ApplicationModule()=default
Default constructor: Allows late initialisation of modules (e.g.
ChimeraTK
InvalidityTracer application module.
Definition: spec_dataValidityPropagation.dox:2
ChimeraTK::ApplicationModule::className
std::string className()
Name of the module class, used for logging and debugging purposes.
Definition: ApplicationModule.h:128
ChimeraTK::Application::getTestableMode
detail::TestableMode & getTestableMode()
Get the TestableMode control object of this application.
Definition: Application.h:117
ChimeraTK::EntityOwner::_testableModeReached
std::atomic< bool > _testableModeReached
Flag used by the testable mode to identify whether a thread within the EntityOwner has reached the po...
Definition: EntityOwner.h:190
ChimeraTK::Module
Base class for ApplicationModule and DeviceModule, to have a common interface for these module types.
Definition: Module.h:21
ChimeraTK::ApplicationModule::getInputModulesRecursively
std::list< EntityOwner * > getInputModulesRecursively(std::list< EntityOwner * > startList) override
Use pointer to the module as unique identifier.
Definition: ApplicationModule.cc:162
ChimeraTK::ApplicationModule::getCircularNetworkHash
size_t getCircularNetworkHash() const override
Get the ID of the circular dependency network (0 if none).
Definition: ApplicationModule.cc:213
ChimeraTK::ApplicationModule::mainLoopWrapper
void mainLoopWrapper()
Wrapper around mainLoop(), to execute additional tasks in the thread before entering the main loop.
Definition: ApplicationModule.cc:97