Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.h
*
* Created on: Jun 07, 2016
* Author: Martin Hierholzer
*/
#ifndef CHIMERATK_APPLICATION_H
#define CHIMERATK_APPLICATION_H
Martin Christoph Hierholzer
committed
#include <atomic>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include <ChimeraTK/ControlSystemAdapter/ApplicationBase.h>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include "Flags.h"
#include "InternalModule.h"
#include "Profiler.h"
//#include "DeviceModule.h"
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
namespace ChimeraTK {
Martin Christoph Hierholzer
committed
class Module;
class AccessorBase;
class VariableNetwork;
class TriggerFanOut;
class TestFacility;
class DeviceModule;
template<typename UserType>
class Accessor;
template<typename UserType>
class FanOut;
template<typename UserType>
class ConsumingFanOut;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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(std::ostream& stream = std::cout);
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/** 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.
* The optional argument controls whether to wait as well for devices to be completely (re-)initialised. Disabling
* this behavior allows to test proper response to runtime exceptions. */
void stepApplication(bool waitForDeviceInitialisation = true);
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/** 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<std::string::size_type>(name.length(), 15)).c_str());
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
}
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);
void unregisterDeviceModule(DeviceModule* deviceModule);
LifeCycleState getLifeCycleState() const { return lifeCycleState; }
VersionNumber getStartVersion() const { return _startVersion; }
protected:
friend class Module;
friend class VariableNetwork;
friend class VariableNetworkNode;
friend class VariableNetworkGraphDumpingVisitor;
friend class XMLGeneratorVisitor;
template<typename UserType>
friend class Accessor;
template<typename UserType>
friend class ExceptionHandlingDecorator;
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/** 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);
/** Helper function to add consumers to fanouts in typedMakeConnection() */
template<typename UserType>
void addConsumersToFanout(boost::shared_ptr<FanOut<UserType>> fanout, VariableNetworkNode& feeder,
std::list<VariableNetworkNode> consumers, boost::shared_ptr<ConsumingFanOut<UserType>> consumingFanOut);
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/** 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;
Martin Killenberg
committed
/** Map of trigger consumers to their corresponding TriggerFanOuts. Note: the
Martin Killenberg
committed
* key is the ID (address) of the externalTiggerImpl. */
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;
Martin Christoph Hierholzer
committed
/** List of DeviceModules */
std::list<DeviceModule*> deviceModuleList;
/** 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};
/** Flag which is set by the TestFacility in runApplication() at the beginning. This is used to make sure
* runApplication() is called by the TestFacility and not manually. */
bool testFacilityRunApplicationCalled{false};
/** Flag whether initialise() has been called already, to make sure it doesn't get called twice. */
bool initialiseCalled{false};
/** Flag whether run() has been called already, to make sure it doesn't get called twice. */
bool runCalled{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};
/** Semaphore counter used in testable mode to check if device initialisation is finished executing. This value may
* only be accessed while holding the testableMode_mutex. This counter is a separate counter from
* testableMode_counter so stepApplication() can be controlled whether to obey this counter. */
size_t testableMode_deviceInitialisationCounter{0};
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/** 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;
/** Counter for how many write() operations have overwritten unread data */
std::atomic<size_t> dataLossCounter{0};
/** Life-cycle state of the application */
std::atomic<LifeCycleState> lifeCycleState{LifeCycleState::initialisation};
/** Version number used at application start, e.g. to propagate initial values */
VersionNumber _startVersion;
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
Martin Christoph Hierholzer
committed
friend class DeviceModule; // needs access to testableMode_variables
template<typename UserType>
friend class DebugPrintAccessorDecorator; // needs access to the idMap
VersionNumber getCurrentVersionNumber() const override {
throw ChimeraTK::logic_error("getCurrentVersionNumber() called on the application. This is probably "
Martin Christoph Hierholzer
committed
"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 "
Martin Christoph Hierholzer
committed
"caused by incorrect ownership of variables/accessors or VariableGroups.");
}
DataValidity getDataValidity() const override {
throw ChimeraTK::logic_error("getDataValidity() called on the application. This is probably "
"caused by incorrect ownership of variables/accessors or VariableGroups.");
}
void incrementDataFaultCounter() override {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("incrementDataFaultCounter() called on the application. This is probably "
"caused by incorrect ownership of variables/accessors or VariableGroups.");
}
void decrementDataFaultCounter() override {
Martin Christoph Hierholzer
committed
throw ChimeraTK::logic_error("decrementDataFaultCounter() called on the application. This is probably "
"caused by incorrect ownership of variables/accessors or VariableGroups.");
void incrementExceptionCounter(bool /*writeAllOutputs*/) override {
throw ChimeraTK::logic_error("incrementExceptionCounter() called on the application. This is probably "
"caused by incorrect ownership of variables/accessors or VariableGroups.");
}
void decrementExceptionCounter() override {
throw ChimeraTK::logic_error("decrementExceptionCounter() called on the application. This is probably "
"caused by incorrect ownership of variables/accessors or VariableGroups.");
}
Martin Christoph Hierholzer
committed
};
} /* namespace ChimeraTK */
#endif /* CHIMERATK_APPLICATION_H */