Newer
Older
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "ApplicationModule.h"
#include "Application.h"
#include "ModuleGroup.h"
Martin Killenberg
committed
#include <iterator>
#include <list>
Martin Christoph Hierholzer
committed
namespace ChimeraTK {
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
ApplicationModule::ApplicationModule(ModuleGroup* owner, const std::string& name, const std::string& description,
const std::unordered_set<std::string>& tags)
: VariableGroup(owner, name, description, tags) {
if(!owner) {
throw ChimeraTK::logic_error("ApplicationModule owner cannot be nullptr");
Martin Christoph Hierholzer
committed
}
if(owner->getModel().isValid()) {
_model = owner->getModel().add(*this);
VariableGroup::_model = {};
// Model::VariableGroupProxy(_model);
Martin Christoph Hierholzer
committed
}
}
/*********************************************************************************************************************/
ApplicationModule& ApplicationModule::operator=(ApplicationModule&& other) noexcept {
assert(!_moduleThread.joinable()); // if the thread is already running, moving is no longer allowed!
// Keep the model as is (except from updating the pointers to the C++ objects). To do so, we have to hide it from
// unregisterModule() which is executed in Module::operator=(), because it would destroy the model.
Model::ApplicationModuleProxy model = std::move(other._model);
other._model = {};
VariableGroup::operator=(std::move(other));
if(model.isValid()) {
model.informMove(*this);
}
return *this;
}
/*********************************************************************************************************************/
void ApplicationModule::run() {
// start the module thread
assert(!_moduleThread.joinable());
_moduleThread = boost::thread(&ApplicationModule::mainLoopWrapper, this);
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
if(_moduleThread.joinable()) {
_moduleThread.interrupt();
while(!_moduleThread.try_join_for(boost::chrono::milliseconds(10))) {
// if thread is not yet joined, send interrupt() to all variables.
for(auto& var : getAccessorListRecursive()) {
auto el{var.getAppAccessorNoType().getHighLevelImplElement()};
if(el->getAccessModeFlags().has(AccessMode::wait_for_new_data)) {
el->interrupt();
}
}
// it may not suffice to send interrupt() once, as the exception might get
// overwritten in the queue, thus we repeat this until the thread was
// joined.
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
ApplicationModule::~ApplicationModule() {
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void ApplicationModule::setCurrentVersionNumber(VersionNumber versionNumber) {
if(versionNumber > _currentVersionNumber) {
_currentVersionNumber = versionNumber;
}
}
/*********************************************************************************************************************/
void ApplicationModule::mainLoopWrapper() {
Application::registerThread("AM_" + className()); // + getName());
// Acquire testable mode lock, so from this point on we are running only one user thread concurrently
Application::getInstance().getTestableMode().lock("start");
Martin Christoph Hierholzer
committed
// Read all variables once to obtain the initial values from the devices and from the control system persistency
Martin Christoph Hierholzer
committed
// layer. This is done in two steps, first for all poll-type variables and then for all push-types, because
// poll-type reads might trigger distribution of values to push-type variables via a ConsumingFanOut.
for(auto& variable : getAccessorListRecursive()) {
if(variable.getDirection().dir != VariableDirection::consuming) {
continue;
}
Martin Christoph Hierholzer
committed
if(variable.getMode() == UpdateMode::poll) {
assert(!variable.getAppAccessorNoType().getHighLevelImplElement()->getAccessModeFlags().has(
AccessMode::wait_for_new_data));
Application::getInstance().getTestableMode().unlock("Initial value read for poll-type " + variable.getName());
Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
Martin Killenberg
committed
variable.getAppAccessorNoType().read();
Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
if(not Application::getInstance().getTestableMode().testLock()) {
// The lock may have already been acquired if the above read() goes to a ConsumingFanOut, which sends out
// the data to a slave decorated by a TestableModeAccessorDecorator. Hence we here must acquire the lock only
// if we do not have it.
Application::getInstance().getTestableMode().lock("Initial value read for poll-type " + variable.getName());
for(auto& variable : getAccessorListRecursive()) {
if(variable.getDirection().dir != VariableDirection::consuming) {
continue;
}
Martin Christoph Hierholzer
committed
if(variable.getMode() == UpdateMode::push) {
Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
Application::getInstance()._circularDependencyDetector.registerDependencyWait(variable);
// Will internally release and lock during the read, hence surround with lock/unlock
Application::getInstance().getTestableMode().lock("Initial value read for push-type " + variable.getName());
Martin Killenberg
committed
variable.getAppAccessorNoType().read();
Application::getInstance().getTestableMode().unlock("Initial value read for push-type " + variable.getName());
Application::getInstance()._circularDependencyDetector.unregisterDependencyWait(variable);
Application::getInstance().getTestableMode().lock("Initial value read for push-type " + variable.getName());
Martin Christoph Hierholzer
committed
}
}
Martin Killenberg
committed
// We are holding the testable mode lock, so we are sure the mechanism will work now.
_testableModeReached = true;
Application::getInstance().getTestableMode().unlock("terminate");
Martin Christoph Hierholzer
committed
Martin Killenberg
committed
/*********************************************************************************************************************/
void ApplicationModule::incrementDataFaultCounter() {
Tomasz Kozak
committed
void ApplicationModule::decrementDataFaultCounter() {
assert(_dataFaultCounter > 0);
--_dataFaultCounter;
Martin Killenberg
committed
/*********************************************************************************************************************/
std::list<EntityOwner*> ApplicationModule::getInputModulesRecursively(std::list<EntityOwner*> startList) {
Martin Killenberg
committed
if(_recursionStopper.recursionDetected()) {
return startList;
}
Martin Killenberg
committed
// If this module is already in the list we found a circular dependency.
// Remember this for the next time the recursive scan calls this function
Martin Killenberg
committed
if(std::count(startList.begin(), startList.end(), this)) {
Martin Killenberg
committed
_recursionStopper.setRecursionDetected();
Martin Killenberg
committed
}
// Whether a circular dependency has been detected or not, we must loop all inputs and add this module to the list
// so the calling code sees the second instance and can also detect the circle. The reason why we have to scan all
// inputs even if a circle is detected is this:
// * A single input starts the scan by adding it's owning module. At this point not all inputs if that module are in
// the circular network.
// * When a circle is detected, it might only be one of multiple entangled circled. If we would break the recursion
// and not scan all the
// inputs this is sufficient to identify that the particular input is in a circle. But at this point we have to
// tell in which network it is and have to scan the complete network to calculate the correct hash value.
Martin Killenberg
committed
startList.push_back(this); // first we add this module to the start list. We will call all inputs with it.
std::list<EntityOwner*> returnList{
startList}; // prepare the return list. Deltas from the inputs will be added to it.
for(auto& accessor : this->getAccessorListRecursive()) {
// not consumed from network -> not an input, just continue
if(accessor.getDirection().dir != VariableDirection::consuming) {
continue;
}
Martin Killenberg
committed
// find the feeder in the network
Martin Christoph Hierholzer
committed
auto proxy = accessor.getModel().visit(Model::returnApplicationModule, Model::keepApplicationModules,
Model::keepPvAccess, Model::adjacentInSearch, Model::returnFirstHit(Model::ApplicationModuleProxy{}));
if(!proxy.isValid()) {
Martin Killenberg
committed
continue;
}
Martin Christoph Hierholzer
committed
auto& feedingModule = proxy.getApplicationModule();
Martin Killenberg
committed
Martin Christoph Hierholzer
committed
auto thisInputsRecursiveModuleList = feedingModule.getInputModulesRecursively(startList);
Martin Killenberg
committed
// only add the modules that were added by the recursive search to the output list
assert(startList.size() <= thisInputsRecursiveModuleList.size());
auto copyStartIter = thisInputsRecursiveModuleList.begin();
std::advance(copyStartIter, startList.size());
returnList.insert(returnList.end(), copyStartIter, thisInputsRecursiveModuleList.end());
}
return returnList;
}
/*********************************************************************************************************************/
size_t ApplicationModule::getCircularNetworkHash() const {
Martin Killenberg
committed
/*********************************************************************************************************************/
void ApplicationModule::setCircularNetworkHash(size_t circularNetworkHash) {
if(_circularNetworkHash != 0 && _circularNetworkHash != circularNetworkHash) {
throw ChimeraTK::logic_error(
"Error: setCircularNetworkHash() called with different values for EntityOwner \"" + _name + "\" ");
}
if(Application::getInstance().getLifeCycleState() != LifeCycleState::initialisation) {
throw ChimeraTK::logic_error("Error: setCircularNetworkHash() called after initialisation.");
}
Martin Killenberg
committed
_circularNetworkHash = circularNetworkHash;
}
/*********************************************************************************************************************/
Martin Killenberg
committed
DataValidity ApplicationModule::getDataValidity() const {
if(_dataFaultCounter == 0) {
return DataValidity::ok;
}
Martin Killenberg
committed
if(_circularNetworkHash != 0) {
// In a circular dependency network, internal inputs are ignored.
// If all external inputs (including the ones from this module) are OK, the
if(Application::getInstance()._circularNetworkInvalidityCounters[_circularNetworkHash] == 0) {
Martin Killenberg
committed
return DataValidity::ok;
}
}
// not a circular network or invalidity counter not 0 -> keep the faulty flag
return DataValidity::faulty;
}
/********************************************************************************************************************/
void ApplicationModule::unregisterModule(Module* module) {
VariableGroup::unregisterModule(module);
if(_model.isValid()) {
auto* vg = dynamic_cast<VariableGroup*>(module);
if(!vg) {
// during destruction unregisterModule is called from the base class destructor where the dynamic_cast already
// fails.
return;
}
_model.remove(*vg);
}
}
Martin Killenberg
committed
/*********************************************************************************************************************/
Martin Killenberg
committed
Martin Christoph Hierholzer
committed
} /* namespace ChimeraTK */