ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
TestableMode.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "TestableMode.h"
5
6#include "Application.h"
7#include "Utilities.h"
8
9namespace ChimeraTK::detail {
10
11 std::shared_timed_mutex TestableMode::_mutex;
12 std::shared_mutex TestableMode::_mutex2;
13
14 /********************************************************************************************************************/
15
16 void TestableMode::enable() {
17 setThreadName("TEST THREAD");
18 _enabled = true;
19 lock("enableTestableMode", false);
20 }
21
22 /********************************************************************************************************************/
23
24 bool TestableMode::Lock::tryLockFor(std::chrono::seconds timeout, bool shared) {
25 assert(!_ownsLock);
26 _isShared = shared;
27 if(shared) {
28 _ownsLock = _mutex.try_lock_shared_for(timeout);
29 if(_ownsLock) {
30 _mutex2.lock_shared();
31 }
32 return _ownsLock;
33 }
34 _ownsLock = _mutex.try_lock_for(timeout);
35 if(_ownsLock) {
36 _mutex2.lock();
37 }
38 return _ownsLock;
39 }
40
41 /********************************************************************************************************************/
42
43 void TestableMode::Lock::unlock() {
44 assert(_ownsLock);
45 _ownsLock = false;
46 if(_isShared) {
47 _mutex2.unlock_shared();
48 _mutex.unlock_shared();
49 }
50 else {
51 _mutex2.unlock();
52 _mutex.unlock();
53 }
54 }
55
56 /********************************************************************************************************************/
57
58 TestableMode::Lock::~Lock() {
59 if(_ownsLock) {
60 unlock();
61 }
62 }
63
64 /********************************************************************************************************************/
65
66 TestableMode::Lock& TestableMode::getLockObject() {
67 // Note: due to a presumed bug in gcc (still present in gcc 7), the
68 // thread_local definition must be in the cc file to prevent seeing different
69 // objects in the same thread under some conditions. Another workaround for
70 // this problem can be found in commit
71 // dc051bfe35ce6c1ed954010559186f63646cf5d4
72 thread_local Lock myLock;
73 return myLock;
74 }
75
76 /********************************************************************************************************************/
77
78 bool TestableMode::testLock() const {
79 if(not _enabled) {
80 return false;
81 }
82 return getLockObject().ownsLock();
83 }
84
85 /********************************************************************************************************************/
86
87 namespace {
89 void terminateTestStalled() {
90 // Do not terminate, just warn, while running in a debugger
92 std::cerr << "*** Test stalled. Continue anyway since running in debugger." << std::endl;
93 return;
94 }
95
96 struct TestStalled : std::exception {
97 [[nodiscard]] const char* what() const noexcept override { return "Test stalled."; }
98 };
99 try {
100 throw TestStalled();
101 }
102 catch(...) {
103 std::terminate();
104 }
105 }
106 } // namespace
107
108 /********************************************************************************************************************/
109
110 void TestableMode::lock(const std::string& name, bool shared) {
111 // don't do anything if testable mode is not enabled
112 if(not _enabled) {
113 return;
114 }
115
116 // debug output if enabled (also prevent spamming the same message)
117 if(_enableDebug) { // LCOV_EXCL_LINE (only cout)
118 logger(Logger::Severity::debug, "TestableMode") // LCOV_EXCL_LINE (only cout)
119 << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
120 << " tries to obtain lock for " << name; // LCOV_EXCL_LINE (only cout)
121 } // LCOV_EXCL_LINE (only cout)
122
123 // obtain the lock
124 boost::thread::id lastSeen_lastOwner = _lastMutexOwner;
125 repeatTryLock:
126 auto success = getLockObject().tryLockFor(std::chrono::seconds(30), shared);
127 boost::thread::id currentLastOwner = _lastMutexOwner;
128 if(!success) {
129 if(currentLastOwner != lastSeen_lastOwner) {
130 lastSeen_lastOwner = currentLastOwner;
131 usleep(10000);
132 }
133 else {
134 std::cerr << "testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE
135 << " could not obtain lock for at least 30 seconds, presumably because " // LCOV_EXCL_LINE
136 << threadName(_lastMutexOwner) << " [" << pthreadId(_lastMutexOwner) // LCOV_EXCL_LINE
137 << "] does not release it." // LCOV_EXCL_LINE
138 << std::endl; // LCOV_EXCL_LINE
139 terminateTestStalled(); // LCOV_EXCL_LINE
140 }
141 goto repeatTryLock;
142 } // LCOV_EXCL_LINE
143
144 _lastMutexOwner = boost::this_thread::get_id();
145
146 // debug output if enabled
147 if(_enableDebug) { // LCOV_EXCL_LINE (only cout)
148 logger(Logger::Severity::debug, "TestableMode") // LCOV_EXCL_LINE (only cout)
149 << "TestableMode::lock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
150 << " obtained lock successfully for " << name; // LCOV_EXCL_LINE (only cout)
151 } // LCOV_EXCL_LINE (only cout)
152 }
153
154 /********************************************************************************************************************/
155
156 void TestableMode::unlock(const std::string& name) {
157 if(not _enabled) {
158 return;
159 }
160 if(_enableDebug) { // LCOV_EXCL_LINE (only cout)
161 logger(Logger::Severity::debug, "TestableMode") // LCOV_EXCL_LINE (only cout)
162 << "TestableMode::unlock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout)
163 << " releases lock for " << name; // LCOV_EXCL_LINE (only cout)
164 } // LCOV_EXCL_LINE (only cout)
165 getLockObject().unlock();
166 }
167
168 /********************************************************************************************************************/
169
170 void TestableMode::step(bool waitForDeviceInitialisation) {
171 // testableMode.counter must be non-zero, otherwise there is no input for the application to process. It is also
172 // sufficient if testableMode.deviceInitialisationCounter is non-zero, if waitForDeviceInitialisation == true. In
173 // that case we only wait for the device initialisation to be completed.
174 if(_counter == 0 && (!waitForDeviceInitialisation || _deviceInitialisationCounter == 0)) {
175 throw ChimeraTK::logic_error("Application::stepApplication() called despite no input was provided "
176 "to the application to process!");
177 }
178 // let the application run until it has processed all data (i.e. the semaphore
179 // counter is 0)
180 size_t oldCounter = 0;
181 auto t0 = std::chrono::steady_clock::now();
182 while(true) {
183 if(_enableDebug && (oldCounter != _counter)) { // LCOV_EXCL_LINE (only cout)
184 logger(Logger::Severity::debug, "TestableMode")
185 << "Application::stepApplication(): testableMode.counter = " << _counter; // LCOV_EXCL_LINE (only cout)
186 oldCounter = _counter; // LCOV_EXCL_LINE (only cout)
187 }
188 unlock("stepApplication");
189 boost::this_thread::yield();
190 lock("stepApplication", false);
191 if(_counter > 0 || (waitForDeviceInitialisation && _deviceInitialisationCounter > 0)) {
192 usleep(1000);
193
194 // If the application does not finish data processing (and hence counters will stay > 0), assume the test
195 // is stalled and terminate the test.
196 if(std::chrono::steady_clock::now() - t0 > std::chrono::seconds(30)) {
197 // print an informative message first, which lists also all variables
198 // currently containing unread data.
199 std::cerr << "*** Tests are stalled due to data which has been sent but not received.\n";
200 std::cerr
201 << " The following variables still contain unread values or had data loss due to a queue overflow:\n";
202 for(auto& pair : _variables) {
203 const auto& variable = pair.second;
204 if(variable.counter > 0) {
205 std::cerr << " - " << variable.name << " [" << variable.processVariable->getId() << "]";
206 // check if process variable still has data in the queue
207 try {
208 if(variable.processVariable->readNonBlocking()) {
209 std::cerr << " (unread data in queue)";
210 }
211 else {
212 std::cerr << " (data loss)";
213 }
214 }
215 catch(ChimeraTK::logic_error&) {
216 // if we receive a logic_error in readNonBlocking() it just means
217 // another thread is waiting on a TransferFuture of this variable,
218 // and we actually were not allowed to read...
219 std::cerr << " (data loss)";
220 }
221 std::cerr << "\n";
222 }
223 }
224 std::cerr << "(end of list)\n";
225 // Check for modules waiting for initial values (prints nothing if there are no such modules)
227 // throw a specialised exception to make sure whoever catches it really knows what he does...
228 terminateTestStalled();
229 }
230 }
231 else {
232 break;
233 }
234 }
235 }
236
237 /********************************************************************************************************************/
238
239 void TestableMode::setThreadName(const std::string& name) {
240 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
241 _threadNames[boost::this_thread::get_id()] = name;
242 _threadPThreadId[boost::this_thread::get_id()] = gettid();
244 }
245
246 /********************************************************************************************************************/
247
248 std::string TestableMode::threadName(const boost::thread::id& threadId) {
249 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
250 if(auto const& it = _threadNames.find(threadId); it != _threadNames.end()) {
251 return it->second;
252 }
253
254 return "*UNKNOWN_THREAD*";
255 }
256
257 /********************************************************************************************************************/
258
259 pid_t TestableMode::pthreadId(const boost::thread::id& threadId) {
260 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
261 if(auto const& it = _threadPThreadId.find(threadId); it != _threadPThreadId.end()) {
262 return it->second;
263 }
264
265 return 0;
266 }
267 /********************************************************************************************************************/
268
269 TestableMode::LastMutexOwner::operator boost::thread::id() {
270 std::lock_guard<std::mutex> lk(_mxLastMutexOwner);
271 return _lastMutexOwner;
272 }
273
274 /********************************************************************************************************************/
275
276 TestableMode::LastMutexOwner& TestableMode::LastMutexOwner::operator=(const boost::thread::id& id) {
277 std::lock_guard<std::mutex> lk(_mxLastMutexOwner);
278 _lastMutexOwner = id;
279 return *this;
280 }
281
282 /********************************************************************************************************************/
283
284} // namespace ChimeraTK::detail
static Application & getInstance()
Obtain instance of the application.
detail::CircularDependencyDetector _circularDependencyDetector
void setThreadName(const std::string &name)
Set name of the current thread.
Definition Utilities.cc:105
bool isBeingDebugged()
Checks whether the current process is being debugged.
Definition Utilities.cc:132
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:124