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