ChimeraTK-ApplicationCore 04.06.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 if(variable.getMode() == UpdateMode::poll) {
124 assert(!variable.getAppAccessorNoType().getHighLevelImplElement()->getAccessModeFlags().has(
125 AccessMode::wait_for_new_data));
126 Application::getInstance().getTestableMode().unlock("Initial value read for poll-type " + variable.getName());
127 Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
128 variable.getAppAccessorNoType().read();
129 Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
130 if(not Application::getInstance().getTestableMode().testLock()) {
131 // The lock may have already been acquired if the above read() goes to a ConsumingFanOut, which sends out
132 // the data to a slave decorated by a TestableModeAccessorDecorator. Hence we here must acquire the lock only
133 // if we do not have it.
135 "Initial value read for poll-type " + variable.getName(), useSharedTestLockForStartup);
136 }
137 }
138 }
139
140 for(auto& variable : accList) {
141 // According to spec, readback values are not considered in the distribution of initial values
142 // However, if they are declared as providing reverse recovery, they have to be considered.
143 auto doNotSkipInIvDistribution = variable.getDirection().dir == VariableDirection::feeding &&
144 variable.getDirection().withReturn && variable.getTags().contains(ChimeraTK::SystemTags::reverseRecovery);
145
146 if(!doNotSkipInIvDistribution && variable.getDirection().dir != VariableDirection::consuming) {
147 continue;
148 }
149
150 if(variable.getMode() == UpdateMode::push) {
151 Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
152 Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
153 // Will internally release and lock during the read, hence surround with lock/unlock
155 "Initial value read for push-type " + variable.getName(), useSharedTestLockForStartup);
156 variable.getAppAccessorNoType().read();
157 Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
158 Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
160 "Initial value read for push-type " + variable.getName(), useSharedTestLockForStartup);
161 }
162 }
163
164 // downgrade to shared lock
165 if(!useSharedTestLockForStartup) {
166 Application::getInstance().getTestableMode().unlock("downgrade to shared lock");
167 Application::getInstance().getTestableMode().lock("downgrade to shared lock", true);
168 }
169
170 // We are holding the testable mode lock, so we are sure the mechanism will work now.
172
173 // enter the main loop
174 mainLoop();
175 Application::getInstance().getTestableMode().unlock("terminate");
176 }
177
178 /********************************************************************************************************************/
179
183
188
189 /********************************************************************************************************************/
190
191 std::list<EntityOwner*> ApplicationModule::getInputModulesRecursively(std::list<EntityOwner*> startList) {
192 if(_recursionStopper.recursionDetected()) {
193 return startList;
194 }
195
196 // If this module is already in the list we found a circular dependency.
197 // Remember this for the next time the recursive scan calls this function
198 if(std::count(startList.begin(), startList.end(), this)) {
199 _recursionStopper.setRecursionDetected();
200 }
201
202 // Whether a circular dependency has been detected or not, we must loop all inputs and add this module to the list
203 // so the calling code sees the second instance and can also detect the circle. The reason why we have to scan all
204 // inputs even if a circle is detected is this:
205 // * A single input starts the scan by adding it's owning module. At this point not all inputs if that module are in
206 // the circular network.
207 // * When a circle is detected, it might only be one of multiple entangled circled. If we would break the recursion
208 // and not scan all the
209 // inputs this is sufficient to identify that the particular input is in a circle. But at this point we have to
210 // tell in which network it is and have to scan the complete network to calculate the correct hash value.
211
212 startList.push_back(this); // first we add this module to the start list. We will call all inputs with it.
213 std::list<EntityOwner*> returnList{
214 startList}; // prepare the return list. Deltas from the inputs will be added to it.
215 for(auto& accessor : this->getAccessorListRecursive()) {
216 // not consumed from network -> not an input, just continue
217 if(accessor.getDirection().dir != VariableDirection::consuming) {
218 continue;
219 }
220
221 // find the feeder in the network
222 auto proxy = accessor.getModel().visit(Model::returnApplicationModule, Model::keepApplicationModules,
223 Model::keepPvAccess, Model::adjacentInSearch, Model::returnFirstHit(Model::ApplicationModuleProxy{}));
224 if(!proxy.isValid()) {
225 continue;
226 }
227 auto& feedingModule = proxy.getApplicationModule();
228
229 auto thisInputsRecursiveModuleList = feedingModule.getInputModulesRecursively(startList);
230 // only add the modules that were added by the recursive search to the output list
231 assert(startList.size() <= thisInputsRecursiveModuleList.size());
232 auto copyStartIter = thisInputsRecursiveModuleList.begin();
233 std::advance(copyStartIter, startList.size());
234
235 returnList.insert(returnList.end(), copyStartIter, thisInputsRecursiveModuleList.end());
236 }
237 return returnList;
238 }
239
240 /********************************************************************************************************************/
241
245
246 /********************************************************************************************************************/
247
248 void ApplicationModule::setCircularNetworkHash(size_t circularNetworkHash) {
249 if(_circularNetworkHash != 0 && _circularNetworkHash != circularNetworkHash) {
250 throw ChimeraTK::logic_error(
251 "Error: setCircularNetworkHash() called with different values for EntityOwner \"" + _name + "\" ");
252 }
253 if(Application::getInstance().getLifeCycleState() != LifeCycleState::initialisation) {
254 throw ChimeraTK::logic_error("Error: setCircularNetworkHash() called after initialisation.");
255 }
256 _circularNetworkHash = circularNetworkHash;
257 }
258
259 /********************************************************************************************************************/
260
262 if(_dataFaultCounter == 0) {
263 return DataValidity::ok;
264 }
265 if(_circularNetworkHash != 0) {
266 // In a circular dependency network, internal inputs are ignored.
267 // If all external inputs (including the ones from this module) are OK, the
268 // data validity is set to OK.
269 if(Application::getInstance()._circularNetworkInvalidityCounters[_circularNetworkHash] == 0) {
270 return DataValidity::ok;
271 }
272 }
273 // not a circular network or invalidity counter not 0 -> keep the faulty flag
274 return DataValidity::faulty;
275 }
276
277 /********************************************************************************************************************/
278
281 if(_model.isValid()) {
282 auto* vg = dynamic_cast<VariableGroup*>(module);
283 if(!vg) {
284 // during destruction unregisterModule is called from the base class destructor where the dynamic_cast already
285 // fails.
286 return;
287 }
288 _model.remove(*vg);
289 }
290 }
291
292 /********************************************************************************************************************/
293
294} /* 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.
@ initialisation
Initialisation phase including ApplicationModule::prepare().