ChimeraTK-ApplicationCore 04.08.00
Loading...
Searching...
No Matches
TestableMode.h
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#pragma once
4
5#include "Logger.h"
7
8#include <ChimeraTK/ControlSystemAdapter/BidirectionalProcessArray.h>
9#include <ChimeraTK/TransferElement.h>
10
11#include <boost/shared_ptr.hpp>
12#include <boost/thread.hpp>
13
14#include <atomic>
15#include <cstddef>
16#include <map>
17#include <shared_mutex>
18
19namespace ChimeraTK {
20 class ConnectionMaker;
21 class DeviceManager;
22 class TriggerFanOut;
23 class TestFacility;
24} /* namespace ChimeraTK */
25
26namespace ChimeraTK::detail {
27 struct TestableMode {
38 void lock(const std::string& name, bool shared);
39
48 void unlock(const std::string& name);
49
55 [[nodiscard]] bool testLock() const;
56
60 [[nodiscard]] bool canStep() const { return _counter != 0; }
61
66 void step(bool waitForDeviceInitialisation);
67
72 void setThreadName(const std::string& name);
73
77 void enable();
78
82 [[nodiscard]] bool isEnabled() const { return _enabled; }
83
87 static size_t getNextVariableId() {
88 static size_t nextId{0};
89 return ++nextId;
90 }
91
92 enum class DecoratorType { READ, WRITE };
93
94 template<typename T>
95 boost::shared_ptr<NDRegisterAccessor<T>> decorate(boost::shared_ptr<NDRegisterAccessor<T>> other,
96 DecoratorType direction, const std::string& name = {}, size_t varId = 0);
97
98 template<typename T>
99 using AccessorPair = std::pair<boost::shared_ptr<NDRegisterAccessor<T>>, boost::shared_ptr<NDRegisterAccessor<T>>>;
100
101 template<typename T>
102 AccessorPair<T> decorate(
103 AccessorPair<T> other, const VariableNetworkNode& producer, const VariableNetworkNode& consumer);
104
108 template<typename UserType>
109 class AccessorDecorator : public ChimeraTK::NDRegisterAccessorDecorator<UserType> {
110 public:
111 AccessorDecorator(detail::TestableMode& testableMode,
112 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, bool handleRead, bool handleWrite,
113 size_t variableIdRead, size_t variableIdWrite);
114
115 // FIXME: https://redmine.msktools.desy.de/issues/12242
116 // NOLINTNEXTLINE(google-default-arguments)
117 bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {}) override;
118
119 // FIXME: https://redmine.msktools.desy.de/issues/12242
120 // NOLINTNEXTLINE(google-default-arguments)
121 bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override;
122
123 void doReadTransferSynchronously() override { _target->readTransfer(); }
124
126 void releaseLock();
127
128 void doPreRead(TransferType type) override;
129
132 void obtainLockAndDecrementCounter(bool hasNewData);
133
136 void decrementCounter();
137
138 void doPostRead(TransferType type, bool hasNewData) override;
139
140 [[nodiscard]] boost::shared_ptr<NDRegisterAccessor<UserType>> decorateDeepInside(
141 [[maybe_unused]] std::function<boost::shared_ptr<NDRegisterAccessor<UserType>>(
142 const boost::shared_ptr<NDRegisterAccessor<UserType>>&)> factory) override {
143 // By returning nullptr, we forbid that DataConsistencyDecorator is put inside of this decorator.
144 // It would mess up our data updates counting scheme.
145 return {};
146 }
147
148 protected:
149 using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D;
150 using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target;
151
152 bool _handleRead, _handleWrite;
153 size_t _variableIdRead, _variableIdWrite;
154 TestableMode& _testableMode;
155
156 bool accountForWriteOperation(const std::function<bool(void)>& writeOperation);
157 };
158
159 private:
160 friend class ChimeraTK::DeviceManager;
161 friend class ChimeraTK::TriggerFanOut;
162 friend class ChimeraTK::TestFacility;
163
168 std::atomic<size_t> _counter{0};
169
174 bool _enabled{false};
175
181 std::atomic<size_t> _deviceInitialisationCounter{0};
182
183 struct VariableDescriptor {
185 std::string name;
186
188 boost::shared_ptr<TransferElement> processVariable;
189
195 std::atomic<size_t> counter{0};
196 };
197
208 static std::shared_timed_mutex _mutex;
209
220 static std::shared_mutex _mutex2;
221
226 std::map<size_t, VariableDescriptor> _variables;
227
236 class LastMutexOwner {
237 public:
238 LastMutexOwner& operator=(const boost::thread::id& id);
239 // NOLINTNEXTLINE(google-explicit-constructor)
240 operator boost::thread::id();
241
242 private:
243 boost::thread::id _lastMutexOwner;
244 std::mutex _mxLastMutexOwner;
245 } _lastMutexOwner;
246
247 // forward declaration
248 class Lock;
249
256 static Lock& getLockObject();
257
265 class Lock {
266 public:
267 [[nodiscard]] bool tryLockFor(std::chrono::seconds timeout, bool shared);
268 void unlock();
269 [[nodiscard]] bool ownsLock() const { return _ownsLock; }
270 ~Lock();
271
272 private:
273 Lock() = default;
274
275 bool _ownsLock{false};
276 bool _isShared{false}; // only meaningful when _ownsLock = true
277
278 friend Lock& TestableMode::getLockObject();
279 };
280
282 std::map<boost::thread::id, std::string> _threadNames;
283
285 std::map<boost::thread::id, pid_t> _threadPThreadId;
286
288 std::mutex _threadNamesMutex;
289
294 std::string threadName(const boost::thread::id& threadId = boost::this_thread::get_id());
295
304 pid_t pthreadId(const boost::thread::id& threadId = boost::this_thread::get_id());
305
306 friend class ChimeraTK::ConnectionMaker;
307 };
308
309 /********************************************************************************************************************/
310 /********************************************************************************************************************/
311 /***** Inline implementations for TestableMode **/
312 /********************************************************************************************************************/
313 /********************************************************************************************************************/
314
315 template<typename T>
316 TestableMode::AccessorPair<T> TestableMode::decorate(
317 AccessorPair<T> other, const VariableNetworkNode& producer, const VariableNetworkNode& consumer) {
318 if(not _enabled) {
319 return other;
320 }
321
322 logger(Logger::Severity::info, "TestableMode")
323 << " Decorating pair " << producer.getQualifiedName() << "[" << other.first->getId() << "] -> "
324 << consumer.getQualifiedName() << "[" << other.second->getId() << "]";
325
326 // create variable IDs
327 size_t varId = detail::TestableMode::getNextVariableId();
328 size_t varIdReturn;
329 AccessorPair<T> result;
330
331 if(producer.getDirection().withReturn) {
332 varIdReturn = detail::TestableMode::getNextVariableId();
333 }
334
335 // decorate the process variable if testable mode is enabled and mode is push-type
336 if(!producer.getDirection().withReturn) {
337 result.first = boost::make_shared<AccessorDecorator<T>>(*this, other.first, false, true, varId, varId);
338 result.second = boost::make_shared<AccessorDecorator<T>>(*this, other.second, true, false, varId, varId);
339 }
340 else {
341 result.first = boost::make_shared<AccessorDecorator<T>>(*this, other.first, true, true, varIdReturn, varId);
342 result.second = boost::make_shared<AccessorDecorator<T>>(*this, other.second, true, true, varId, varIdReturn);
343 }
344
345 // put the decorators into the list
346 auto& variable = _variables.at(varId);
347 variable.name = "Internal:" + producer.getQualifiedName();
348 if(consumer.getType() != NodeType::invalid) {
349 variable.name += "->" + consumer.getQualifiedName();
350 }
351 if(producer.getDirection().withReturn) {
352 auto& returnVariable = _variables.at(varIdReturn);
353 returnVariable.name = variable.name + " (return)";
354 }
355
356 return result;
357 }
358
359 /********************************************************************************************************************/
360
361 template<typename T>
362 boost::shared_ptr<NDRegisterAccessor<T>> TestableMode::decorate(
363 boost::shared_ptr<NDRegisterAccessor<T>> other, DecoratorType direction, const std::string& name, size_t varId) {
364 if(not _enabled) {
365 return other;
366 }
367
368 logger(Logger::Severity::info, "TestableMode")
369 << " Decorating single " << (direction == DecoratorType::READ ? "consumer " : "feeder ") << name << "["
370 << other->getId() << "]";
371
372 if(varId == 0) {
373 varId = detail::TestableMode::getNextVariableId();
374 }
375
376 _variables[varId].processVariable = other;
377 if(not name.empty()) {
378 _variables.at(varId).name = name;
379 }
380
381 auto pvarDec = boost::make_shared<AccessorDecorator<T>>(
382 *this, other, direction == DecoratorType::READ, direction == DecoratorType::WRITE, varId, varId);
383
384 return pvarDec;
385 }
386
387 /********************************************************************************************************************/
388 /********************************************************************************************************************/
389 /***** Implementations for TestableMode::AccessorDecorator **/
390 /********************************************************************************************************************/
391 /********************************************************************************************************************/
392
393 template<typename UserType>
394 TestableMode::AccessorDecorator<UserType>::AccessorDecorator(detail::TestableMode& testableMode,
395 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, bool handleRead, bool handleWrite,
396 size_t variableIdRead, size_t variableIdWrite)
397 : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), _handleRead(handleRead), _handleWrite(handleWrite),
398 _variableIdRead(variableIdRead), _variableIdWrite(variableIdWrite), _testableMode(testableMode) {
399 assert(_variableIdRead != 0);
400 assert(_variableIdWrite != 0);
401
402 // if receiving end, register for testable mode (stall detection)
403 if(this->isReadable() && handleRead) {
404 _testableMode._variables[_variableIdRead].processVariable = accessor;
405 assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data));
406 }
407
408 // if this decorating a bidirectional process variable, set the
409 // valueRejectCallback
410 auto bidir = boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>(accessor);
411 if(bidir) {
412 bidir->setValueRejectCallback([this] { decrementCounter(); });
413 }
414 else {
415 assert(!(handleRead && handleWrite));
416 }
417 }
418
419 /********************************************************************************************************************/
420
421 template<typename UserType>
422 // FIXME: https://redmine.msktools.desy.de/issues/12242
423 // NOLINTNEXTLINE(google-default-arguments)
424 bool TestableMode::AccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) {
425 if(!_handleWrite) {
426 return _target->writeTransfer(versionNumber);
427 }
428 return accountForWriteOperation([this, versionNumber]() { return _target->writeTransfer(versionNumber); });
429 }
430
431 /********************************************************************************************************************/
432
433 template<typename UserType>
434 // FIXME: https://redmine.msktools.desy.de/issues/12242
435 // NOLINTNEXTLINE(google-default-arguments)
436 bool TestableMode::AccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) {
437 if(!_handleWrite) {
438 return _target->writeTransferDestructively(versionNumber);
439 }
440
441 return accountForWriteOperation(
442 [this, versionNumber]() { return _target->writeTransferDestructively(versionNumber); });
443 }
444
445 /********************************************************************************************************************/
446
447 template<typename UserType>
448 void TestableMode::AccessorDecorator<UserType>::releaseLock() {
449 if(_testableMode.testLock()) {
450 _testableMode.unlock("doReadTransfer " + this->getName());
451 }
452 }
453
454 /********************************************************************************************************************/
455
456 template<typename UserType>
457 void TestableMode::AccessorDecorator<UserType>::doPreRead(TransferType type) {
458 _target->preRead(type);
459
460 // Blocking reads have to release the lock so the data transport can happen
461 if(_handleRead && type == TransferType::read &&
462 TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) {
463 releaseLock();
464 }
465 }
466
467 /********************************************************************************************************************/
468
469 template<typename UserType>
470 void TestableMode::AccessorDecorator<UserType>::obtainLockAndDecrementCounter(bool hasNewData) {
471 if(!_testableMode.testLock()) {
472 _testableMode.lock("doReadTransfer " + this->getName(), true);
473 }
474 if(!hasNewData) {
475 return;
476 }
477 auto& variable = _testableMode._variables.at(_variableIdRead);
478 if(variable.counter > 0) {
479 assert(_testableMode._counter > 0);
480 --_testableMode._counter;
481 --variable.counter;
482 logger(Logger::Severity::trace, "TestableMode")
483 << "TestableModeAccessorDecorator[name='" << this->getName() << "', id=" << _variableIdRead
484 << "]: testableMode.counter decreased, now at value " << _testableMode._counter << " / " << variable.counter;
485 }
486 else {
487 logger(Logger::Severity::trace, "TestableMode")
488 << "TestableModeAccessorDecorator[name='" << this->getName() << "', id=" << _variableIdRead
489 << "]: testableMode.counter NOT decreased, was already at value " << _testableMode._counter << " / "
490 << variable.counter << "\n"
491 << variable.name;
492 }
493 }
494
495 /********************************************************************************************************************/
496
497 template<typename UserType>
498 void TestableMode::AccessorDecorator<UserType>::decrementCounter() {
499 obtainLockAndDecrementCounter(true);
500 releaseLock();
501 }
502
503 /********************************************************************************************************************/
504
505 template<typename UserType>
506 void TestableMode::AccessorDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) {
507 if(_handleRead) {
508 obtainLockAndDecrementCounter(hasNewData);
509 }
510 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData);
511 }
512
513 template<typename UserType>
514 bool TestableMode::AccessorDecorator<UserType>::accountForWriteOperation(
515 const std::function<bool(void)>& writeOperation) {
516 bool dataLost = false;
517 if(!_testableMode.testLock()) {
518 // may happen if first write in thread is done before first blocking read
519 _testableMode.lock("write " + this->getName(), true);
520 }
521
522 // Increment counters before write(), since another thread might react to the value on the queue already and
523 // try to do something with the counter( e.g. decrement it conditionally, see obtainLockAndDecrementCounter()).
524 _testableMode._variables.at(_variableIdWrite).counter++;
525 _testableMode._counter++;
526
527 dataLost = writeOperation();
528
529 if(dataLost) {
530 // if data has been lost, decrement counter again since we never actually put data onto the queue.
531 _testableMode._variables.at(_variableIdWrite).counter--;
532 _testableMode._counter--;
533 }
534
535 if(!dataLost) {
536 logger(Logger::Severity::trace, "TestableMode")
537 << "TestableModeAccessorDecorator::write[name='" << this->getName() << "', id=" << _variableIdWrite
538 << "]: testableMode.counter increased, now at value " << _testableMode._counter;
539 }
540 else {
541 logger(Logger::Severity::trace, "TestableMode")
542 << "TestableModeAccessorDecorator::write[name='" << this->getName() << "', id=" << _variableIdWrite
543 << "]: testableMode.counter not increased due to lost data";
544 }
545 return dataLost;
546 }
547
548 /********************************************************************************************************************/
549
550} // namespace ChimeraTK::detail
Implements access to a ChimeraTK::Device.
Helper class to facilitate tests of applications based on ApplicationCore.
InternalModule which waits for a trigger, then reads a number of variables and distributes each of th...
void setThreadName(const std::string &name)
Set name of the current thread.
Definition Utilities.cc:105
InvalidityTracer application module.
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:156