ChimeraTK-ApplicationCore 04.06.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 void setEnableDebug(bool enable = true) { _enableDebug = enable; }
83
87 void setEnableDebugDecorating(bool enable = true) { _debugDecorating = enable; }
88
92 [[nodiscard]] bool isEnabled() const { return _enabled; }
93
97 static size_t getNextVariableId() {
98 static size_t nextId{0};
99 return ++nextId;
100 }
101
102 enum class DecoratorType { READ, WRITE };
103
104 template<typename T>
105 boost::shared_ptr<NDRegisterAccessor<T>> decorate(boost::shared_ptr<NDRegisterAccessor<T>> other,
106 DecoratorType direction, const std::string& name = {}, size_t varId = 0);
107
108 template<typename T>
109 using AccessorPair = std::pair<boost::shared_ptr<NDRegisterAccessor<T>>, boost::shared_ptr<NDRegisterAccessor<T>>>;
110
111 template<typename T>
112 AccessorPair<T> decorate(
113 AccessorPair<T> other, const VariableNetworkNode& producer, const VariableNetworkNode& consumer);
114
118 template<typename UserType>
119 class AccessorDecorator : public ChimeraTK::NDRegisterAccessorDecorator<UserType> {
120 public:
121 AccessorDecorator(detail::TestableMode& testableMode,
122 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, bool handleRead, bool handleWrite,
123 size_t variableIdRead, size_t variableIdWrite);
124
125 // FIXME: https://redmine.msktools.desy.de/issues/12242
126 // NOLINTNEXTLINE(google-default-arguments)
127 bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {}) override;
128
129 // FIXME: https://redmine.msktools.desy.de/issues/12242
130 // NOLINTNEXTLINE(google-default-arguments)
131 bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override;
132
133 void doReadTransferSynchronously() override { _target->readTransfer(); }
134
136 void releaseLock();
137
138 void doPreRead(TransferType type) override;
139
142 void obtainLockAndDecrementCounter(bool hasNewData);
143
146 void decrementCounter();
147
148 void doPostRead(TransferType type, bool hasNewData) override;
149
150 [[nodiscard]] boost::shared_ptr<NDRegisterAccessor<UserType>> decorateDeepInside(
151 [[maybe_unused]] std::function<boost::shared_ptr<NDRegisterAccessor<UserType>>(
152 const boost::shared_ptr<NDRegisterAccessor<UserType>>&)> factory) override {
153 // By returning nullptr, we forbid that DataConsistencyDecorator is put inside of this decorator.
154 // It would mess up our data updates counting scheme.
155 return {};
156 }
157
158 protected:
159 using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D;
160 using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target;
161
162 bool _handleRead, _handleWrite;
163 size_t _variableIdRead, _variableIdWrite;
164 TestableMode& _testableMode;
165
166 bool accountForWriteOperation(const std::function<bool(void)>& writeOperation);
167 };
168
169 private:
170 friend class ChimeraTK::DeviceManager;
171 friend class ChimeraTK::TriggerFanOut;
172 friend class ChimeraTK::TestFacility;
173
177 bool _enableDebug{false};
178
183 std::atomic<size_t> _counter{0};
184
189 bool _enabled{false};
190
196 std::atomic<size_t> _deviceInitialisationCounter{0};
197
198 struct VariableDescriptor {
200 std::string name;
201
203 boost::shared_ptr<TransferElement> processVariable;
204
210 std::atomic<size_t> counter{0};
211 };
212
223 static std::shared_timed_mutex _mutex;
224
235 static std::shared_mutex _mutex2;
236
241 std::map<size_t, VariableDescriptor> _variables;
242
251 class LastMutexOwner {
252 public:
253 LastMutexOwner& operator=(const boost::thread::id& id);
254 // NOLINTNEXTLINE(google-explicit-constructor)
255 operator boost::thread::id();
256
257 private:
258 boost::thread::id _lastMutexOwner;
259 std::mutex _mxLastMutexOwner;
260 } _lastMutexOwner;
261
262 // forward declaration
263 class Lock;
264
271 static Lock& getLockObject();
272
280 class Lock {
281 public:
282 [[nodiscard]] bool tryLockFor(std::chrono::seconds timeout, bool shared);
283 void unlock();
284 [[nodiscard]] bool ownsLock() const { return _ownsLock; }
285 ~Lock();
286
287 private:
288 Lock() = default;
289
290 bool _ownsLock{false};
291 bool _isShared{false}; // only meaningful when _ownsLock = true
292
293 friend Lock& TestableMode::getLockObject();
294 };
295
297 std::map<boost::thread::id, std::string> _threadNames;
298
300 std::map<boost::thread::id, pid_t> _threadPThreadId;
301
303 std::mutex _threadNamesMutex;
304
309 std::string threadName(const boost::thread::id& threadId = boost::this_thread::get_id());
310
319 pid_t pthreadId(const boost::thread::id& threadId = boost::this_thread::get_id());
320
321 bool _debugDecorating{false};
322 friend class ChimeraTK::ConnectionMaker;
323 };
324
325 /********************************************************************************************************************/
326 /********************************************************************************************************************/
327 /***** Inline implementations for TestableMode **/
328 /********************************************************************************************************************/
329 /********************************************************************************************************************/
330
331 template<typename T>
332 TestableMode::AccessorPair<T> TestableMode::decorate(
333 AccessorPair<T> other, const VariableNetworkNode& producer, const VariableNetworkNode& consumer) {
334 if(not _enabled) {
335 return other;
336 }
337
338 if(_debugDecorating) {
339 logger(Logger::Severity::debug, "TestableMode")
340 << " Decorating pair " << producer.getQualifiedName() << "[" << other.first->getId() << "] -> "
341 << consumer.getQualifiedName() << "[" << other.second->getId() << "]";
342 }
343
344 // create variable IDs
345 size_t varId = detail::TestableMode::getNextVariableId();
346 size_t varIdReturn;
347 AccessorPair<T> result;
348
349 if(producer.getDirection().withReturn) {
350 varIdReturn = detail::TestableMode::getNextVariableId();
351 }
352
353 // decorate the process variable if testable mode is enabled and mode is push-type
354 if(!producer.getDirection().withReturn) {
355 result.first = boost::make_shared<AccessorDecorator<T>>(*this, other.first, false, true, varId, varId);
356 result.second = boost::make_shared<AccessorDecorator<T>>(*this, other.second, true, false, varId, varId);
357 }
358 else {
359 result.first = boost::make_shared<AccessorDecorator<T>>(*this, other.first, true, true, varIdReturn, varId);
360 result.second = boost::make_shared<AccessorDecorator<T>>(*this, other.second, true, true, varId, varIdReturn);
361 }
362
363 // put the decorators into the list
364 auto& variable = _variables.at(varId);
365 variable.name = "Internal:" + producer.getQualifiedName();
366 if(consumer.getType() != NodeType::invalid) {
367 variable.name += "->" + consumer.getQualifiedName();
368 }
369 if(producer.getDirection().withReturn) {
370 auto& returnVariable = _variables.at(varIdReturn);
371 returnVariable.name = variable.name + " (return)";
372 }
373
374 return result;
375 }
376
377 /********************************************************************************************************************/
378
379 template<typename T>
380 boost::shared_ptr<NDRegisterAccessor<T>> TestableMode::decorate(
381 boost::shared_ptr<NDRegisterAccessor<T>> other, DecoratorType direction, const std::string& name, size_t varId) {
382 if(not _enabled) {
383 return other;
384 }
385
386 if(_debugDecorating) {
387 logger(Logger::Severity::debug, "TestableMode")
388 << " Decorating single " << (direction == DecoratorType::READ ? "consumer " : "feeder ") << name << "["
389 << other->getId() << "]";
390 }
391
392 if(varId == 0) {
393 varId = detail::TestableMode::getNextVariableId();
394 }
395
396 _variables[varId].processVariable = other;
397 if(not name.empty()) {
398 _variables.at(varId).name = name;
399 }
400
401 auto pvarDec = boost::make_shared<AccessorDecorator<T>>(
402 *this, other, direction == DecoratorType::READ, direction == DecoratorType::WRITE, varId, varId);
403
404 return pvarDec;
405 }
406
407 /********************************************************************************************************************/
408 /********************************************************************************************************************/
409 /***** Implementations for TestableMode::AccessorDecorator **/
410 /********************************************************************************************************************/
411 /********************************************************************************************************************/
412
413 template<typename UserType>
414 TestableMode::AccessorDecorator<UserType>::AccessorDecorator(detail::TestableMode& testableMode,
415 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, bool handleRead, bool handleWrite,
416 size_t variableIdRead, size_t variableIdWrite)
417 : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), _handleRead(handleRead), _handleWrite(handleWrite),
418 _variableIdRead(variableIdRead), _variableIdWrite(variableIdWrite), _testableMode(testableMode) {
419 assert(_variableIdRead != 0);
420 assert(_variableIdWrite != 0);
421
422 // if receiving end, register for testable mode (stall detection)
423 if(this->isReadable() && handleRead) {
424 _testableMode._variables[_variableIdRead].processVariable = accessor;
425 assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data));
426 }
427
428 // if this decorating a bidirectional process variable, set the
429 // valueRejectCallback
430 auto bidir = boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>(accessor);
431 if(bidir) {
432 bidir->setValueRejectCallback([this] { decrementCounter(); });
433 }
434 else {
435 assert(!(handleRead && handleWrite));
436 }
437 }
438
439 /********************************************************************************************************************/
440
441 template<typename UserType>
442 // FIXME: https://redmine.msktools.desy.de/issues/12242
443 // NOLINTNEXTLINE(google-default-arguments)
444 bool TestableMode::AccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) {
445 if(!_handleWrite) {
446 return _target->writeTransfer(versionNumber);
447 }
448 return accountForWriteOperation([this, versionNumber]() { return _target->writeTransfer(versionNumber); });
449 }
450
451 /********************************************************************************************************************/
452
453 template<typename UserType>
454 // FIXME: https://redmine.msktools.desy.de/issues/12242
455 // NOLINTNEXTLINE(google-default-arguments)
456 bool TestableMode::AccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) {
457 if(!_handleWrite) {
458 return _target->writeTransferDestructively(versionNumber);
459 }
460
461 return accountForWriteOperation(
462 [this, versionNumber]() { return _target->writeTransferDestructively(versionNumber); });
463 }
464
465 /********************************************************************************************************************/
466
467 template<typename UserType>
468 void TestableMode::AccessorDecorator<UserType>::releaseLock() {
469 if(_testableMode.testLock()) {
470 _testableMode.unlock("doReadTransfer " + this->getName());
471 }
472 }
473
474 /********************************************************************************************************************/
475
476 template<typename UserType>
477 void TestableMode::AccessorDecorator<UserType>::doPreRead(TransferType type) {
478 _target->preRead(type);
479
480 // Blocking reads have to release the lock so the data transport can happen
481 if(_handleRead && type == TransferType::read &&
482 TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) {
483 releaseLock();
484 }
485 }
486
487 /********************************************************************************************************************/
488
489 template<typename UserType>
490 void TestableMode::AccessorDecorator<UserType>::obtainLockAndDecrementCounter(bool hasNewData) {
491 if(!_testableMode.testLock()) {
492 _testableMode.lock("doReadTransfer " + this->getName(), true);
493 }
494 if(!hasNewData) {
495 return;
496 }
497 auto& variable = _testableMode._variables.at(_variableIdRead);
498 if(variable.counter > 0) {
499 assert(_testableMode._counter > 0);
500 --_testableMode._counter;
501 --variable.counter;
502 if(_testableMode._enableDebug) {
503 logger(Logger::Severity::debug, "TestableMode")
504 << "TestableModeAccessorDecorator[name='" << this->getName() << "', id=" << _variableIdRead
505 << "]: testableMode.counter decreased, now at value " << _testableMode._counter << " / "
506 << variable.counter;
507 }
508 }
509 else {
510 if(_testableMode._enableDebug) {
511 logger(Logger::Severity::debug, "TestableMode")
512 << "TestableModeAccessorDecorator[name='" << this->getName() << "', id=" << _variableIdRead
513 << "]: testableMode.counter NOT decreased, was already at value " << _testableMode._counter << " / "
514 << variable.counter << "\n"
515 << variable.name;
516 }
517 }
518 }
519
520 /********************************************************************************************************************/
521
522 template<typename UserType>
523 void TestableMode::AccessorDecorator<UserType>::decrementCounter() {
524 obtainLockAndDecrementCounter(true);
525 releaseLock();
526 }
527
528 /********************************************************************************************************************/
529
530 template<typename UserType>
531 void TestableMode::AccessorDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) {
532 if(_handleRead) {
533 obtainLockAndDecrementCounter(hasNewData);
534 }
535 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData);
536 }
537
538 template<typename UserType>
539 bool TestableMode::AccessorDecorator<UserType>::accountForWriteOperation(
540 const std::function<bool(void)>& writeOperation) {
541 bool dataLost = false;
542 if(!_testableMode.testLock()) {
543 // may happen if first write in thread is done before first blocking read
544 _testableMode.lock("write " + this->getName(), true);
545 }
546
547 // Increment counters before write(), since another thread might react to the value on the queue already and
548 // try to do something with the counter( e.g. decrement it conditionally, see obtainLockAndDecrementCounter()).
549 _testableMode._variables.at(_variableIdWrite).counter++;
550 _testableMode._counter++;
551
552 dataLost = writeOperation();
553
554 if(dataLost) {
555 // if data has been lost, decrement counter again since we never actually put data onto the queue.
556 _testableMode._variables.at(_variableIdWrite).counter--;
557 _testableMode._counter--;
558 }
559
560 if(_testableMode._enableDebug) {
561 if(!dataLost) {
562 logger(Logger::Severity::debug, "TestableMode")
563 << "TestableModeAccessorDecorator::write[name='" << this->getName() << "', id=" << _variableIdWrite
564 << "]: testableMode.counter increased, now at value " << _testableMode._counter;
565 }
566 else {
567 logger(Logger::Severity::debug, "TestableMode")
568 << "TestableModeAccessorDecorator::write[name='" << this->getName() << "', id=" << _variableIdWrite
569 << "]: testableMode.counter not increased due to lost data";
570 }
571 }
572 return dataLost;
573 }
574
575 /********************************************************************************************************************/
576
577} // 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:124