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 "TestDecoratorRegisterAccessor.h"
Martin Christoph Hierholzer
committed
#include "DebugDecoratorRegisterAccessor.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.getName() << "' 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);
boost::fusion::for_each(ChimeraTK::userTypeMap(), 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() == 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
throw ChimeraTK::logic_error("Error: Cannot connect array variables with difference number of elements!");
Martin Christoph Hierholzer
committed
// if both nodes already have an owner, we are done
if(a.hasOwner() && b.hasOwner()) {
Martin Christoph Hierholzer
committed
if(&(a.getOwner()) != &(b.getOwner())) { /// @todo TODO merge networks?
std::stringstream what;
what << "*** ERROR: nodes to be connected should have the same owner!" << 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);
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error(what.str());
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 == 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() == 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 = {};
if(node.getDirection() == VariableDirection::consuming) { // Application-to-controlsystem must be push-type
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() == 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) {
auto pvarDec = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(pvar);
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
pvarPair = createSynchronizedProcessArray<UserType>(nElements, name, node.getUnit(), node.getDescription(),
{}, 3, false, {}, flags);
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<TestDecoratorRegisterAccessor<UserType>>(pvarPair.first);
pvarPair.second = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(pvarPair.second);
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) {
assert(node.getDirection() == VariableDirection::feeding);
assert(consumer.getDirection() == VariableDirection::consuming);
pvarPair.first = boost::make_shared<DebugDecoratorRegisterAccessor<UserType>>(pvarPair.first, node.getQualifiedName());
pvarPair.second = boost::make_shared<DebugDecoratorRegisterAccessor<UserType>>(pvarPair.second, consumer.getQualifiedName());
}
else {
pvarPair.first = boost::make_shared<DebugDecoratorRegisterAccessor<UserType>>(pvarPair.first, node.getQualifiedName());
pvarPair.second = boost::make_shared<DebugDecoratorRegisterAccessor<UserType>>(pvarPair.second, node.getQualifiedName());
}
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() {
// 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
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
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);
boost::fusion::for_each(ChimeraTK::userTypeMap(), 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, 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.getAppAccessor<UserType>().replace(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, 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.getAppAccessor<UserType>().replace(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.getAppAccessor<UserType>().replace(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, 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.getAppAccessor<UserType>().replace(impls.first);
consumer.getAppAccessor<UserType>().replace(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.getAppAccessor<UserType>().replace(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, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(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.getAppAccessor<UserType>().replace(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>();
feeder.getAppAccessor<UserType>().replace(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());
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(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.getAppAccessor<UserType>().replace(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, 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();
auto pvarDec = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(feedingImpl);
testableMode_names[idMap[pvarDec->getId()]] = "Constant";
consumer.getAppAccessor<UserType>().replace(pvarDec);
}
else {
consumer.getAppAccessor<UserType>().replace(feedingImpl);
}
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(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
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];
// 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)
Martin Christoph Hierholzer
committed
}
}
/*********************************************************************************************************************/
void Application::testableModeUnlock(const std::string& name) {
if(!getInstance().testableMode) return;
if(getInstance().enableDebugTestableMode && (!getInstance().testableMode_repeatingMutexOwner // LCOV_EXCL_LINE (only cout)
|| getInstance().testableMode_lastMutexOwner != std::this_thread::get_id())) { // LCOV_EXCL_LINE (only cout)
std::cout << "Application::testableModeUnlock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
<< " releases lock for " << name << std::endl; // LCOV_EXCL_LINE (only cout)
} // LCOV_EXCL_LINE (only cout)
Martin Christoph Hierholzer
committed
getTestableModeLockObject().unlock();
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
std::string& Application::threadName() {
// Note: due to a presumed bug in gcc (still present in gcc 7), the thread_local definition must be in the cc file
// to prevent seeing different objects in the same thread under some conditions.
// Another workaround for this problem can be found in commit dc051bfe35ce6c1ed954010559186f63646cf5d4
thread_local std::string name{"**UNNAMED**"};
return name;
}
/*********************************************************************************************************************/
std::unique_lock<std::mutex>& Application::getTestableModeLockObject() {
// Note: due to a presumed bug in gcc (still present in gcc 7), the thread_local definition must be in the cc file
// to prevent seeing different objects in the same thread under some conditions.
// Another workaround for this problem can be found in commit dc051bfe35ce6c1ed954010559186f63646cf5d4
thread_local std::unique_lock<std::mutex> myLock(Application::testableMode_mutex, std::defer_lock);
return myLock;
}