Skip to content
Snippets Groups Projects
Application.h 16.69 KiB
/*
 * 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"
//#include "DeviceModule.h"

namespace ChimeraTK {

  class Module;
  class AccessorBase;
  class VariableNetwork;
  class TriggerFanOut;
  class TestFacility;
  class DeviceModule;

  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);
    }

    void registerDeviceModule(DeviceModule* deviceModule);

   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) override {
      throw ChimeraTK::logic_error("setCurrentVersionNumber() called on the application. This is probably "
                                   "caused by "
                                   "incorrect ownership of variables/accessors or VariableGroups.");
    }
    std::list<DeviceModule*> deviceModuleList;
  };

} /* namespace ChimeraTK */

#endif /* CHIMERATK_APPLICATION_H */