ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
ExceptionHandlingDecorator.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
4
5#include "DeviceManager.h"
6#include "RecoveryHelper.h"
7
8#include <ChimeraTK/SystemTags.h>
9#include <ChimeraTK/TransferElement.h>
10
11#include <utility>
12
13namespace ChimeraTK {
14
15 /********************************************************************************************************************/
16
17 template<typename UserType>
19 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, const VariableNetworkNode& networkNode,
20 boost::shared_ptr<RecoveryHelper> recoveryHelper)
21 : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), _recoveryHelper(std::move(recoveryHelper)),
22 _direction(networkNode.getDirection()) {
23 const auto& deviceAlias = networkNode.getDeviceAlias();
24 const auto& registerName = networkNode.getRegisterName();
25
26 assert(Application::getInstance()._deviceManagerMap.count(deviceAlias) != 0);
27 auto deviceManager = Application::getInstance()._deviceManagerMap[deviceAlias];
28 _deviceManager = deviceManager;
29
30 // Consuming from the network means writing to the device what you consumed.
32 deviceManager->_writeRegisterPaths.emplace_back(registerName);
33
34 // writeable registers get a recoveryAccessor
35 // Notice: There will be write-accessors without recovery accessors in future (intentionally turned off by the
36 // application programmer). When this feature is added the VariableNetworkNode will get a new data member to
37 // indicate this.
38 auto nElements = networkNode.getNumberOfElements();
39
40 // The device in the deviceModule does not have a valid backend yet. It is set when open() is called, which has
41 // not happened yet. We have to get the backend from the application.
42 auto deviceBackend = Application::getInstance()._deviceManagerMap.at(deviceAlias)->getDevice().getBackend();
43
44 _recoveryAccessor = deviceBackend->getRegisterAccessor<UserType>(
45 registerName, nElements, 0, {}); // recovery accessors don't have wait_for_new_data
46 // version number and write order are still {nullptr} and 0 (i.e. invalid)
48
49 // We need to do this here in addition to doing it unconditionally in the ReverseRecoveryDecorator
50 // to block recovery from write-only variables
51 if(networkNode.getTags().contains(ChimeraTK::SystemTags::reverseRecovery)) {
53 }
54
55 // add recovery accessor to DeviceManager so the last known value is restored during device recovery, unless
56 // the data type is Void, in which case there is no value to recover and writing will likely trigger some unwanted
57 // action.
58 if(!std::is_same<UserType, ChimeraTK::Void>::value) {
59 deviceManager->addRecoveryAccessor(_recoveryHelper);
60 }
61
62 if(_direction.withReturn && _recoveryAccessor->isReadable()) {
63 deviceManager->_readRegisterPaths.emplace_back(registerName);
64 }
65 }
67 deviceManager->_readRegisterPaths.emplace_back(registerName);
68 }
69 else {
70 throw ChimeraTK::logic_error("Invalid variable direction in " + networkNode.getRegisterName());
71 }
72 }
73
74 /********************************************************************************************************************/
75
76 template<typename UserType>
77 void ExceptionHandlingDecorator<UserType>::doPreWrite(TransferType type, VersionNumber versionNumber) {
78 auto deviceManager = _deviceManager.lock();
79
80 /* For writeable accessors, copy data to the recoveryAccessor before performing the write.
81 * Otherwise, the decorated accessor may have swapped the data out of the user buffer already.
82 * This obtains a shared lock from the DeviceModule, hence, the regular writing happening here
83 * can be performed in shared mode of the mutex and accessors are not blocking each other.
84 * In case of recovery, the DeviceModule thread will take an exclusive lock so that this thread can not
85 * modify the recoveryAccessor's user buffer while data is written to the device.
86 */
87 {
88 _inhibitWriteTransfer = false;
89 _hasThrownLogicError = false;
90 _dataLostInPreviousWrite = false;
91 auto recoverylock{deviceManager->getRecoverySharedLock()};
92
93 if(_recoveryAccessor != nullptr) {
94 if(!_recoveryHelper->wasWritten && (_recoveryHelper->writeOrder != 0)) {
95 _dataLostInPreviousWrite = true;
96 }
97
98 // Access to _recoveryAccessor is only possible channel-wise
99 for(unsigned int ch = 0; ch < _recoveryAccessor->getNumberOfChannels(); ++ch) {
100 _recoveryAccessor->accessChannel(ch) = buffer_2D[ch];
101 }
102 _recoveryHelper->versionNumber = versionNumber;
103 _recoveryHelper->writeOrder = deviceManager->writeOrder();
104 _recoveryHelper->wasWritten = false;
105 }
106 else {
107 _hasThrownLogicError = true;
108 throw ChimeraTK::logic_error(
109 "ChimeraTK::ExceptionhandlingDecorator: Calling write() on a non-writeable accessor is not supported ");
110 }
111
112 boost::shared_lock<boost::shared_mutex> errorLock(deviceManager->_errorMutex);
113 if(deviceManager->_deviceHasError) {
114 _inhibitWriteTransfer = true;
115 return;
116 }
117
118 ++deviceManager->_synchronousTransferCounter; // must be under the lock
119
120 } // lock guard goes out of scope
121
122 // Now delegate call to the generic decorator, which swaps the buffer, without adding our exception handling with
123 // the generic transfer preWrite and postWrite are only delegated if the transfer is allowed.
124 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(type, versionNumber);
125 }
126
127 /********************************************************************************************************************/
128
129 template<typename UserType>
130 void ExceptionHandlingDecorator<UserType>::doPostWrite(TransferType type, VersionNumber versionNumber) {
131 auto deviceManager = _deviceManager.lock();
132
133 if(_hasThrownLogicError) {
134 // preWrite has not been delegated, so there is nothing to do here. Let
135 // postWrite() throw the active exception we have. Don't clear logic erros here.
136 return;
137 }
138 if(!_inhibitWriteTransfer) {
139 --deviceManager->_synchronousTransferCounter;
140 try {
141 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostWrite(type, versionNumber);
142 {
143 auto recoverylock{deviceManager->getRecoverySharedLock()};
144 // the transfer was successful or doPostRead did not throw and we reach this point,
145 // so we mark these data as written
146 _recoveryHelper->wasWritten = true;
147 } // end scope for recovery lock
148 }
149 catch(ChimeraTK::runtime_error& e) {
150 auto description = std::string(e.what()) + " (seen by '" + _target->getName() + "')";
151 // Report exception to the exception backend. This would be done by the TransferElement base class only if
152 // we would let the exception through, hence we have to take care of this here.
153 this->_exceptionBackend->setException(description);
154 // Report exception to the DeviceModule
155 deviceManager->reportException(description);
156 }
157 }
158 assert(_activeException == nullptr);
159 }
160
161 /********************************************************************************************************************/
162
163 template<typename UserType>
164 void ExceptionHandlingDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) {
165 auto deviceManager = _deviceManager.lock();
166
167 // preRead has not been called when the transfer was not allowed. Don't call postRead in this case.
168 if(!_hasThrownToInhibitTransfer) {
169 try {
170 if(!TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) { // was as synchronous transfer
171 --deviceManager->_synchronousTransferCounter;
172 }
173 _target->setActiveException(this->_activeException);
174 _target->postRead(type, hasNewData);
175 if(hasNewData) {
176 // Reset the flag after a successful read.
177 // It is only reset if there was new data. A readNonBlocking on a faulty device is not different to a
178 // readNonBlocking on working device: There just is no new data. We only reset it on the next successful read
179 // with the initial value, otherwise keep the exception flag.
180 _hasReportedException = false;
181 }
182 }
183 catch(ChimeraTK::runtime_error& e) {
184 auto description = std::string(e.what()) + " (seen by '" + _target->getName() + "')";
185 // Report exception to the exception backend. This would be done by the TransferElement base class only if
186 // we would let the exception through, hence we have to take care of this here.
187 this->_exceptionBackend->setException(description);
188 // Report exception to the DeviceModule
189 deviceManager->reportException(description);
190 _hasReportedException = true;
191 }
192 }
193 else {
194 _activeException = nullptr;
195 }
196
197 if(_hasReportedException || _hasThrownToInhibitTransfer) {
198 _dataValidity = DataValidity::faulty;
199 // Note: This assertion does not hold
200 // See discussion in https://github.com/ChimeraTK/DeviceAccess/pull/178
201 // assert(_deviceModule->getExceptionVersionNumber() > _versionNumber);
202 if(deviceManager->getExceptionVersionNumber() > _versionNumber) {
203 _versionNumber = deviceManager->getExceptionVersionNumber();
204 }
205 }
206 else {
207 _dataValidity = _target->dataValidity();
208 // Note: This assertion does not hold
209 // See discussion in https://github.com/ChimeraTK/DeviceAccess/pull/178
210 // assert(_target->getVersionNumber() >= _versionNumber);
211 if(_target->getVersionNumber() > _versionNumber) {
212 _versionNumber = _target->getVersionNumber();
213 }
214 }
215
216 // only replace the user buffer if there really is new data
217 if(hasNewData) {
218 for(size_t i = 0; i < buffer_2D.size(); ++i) {
219 buffer_2D[i].swap(this->_target->accessChannel(static_cast<unsigned int>(i)));
220 }
221 }
222 assert(_activeException == nullptr);
223 }
224
225 /********************************************************************************************************************/
226
227 template<typename UserType>
229 auto deviceManager = _deviceManager.lock();
230
231 _hasThrownToInhibitTransfer = false;
232
233 if(TransferElement::_versionNumber == VersionNumber(nullptr)) {
234 deviceManager->waitForInitialValues();
235 // we don't have to store the shared lock. Once we acquired it the deviceModule will never take it again.
236 }
237
238 if(!TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) {
239 boost::shared_lock<boost::shared_mutex> errorLock(deviceManager->_errorMutex);
240 if(deviceManager->_deviceHasError) {
241 _hasThrownToInhibitTransfer = true;
242 throw ChimeraTK::runtime_error("ExceptionHandlingDecorator has thrown to skip read transfer");
243 }
244 // must hold the errorMutex while modifying the counter, and it must not be given up
245 // after the decision to do the transfer until here
246 ++deviceManager->_synchronousTransferCounter;
247 }
248
249 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(type);
250 }
251
252 /********************************************************************************************************************/
253
254 template<typename UserType>
256 return genericWriteWrapper([&] { return _target->writeTransferDestructively(versionNumber); });
257 }
258
259 /********************************************************************************************************************/
260
261 template<typename UserType>
263 return genericWriteWrapper([&] { return _target->writeTransferDestructively(versionNumber); });
264 }
265
266 /********************************************************************************************************************/
267
268 template<typename UserType>
269 template<typename Callable>
271 if(_inhibitWriteTransfer) {
272 return _dataLostInPreviousWrite;
273 }
274 bool transferReportsPreviousDataLost = false;
275 try {
276 transferReportsPreviousDataLost = writeFunction();
277 }
278 catch(ChimeraTK::runtime_error&) {
279 _activeException = std::current_exception();
280 }
281 return (transferReportsPreviousDataLost || _dataLostInPreviousWrite);
282 }
283
284 /********************************************************************************************************************/
285
287
288 /********************************************************************************************************************/
289
290} /* namespace ChimeraTK */
std::map< std::string, boost::shared_ptr< DeviceManager > > _deviceManagerMap
Map of DeviceManagers.
static Application & getInstance()
Obtain instance of the application.
Decorator of the NDRegisterAccessor which facilitates tests of the application.
boost::weak_ptr< DeviceManager > _deviceManager
ExceptionHandlingDecorator(boost::shared_ptr< ChimeraTK::NDRegisterAccessor< UserType > > accessor, const VariableNetworkNode &networkNode, boost::shared_ptr< RecoveryHelper > recoveryHelper)
Decorate the accessors which is handed in the constructor.
boost::shared_ptr< RecoveryHelper > _recoveryHelper
void doPostRead(TransferType type, bool hasNewData) override
void doPreWrite(TransferType type, VersionNumber versionNumber) override
void doPostWrite(TransferType type, VersionNumber versionNumber) override
bool doWriteTransferDestructively(VersionNumber versionNumber) override
bool doWriteTransfer(VersionNumber versionNumber) override
boost::shared_ptr< NDRegisterAccessor< UserType > > _recoveryAccessor
Class describing a node of a variable network.
const std::string & getRegisterName() const
const std::unordered_set< std::string > & getTags() const
const std::string & getDeviceAlias() const
InvalidityTracer application module.
INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(DebugPrintAccessorDecorator)
enum ChimeraTK::VariableDirection::@0 dir
Enum to define directions of variables.
bool withReturn
Presence of return channel.
Definition Flags.h:21