ChimeraTK-DeviceAccess  03.18.00
TransferGroup.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 "TransferGroup.h"
5 
7 #include "Exception.h"
8 #include "TransferElement.h"
10 
11 #include <iostream>
12 
13 namespace ChimeraTK {
14 
15  /********************************************************************************************************************/
16 
17  void TransferGroup::runPostReads(const std::set<boost::shared_ptr<TransferElement>>& elements,
18  const std::exception_ptr& firstDetectedRuntimeError) noexcept {
19  for(const auto& elem : elements) {
20  // check for exceptions on any of the element's low level elements
21  for(const auto& lowLevelElem : elem->getHardwareAccessingElements()) {
22  // In case there are multiple exceptions we take the last one, but this does not matter. They are all
23  // ChimeraTK::runtime_errors and the first detected runtime error, which is re-thrown, has already been
24  // determined by previously.
25  if(lowLevelElem->_activeException) {
26  // copy the runtime error from low level element into the high level element so it is processed in post-read
27  elem->_activeException = lowLevelElem->_activeException;
28  }
29  }
30 
31  try {
32  // call with updateDataBuffer = false if there has been any ChimeraTK::runtime_error in the transfer phase,
33  // true otherwise
34  elem->postReadAndHandleExceptions(TransferType::read, firstDetectedRuntimeError == nullptr);
35  }
36  catch(ChimeraTK::runtime_error&) {
37  ++_nRuntimeErrors;
38  }
39  }
40  }
41 
42  /********************************************************************************************************************/
43 
45  // reset exception flags
46  for(auto& it : _lowLevelElementsAndExceptionFlags) {
47  it.second = false;
48  }
49 
50  // check pre-conditions so preRead() does not throw logic errors
51  for(const auto& backend : _exceptionBackends) {
52  if(backend && !backend->isOpen()) {
53  throw ChimeraTK::logic_error("DeviceBackend " + backend->readDeviceInfo() + "is not opened!");
54  }
55  }
56  if(!isReadable()) {
57  throw ChimeraTK::logic_error("TransferGroup::read() called, but not all elements are readable.");
58  }
59 
60  std::exception_ptr firstDetectedRuntimeError{nullptr};
61 
62  for(const auto& elem : _highLevelElements) {
63  elem->preReadAndHandleExceptions(TransferType::read);
64  if((elem->_activeException != nullptr) && (firstDetectedRuntimeError == nullptr)) {
65  firstDetectedRuntimeError = elem->_activeException;
66  }
67  }
68 
69  for(const auto& elem : _copyDecorators) {
70  elem->preReadAndHandleExceptions(TransferType::read);
71  if((elem->_activeException != nullptr) && (firstDetectedRuntimeError == nullptr)) {
72  firstDetectedRuntimeError = elem->_activeException;
73  }
74  }
75 
76  if(firstDetectedRuntimeError == nullptr) {
77  // only execute the transfers if there has been no exception yet
78  for(const auto& it : _lowLevelElementsAndExceptionFlags) {
79  const auto& elem = it.first;
80  elem->handleTransferException([&] { elem->readTransfer(); });
81  if((elem->_activeException != nullptr) && (firstDetectedRuntimeError == nullptr)) {
82  firstDetectedRuntimeError = elem->_activeException;
83  }
84  }
85  }
86 
87  // Exceptions from copy decorators are ignored. The same exception will be thrown by their target accessors in the
88  // second call to runPostReads() anyway. In case the target is decorated with an ExceptionHandlingDecorator (in
89  // ApplicationCore), the copy decorators exception would not be handled by an ExceptionHandlingDecorator and hence
90  // terminate the application.
91  _nRuntimeErrors = 0;
92  runPostReads(_copyDecorators, firstDetectedRuntimeError);
93  _nRuntimeErrors = 0;
94  runPostReads(_highLevelElements, firstDetectedRuntimeError);
95 
96  // re-throw exceptions in the order of occurrence
97 
98  // The runtime error which was seen as _activeException in the pre or transfer phase might
99  // have been handled in postRead, and thus become invalid (for instance because it
100  // is suppressed by the ApplicationCore::ExceptionHandlingDecorator).
101  // Only re-throw here if an exception has been re-thrown in the postRead step.
102  // FIXME(?): If the firstDetectedRuntimeError has been handled, but another runtime_error
103  // has been thrown, this logic will throw the wrong exception here. It could be
104  // prevented with a complicated logic that loops all lowLevelElements in the order
105  // that was used to execute the transfer, for each of them it loops all its associated high
106  // level element, and for each high level element it loops all the low level elements again
107  // because some might and others might not have exceptions, and if there was an exception for
108  // that high level element on any of its low level elements, postRead must be called with
109  // an exception set each time. (I guess this sentence in close to not understandable, that's
110  // why I am not trying to implement it).
111  // In practice this will not happen because either all elements will have an ExceptionHandlingDecorator, or none.
112 
113  if(_nRuntimeErrors != 0) {
114  assert(firstDetectedRuntimeError != nullptr); // postRead must only rethrow, so there must be a detected
115  // runtime_error
117  std::rethrow_exception(firstDetectedRuntimeError);
118  }
119  } // namespace ChimeraTK
120 
121  /********************************************************************************************************************/
122 
123  void TransferGroup::write(VersionNumber versionNumber) {
124  // check pre-conditions so preRead() does not throw logic errors
125  for(const auto& backend : _exceptionBackends) {
126  if(backend && !backend->isOpen()) {
127  throw ChimeraTK::logic_error("DeviceBackend " + backend->readDeviceInfo() + "is not opened!");
128  }
129  }
130  if(!isWriteable()) {
131  throw ChimeraTK::logic_error("TransferGroup::write() called, but not all elements are writeable.");
132  }
133 
134  for(auto& it : _lowLevelElementsAndExceptionFlags) {
135  it.second = false;
136  }
137 
138  std::exception_ptr firstDetectedRuntimeError{nullptr};
139  for(const auto& elem : _highLevelElements) {
140  elem->preWriteAndHandleExceptions(TransferType::write, versionNumber);
141  if((elem->_activeException != nullptr) && (firstDetectedRuntimeError == nullptr)) {
142  firstDetectedRuntimeError = elem->_activeException;
143  }
144  }
145 
146  if(firstDetectedRuntimeError == nullptr) {
147  for(const auto& it : _lowLevelElementsAndExceptionFlags) {
148  const auto& elem = it.first;
149  elem->handleTransferException([&] { elem->writeTransfer(versionNumber); });
150  if((elem->_activeException != nullptr) && (firstDetectedRuntimeError == nullptr)) {
151  firstDetectedRuntimeError = elem->_activeException;
152  }
153  }
154  }
155 
156  _nRuntimeErrors = 0;
157  for(const auto& elem : _highLevelElements) {
158  // check for exceptions on any of the element's low level elements
159  for(const auto& lowLevelElem : elem->getHardwareAccessingElements()) {
160  // In case there are multiple exceptions we take the last one, but this does not matter. They are all
161  // ChimeraTK::runtime_errors and the first detected runtime error, which is re-thrown, has already been
162  // determined by previously.
163  if(lowLevelElem->_activeException) {
164  // copy the runtime error from low level element into the high level element so it is processed in post-read
165  elem->_activeException = lowLevelElem->_activeException;
166  }
167  }
168 
169  try {
170  elem->postWrite(TransferType::write, versionNumber);
171  }
172  catch(ChimeraTK::runtime_error&) {
173  ++_nRuntimeErrors;
174  }
175  }
176 
177  if(_nRuntimeErrors != 0) {
178  assert(firstDetectedRuntimeError != nullptr);
180  std::rethrow_exception(firstDetectedRuntimeError);
181  }
182  }
183 
184  /********************************************************************************************************************/
185 
187  return isReadable() && !isWriteable();
188  }
189 
190  /********************************************************************************************************************/
191 
195  }
196  return _isReadable;
197  }
198 
199  /********************************************************************************************************************/
200 
204  }
205  return _isWriteable;
206  }
207 
208  /********************************************************************************************************************/
209 
210  void TransferGroup::addAccessorImpl(TransferElementAbstractor& accessor, bool isTemporary) {
211  // check if accessor is already in a transfer group
212  if(accessor.getHighLevelImplElement()->_isInTransferGroup) {
213  throw ChimeraTK::logic_error("The given accessor is already in a TransferGroup and cannot be added "
214  "to another.");
215  }
216 
217  // Only accessors without wait_for_new_data can be used in a transfer group.
220  "A TransferGroup can only be used with transfer elements that don't have aAccessMode::wait_for_new_data.");
221  }
222 
223  // set flag on the accessors that it is now in a transfer group
224  accessor.getHighLevelImplElement()->_isInTransferGroup = true;
225 
226  _exceptionBackends.insert(accessor.getHighLevelImplElement()->getExceptionBackend());
227 
228  auto highLevelElementsWithNewAccessor = _highLevelElements;
229  highLevelElementsWithNewAccessor.insert(accessor.getHighLevelImplElement());
230 
231  // try replacing all internal elements in all high-level elements
232  for(const auto& hlElem1 : highLevelElementsWithNewAccessor) {
233  auto list = hlElem1->getInternalElements();
234  list.push_front(hlElem1);
235 
236  for(const auto& replacement : list) {
237  // try on the abstractor first, to make sure we replace at the highest
238  // level if possible, but only if this isn't a temporary abstractor
239  if(not isTemporary) accessor.replaceTransferElement(replacement);
240  // try on all high-level elements already stored in the list
241  for(const auto& hlElem : highLevelElementsWithNewAccessor) {
242  hlElem->replaceTransferElement(replacement); // note: this does nothing, if the replacement cannot
243  // be used by the hlElem!
244  }
245  }
246  }
247 
248  // store the accessor in the list of high-level elements
249  // this must be done only now, since it may have been replaced during the
250  // replacement process
251  _highLevelElements.insert(accessor.getHighLevelImplElement());
252 
253  // update the list of hardware-accessing elements, since we might just have
254  // made some of them redundant since we are using a set to store the elements,
255  // duplicates are intrinsically avoided.
257  for(const auto& hlElem : _highLevelElements) {
258  for(const auto& hwElem : hlElem->getHardwareAccessingElements()) {
259  _lowLevelElementsAndExceptionFlags.insert({hwElem, false});
260  }
261  }
262 
263  // update the list of CopyRegisterDecorators
264  _copyDecorators.clear();
265  for(const auto& hlElem : _highLevelElements) {
266  if(boost::dynamic_pointer_cast<ChimeraTK::CopyRegisterDecoratorTrait>(hlElem) != nullptr) {
267  _copyDecorators.insert(hlElem);
268  }
269  for(const auto& hwElem : hlElem->getInternalElements()) {
270  if(boost::dynamic_pointer_cast<ChimeraTK::CopyRegisterDecoratorTrait>(hwElem) != nullptr) {
271  _copyDecorators.insert(hwElem);
272  }
273  }
274  }
275 
276  // read-write flag may need to be updated
278  }
279 
280  /********************************************************************************************************************/
281 
282  namespace detail {
285  struct TransferGroupTransferElementAbstractor : TransferElementAbstractor {
286  explicit TransferGroupTransferElementAbstractor(boost::shared_ptr<TransferElement> impl)
287  : TransferElementAbstractor(std::move(impl)) {}
288 
289  void replaceTransferElement(boost::shared_ptr<TransferElement> newElement) {
290  _impl->replaceTransferElement(std::move(newElement));
291  }
292  };
293  } // namespace detail
294 
295  /********************************************************************************************************************/
296 
297  void TransferGroup::addAccessor(const boost::shared_ptr<TransferElement>& accessor) {
299  auto x = detail::TransferGroupTransferElementAbstractor(accessor);
300  addAccessorImpl(x, true);
301  }
302 
303  /********************************************************************************************************************/
304 
306  addAccessorImpl(accessor, false);
307  }
308 
309  /********************************************************************************************************************/
310 
312  _isReadable = true;
313  _isWriteable = true;
314  for(const auto& elem : _highLevelElements) {
315  if(!elem->isReadable()) _isReadable = false;
316  if(!elem->isWriteable()) _isWriteable = false;
317  }
319  }
320 
321  /********************************************************************************************************************/
322 
324  std::cout << "=== Accessors added to this group: " << std::endl;
325  for(const auto& elem : _highLevelElements) {
326  std::cout << " - " << elem->getName() << std::endl;
327  }
328  std::cout << "=== Low-level transfer elements in this group: " << std::endl;
329  for(auto& elem : _lowLevelElementsAndExceptionFlags) {
330  std::cout << " - " << elem.first->getName() << std::endl;
331  }
332  std::cout << "===" << std::endl;
333  }
334 
335  /********************************************************************************************************************/
336 
337 } /* namespace ChimeraTK */
ChimeraTK::TransferElementAbstractor::getAccessModeFlags
AccessModeFlags getAccessModeFlags() const
Return the AccessModeFlags for this TransferElement.
Definition: TransferElementAbstractor.h:51
ChimeraTK::TransferGroup::_copyDecorators
std::set< boost::shared_ptr< TransferElement > > _copyDecorators
List of all CopyRegisterDecorators in the group.
Definition: TransferGroup.h:77
TransferGroup.h
ChimeraTK::TransferGroup::write
void write(VersionNumber versionNumber={})
Trigger write transfer for all accessors in the group.
Definition: TransferGroup.cc:123
ChimeraTK::TransferGroup::addAccessor
void addAccessor(TransferElementAbstractor &accessor)
Add a register accessor to the group.
Definition: TransferGroup.cc:305
TransferElement.h
ChimeraTK::TransferElementAbstractor::TransferElementAbstractor
TransferElementAbstractor(boost::shared_ptr< TransferElement > impl)
Construct from TransferElement implementation.
Definition: TransferElementAbstractor.h:31
ChimeraTK::TransferGroup::isReadable
bool isReadable()
Check if transfer group is readable.
Definition: TransferGroup.cc:192
ChimeraTK::TransferGroup::isReadOnly
bool isReadOnly()
Check if transfer group is read-only.
Definition: TransferGroup.cc:186
ChimeraTK::runtime_error
Exception thrown when a runtime error has occured.
Definition: Exception.h:18
ChimeraTK::TransferGroup::isWriteable
bool isWriteable()
Check if transfer group is writeable.
Definition: TransferGroup.cc:201
ChimeraTK::TransferGroup::read
void read()
Trigger read transfer for all accessors in the group.
Definition: TransferGroup.cc:44
ChimeraTK::AccessModeFlags::has
bool has(AccessMode flag) const
Check if a certain flag is in the set.
Definition: AccessMode.cc:20
ChimeraTK::TransferElementAbstractor
Base class for register accessors abstractors independent of the UserType.
Definition: TransferElementAbstractor.h:28
ChimeraTK::TransferGroup::_lowLevelElementsAndExceptionFlags
std::map< boost::shared_ptr< TransferElement >, bool > _lowLevelElementsAndExceptionFlags
List of low-level TransferElements in this group, which are directly responsible for the hardware acc...
Definition: TransferGroup.h:71
ChimeraTK::TransferGroup::_cachedReadableWriteableIsValid
bool _cachedReadableWriteableIsValid
Flag whether to update the cached information.
Definition: TransferGroup.h:95
ChimeraTK::TransferGroup::_exceptionBackends
std::set< boost::shared_ptr< DeviceBackend > > _exceptionBackends
List of all exception backends.
Definition: TransferGroup.h:86
TransferElementAbstractor.h
ChimeraTK::AccessMode::wait_for_new_data
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
ChimeraTK::TransferElementAbstractor::replaceTransferElement
void replaceTransferElement(const boost::shared_ptr< TransferElement > &newElement)
Search for all underlying TransferElements which are considered identical (see mayReplaceOther()) wit...
Definition: TransferElementAbstractor.cc:24
ChimeraTK::TransferElementAbstractor::getHighLevelImplElement
const boost::shared_ptr< TransferElement > & getHighLevelImplElement()
Obtain the highest level implementation TransferElement.
Definition: TransferElementAbstractor.h:145
ChimeraTK::TransferGroup::updateIsReadableWriteable
void updateIsReadableWriteable()
Helper function to update the cached state variables.
Definition: TransferGroup.cc:311
ChimeraTK::TransferGroup::_isReadable
bool _isReadable
Cached value whether all elements are readable.
Definition: TransferGroup.h:89
CopyRegisterDecorator.h
ChimeraTK::TransferType::write
@ write
ChimeraTK::TransferGroup::runPostReads
void runPostReads(const std::set< boost::shared_ptr< TransferElement >> &elements, const std::exception_ptr &firstDetectedRuntimeError) noexcept
Definition: TransferGroup.cc:17
ChimeraTK::TransferGroup::_nRuntimeErrors
size_t _nRuntimeErrors
Definition: TransferGroup.h:105
ChimeraTK::VersionNumber
Class for generating and holding version numbers without exposing a numeric representation.
Definition: VersionNumber.h:23
Exception.h
ChimeraTK::TransferElementAbstractor::_impl
boost::shared_ptr< TransferElement > _impl
Untyped pointer to implementation.
Definition: TransferElementAbstractor.h:225
ChimeraTK
Definition: DummyBackend.h:16
ChimeraTK::TransferGroup::_isWriteable
bool _isWriteable
Cached value whether all elements are writeable.
Definition: TransferGroup.h:92
ChimeraTK::TransferGroup::dump
void dump()
Print information about the accessors in this group to screen, which might help to understand which t...
Definition: TransferGroup.cc:323
ChimeraTK::TransferType::read
@ read
ChimeraTK::TransferGroup::_highLevelElements
std::set< boost::shared_ptr< TransferElement > > _highLevelElements
List of high-level TransferElements in this group which are directly used by the user.
Definition: TransferGroup.h:80
ChimeraTK::logic_error
Exception thrown when a logic error has occured.
Definition: Exception.h:51