ChimeraTK-ApplicationCore 04.08.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 logger(Logger::Severity::trace, "TestableMode")
118 << "Application::testableModeLock(): Thread " << threadName() << " tries to obtain lock for " << name;
119
120 // obtain the lock
121 boost::thread::id lastSeen_lastOwner = _lastMutexOwner;
122 repeatTryLock:
123 auto success = getLockObject().tryLockFor(std::chrono::seconds(30), shared);
124 boost::thread::id currentLastOwner = _lastMutexOwner;
125 if(!success) {
126 if(currentLastOwner != lastSeen_lastOwner) {
127 lastSeen_lastOwner = currentLastOwner;
128 usleep(10000);
129 }
130 else {
131 std::cerr << "testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE
132 << " could not obtain lock for at least 30 seconds, presumably because " // LCOV_EXCL_LINE
133 << threadName(_lastMutexOwner) << " [" << pthreadId(_lastMutexOwner) // LCOV_EXCL_LINE
134 << "] does not release it." // LCOV_EXCL_LINE
135 << std::endl; // LCOV_EXCL_LINE
136 terminateTestStalled(); // LCOV_EXCL_LINE
137 }
138 goto repeatTryLock;
139 } // LCOV_EXCL_LINE
140
141 _lastMutexOwner = boost::this_thread::get_id();
142
143 // debug output if enabled
144 logger(Logger::Severity::trace, "TestableMode")
145 << "TestableMode::lock(): Thread " << threadName() << " obtained lock successfully for " << name;
146 }
147
148 /********************************************************************************************************************/
149
150 void TestableMode::unlock(const std::string& name) {
151 if(not _enabled) {
152 return;
153 }
154 logger(Logger::Severity::trace, "TestableMode")
155 << "TestableMode::unlock(): Thread " << threadName() << " releases lock for " << name;
156 getLockObject().unlock();
157 }
158
159 /********************************************************************************************************************/
160
161 void TestableMode::step(bool waitForDeviceInitialisation) {
162 // testableMode.counter must be non-zero, otherwise there is no input for the application to process. It is also
163 // sufficient if testableMode.deviceInitialisationCounter is non-zero, if waitForDeviceInitialisation == true. In
164 // that case we only wait for the device initialisation to be completed.
165 if(_counter == 0 && (!waitForDeviceInitialisation || _deviceInitialisationCounter == 0)) {
166 throw ChimeraTK::logic_error("Application::stepApplication() called despite no input was provided "
167 "to the application to process!");
168 }
169 // let the application run until it has processed all data (i.e. the semaphore
170 // counter is 0)
171 size_t oldCounter = 0;
172 auto t0 = std::chrono::steady_clock::now();
173 while(true) {
174 if((oldCounter != _counter)) { // LCOV_EXCL_LINE (only logging)
175 logger(Logger::Severity::trace, "TestableMode")
176 << "Application::stepApplication(): testableMode.counter = " << _counter; // LCOV_EXCL_LINE (only logging)
177 oldCounter = _counter; // LCOV_EXCL_LINE (only logging)
178 }
179 unlock("stepApplication");
180 boost::this_thread::yield();
181 lock("stepApplication", false);
182 if(_counter > 0 || (waitForDeviceInitialisation && _deviceInitialisationCounter > 0)) {
183 usleep(1000);
184
185 // If the application does not finish data processing (and hence counters will stay > 0), assume the test
186 // is stalled and terminate the test.
187 if(std::chrono::steady_clock::now() - t0 > std::chrono::seconds(30)) {
188 // print an informative message first, which lists also all variables
189 // currently containing unread data.
190 std::cerr << "*** Tests are stalled due to data which has been sent but not received.\n";
191 std::cerr
192 << " The following variables still contain unread values or had data loss due to a queue overflow:\n";
193 for(auto& pair : _variables) {
194 const auto& variable = pair.second;
195 if(variable.counter > 0) {
196 std::cerr << " - " << variable.name << " [" << variable.processVariable->getId() << "]";
197 // check if process variable still has data in the queue
198 try {
199 if(variable.processVariable->readNonBlocking()) {
200 std::cerr << " (unread data in queue)";
201 }
202 else {
203 std::cerr << " (data loss)";
204 }
205 }
206 catch(ChimeraTK::logic_error&) {
207 // if we receive a logic_error in readNonBlocking() it just means
208 // another thread is waiting on a TransferFuture of this variable,
209 // and we actually were not allowed to read...
210 std::cerr << " (data loss)";
211 }
212 std::cerr << "\n";
213 }
214 }
215 std::cerr << "(end of list)\n";
216 // Check for modules waiting for initial values (prints nothing if there are no such modules)
218 // throw a specialised exception to make sure whoever catches it really knows what he does...
219 terminateTestStalled();
220 }
221 }
222 else {
223 break;
224 }
225 }
226 }
227
228 /********************************************************************************************************************/
229
230 void TestableMode::setThreadName(const std::string& name) {
231 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
232 _threadNames[boost::this_thread::get_id()] = name;
233 _threadPThreadId[boost::this_thread::get_id()] = gettid();
235 }
236
237 /********************************************************************************************************************/
238
239 std::string TestableMode::threadName(const boost::thread::id& threadId) {
240 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
241 if(auto const& it = _threadNames.find(threadId); it != _threadNames.end()) {
242 return it->second;
243 }
244
245 return "*UNKNOWN_THREAD*";
246 }
247
248 /********************************************************************************************************************/
249
250 pid_t TestableMode::pthreadId(const boost::thread::id& threadId) {
251 std::unique_lock<std::mutex> myLock(_threadNamesMutex);
252 if(auto const& it = _threadPThreadId.find(threadId); it != _threadPThreadId.end()) {
253 return it->second;
254 }
255
256 return 0;
257 }
258 /********************************************************************************************************************/
259
260 TestableMode::LastMutexOwner::operator boost::thread::id() {
261 std::lock_guard<std::mutex> lk(_mxLastMutexOwner);
262 return _lastMutexOwner;
263 }
264
265 /********************************************************************************************************************/
266
267 TestableMode::LastMutexOwner& TestableMode::LastMutexOwner::operator=(const boost::thread::id& id) {
268 std::lock_guard<std::mutex> lk(_mxLastMutexOwner);
269 _lastMutexOwner = id;
270 return *this;
271 }
272
273 /********************************************************************************************************************/
274
275} // 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:156