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