-
Martin Christoph Hierholzer authoredMartin Christoph Hierholzer authored
Application.cc 10.79 KiB
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "Application.h"
#include "CircularDependencyDetector.h"
#include "ConnectionMaker.h"
#include "DeviceManager.h"
#include "Utilities.h"
#include "VariableNetworkGraphDumpingVisitor.h"
#include "VariableNetworkModuleGraphDumpingVisitor.h"
#include "VariableNetworkNode.h"
#include "XMLGeneratorVisitor.h"
#include <ChimeraTK/BackendFactory.h>
#include <boost/fusion/container/map.hpp>
#include <exception>
#include <fstream>
#include <string>
#include <thread>
using namespace ChimeraTK;
/*********************************************************************************************************************/
Application::Application(const std::string& name) : ApplicationBase(name), ModuleGroup(nullptr, name) {
// Create the model and its root.
Application::_model = Model::RootProxy(*this);
// Make sure the ModuleGroup base class has the model, too.
ModuleGroup::_model = Model::ModuleGroupProxy(_model);
// check if the application name has been set
if(applicationName.empty()) {
Application::shutdown();
throw ChimeraTK::logic_error("Error: An instance of Application must have its applicationName set.");
}
// check if application name contains illegal characters
if(!Utilities::checkName(name, false)) {
Application::shutdown();
throw ChimeraTK::logic_error(
"Error: The application name may only contain alphanumeric characters and underscores.");
}
}
/*********************************************************************************************************************/
void Application::enableTestableMode() {
assert(not initialiseCalled);
testableMode.enable();
}
/*********************************************************************************************************************/
void Application::registerThread(const std::string& name) {
getInstance().testableMode.setThreadName(name);
pthread_setname_np(pthread_self(), name.substr(0, std::min<std::string::size_type>(name.length(), 15)).c_str());
}
/*********************************************************************************************************************/
void Application::incrementDataLossCounter(const std::string& name) {
if(getInstance().debugDataLoss) {
std::cout << "Data loss in variable " << name << std::endl;
}
getInstance().dataLossCounter++;
}
/*********************************************************************************************************************/
size_t Application::getAndResetDataLossCounter() {
size_t counter = getInstance().dataLossCounter.load(std::memory_order_relaxed);
while(!getInstance().dataLossCounter.compare_exchange_weak(
counter, 0, std::memory_order_release, std::memory_order_relaxed)) {
}
return counter;
}
/*********************************************************************************************************************/
void Application::initialise() {
if(initialiseCalled) {
throw ChimeraTK::logic_error("Application::initialise() was already called before.");
}
ConnectionMaker cm(*this);
cm.setDebugConnections(enableDebugMakeConnections);
cm.connect();
initialiseCalled = true;
}
/*********************************************************************************************************************/
void Application::optimiseUnmappedVariables(const std::set<std::string>& /*names*/) {
// TODO: This is not working anymore
// Follow up with https://redmine.msktools.desy.de/issues/10371
}
/*********************************************************************************************************************/
void Application::run() {
assert(not applicationName.empty());
if(testableMode.enabled) {
if(!testFacilityRunApplicationCalled) {
throw ChimeraTK::logic_error(
"Testable mode enabled but Application::run() called directly. Call TestFacility::runApplication() instead.");
}
}
if(runCalled) {
throw ChimeraTK::logic_error("Application::run() has already been called before.");
}
runCalled = true;
// set all initial version numbers in the modules to the same value
for(auto& module : getSubmoduleListRecursive()) {
if(module->getModuleType() != ModuleType::ApplicationModule) continue;
module->setCurrentVersionNumber(getStartVersion());
}
// prepare the modules
for(auto& module : getSubmoduleListRecursive()) {
module->prepare();
}
// Switch life-cycle state to run
lifeCycleState = LifeCycleState::run;
// start the necessary threads for the FanOuts etc.
for(auto& internalModule : internalModuleList) {
internalModule->activate();
}
// start the threads for the modules
for(auto& module : getSubmoduleListRecursive()) {
module->run();
}
// When in testable mode, wait for all modules to report that they have reached the testable mode.
// We have to start all module threads first because some modules might only send the initial
// values in their main loop, and following modules need them to enter testable mode.
// just a small helper lambda to avoid code repetition
auto waitForTestableMode = [](EntityOwner* module) {
while(!module->hasReachedTestableMode()) {
Application::getInstance().getTestableMode().unlock("releaseForReachTestableMode");
usleep(100);
Application::getInstance().getTestableMode().lock("acquireForReachTestableMode");
}
};
if(Application::getInstance().getTestableMode().enabled) {
for(auto& internalModule : internalModuleList) {
waitForTestableMode(internalModule.get());
}
for(auto& module : getSubmoduleListRecursive()) {
waitForTestableMode(module);
}
}
// Launch circular dependency detector thread
circularDependencyDetector.startDetectBlockedModules();
}
/*********************************************************************************************************************/
void Application::shutdown() {
// switch life-cycle state
lifeCycleState = LifeCycleState::shutdown;
// first allow to run the application threads again, if we are in testable
// mode
if(testableMode.enabled && testableMode.testLock()) {
testableMode.unlock("shutdown");
}
// 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();
}
// shutdown all DeviceManagers, otherwise application modules might hang if still waiting for initial values from
// devices
for(auto& pair : _deviceManagerMap) {
pair.second->terminate();
}
// next deactivate the modules, as they have running threads inside as well
for(auto& module : getSubmoduleListRecursive()) {
module->terminate();
}
circularDependencyDetector.terminate();
ApplicationBase::shutdown();
}
/*********************************************************************************************************************/
void Application::generateXML() {
assert(not applicationName.empty());
XMLGeneratorVisitor visitor;
visitor.dispatch(*this);
visitor.save(applicationName + ".xml");
}
/*********************************************************************************************************************/
void Application::dumpConnections(std::ostream& stream) { // LCOV_EXCL_LINE
#if 0
stream << "==== List of all variable connections of the current Application =====" << std::endl; // LCOV_EXCL_LINE
for(auto& network : networkList) { // LCOV_EXCL_LINE
network.dump("", stream); // LCOV_EXCL_LINE
} // LCOV_EXCL_LINE
stream << "==== List of all circular connections in the current Application ====" << std::endl; // LCOV_EXCL_LINE
for(auto& circularDependency : circularDependencyNetworks) {
stream << "Circular dependency network " << circularDependency.first << " : ";
for(auto& module : circularDependency.second) {
stream << module->getName() << ", ";
}
stream << std::endl;
}
stream << "======================================================================" << std::endl; // LCOV_EXCL_LINE
#endif
} // LCOV_EXCL_LINE
/*********************************************************************************************************************/
/*
void Application::dumpConnectionGraph(const std::string& fileName) const {
std::fstream file{fileName, std::ios_base::out};
VariableNetworkGraphDumpingVisitor visitor{file};
visitor.dispatch(*this);
}
*/
/*********************************************************************************************************************/
/*
void Application::dumpModuleConnectionGraph(const std::string& fileName) const {
std::fstream file{fileName, std::ios_base::out};
VariableNetworkModuleGraphDumpingVisitor visitor{file};
visitor.dispatch(*this);
}
*/
/*********************************************************************************************************************/
/*
void Application::markCircularConsumers(VariableNetwork& variableNetwork) {
for(auto& node : variableNetwork.getConsumingNodes()) {
// A variable network is a tree-like network of VariableNetworkNodes (one feeder and one or more multiple consumers)
// A circular network is a list of modules (EntityOwners) which have a circular dependency
auto circularNetwork = node.scanForCircularDepencency();
if(not circularNetwork.empty()) {
auto circularNetworkHash = boost::hash_range(circularNetwork.begin(), circularNetwork.end());
circularDependencyNetworks[circularNetworkHash] = circularNetwork;
circularNetworkInvalidityCounters[circularNetworkHash] = 0;
}
}
}
*/
/*********************************************************************************************************************/
Application& Application::getInstance() {
return dynamic_cast<Application&>(ApplicationBase::getInstance());
}
/*********************************************************************************************************************/
boost::shared_ptr<DeviceManager> Application::getDeviceManager(const std::string& aliasOrCDD) {
auto& dmm = Application::getInstance()._deviceManagerMap;
if(dmm.find(aliasOrCDD) == dmm.end()) {
// Add initialisation handler below, since we also need to add it if the DeviceModule already exists
dmm[aliasOrCDD] = boost::make_shared<DeviceManager>(&Application::getInstance(), aliasOrCDD);
}
return dmm.at(aliasOrCDD);
}
/*********************************************************************************************************************/