Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.h
*
* Created on: Jun 07, 2016
* Author: Martin Hierholzer
*/
#ifndef CHIMERATK_APPLICATION_H
#define CHIMERATK_APPLICATION_H
#include <mutex>
Martin Christoph Hierholzer
committed
#include <atomic>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include <mtca4u/DeviceBackend.h>
Martin Christoph Hierholzer
committed
#include <ChimeraTK/ControlSystemAdapter/ApplicationBase.h>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include "ApplicationException.h"
#include "VariableNetwork.h"
#include "Flags.h"
#include "InternalModule.h"
Martin Christoph Hierholzer
committed
#include "EntityOwner.h"
#include "Profiler.h"
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
namespace ChimeraTK {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
class Module;
Martin Christoph Hierholzer
committed
class AccessorBase;
Martin Christoph Hierholzer
committed
class VariableNetwork;
Martin Christoph Hierholzer
committed
class TriggerFanOut;
Martin Christoph Hierholzer
committed
class TestFacility;
Martin Christoph Hierholzer
committed
template<typename UserType>
class Accessor;
Martin Christoph Hierholzer
committed
template<typename UserType>
class TestDecoratorRegisterAccessor;
Martin Christoph Hierholzer
committed
class Application : public ApplicationBase, public EntityOwner {
Martin Christoph Hierholzer
committed
public:
Martin Christoph Hierholzer
committed
/** The constructor takes the application name as an argument. The name must have a non-zero length and must not
* contain any spaces or special characters. Use only alphanumeric characters and underscores. */
Application(const std::string& name);
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
~Application() {}
Martin Christoph Hierholzer
committed
/** This will remove the global pointer to the instance and allows creating another instance
Martin Christoph Hierholzer
committed
* afterwards. This is mostly useful for writing tests, as it allows to run several applications sequentially
* in the same executable. Note that any ApplicationModules etc. owned by this Application are no longer
* valid after destroying the Application and must be destroyed as well (or at least no longer used). */
Martin Christoph Hierholzer
committed
void shutdown() override;
Martin Christoph Hierholzer
committed
/** Define the connections between process variables. Must be implemented by the application developer. */
virtual void defineConnections() = 0;
Martin Christoph Hierholzer
committed
void initialise() override;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
void run() override;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Instead of running the application, just initialise it and output the published variables to an XML file. */
void generateXML();
Martin Christoph Hierholzer
committed
/** Output the connections requested in the initialise() function to std::cout. This may be done also before
* makeConnections() has been called. */
void dumpConnections();
Martin Christoph Hierholzer
committed
/** Enable warning about unconnected variables. This can be helpful to identify missing connections but is
* disabled by default since it may often be very noisy. */
void warnUnconnectedVariables() { enableUnconnectedVariablesWarning = true; }
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Obtain instance of the application. Will throw an exception if called before the instance has been
* created by the control system adapter, or if the instance is not based on the Application class. */
static Application& getInstance();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Enable the testable mode. This allows to pause and resume the application for testing purposes using the
* functions pauseApplication() and resumeApplication(). The application will start in paused state.
*
* This function must be called before the application is initialised (i.e. before the call to initialise()).
*
* Note: Enabling the testable mode will have a singificant impact on the performance, since it will prevent
* any module threads to run at the same time! */
void enableTestableMode() {
testableMode = true;
testableModeLock("enableTestableMode");
Martin Christoph Hierholzer
committed
threadName() = "TEST THREAD";
Martin Christoph Hierholzer
committed
/** Resume the application until all application threads are stuck in a blocking read operation. Works only when
* the testable mode was enabled. */
void stepApplication();
Martin Christoph Hierholzer
committed
/** Enable some additional (potentially noisy) debug output for the testable mode. Can be useful if tests
* of applications seem to hang for no reason in stepApplication. */
void debugTestableMode() { enableDebugTestableMode = true; }
Martin Christoph Hierholzer
committed
/** This is a testable version of mtca4u::TransferElement::readAny(). Always use this version instead of the
* original version provided by DeviceAccess. If the testable mode is not enabled, just the original version
* is called instead. Only with the testable mode enabled, special precautions are taken to make this blocking
* call testable. */
static mtca4u::TransferElement::ID readAny(std::list<std::reference_wrapper<TransferElement>> elementsToRead);
Martin Christoph Hierholzer
committed
/** Lock the testable mode mutex for the current thread. Internally, a thread-local std::unique_lock<std::mutex>
* will be created and re-used in subsequent calls within the same thread to this function and to
* testableModeUnlock().
*
* This function should generally not be used in user code. */
Martin Christoph Hierholzer
committed
static void testableModeLock(const std::string& name);
Martin Christoph Hierholzer
committed
/** Unlock the testable mode mutex for the current thread. See also testableModeLock().
*
* Initially the lock will not be owned by the current thread, so the first call to this function will throw an
* exception (see std::unique_lock::unlock()), unless testableModeLock() has been called first.
*
* This function should generally not be used in user code. */
Martin Christoph Hierholzer
committed
static void testableModeUnlock(const std::string& name);
Martin Christoph Hierholzer
committed
/** Test if the testable mode mutex is locked by the current thread.
*
* This function should generally not be used in user code. */
static bool testableModeTestLock() {
if(!getInstance().testableMode) return false;
Martin Christoph Hierholzer
committed
return getTestableModeLockObject().owns_lock();
}
Martin Christoph Hierholzer
committed
/** Get string holding the name of the current thread. This is used e.g. for debugging output of the testable
* mode and for the internal profiler. */
static std::string& threadName() {
Martin Christoph Hierholzer
committed
thread_local static std::string name{"**UNNAMED**"};
return name;
}
/** Register the thread in the application system and give it a name. This should be done for all threads used by
* the application to help with debugging and to allow profiling. */
static void registerThread(const std::string &name) {
threadName() = name;
Profiler::registerThread(name);
}
Martin Christoph Hierholzer
committed
ModuleType getModuleType() const override { return ModuleType::ModuleGroup; }
std::string getQualifiedName() const override {
return "/" + _name;
}
Martin Christoph Hierholzer
committed
/** Special exception class which will be thrown if tests with the testable mode are stalled. Normally this
* exception should never be caught. The only reason for catching it might be a situation where the expected
* behaviour of an app is to do nothing and one wants to test this. Note that the stall condition only appears
* after a certain timeout, thus tests relying on this will take time.
*
* This exception must not be based on a generic exception class to prevent catching it unintentionally. */
class TestsStalled {};
Martin Christoph Hierholzer
committed
protected:
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
friend class Module;
Martin Christoph Hierholzer
committed
friend class VariableNetwork;
Martin Christoph Hierholzer
committed
friend class VariableNetworkNode;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
template<typename UserType>
friend class Accessor;
Martin Christoph Hierholzer
committed
/** Check if all connections are valid. Internally called in initialise(). */
void checkConnections();
Martin Christoph Hierholzer
committed
/** Obtain the lock object for the testable mode lock for the current thread. The returned object has
* thread_local storage duration and must only be used inside the current thread. Initially (i.e. after
* the first call in one particular thread) the lock will not be owned by the returned object, so it is
* important to catch the corresponding exception when calling std::unique_lock::unlock(). */
static std::unique_lock<std::mutex>& getTestableModeLockObject() {
thread_local static std::unique_lock<std::mutex> myLock(Application::testableMode_mutex, std::defer_lock);
return myLock;
}
Martin Christoph Hierholzer
committed
/** Register the connections to constants for previously unconnected nodes. */
void processUnconnectedNodes();
Martin Christoph Hierholzer
committed
/** Make the connections between accessors as requested in the initialise() function. */
void makeConnections();
/** Apply optimisations to the VariableNetworks, e.g. by merging networks sharing the same feeder. */
void optimiseConnections();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Make the connections for a single network */
void makeConnectionsForNetwork(VariableNetwork &network);
/** UserType-dependent part of makeConnectionsForNetwork() */
Martin Christoph Hierholzer
committed
template<typename UserType>
void typedMakeConnection(VariableNetwork &network);
Martin Christoph Hierholzer
committed
/** Register a connection between two VariableNetworkNode */
Martin Christoph Hierholzer
committed
VariableNetwork& connect(VariableNetworkNode a, VariableNetworkNode b);
Martin Christoph Hierholzer
committed
/** Perform the actual connection of an accessor to a device register */
Martin Christoph Hierholzer
committed
template<typename UserType>
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> createDeviceVariable(const std::string &deviceAlias,
Martin Christoph Hierholzer
committed
const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements);
Martin Christoph Hierholzer
committed
/** Create a process variable with the PVManager, which is exported to the control system adapter. nElements will
be the array size of the created variable. */
Martin Christoph Hierholzer
committed
template<typename UserType>
Martin Christoph Hierholzer
committed
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> createProcessVariable(VariableNetworkNode const &node);
Martin Christoph Hierholzer
committed
/** Create a local process variable which is not exported. The first element in the returned pair will be the
Martin Christoph Hierholzer
committed
* sender, the second the receiver. */
Martin Christoph Hierholzer
committed
template<typename UserType>
std::pair< boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>>, boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> >
Martin Christoph Hierholzer
committed
createApplicationVariable(VariableNetworkNode const &node);
Martin Christoph Hierholzer
committed
/** List of InternalModules */
std::list<boost::shared_ptr<InternalModule>> internalModuleList;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** List of variable networks */
std::list<VariableNetwork> networkList;
Martin Christoph Hierholzer
committed
/** List of constant variable nodes */
std::list<VariableNetworkNode> constantList;
Martin Christoph Hierholzer
committed
/** Map of trigger sources to their corresponding TriggerFanOuts. Note: the key is the unique ID of the
* triggering node. */
std::map<const void*, boost::shared_ptr<TriggerFanOut>> triggerMap;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Create a new, empty network */
VariableNetwork& createNetwork();
Martin Christoph Hierholzer
committed
/** Instance of VariableNetwork to indicate an invalid network */
VariableNetwork invalidNetwork;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Map of DeviceBackends used by this application. The map key is the alias name from the DMAP file */
std::map<std::string, boost::shared_ptr<mtca4u::DeviceBackend>> deviceMap;
Martin Christoph Hierholzer
committed
/** Flag if connections should be made in testable mode (i.e. the TestDecoratorRegisterAccessor is put around all
* push-type input accessors etc.). */
bool testableMode{false};
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Mutex used in testable mode to take control over the application threads. Use only through the lock object
Martin Christoph Hierholzer
committed
* obtained through getLockObjectForCurrentThread().
*
* This member is static, since it should survive destroying an application instance and creating a new one.
* Otherwise getTestableModeLockObject() would not work, since it relies on thread_local instances which have to
* be static. The static storage duration presents no problem in either case, since there can only be one single
* instance of Application at a time (see ApplicationBase constructor). */
static std::mutex testableMode_mutex;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Semaphore counter used in testable mode to check if application code is finished executing. This value may
* only be accessed while holding the testableMode_mutex. */
Martin Christoph Hierholzer
committed
size_t testableMode_counter{0};
Martin Christoph Hierholzer
committed
/** Flag if noisy debug output is enabled for the testable mode */
bool enableDebugTestableMode{false};
Martin Christoph Hierholzer
committed
/** Flag whether to warn about unconnected variables or not */
bool enableUnconnectedVariablesWarning{false};
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Map from accessor ID to the variable ID used in the other maps here, e.g. for the testable mode. This allows
* associating sender and receiver pairs of the same ProcessArray. */
std::map<mtca4u::TransferElement::ID, size_t> idMap;
/** Map from ProcessArray uniqueId to the variable ID for control system variables. This is required for the
* TestFacility. */
std::map<size_t, size_t> pvIdMap;
/** Return a fresh variable ID which can be assigned to a sender/receiver pair. The ID will always be non-zero. */
static size_t getNextVariableId() {
static size_t nextId{0};
return ++nextId;
}
Martin Christoph Hierholzer
committed
/** Last thread which successfully obtained the lock for the testable mode. This is used to prevent spamming
* repeating messages if the same thread acquires and releases the lock in a loop without another thread
* activating in between. */
std::thread::id testableMode_lastMutexOwner;
Martin Christoph Hierholzer
committed
/** Counter how often the same thread has acquired the testable mode mutex in a row without another thread
* owning it in between. This is an indicator for the test being stalled due to data send through a process
* variable but not read by the receiver. */
std::atomic<size_t> testableMode_repeatingMutexOwner{false};
/** Testable mode: like testableMode_counter but broken out for each variable. This is not actually used as a
* semaphore counter but only in case of a detected stall (see testableMode_repeatingMutexOwner) to print
* a list of variables which still contain unread values. The index of the map is the unique ID of the
* variable. */
std::map<size_t, size_t> testableMode_perVarCounter;
Martin Christoph Hierholzer
committed
/** Map of unique IDs to namess, used along with testableMode_perVarCounter to print sensible information. */
std::map<size_t, std::string> testableMode_names;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/** Map of unique IDs to process variables which have been decorated with the TestDecoratorRegisterAccessor. */
std::map<size_t, boost::shared_ptr<TransferElement>> testableMode_processVars;
/** Map of unique IDs to flags whether the update mode is UpdateMode::poll (so we do not use the decorator) */
std::map<size_t, bool> testableMode_isPollMode;
Martin Christoph Hierholzer
committed
template<typename UserType>
friend class TestDecoratorRegisterAccessor; // needs access to the testableMode_mutex and testableMode_counter
template<typename UserType>
Martin Christoph Hierholzer
committed
friend class TestDecoratorTransferFuture; // needs access to the testableMode_mutex and testableMode_counter
friend class TestFacility; // needs access to testableMode_variables
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
};
} /* namespace ChimeraTK */
#endif /* CHIMERATK_APPLICATION_H */