ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
TransferElement.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 "AccessMode.h"
6#include "DeviceBackend.h"
7#include "Exception.h"
8#include "TransferElementID.h"
9#include "VersionNumber.h"
10
11#include <ChimeraTK/cppext/future_queue.hpp>
12
13#include <boost/bind/bind.hpp>
14#include <boost/enable_shared_from_this.hpp>
15#include <boost/numeric/conversion/cast.hpp>
16#include <boost/shared_ptr.hpp>
17#include <boost/thread.hpp>
18#include <boost/thread/future.hpp>
19
20#include <functional>
21#include <iostream>
22#include <list>
23#include <string>
24#include <typeinfo>
25#include <utility>
26#include <vector>
27
28namespace ChimeraTK {
29 class PersistentDataStorage;
30 class TransferGroup;
31 class ReadAnyGroup;
32
42 enum class DataValidity {
43 ok,
44 faulty
45 };
46
47 std::ostream& operator<<(std::ostream& os, const DataValidity& validity);
48
53
54 namespace detail {
61 class DiscardValueException {};
62
63 } /* namespace detail */
64
65 /********************************************************************************************************************/
66
68 class TransferElement : public boost::enable_shared_from_this<TransferElement> {
69 public:
71 TransferElement(std::string name, AccessModeFlags accessModeFlags, std::string unit = std::string(unitNotSet),
72 std::string description = std::string())
73 : _name(std::move(name)), _unit(std::move(unit)), _description(std::move(description)),
74 _accessModeFlags(std::move(accessModeFlags)) {}
75
77 TransferElement(const TransferElement& other) = delete;
79 TransferElement& operator=(const TransferElement& other) = delete;
81
83 virtual ~TransferElement() = default;
84
86 using SharedPtr = boost::shared_ptr<TransferElement>;
87
89 const std::string& getName() const { return _name; }
90
93 const std::string& getUnit() const { return _unit; }
94
96 const std::string& getDescription() const { return _description; }
97
101 virtual const std::type_info& getValueType() const = 0;
102
105
109
113
117 void read() {
119 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
120 "' which is part of a TransferGroup is not allowed.");
121 }
123 throw ChimeraTK::logic_error("Directly calling read() on the TransferElement '" + _name +
124 "' which is part of a ReadAnyGroup is not allowed.");
125 }
126 this->readTransactionInProgress = false;
127
128 preReadAndHandleExceptions(TransferType::read);
129 if(!_activeException) {
130 handleTransferException([&] { readTransfer(); });
131 }
132
133 postReadAndHandleExceptions(TransferType::read, !_activeException);
134 }
135
149 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
150 "' which is part of a TransferGroup is not allowed.");
151 }
153 throw ChimeraTK::logic_error("Directly calling readNonBlocking() on the TransferElement '" + _name +
154 "' which is part of a ReadAnyGroup is not allowed.");
155 }
156 this->readTransactionInProgress = false;
157 preReadAndHandleExceptions(TransferType::readNonBlocking);
158 bool updateDataBuffer = false;
159 if(!_activeException) {
160 handleTransferException([&] { updateDataBuffer = readTransferNonBlocking(); });
161 }
162
163 bool retVal = updateDataBuffer;
164 if(_activeException) {
165 auto previousVersionNumber = _versionNumber;
166 auto previousDataValidity = _dataValidity;
167 // always call postRead with updateDataBuffer = false in case of an exception
168 postReadAndHandleExceptions(TransferType::readNonBlocking, false);
169 // Usually we do not reach this point because postRead() is re-throwing the _activeException.
170 // If we reach this point the exception has been suppressed. We have to calculate a
171 // new return value because the dataBuffer has not changed, but the meta data
172 // could have, in which case we have to return true.
173 retVal = (previousVersionNumber != _versionNumber) || (previousDataValidity != _dataValidity);
174 }
175 else {
176 // call postRead with updateDataBuffer as returned by readTransferNonBlocking
177 postReadAndHandleExceptions(TransferType::readNonBlocking, updateDataBuffer);
178 }
179 return retVal;
180 }
181
186 bool readLatest() {
188 bool updateDataBuffer = false;
189 // Call readNonBlocking until there is no new data to be read any more
190 while(readNonBlocking()) {
191 // remember whether we have new data
192 updateDataBuffer = true;
193 }
194 return updateDataBuffer;
195 }
196 // Without wait_for_new_data readNonBlocking always returns true, and the while loop above would never end.
197 // Hence we just call the (synchronous) read and return true;
198 read();
199 return true;
200 }
201
205 bool write(ChimeraTK::VersionNumber versionNumber = {}) {
207 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
208 "' which is part of a TransferGroup is not allowed.");
209 }
210 this->writeTransactionInProgress = false;
211 bool previousDataLost = true; // the value here does not matter. If there was an exception, it will be re-thrown
212 // in postWrite, so it is never returned
213
214 preWriteAndHandleExceptions(TransferType::write, versionNumber);
215 if(!_activeException) {
216 handleTransferException([&] { previousDataLost = writeTransfer(versionNumber); });
217 }
218
219 postWriteAndHandleExceptions(TransferType::write, versionNumber);
220 return previousDataLost;
221 }
222
229 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
230 "' which is part of a TransferGroup is not allowed.");
231 }
232 this->writeTransactionInProgress = false;
233
234 preWriteAndHandleExceptions(TransferType::writeDestructively, versionNumber);
235 bool previousDataLost = true; // the value here does not matter. If there was an exception, it will be re-thrown
236 // in postWrite, so it is never returned
237 if(!_activeException) {
238 handleTransferException([&] { previousDataLost = writeTransferDestructively(versionNumber); });
239 }
240
241 postWriteAndHandleExceptions(TransferType::writeDestructively, versionNumber);
242 return previousDataLost;
243 }
244
261
264 virtual bool isReadOnly() const = 0;
265
268 virtual bool isReadable() const = 0;
269
272 virtual bool isWriteable() const = 0;
273
279 void setActiveException(std::exception_ptr& setThisException) {
280 if(setThisException) {
281 _activeException = setThisException;
282 setThisException = nullptr;
283 }
284 }
285
296 virtual void setExceptionBackend(boost::shared_ptr<DeviceBackend> exceptionBackend) {
297 _exceptionBackend = std::move(exceptionBackend);
298 }
299
302 boost::shared_ptr<DeviceBackend> getExceptionBackend() { return _exceptionBackend; }
303
307 cppext::future_queue<void> getReadQueue() { return _readQueue; }
308
309 protected:
314 boost::shared_ptr<DeviceBackend> _exceptionBackend;
315
316 private:
320 template<typename Callable>
321 void handleTransferException(Callable function) {
322 try {
323 function();
324 }
326 _activeException = std::current_exception();
327 }
328 catch(boost::thread_interrupted&) {
329 _activeException = std::current_exception();
330 }
331 }
332
333 // helper function that just gets rid of the DiscardValueException and otherwise does a pop_wait on the _readQueue.
334 // It does not deal with other exceptions. This is done in handleTransferException.
335 void readTransferAsyncWaitingImpl() {
336 retry:
337 try {
338 _readQueue.pop_wait();
339 }
340 catch(detail::DiscardValueException&) {
341 goto retry;
342 }
343 }
344
345 public:
357 readTransferAsyncWaitingImpl();
358 }
359 else {
361 }
362 }
363
364 protected:
377 virtual void doReadTransferSynchronously() = 0;
378
379 private:
380 // helper function that just gets rid of the DiscardValueException and otherwise does a pop on the _readQueue.
381 // It does not deal with other exceptions. This is done in handleTransferException.
382 bool readTransferAsyncNonWaitingImpl() {
383 retry:
384 try {
385 return _readQueue.pop();
386 }
387 catch(detail::DiscardValueException&) {
388 goto retry;
389 }
390 }
391
392 public:
405 return readTransferAsyncNonWaitingImpl();
406 }
408 return true;
409 }
410
411 private:
413 void preReadAndHandleExceptions(TransferType type) noexcept {
414 try {
415 preRead(type);
416 }
417 catch(ChimeraTK::logic_error&) {
418 _activeException = std::current_exception();
419 }
421 _activeException = std::current_exception();
422 }
423 catch(boost::thread_interrupted&) {
424 _activeException = std::current_exception();
425 }
426 }
427
428 public:
434 if(readTransactionInProgress) return;
435 _activeException = {nullptr};
436
437 readTransactionInProgress = true; // remember that doPreRead has been called. It might throw, so we remember
438 // before we call it
439 doPreRead(type);
440 }
441
448 protected:
449 virtual void doPreRead(TransferType) {}
450
451 private:
454 void postReadAndHandleExceptions(TransferType type, bool updateDataBuffer) {
455 try {
456 postRead(type, updateDataBuffer);
457 }
458 catch(ChimeraTK::runtime_error& ex) {
460 _exceptionBackend->setException(ex.what());
461 }
462 throw;
463 }
464 }
465
466 public:
474 void postRead(TransferType type, bool updateDataBuffer) {
475 // only delegate to doPostRead() the first time postRead() is called in a row.
476 if(readTransactionInProgress) {
477 readTransactionInProgress = false;
478 doPostRead(type, updateDataBuffer);
479 }
480
481 // Throw on each call of postRead(). All high-level elements for a shared low-level transfer element must see the
482 // exception. Note: doPostRead can throw an exception, but in that case _activeException must be false (we can
483 // only have one exception at a time). In case other code is added here later which needs to be executed after
484 // doPostRead() always, a try-catch block may be necessary.
485 if(_activeException) {
486 // don't clear the active connection. This is done in preRead().
487 std::rethrow_exception(_activeException);
488 }
489 }
490
503 protected:
504 virtual void doPostRead(TransferType, bool /*updateDataBuffer*/) {}
505
506 private:
508 void preWriteAndHandleExceptions(TransferType type, ChimeraTK::VersionNumber versionNumber) noexcept {
509 try {
510 preWrite(type, versionNumber);
511 }
512 catch(ChimeraTK::logic_error&) {
513 _activeException = std::current_exception();
514 }
516 _activeException = std::current_exception();
517 }
518 catch(boost::thread_interrupted&) {
519 _activeException = std::current_exception();
520 }
521 }
522
523 public:
532 if(writeTransactionInProgress) return;
533
534 _activeException = {};
535 if(versionNumber < getVersionNumber()) {
536 throw ChimeraTK::logic_error("The version number " + std::string(versionNumber) +
537 " passed to write() of TransferElement '" + _name + "' is less than the last version number used " +
538 std::string(getVersionNumber()) + ".");
539 }
540 writeTransactionInProgress = true; // must not be set, if the logic_error is thrown above due to the old version
541 doPreWrite(type, versionNumber);
542 }
543
550 protected:
552
553 private:
556 void postWriteAndHandleExceptions(TransferType type, VersionNumber versionNumber) {
557 try {
558 postWrite(type, versionNumber);
559 }
560 catch(ChimeraTK::runtime_error& ex) {
562 _exceptionBackend->setException(ex.what());
563 }
564 throw;
565 }
566 }
567
568 public:
575 void postWrite(TransferType type, VersionNumber versionNumber) {
576 if(writeTransactionInProgress) {
577 writeTransactionInProgress = false;
578 doPostWrite(type, versionNumber);
579 }
580
581 // Note: doPostWrite can throw an exception, but in that case hasSeenException must be false (we can only have one
582 // exception at a time). In case other code is added here later which needs to be executed after doPostWrite()
583 // always, a try-catch block may be necessary.
584 // Another note: If writeTransactionInProgress == false, there can still be an exception, if the version number
585 // used in a write was too old (see preWrite).
586 if(_activeException) {
587 std::rethrow_exception(_activeException);
588 }
589
590 // only after a successful write the version number is updated
591 _versionNumber = versionNumber;
592 }
593
600 protected:
602
603 public:
612 bool writeTransfer(ChimeraTK::VersionNumber versionNumber) { return doWriteTransfer(versionNumber); }
613
614 protected:
622 virtual bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) = 0;
623
624 public:
637 return doWriteTransferDestructively(versionNumber);
638 }
639
640 protected:
652 return doWriteTransfer(versionNumber);
653 }
654
655 public:
683 virtual bool mayReplaceOther(const boost::shared_ptr<TransferElement const>& other) const {
684 (void)other; // prevent warning
685 return false;
686 }
687
696 virtual std::vector<boost::shared_ptr<TransferElement>> getHardwareAccessingElements() = 0;
697
717 virtual std::list<boost::shared_ptr<TransferElement>> getInternalElements() = 0;
718
728 virtual boost::shared_ptr<TransferElement> getHighLevelImplElement() { return shared_from_this(); }
729
736 // FIXME #11279 Implement API breaking changes from linter warnings
737 // NOLINTNEXTLINE(performance-unnecessary-value-param)
738 virtual void replaceTransferElement([[maybe_unused]] boost::shared_ptr<TransferElement> newElement) {}
739
744 virtual boost::shared_ptr<TransferElement> makeCopyRegisterDecorator() = 0;
745
748 static constexpr char unitNotSet[] = "n./a.";
749
760 // FIXME #11279 Implement API breaking changes from linter warnings
761 // NOLINTNEXTLINE(performance-unnecessary-value-param)
762 virtual void setPersistentDataStorage(boost::shared_ptr<ChimeraTK::PersistentDataStorage>) {}
763
768 TransferElementID getId() const { return _id; }
769
789 virtual void interrupt() {}
790
797 template<typename QUEUE_TYPE>
798 void interrupt_impl(QUEUE_TYPE& dataTransportQueue) {
799 dataTransportQueue.push_overwrite_exception(std::make_exception_ptr(boost::thread_interrupted()));
800 }
801
803 bool isReadTransactionInProgress() const { return readTransactionInProgress; }
804
806 bool isWriteTransactionInProgress() const { return writeTransactionInProgress; }
807
809 [[nodiscard]] ReadAnyGroup* getReadAnyGroup() const { return _inReadAnyGroup; }
810
812 virtual void setInReadAnyGroup(ReadAnyGroup* rag) { _inReadAnyGroup = rag; }
813
814 protected:
816 std::string _name;
817
819 std::string _unit;
820
822 std::string _description;
823
826
829
833
840
843
844 friend class TransferGroup;
845 friend class ReadAnyGroup;
846
847 private:
853 bool readTransactionInProgress{false};
854
857 bool writeTransactionInProgress{false};
858
859 protected:
863 cppext::future_queue<void> _readQueue;
864
868
872
875 std::exception_ptr _activeException{nullptr};
876 }; // namespace ChimeraTK
877
878} /* namespace ChimeraTK */
Set of AccessMode flags with additional functionality for an easier handling.
Definition AccessMode.h:48
bool has(AccessMode flag) const
Check if a certain flag is in the set.
Definition AccessMode.cc:20
Group several registers (= TransferElement) to allow waiting for an update of any of the registers.
Base class for register accessors which can be part of a TransferGroup.
bool writeTransferDestructively(ChimeraTK::VersionNumber versionNumber)
Write the data to the device.
void setDataValidity(DataValidity validity=DataValidity::ok)
Set the current DataValidity for this TransferElement.
cppext::future_queue< void > getReadQueue()
Function to get a copy of the read queue.
bool readNonBlocking()
Read the next value, if available in the input buffer.
std::string _name
Identifier uniquely identifying the TransferElement.
TransferElementID _id
The ID of this TransferElement.
ReadAnyGroup * _inReadAnyGroup
ReadAnyGroup this TransferElement has been added to, nullptr if not in a ReadAnyGroup.
virtual void setExceptionBackend(boost::shared_ptr< DeviceBackend > exceptionBackend)
Set the backend to which the exception has to be reported.
boost::shared_ptr< TransferElement > SharedPtr
A typedef for more compact syntax.
AccessModeFlags _accessModeFlags
The access mode flags for this transfer element.
DataValidity _dataValidity
The validity of the data in the application buffer.
void preRead(TransferType type)
Perform any pre-read tasks if necessary.
virtual std::list< boost::shared_ptr< TransferElement > > getInternalElements()=0
Obtain the full list of TransferElements internally used by this TransferElement.
virtual ~TransferElement()=default
Abstract base classes need a virtual destructor.
TransferElementID getId() const
Obtain unique ID for this TransferElement, see TransferElementID for details.
virtual void doPreRead(TransferType)
Backend specific implementation of preRead().
virtual void doPostRead(TransferType, bool)
Backend specific implementation of postRead().
std::string _description
Description of this variable/register.
ChimeraTK::VersionNumber getVersionNumber() const
Returns the version number that is associated with the last transfer (i.e.
virtual const std::type_info & getValueType() const =0
Returns the std::type_info for the value type of this transfer element.
virtual void setPersistentDataStorage(boost::shared_ptr< ChimeraTK::PersistentDataStorage >)
Associate a persistent data storage object to be updated on each write operation of this ProcessArray...
DataValidity dataValidity() const
Return current validity of the data.
std::exception_ptr _activeException
Exception to be rethrown in postXXX() in case hasSeenException == true Can be set via setActiveExcept...
virtual void replaceTransferElement(boost::shared_ptr< TransferElement > newElement)
Search for all underlying TransferElements which are considered identical (see sameRegister()) with t...
void postWrite(TransferType type, VersionNumber versionNumber)
Perform any post-write clean-ups if necessary.
virtual void doPostWrite(TransferType, VersionNumber)
Backend specific implementation of postWrite().
void interrupt_impl(QUEUE_TYPE &dataTransportQueue)
Implementation of interrupt()
ReadAnyGroup * getReadAnyGroup() const
Obtain the ReadAnyGroup this TransferElement is part of, or nullptr if not in a ReadAnyGroup.
void makeUniqueId()
Allow generating a unique ID from derived classes.
const std::string & getUnit() const
Returns the engineering unit.
bool isReadTransactionInProgress() const
Check whether a read transaction is in progress, i.e.
AccessModeFlags getAccessModeFlags() const
Return the AccessModeFlags for this TransferElement.
std::string _unit
Engineering unit.
TransferElement(const TransferElement &other)=delete
Copying and moving is not allowed.
virtual bool isReadable() const =0
Check if transfer element is readable.
bool writeDestructively(ChimeraTK::VersionNumber versionNumber={})
Just like write(), but allows the implementation to destroy the content of the user buffer in the pro...
bool readLatest()
Read the latest value, discarding any other update since the last read if present.
virtual void doReadTransferSynchronously()=0
Implementation version of readTransfer() for synchronous reads.
virtual bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber)
Implementation version of writeTransferDestructively().
VersionNumber _versionNumber
The version number of the last successful transfer.
void read()
Read the data from the device.
virtual void doPreWrite(TransferType, VersionNumber)
Backend specific implementation of preWrite().
void preWrite(TransferType type, ChimeraTK::VersionNumber versionNumber)
Transfer the data from the user buffer into the device send buffer, while converting the data from th...
bool _isInTransferGroup
Flag whether this TransferElement has been added to a TransferGroup or not.
virtual bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber)=0
Implementation version of writeTransfer().
TransferElement & operator=(TransferElement &&other)=delete
bool writeTransfer(ChimeraTK::VersionNumber versionNumber)
Write the data to the device.
static constexpr char unitNotSet[]
Constant string to be used as a unit when the unit is not provided or known.
cppext::future_queue< void > _readQueue
The queue for asynchronous read transfers.
virtual void setInReadAnyGroup(ReadAnyGroup *rag)
Set the ReadAnyGroup of which this TransferElement is part of.
bool readTransferNonBlocking()
Read the data from the device but do not fill it into the user buffer of this TransferElement.
virtual std::vector< boost::shared_ptr< TransferElement > > getHardwareAccessingElements()=0
Obtain the underlying TransferElements with actual hardware access.
const std::string & getDescription() const
Returns the description of this variable/register.
virtual bool isWriteable() const =0
Check if transfer element is writeable.
TransferElement(TransferElement &&other)=delete
virtual bool mayReplaceOther(const boost::shared_ptr< TransferElement const > &other) const
Check whether the TransferElement can be used in places where the TransferElement "other" is currentl...
void readTransfer()
Read the data from the device but do not fill it into the user buffer of this TransferElement.
void setActiveException(std::exception_ptr &setThisException)
Set an active exception.
bool isWriteTransactionInProgress() const
Check whether a write transaction is in progress, i.e.
boost::shared_ptr< DeviceBackend > getExceptionBackend()
Return the exception backend.
const std::string & getName() const
Returns the name that identifies the process variable.
TransferElement & operator=(const TransferElement &other)=delete
bool write(ChimeraTK::VersionNumber versionNumber={})
Write the data to device.
void postRead(TransferType type, bool updateDataBuffer)
Transfer the data from the device receive buffer into the user buffer, while converting the data into...
TransferElement(std::string name, AccessModeFlags accessModeFlags, std::string unit=std::string(unitNotSet), std::string description=std::string())
Creates a transfer element with the specified name.
boost::shared_ptr< DeviceBackend > _exceptionBackend
The backend to which the runtime_errors are reported via DeviceBackend::setException().
virtual boost::shared_ptr< TransferElement > makeCopyRegisterDecorator()=0
Create a CopyRegisterDecorator of the right type decorating this TransferElement.
virtual void interrupt()
Return from a blocking read immediately and throw boost::thread_interrupted.
virtual bool isReadOnly() const =0
Check if transfer element is read only, i.e.
virtual boost::shared_ptr< TransferElement > getHighLevelImplElement()
Obtain the highest level implementation TransferElement.
Simple class holding a unique ID for a TransferElement.
void makeUnique()
Assign an ID to this instance.
Group multiple data accessors to efficiently trigger data transfers on the whole group.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:14
std::ostream & operator<<(std::ostream &stream, const DataDescriptor::FundamentalType &fundamentalType)
DataValidity
The current state of the data.
@ faulty
The data is considered valid.
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
TransferType
Used to indicate the applicable operation on a Transferelement.
STL namespace.