Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.cc
*
* Created on: Jun 10, 2016
* Author: Martin Hierholzer
*/
#include <string>
#include <thread>
Martin Christoph Hierholzer
committed
#include <exception>
#include <fstream>
Martin Christoph Hierholzer
committed
#include <boost/fusion/container/map.hpp>
#include <ChimeraTK/BackendFactory.h>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include "Application.h"
#include "ApplicationModule.h"
Martin Christoph Hierholzer
committed
#include "ThreadedFanOut.h"
#include "ConsumingFanOut.h"
#include "FeedingFanOut.h"
#include "TriggerFanOut.h"
Martin Christoph Hierholzer
committed
#include "VariableNetworkNode.h"
Martin Christoph Hierholzer
committed
#include "ScalarAccessor.h"
#include "ArrayAccessor.h"
#include "ConstantAccessor.h"
Martin Christoph Hierholzer
committed
#include "TestableModeAccessorDecorator.h"
Martin Christoph Hierholzer
committed
#include "DebugPrintAccessorDecorator.h"
#include "Visitor.h"
#include "VariableNetworkGraphDumpingVisitor.h"
#include "XMLGeneratorVisitor.h"
Martin Christoph Hierholzer
committed
using namespace ChimeraTK;
Martin Christoph Hierholzer
committed
std::mutex Application::testableMode_mutex;
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
Application::Application(const std::string& name)
: ApplicationBase(name),
EntityOwner(name, "")
{
// check if the application name has been set
if(applicationName == "") {
shutdown();
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Error: An instance of Application must have its applicationName set.");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// check if application name contains illegal characters
std::string legalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
bool nameContainsIllegalChars = name.find_first_not_of(legalChars) != std::string::npos;
if(nameContainsIllegalChars) {
shutdown();
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Error: The application name may only contain alphanumeric characters and underscores.");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::initialise() {
// call the user-defined defineConnections() function which describes the structure of the application
defineConnections();
Martin Christoph Hierholzer
committed
// connect any unconnected accessors with constant values
processUnconnectedNodes();
// realise the connections between variable accessors as described in the initialise() function
makeConnections();
}
/*********************************************************************************************************************/
/** Functor class to create a constant for otherwise unconnected variables, suitable for boost::fusion::for_each(). */
namespace {
struct CreateConstantForUnconnectedVar {
/// @todo test unconnected variables for all types!
CreateConstantForUnconnectedVar(const std::type_info &typeInfo, bool makeFeeder, size_t length)
: _typeInfo(typeInfo), _makeFeeder(makeFeeder), _length(length) {}
template<typename PAIR>
void operator()(PAIR&) const {
if(typeid(typename PAIR::first_type) != _typeInfo) return;
theNode = VariableNetworkNode::makeConstant<typename PAIR::first_type>(_makeFeeder, typename PAIR::first_type(), _length);
done = true;
}
const std::type_info &_typeInfo;
bool _makeFeeder;
size_t _length;
mutable bool done{false};
mutable VariableNetworkNode theNode;
};
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::processUnconnectedNodes() {
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &accessor : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(!accessor.hasOwner()) {
Martin Christoph Hierholzer
committed
if(enableUnconnectedVariablesWarning) {
std::cerr << "*** Warning: Variable '" << accessor.getQualifiedName() << "' is not connected. " // LCOV_EXCL_LINE
"Reading will always result in 0, writing will be ignored." << std::endl; // LCOV_EXCL_LINE
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
networkList.emplace_back();
Martin Christoph Hierholzer
committed
networkList.back().addNode(accessor);
Martin Christoph Hierholzer
committed
bool makeFeeder = !(networkList.back().hasFeedingNode());
Martin Christoph Hierholzer
committed
size_t length = accessor.getNumberOfElements();
auto callable = CreateConstantForUnconnectedVar(accessor.getValueType(), makeFeeder, length);
Martin Christoph Hierholzer
committed
boost::fusion::for_each(ChimeraTK::userTypeMap(), std::ref(callable));
assert(callable.done);
constantList.emplace_back(callable.theNode);
Martin Christoph Hierholzer
committed
networkList.back().addNode(constantList.back());
}
}
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::checkConnections() {
// check all networks for validity
for(auto &network : networkList) {
network.check();
}
Martin Christoph Hierholzer
committed
// check if all accessors are connected
Martin Christoph Hierholzer
committed
// note: this in principle cannot happen, since processUnconnectedNodes() is called before
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &accessor : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(!accessor.hasOwner()) {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("The accessor '"+accessor.getName()+"' of the module '"+module->getName()+ // LCOV_EXCL_LINE
"' was not connected!"); // LCOV_EXCL_LINE
Martin Christoph Hierholzer
committed
}
}
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
void Application::run() {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
assert(applicationName != "");
Martin Christoph Hierholzer
committed
// prepare the modules
for(auto &module : getSubmoduleListRecursive()) {
module->prepare();
}
Martin Christoph Hierholzer
committed
// start the necessary threads for the FanOuts etc.
for(auto &internalModule : internalModuleList) {
internalModule->activate();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// read all input variables once, to set the startup value e.g. coming from the config file
// (without triggering an action inside the application)
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &variable : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(variable.getDirection().dir == VariableDirection::consuming) {
Martin Christoph Hierholzer
committed
variable.getAppAccessorNoType().readLatest();
Martin Christoph Hierholzer
committed
}
}
}
Martin Christoph Hierholzer
committed
// start the threads for the modules
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->run();
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::shutdown() {
Martin Christoph Hierholzer
committed
// first allow to run the application threads again, if we are in testable mode
Martin Christoph Hierholzer
committed
if(testableMode && testableModeTestLock()) {
testableModeUnlock("shutdown");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// deactivate the FanOuts first, since they have running threads inside accessing the modules etc.
// (note: the modules are members of the Application implementation and thus get destroyed after this destructor)
for(auto &internalModule : internalModuleList) {
internalModule->deactivate();
Martin Christoph Hierholzer
committed
}
// next deactivate the modules, as they have running threads inside as well
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->terminate();
}
ApplicationBase::shutdown();
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::generateXML() {
assert(applicationName != "");
Martin Christoph Hierholzer
committed
// define the connections
defineConnections();
Martin Christoph Hierholzer
committed
// also search for unconnected nodes - this is here only executed to print the warnings
processUnconnectedNodes();
Martin Christoph Hierholzer
committed
XMLGeneratorVisitor visitor;
visitor.dispatch(*this);
visitor.save(applicationName + ".xml");
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode b) {
// if one of the nodes has the value type AnyType, set it to the type of the other
Martin Christoph Hierholzer
committed
// if both are AnyType, nothing changes.
if(a.getValueType() == typeid(AnyType)) {
a.setValueType(b.getValueType());
}
else if(b.getValueType() == typeid(AnyType)) {
b.setValueType(a.getValueType());
}
// if one of the nodes has not yet a defined number of elements, set it to the number of elements of the other.
// if both are undefined, nothing changes.
if(a.getNumberOfElements() == 0) {
a.setNumberOfElements(b.getNumberOfElements());
}
else if(b.getNumberOfElements() == 0) {
b.setNumberOfElements(a.getNumberOfElements());
}
if(a.getNumberOfElements() != b.getNumberOfElements()) {
Martin Christoph Hierholzer
committed
std::stringstream what;
what << "*** ERROR: Cannot connect array variables with difference number of elements!" << std::endl;
what << "Node A:" << std::endl;
a.dump(what);
what << "Node B:" << std::endl;
b.dump(what);
throw ChimeraTK::logic_error(what.str());
// if both nodes already have an owner, we are either already done (same owners) or we need to try to merge the networks
Martin Christoph Hierholzer
committed
if(a.hasOwner() && b.hasOwner()) {
if(&(a.getOwner()) != &(b.getOwner())) {
auto &networkToMerge = b.getOwner();
bool success = a.getOwner().merge(networkToMerge);
if(!success) {
std::stringstream what;
what << "*** ERROR: Trying to connect two nodes which are already part of different networks, and merging these"
Martin Christoph Hierholzer
committed
" networks is not possible (cannot have two non-control-system or two control-system feeders)!" << std::endl;
what << "Node A:" << std::endl;
a.dump(what);
what << "Node B:" << std::endl;
b.dump(what);
what << "Owner of node A:" << std::endl;
a.getOwner().dump("",what);
what << "Owner of node B:" << std::endl;
b.getOwner().dump("",what);
throw ChimeraTK::logic_error(what.str());
}
for(auto n = networkList.begin(); n != networkList.end(); ++n) {
if(&*n == &networkToMerge) {
networkList.erase(n);
break;
}
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// add b to the existing network of a
Martin Christoph Hierholzer
committed
else if(a.hasOwner()) {
a.getOwner().addNode(b);
}
Martin Christoph Hierholzer
committed
// add a to the existing network of b
Martin Christoph Hierholzer
committed
else if(b.hasOwner()) {
b.getOwner().addNode(a);
}
Martin Christoph Hierholzer
committed
// create new network
Martin Christoph Hierholzer
committed
else {
networkList.emplace_back();
networkList.back().addNode(a);
networkList.back().addNode(b);
}
Martin Christoph Hierholzer
committed
return a.getOwner();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createDeviceVariable(const std::string &deviceAlias,
Martin Christoph Hierholzer
committed
const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements) {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// open device if needed
if(deviceMap.count(deviceAlias) == 0) {
deviceMap[deviceAlias] = ChimeraTK::BackendFactory::getInstance().createBackend(deviceAlias);
if(!deviceMap[deviceAlias]->isOpen()) deviceMap[deviceAlias]->open();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// use wait_for_new_data mode if push update mode was requested
ChimeraTK::AccessModeFlags flags{};
Martin Christoph Hierholzer
committed
if(mode == UpdateMode::push && direction.dir == VariableDirection::consuming) flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// obatin the register accessor from the device
auto accessor = deviceMap[deviceAlias]->getRegisterAccessor<UserType>(registerName, nElements, 0, flags);
Martin Christoph Hierholzer
committed
// create variable ID
idMap[accessor->getId()] = getNextVariableId();
Martin Christoph Hierholzer
committed
// return accessor
return accessor;
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createProcessVariable(VariableNetworkNode const &node) {
Martin Christoph Hierholzer
committed
// determine the SynchronizationDirection
SynchronizationDirection dir;
Martin Christoph Hierholzer
committed
if(node.getDirection().withReturn) {
dir = SynchronizationDirection::bidirectional;
}
else if(node.getDirection().dir == VariableDirection::feeding) {
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::controlSystemToDevice;
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::deviceToControlSystem;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
AccessModeFlags flags = {};
Martin Christoph Hierholzer
committed
if(node.getDirection().dir == VariableDirection::consuming) { // Application-to-controlsystem must be push-type
Martin Christoph Hierholzer
committed
flags = {AccessMode::wait_for_new_data};
}
else {
for(auto &consumer : node.getOwner().getConsumingNodes()) {
if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
Martin Christoph Hierholzer
committed
auto pvar = _processVariableManager->createProcessArray<UserType>(dir, node.getPublicName(),
node.getNumberOfElements(), node.getOwner().getUnit(), node.getOwner().getDescription(),
{}, false, 3, flags);
Martin Christoph Hierholzer
committed
assert(pvar->getName() != "");
Martin Christoph Hierholzer
committed
// create variable ID
idMap[pvar->getId()] = getNextVariableId();
pvIdMap[pvar->getUniqueId()] = idMap[pvar->getId()];
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// Decorate the process variable if testable mode is enabled and this is the receiving end of the variable.
// Also don't decorate, if the mode is polling. Instead flag the variable to be polling, so the TestFacility is aware of this.
Martin Christoph Hierholzer
committed
if(testableMode && node.getDirection().dir == VariableDirection::feeding) {
// The transfer mode of this process variable is considered to be polling, if only one consumer exists and this
// consumer is polling. Reason: mulitple consumers will result in the use of a FanOut, so the communication up to
// the FanOut will be push-type, even if all consumers are poll-type.
/// @todo Check if this is true!
auto mode = UpdateMode::push;
if(node.getOwner().countConsumingNodes() == 1) {
if(node.getOwner().getConsumingNodes().front().getMode() == UpdateMode::poll) mode = UpdateMode::poll;
}
if(mode != UpdateMode::poll) {
Martin Christoph Hierholzer
committed
auto pvarDec = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvar, true, false);
Martin Christoph Hierholzer
committed
testableMode_names[idMap[pvarDec->getId()]] = "ControlSystem:"+node.getPublicName();
return pvarDec;
}
else {
Martin Christoph Hierholzer
committed
testableMode_isPollMode[idMap[pvar->getId()]] = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// return the process variable
return pvar;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
std::pair< boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> >
Martin Christoph Hierholzer
committed
Application::createApplicationVariable(VariableNetworkNode const &node, VariableNetworkNode const &consumer) {
Martin Christoph Hierholzer
committed
// obtain the meta data
size_t nElements = node.getNumberOfElements();
std::string name = node.getName();
Martin Christoph Hierholzer
committed
assert(name != "");
Martin Christoph Hierholzer
committed
AccessModeFlags flags = {};
Martin Christoph Hierholzer
committed
if(consumer.getType() != NodeType::invalid) {
if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
}
else {
if(node.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
}
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
std::pair< boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>,
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> > pvarPair;
Martin Christoph Hierholzer
committed
if(consumer.getType() != NodeType::invalid) assert(node.getDirection().withReturn == consumer.getDirection().withReturn);
Martin Christoph Hierholzer
committed
if(!node.getDirection().withReturn) {
pvarPair = createSynchronizedProcessArray<UserType>(nElements, name, node.getUnit(), node.getDescription(),
{}, 3, false, {}, flags);
}
else {
pvarPair = createBidirectionalSynchronizedProcessArray<UserType>(nElements, name, node.getUnit(), node.getDescription(),
Martin Christoph Hierholzer
committed
{}, 3, {}, {}, flags);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
assert(pvarPair.first->getName() != "");
assert(pvarPair.second->getName() != "");
Martin Christoph Hierholzer
committed
// create variable ID
auto varId = getNextVariableId();
idMap[pvarPair.first->getId()] = varId;
idMap[pvarPair.second->getId()] = varId;
Martin Christoph Hierholzer
committed
// decorate the process variable if testable mode is enabled and mode is push-type
if(testableMode && node.getMode() == UpdateMode::push) {
Martin Christoph Hierholzer
committed
pvarPair.first = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.first, true, true);
pvarPair.second = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.second, true, true);
Martin Christoph Hierholzer
committed
// put the decorators into the list
Martin Christoph Hierholzer
committed
testableMode_names[varId] = "Internal:"+node.getQualifiedName();
Martin Christoph Hierholzer
committed
if(consumer.getType() != NodeType::invalid) {
testableMode_names[varId] += "->"+consumer.getQualifiedName();
}
}
// if debug mode was requested for either node, decorate both accessors
if( debugMode_variableList.count(node.getUniqueId()) ||
( consumer.getType() != NodeType::invalid && debugMode_variableList.count(consumer.getUniqueId()) ) ) {
if(consumer.getType() != NodeType::invalid) {
Martin Christoph Hierholzer
committed
assert(node.getDirection().dir == VariableDirection::feeding);
assert(consumer.getDirection().dir == VariableDirection::consuming);
Martin Christoph Hierholzer
committed
pvarPair.first = boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName());
pvarPair.second = boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, consumer.getQualifiedName());
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
pvarPair.first = boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName());
pvarPair.second = boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, node.getQualifiedName());
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// return the pair
return pvarPair;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::makeConnections() {
Martin Christoph Hierholzer
committed
// finalise connections: decide still-undecided details, in particular for control-system and device varibales, which
// get created "on the fly".
Martin Christoph Hierholzer
committed
// apply optimisations
Martin Christoph Hierholzer
committed
// note: checks may not be run before since sometimes networks may only be valid after optimisations
optimiseConnections();
Martin Christoph Hierholzer
committed
// run checks
checkConnections();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// make the connections for all networks
for(auto &network : networkList) {
makeConnectionsForNetwork(network);
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// set all initial version numbers in the modules to the same value
VersionNumber startVersion;
for(auto &module : getSubmoduleListRecursive()) {
module->setCurrentVersionNumber(startVersion);
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::finaliseNetworks() {
Martin Christoph Hierholzer
committed
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
// check for control system variables which should be made bidirectional
for(auto &network : networkList) {
size_t nBidir = network.getFeedingNode().getDirection().withReturn ? 1 : 0;
for(auto &consumer : network.getConsumingNodes()) {
if(consumer.getDirection().withReturn) ++nBidir;
}
if(nBidir != 1) continue; // only if there is exactly one node with return channel we need to guess its peer
size_t nCS = network.getFeedingNode().getType() == NodeType::ControlSystem ? 1 : 0;
for(auto &consumer : network.getConsumingNodes()) {
if(consumer.getType() == NodeType::ControlSystem) ++nCS;
}
if(nCS != 1) continue; // multiple CS-type variables found - cannot guess the peer
if(network.getFeedingNode().getType() == NodeType::ControlSystem) {
network.getFeedingNode().setDirection({VariableDirection::feeding,true});
}
else {
for(auto &consumer : network.getConsumingNodes()) {
if(consumer.getType() == NodeType::ControlSystem) {
consumer.setDirection({VariableDirection::consuming,true});
break;
}
}
}
}
}
/*********************************************************************************************************************/
void Application::optimiseConnections() {
// list of iterators of networks to be removed from the networkList after the merge operation
std::list<VariableNetwork*> deleteNetworks;
// search for networks with the same feeder
for(auto it1 = networkList.begin(); it1 != networkList.end(); ++it1) {
for(auto it2 = it1; it2 != networkList.end(); ++it2) {
if(it1 == it2) continue;
auto feeder1 = it1->getFeedingNode();
auto feeder2 = it2->getFeedingNode();
// this optimisation is only necessary for device-type nodes, since application and control-system nodes will
// automatically create merged networks when having the same feeder
/// @todo check if this assumtion is true! control-system nodes can be created with different types, too!
if(feeder1.getType() != NodeType::Device || feeder2.getType() != NodeType::Device) continue;
// check if referrring to same register
if(feeder1.getDeviceAlias() != feeder2.getDeviceAlias()) continue;
if(feeder1.getRegisterName() != feeder2.getRegisterName()) continue;
// check if directions are the same
if(feeder1.getDirection() != feeder2.getDirection()) continue;
// check if value types and number of elements are compatible
if(feeder1.getValueType() != feeder2.getValueType()) continue;
if(feeder1.getNumberOfElements() != feeder2.getNumberOfElements()) continue;
// check if transfer mode is the same
if(feeder1.getMode() != feeder2.getMode()) continue;
// check if triggers are compatible, if present
if(feeder1.hasExternalTrigger() != feeder2.hasExternalTrigger()) continue;
if(feeder1.hasExternalTrigger()) {
if(feeder1.getExternalTrigger() != feeder2.getExternalTrigger()) continue;
}
// everything should be compatible at this point: merge the networks. We will merge the network of the outer
// loop into the network of the inner loop, since the network of the outer loop will not be found a second time
// in the inner loop.
for(auto consumer : it1->getConsumingNodes()) {
consumer.clearOwner();
it2->addNode(consumer);
}
Martin Christoph Hierholzer
committed
// if trigger present, remove corresponding trigger receiver node from the trigger network
Martin Christoph Hierholzer
committed
if(feeder1.hasExternalTrigger()) {
for(auto &itTrig : networkList) {
if(itTrig.getFeedingNode() != feeder1.getExternalTrigger()) continue;
itTrig.removeNodeToTrigger(it1->getFeedingNode());
}
Martin Christoph Hierholzer
committed
}
// schedule the outer loop network for deletion and stop processing it
deleteNetworks.push_back(&(*it1));
break;
}
}
// remove networks from the network list
for(auto net : deleteNetworks) {
networkList.remove(*net);
}
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
void Application::dumpConnections() { // LCOV_EXCL_LINE
std::cout << "==== List of all variable connections of the current Application ====" << std::endl; // LCOV_EXCL_LINE
for(auto &network : networkList) { // LCOV_EXCL_LINE
network.dump(); // LCOV_EXCL_LINE
} // LCOV_EXCL_LINE
std::cout << "=====================================================================" << std::endl; // LCOV_EXCL_LINE
} // LCOV_EXCL_LINE
void Application::dumpConnectionGraph(const std::string& fileName) {
std::fstream file{fileName, std::ios_base::out};
VariableNetworkGraphDumpingVisitor visitor{file};
visitor.dispatch(*this);
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller(Application &owner, VariableNetwork &network)
: _owner(owner), _network(network) {}
/*********************************************************************************************************************/
template<typename PAIR>
void Application::TypedMakeConnectionCaller::operator()(PAIR&) const {
if(typeid(typename PAIR::first_type) != _network.getValueType()) return;
_owner.typedMakeConnection<typename PAIR::first_type>(_network);
done = true;
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
void Application::makeConnectionsForNetwork(VariableNetwork &network) {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// if the network has been created already, do nothing
if(network.isCreated()) return;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// if the trigger type is external, create the trigger first
if(network.getFeedingNode().hasExternalTrigger()) {
VariableNetwork &dependency = network.getFeedingNode().getExternalTrigger().getOwner();
Martin Christoph Hierholzer
committed
if(!dependency.isCreated()) makeConnectionsForNetwork(dependency);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// defer actual network creation to templated function
Martin Christoph Hierholzer
committed
auto callable = TypedMakeConnectionCaller(*this, network);
Martin Christoph Hierholzer
committed
boost::fusion::for_each(ChimeraTK::userTypeMap(), std::ref(callable));
Martin Christoph Hierholzer
committed
assert(callable.done);
Martin Christoph Hierholzer
committed
// mark the network as created
network.markCreated();
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
template<typename UserType>
void Application::typedMakeConnection(VariableNetwork &network) {
bool connectionMade = false; // to check the logic...
size_t nNodes = network.countConsumingNodes()+1;
Martin Christoph Hierholzer
committed
auto feeder = network.getFeedingNode();
Martin Christoph Hierholzer
committed
auto consumers = network.getConsumingNodes();
Martin Christoph Hierholzer
committed
bool useExternalTrigger = network.getTriggerType() == VariableNetwork::TriggerType::external;
Martin Christoph Hierholzer
committed
bool useFeederTrigger = network.getTriggerType() == VariableNetwork::TriggerType::feeder;
Martin Christoph Hierholzer
committed
bool constantFeeder = feeder.getType() == NodeType::Constant;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// 1st case: the feeder requires a fixed implementation
Martin Christoph Hierholzer
committed
if(feeder.hasImplementation() && !constantFeeder) {
Martin Christoph Hierholzer
committed
// Create feeding implementation. Note: though the implementation is derived from the feeder, it will be used as
// the implementation of the (or one of the) consumer. Logically, implementations are always pairs of
// implementations (sender and receiver), but in this case the feeder already has a fixed implementation pair.
// So our feedingImpl will contain the consumer-end of the implementation pair. This is the reason why the
// functions createProcessScalar() and createDeviceAccessor() get the VariableDirection::consuming.
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl;
Martin Christoph Hierholzer
committed
if(feeder.getType() == NodeType::Device) {
feedingImpl = createDeviceVariable<UserType>(feeder.getDeviceAlias(), feeder.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::consuming, false}, feeder.getMode(), feeder.getNumberOfElements());
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(feeder.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
feedingImpl = createProcessVariable<UserType>(feeder);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// if we just have two nodes, directly connect them
Martin Christoph Hierholzer
committed
if(nNodes == 2 && !useExternalTrigger) {
Martin Christoph Hierholzer
committed
auto consumer = consumers.front();
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation(feedingImpl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto consumingImpl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
// connect the Device with e.g. a ControlSystem node via a ThreadedFanOut
auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
Martin Christoph Hierholzer
committed
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto consumingImpl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
// connect the ControlSystem with e.g. a Device node via an ThreadedFanOut
auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
Martin Christoph Hierholzer
committed
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
Martin Christoph Hierholzer
committed
connectionMade = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(feedingImpl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
else { /* !(nNodes == 2 && !useExternalTrigger) */
Martin Christoph Hierholzer
committed
// create the right FanOut type
boost::shared_ptr<FanOut<UserType>> fanOut;
Martin Christoph Hierholzer
committed
boost::shared_ptr<ConsumingFanOut<UserType>> consumingFanOut;
Martin Christoph Hierholzer
committed
if(useExternalTrigger) {
// if external trigger is enabled, use externally triggered threaded FanOut
Martin Christoph Hierholzer
committed
auto triggerNode = feeder.getExternalTrigger();
auto triggerFanOut = triggerMap[triggerNode.getUniqueId()];
if(!triggerFanOut) {
Martin Christoph Hierholzer
committed
triggerFanOut = boost::make_shared<TriggerFanOut>(network.getExternalTriggerImpl());
triggerMap[triggerNode.getUniqueId()] = triggerFanOut;
internalModuleList.push_back(triggerFanOut);
}
fanOut = triggerFanOut->addNetwork(feedingImpl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(useFeederTrigger) {
// if the trigger is provided by the pushing feeder, use the treaded version of the FanOut to distribute
// new values immediately to all consumers.
Martin Christoph Hierholzer
committed
auto threadedFanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
internalModuleList.push_back(threadedFanOut);
fanOut = threadedFanOut;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
assert(network.hasApplicationConsumer()); // checkConnections should catch this
Martin Christoph Hierholzer
committed
consumingFanOut = boost::make_shared<ConsumingFanOut<UserType>>(feedingImpl);
fanOut = consumingFanOut;
Martin Christoph Hierholzer
committed
// In case we have one or more trigger receivers among our consumers, we produce exactly one application variable
// for it. We never need more, since the distribution is done with a TriggerFanOut.
bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver
auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used
// add all consumers to the FanOut
Martin Christoph Hierholzer
committed
for(auto &consumer : consumers) {
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
if(consumingFanOut && consumer.getMode() == UpdateMode::poll) {
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation<UserType>(consumingFanOut);
Martin Christoph Hierholzer
committed
consumingFanOut.reset();
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impls.first);
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation<UserType>(impls.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first);
usedTriggerReceiver = true;
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
connectionMade = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
// 2nd case: the feeder does not require a fixed implementation
Martin Christoph Hierholzer
committed
else if(!constantFeeder) { /* !feeder.hasImplementation() */
Martin Christoph Hierholzer
committed
// we should be left with an application feeder node
Martin Christoph Hierholzer
committed
if(feeder.getType() != NodeType::Application) {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
assert(!useExternalTrigger);
Martin Christoph Hierholzer
committed
// if we just have two nodes, directly connect them
if(nNodes == 2) {
Martin Christoph Hierholzer
committed
auto consumer = consumers.front();
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(feeder,consumer);
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(impls.first);
consumer.setAppAccessorImplementation<UserType>(impls.second);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(feeder,consumer);
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(impls.first);
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(impls.second);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Constant) {
Martin Christoph Hierholzer
committed
auto impl = consumer.getConstAccessor<UserType>();
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
}
else {
// create FanOut and use it as the feeder implementation
Martin Christoph Hierholzer
committed
auto fanOut = boost::make_shared<FeedingFanOut<UserType>>(feeder.getName(), feeder.getUnit(),
Martin Christoph Hierholzer
committed
feeder.getDescription(), feeder.getNumberOfElements(),
feeder.getDirection().withReturn);
Martin Christoph Hierholzer
committed
feeder.setAppAccessorImplementation<UserType>(fanOut);
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// In case we have one or more trigger receivers among our consumers, we produce exactly one application variable
// for it. We never need more, since the distribution is done with a TriggerFanOut.
bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver
auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used
Martin Christoph Hierholzer
committed
for(auto &consumer : consumers) {
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impls.first);
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation<UserType>(impls.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first);
usedTriggerReceiver = true;
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
}
connectionMade = true;
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
else { /* constantFeeder */
assert(feeder.getType() == NodeType::Constant);
auto feedingImpl = feeder.getConstAccessor<UserType>();
assert(feedingImpl != nullptr);
Martin Christoph Hierholzer
committed
for(auto &consumer : consumers) {
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
if(testableMode) {
idMap[feedingImpl->getId()] = getNextVariableId();
Martin Christoph Hierholzer
committed
auto pvarDec = boost::make_shared<TestableModeAccessorDecorator<UserType>>(feedingImpl, true, true);
Martin Christoph Hierholzer
committed
testableMode_names[idMap[pvarDec->getId()]] = "Constant";
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation<UserType>(pvarDec);
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
consumer.setAppAccessorImplementation<UserType>(feedingImpl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto impl = createProcessVariable<UserType>(consumer);
impl->accessChannel(0) = feedingImpl->accessChannel(0);
impl->write();
}
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
{VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
impl->accessChannel(0) = feedingImpl->accessChannel(0);
impl->write();
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Using constants as triggers is not supported!");
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
}
connectionMade = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
if(!connectionMade) { // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error( // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
"The variable network cannot be handled. Implementation missing!"); // LCOV_EXCL_LINE (assert-like)
} // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
VariableNetwork& Application::createNetwork() {
networkList.emplace_back();
return networkList.back();
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Application& Application::getInstance() {
return dynamic_cast<Application&>(ApplicationBase::getInstance());
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::stepApplication() {
// testableMode_counter must be non-zero, otherwise there is no input for the application to process
if(testableMode_counter == 0) {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error(
Martin Christoph Hierholzer
committed
"Application::stepApplication() called despite no input was provided to the application to process!");
}
// let the application run until it has processed all data (i.e. the semaphore counter is 0)
Martin Christoph Hierholzer
committed
size_t oldCounter = 0;
Martin Christoph Hierholzer
committed
while(testableMode_counter > 0) {
if(enableDebugTestableMode && ( oldCounter != testableMode_counter) ) { // LCOV_EXCL_LINE (only cout)
std::cout << "Application::stepApplication(): testableMode_counter = " << testableMode_counter << std::endl; // LCOV_EXCL_LINE (only cout)
oldCounter = testableMode_counter; // LCOV_EXCL_LINE (only cout)
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
testableModeUnlock("stepApplication");
Martin Christoph Hierholzer
committed
boost::this_thread::yield();
Martin Christoph Hierholzer
committed
testableModeLock("stepApplication");
Martin Christoph Hierholzer
committed
}
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::testableModeLock(const std::string& name) {
Martin Christoph Hierholzer
committed
// don't do anything if testable mode is not enabled
Martin Christoph Hierholzer
committed
if(!getInstance().testableMode) return;
Martin Christoph Hierholzer
committed
// debug output if enabled (also prevent spamming the same message)
if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE (only cout)
std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
<< " tries to obtain lock for " << name << std::endl; // LCOV_EXCL_LINE (only cout)
} // LCOV_EXCL_LINE (only cout)
Martin Christoph Hierholzer
committed
// if last lock was obtained repeatedly by the same thread, sleep a short time before obtaining the lock to give the
// other threads a chance to get the lock first
Martin Christoph Hierholzer
committed
if(getInstance().testableMode_repeatingMutexOwner > 0) usleep(10000);
Martin Christoph Hierholzer
committed
// obtain the lock
Martin Christoph Hierholzer
committed
getTestableModeLockObject().lock();
Martin Christoph Hierholzer
committed
// check if the last owner of the mutex was this thread, which may be a hint that no other thread is waiting for the
// lock
Martin Christoph Hierholzer
committed
if(getInstance().testableMode_lastMutexOwner == std::this_thread::get_id()) {
Martin Christoph Hierholzer
committed
// debug output if enabled
if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE (only cout)
std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
<< " repeatedly obtained lock successfully for " << name // LCOV_EXCL_LINE (only cout)
<< ". Further messages will be suppressed." << std::endl; // LCOV_EXCL_LINE (only cout)
} // LCOV_EXCL_LINE (only cout)
Martin Christoph Hierholzer
committed
// increase counter for stall detection
Martin Christoph Hierholzer
committed
getInstance().testableMode_repeatingMutexOwner++;
Martin Christoph Hierholzer
committed
// detect stall: if the same thread got the mutex with no other thread obtaining it in between for one second, we
// assume no other thread is able to process data at this time. The test should fail in this case
Martin Christoph Hierholzer
committed
if(getInstance().testableMode_repeatingMutexOwner > 100) {
Martin Christoph Hierholzer
committed
// print an informative message first, which lists also all variables currently containing unread data.
Martin Christoph Hierholzer
committed
std::cout << "*** Tests are stalled due to data which has been sent but not received." << std::endl;
Martin Christoph Hierholzer
committed
std::cout << " The following variables still contain unread values or had data loss due to a queue overflow:" << std::endl;
for(auto &pair : Application::getInstance().testableMode_perVarCounter) {
if(pair.second > 0) {
Martin Christoph Hierholzer
committed
std::cout << " - " << Application::getInstance().testableMode_names[pair.first] << " ["
<< getInstance().testableMode_processVars[pair.first]->getId() << "]";
Martin Christoph Hierholzer
committed
// check if process variable still has data in the queue
try {
if(getInstance().testableMode_processVars[pair.first]->readNonBlocking()) {
std::cout << " (unread data in queue)";
}
else {
std::cout << " (data loss)";
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
catch(std::logic_error&) {
// if we receive a logic_error in readNonBlocking() it just means another thread is waiting on a
// TransferFuture of this variable, and we actually were not allowed to read...
Martin Christoph Hierholzer
committed
std::cout << " (data loss)";
}
std::cout << std::endl;
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
// throw a specialised exception to make sure whoever catches it really knows what he does...
Martin Christoph Hierholzer
committed
throw TestsStalled();
}
}
else {
Martin Christoph Hierholzer
committed
// last owner of the mutex was different: reset the counter and store the thread id
Martin Christoph Hierholzer
committed
getInstance().testableMode_repeatingMutexOwner = 0;
Martin Christoph Hierholzer
committed
getInstance().testableMode_lastMutexOwner = std::this_thread::get_id();
Martin Christoph Hierholzer
committed
// debug output if enabled
if(getInstance().enableDebugTestableMode) { // LCOV_EXCL_LINE (only cout)
std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
<< " obtained lock successfully for " << name << std::endl; // LCOV_EXCL_LINE (only cout)
} // LCOV_EXCL_LINE (only cout)