/* * Application.h * * Created on: Jun 07, 2016 * Author: Martin Hierholzer */ #ifndef CHIMERATK_APPLICATION_H #define CHIMERATK_APPLICATION_H #include <atomic> #include <mutex> #include <ChimeraTK/ControlSystemAdapter/ApplicationBase.h> #include <ChimeraTK/DeviceBackend.h> #include "EntityOwner.h" #include "Flags.h" #include "InternalModule.h" #include "Profiler.h" #include "VariableNetwork.h" namespace ChimeraTK { class Module; class AccessorBase; class VariableNetwork; class TriggerFanOut; class TestFacility; template<typename UserType> class Accessor; class Application : public ApplicationBase, public EntityOwner { public: /** 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); ~Application() {} using ApplicationBase::getName; /** This will remove the global pointer to the instance and allows creating * another instance 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). */ void shutdown() override; /** Define the connections between process variables. Must be implemented by * the application developer. */ virtual void defineConnections() = 0; void initialise() override; void run() override; /** Instead of running the application, just initialise it and output the * published variables to an XML file. */ void generateXML(); /** Output the connections requested in the initialise() function to * std::cout. This may be done also before * makeConnections() has been called. */ void dumpConnections(); /** Create Graphviz dot graph and write to file. The graph will contain the * connections made in the initilise() function. @see dumpConnections */ void dumpConnectionGraph(const std::string& filename = {"connections-graph.dot"}); /** 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; } /** 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(); /** 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() { threadName() = "TEST THREAD"; testableMode = true; testableModeLock("enableTestableMode"); } /** * Returns true if application is in testable mode else returns * false. **/ bool isTestableModeEnabled() { return testableMode; } /** Resume the application until all application threads are stuck in a * blocking read operation. Works only when the testable mode was enabled. */ void stepApplication(); /** 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; } /** 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. */ static void testableModeLock(const std::string& name); /** 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. */ static void testableModeUnlock(const std::string& name); /** 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; return getTestableModeLockObject().owns_lock(); } /** 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(); /** 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); pthread_setname_np(pthread_self(), name.substr(0, std::min(name.length(), 15UL)).c_str()); } ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } std::string getQualifiedName() const override { return "/" + _name; } std::string getFullDescription() const override { return ""; } /** 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 {}; /** Enable debug output for a given variable. */ void enableVariableDebugging(const VariableNetworkNode& node) { debugMode_variableList.insert(node.getUniqueId()); } /** Incremenet counter for how many write() operations have overwritten unread * data */ static void incrementDataLossCounter() { getInstance().dataLossCounter++; } static size_t 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; } /** Convenience function for creating constants. See * VariableNetworkNode::makeConstant() for details. */ template<typename UserType> static VariableNetworkNode makeConstant(UserType value, size_t length = 1, bool makeFeeder = true) { return VariableNetworkNode::makeConstant(makeFeeder, value, length); } protected: friend class Module; friend class VariableNetwork; friend class VariableNetworkNode; friend class VariableNetworkGraphDumpingVisitor; friend class XMLGeneratorVisitor; template<typename UserType> friend class Accessor; /** Finalise connections, i.e. decide still-undecided details mostly for * Device and ControlSystem variables */ void finaliseNetworks(); /** Check if all connections are valid. Internally called in initialise(). */ void checkConnections(); /** 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(); /** Register the connections to constants for previously unconnected nodes. */ void processUnconnectedNodes(); /** 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(); /** Make the connections for a single network */ void makeConnectionsForNetwork(VariableNetwork& network); /** UserType-dependent part of makeConnectionsForNetwork() */ template<typename UserType> void typedMakeConnection(VariableNetwork& network); /** Functor class to call typedMakeConnection() with the right template * argument. */ struct TypedMakeConnectionCaller { TypedMakeConnectionCaller(Application& owner, VariableNetwork& network); template<typename PAIR> void operator()(PAIR&) const; Application& _owner; VariableNetwork& _network; mutable bool done{false}; }; /** Register a connection between two VariableNetworkNode */ VariableNetwork& connect(VariableNetworkNode a, VariableNetworkNode b); /** Perform the actual connection of an accessor to a device register */ template<typename UserType> boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> createDeviceVariable(const std::string& deviceAlias, const std::string& registerName, VariableDirection direction, UpdateMode mode, size_t nElements); /** 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. */ template<typename UserType> boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> createProcessVariable(VariableNetworkNode const& node); /** Create a local process variable which is not exported. The first element * in the returned pair will be the sender, the second the receiver. If two * nodes are passed, the first node should be the sender and the second the * receiver. */ template<typename UserType> std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> createApplicationVariable(VariableNetworkNode const& node, VariableNetworkNode const& consumer = {}); /** List of InternalModules */ std::list<boost::shared_ptr<InternalModule>> internalModuleList; /** List of variable networks */ std::list<VariableNetwork> networkList; /** List of constant variable nodes */ std::list<VariableNetworkNode> constantList; /** 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; /** Create a new, empty network */ VariableNetwork& createNetwork(); /** Instance of VariableNetwork to indicate an invalid network */ VariableNetwork invalidNetwork; /** 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<ChimeraTK::DeviceBackend>> deviceMap; /** Flag if connections should be made in testable mode (i.e. the * TestableModeAccessorDecorator is put around all push-type input accessors * etc.). */ bool testableMode{false}; /** Mutex used in testable mode to take control over the application threads. * Use only through the lock object 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; /** 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. */ size_t testableMode_counter{0}; /** Flag if noisy debug output is enabled for the testable mode */ bool enableDebugTestableMode{false}; /** Flag whether to warn about unconnected variables or not */ bool enableUnconnectedVariablesWarning{false}; /** 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; } /** 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; /** 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; /** Map of unique IDs to namess, used along with testableMode_perVarCounter to * print sensible information. */ std::map<size_t, std::string> testableMode_names; /** Map of unique IDs to process variables which have been decorated with the * TestableModeAccessorDecorator. */ 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; /** List of variables for which debug output was requested via * enableVariableDebugging(). Stored is the unique id of the * VariableNetworkNode.*/ std::unordered_set<const void*> debugMode_variableList; template<typename UserType> friend class TestableModeAccessorDecorator; // needs access to the // testableMode_mutex and // testableMode_counter and the // idMap friend class TestFacility; // needs access to testableMode_variables template<typename UserType> friend class DebugPrintAccessorDecorator; // needs access to the idMap /** Counter for how many write() operations have overwritten unread data */ std::atomic<size_t> dataLossCounter{0}; VersionNumber getCurrentVersionNumber() const override { throw ChimeraTK::logic_error("getCurrentVersionNumber() called on the application. This is probably " "caused by " "incorrect ownership of variables/accessors or VariableGroups."); } void setCurrentVersionNumber(VersionNumber) { throw ChimeraTK::logic_error("setCurrentVersionNumber() called on the application. This is probably " "caused by " "incorrect ownership of variables/accessors or VariableGroups."); } }; } /* namespace ChimeraTK */ #endif /* CHIMERATK_APPLICATION_H */