ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
testDoubleBuffering.cpp
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#define BOOST_TEST_DYN_LINK
5#define BOOST_TEST_MODULE SubdeviceBackendUnifiedTest
6#include <boost/test/unit_test.hpp>
7using namespace boost::unit_test_framework;
8
9#include "Device.h"
10#include "DummyBackend.h"
12#include "TransferGroup.h"
13#include "UnifiedBackendTest.h"
14
15#include <boost/thread/barrier.hpp>
16
17using namespace ChimeraTK;
18
19BOOST_AUTO_TEST_SUITE(DoubleBufferingBackendUnifiedTestSuite)
20
21/**********************************************************************************************************************/
22
23
30
31 static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::map<std::string, std::string> parameters) {
32 return returnInstance<DummyForDoubleBuffering>(
33 parameters.at("map"), convertPathRelativeToDmapToAbs(parameters.at("map")));
34 }
35
42
43 void read(uint64_t bar, uint64_t address, int32_t* data, size_t sizeInBytes) override {
44 // Note, although ExceptionDummy::read() cannot be called concurrently with read or write from the
45 // fw simulating side, this limitation should not matter here since we only interrupt
46 // DummyForDoubleBuffering::read() and not it's base implementation
47
48 for(unsigned i = 0; i < 2; i++) {
49 if(blockNextRead[i]) {
50 blockedInRead[i].wait();
51 unblockRead[i].wait();
52 blockNextRead[i] = false;
53 }
54 }
55 // finalize reading by calling Exception backend read
56 ChimeraTK::ExceptionDummy::read(bar, address, data, sizeInBytes);
57 }
58 // use this to request that next read blocks.
59 // array index corresponds to that of barrier arrays
60 // We know that read is called only 2nd after write (for the buffer-switching enable ctrl register),
61 // so in this sense it requests blocking after only part of the double-buffer read operation is done
62 static thread_local bool blockNextRead[2];
63 // one pair of barriers per reader thread
64 // after request that read blocks, you must wait on this
65 std::array<boost::barrier, 2> blockedInRead{boost::barrier{2}, boost::barrier{2}};
66 // use this to unblock the read
67 std::array<boost::barrier, 2> unblockRead{boost::barrier{2}, boost::barrier{2}};
68};
69thread_local bool DummyForDoubleBuffering::blockNextRead[2] = {false, false};
70
72
73static std::string rawDeviceCdd("(DummyForDoubleBuffering?map=doubleBuffer.map)");
74std::string lmap = "(logicalNameMap?map=doubleBuffer.xlmap&target=" + rawDeviceCdd + ")";
75static auto backdoor =
76 boost::dynamic_pointer_cast<ExceptionDummy>(BackendFactory::getInstance().createBackend(rawDeviceCdd));
77
78/**********************************************************************************************************************/
79
80template<typename Register>
81struct AreaType : Register {
82 static uint32_t _currentBufferNumber;
83
84 bool isWriteable() { return false; }
85 bool isReadable() { return true; }
86 ChimeraTK::AccessModeFlags supportedFlags() { return {/* TODO later: ChimeraTK::AccessMode::wait_for_new_data */}; }
87 size_t nChannels() { return 1; }
88 size_t writeQueueLength() { return std::numeric_limits<size_t>::max(); }
89 size_t nRuntimeErrorCases() { return 1; }
90
91 static constexpr auto capabilities = TestCapabilities<>()
93 .disableAsyncReadInconsistency()
94 .disableTestWriteNeverLosesData()
95 .disableSwitchReadOnly()
96 .disableSwitchWriteOnly()
97 .disableTestRawTransfer();
98
99 template<typename UserType>
100 std::vector<std::vector<UserType>> generateValue() {
101 auto values = this->getRemoteValue<typename Register::minimumUserType>();
102 for(size_t i = 0; i < this->nChannels(); ++i)
103 for(size_t j = 0; j < this->nElementsPerChannel(); ++j) {
104 values[i][j] += this->increment * (i + j + 1);
105 }
106 return values;
107 }
108
109 template<typename UserType>
110 std::vector<std::vector<UserType>> getRemoteValue(bool = false) {
111 // For Variables we don't have a backdoor. We have to use the normal read and write
112 // functions which are good enough. It seems like a self consistency test, but all
113 // functionality the variable has to provide is that I can write something, and
114 // read it back, which is tested with it.
115
116 // We might have to open the backend to perform the operation. We have to remember
117 // that we did so and close it again it we did. Some tests require the backend to be closed.
118
119 auto currentBufferNumber = backdoor->getRegisterAccessor<uint32_t>("APP.0.WORD_DUB_BUF_CURR", 0, 0, {});
120 auto buffer0 = backdoor->getRegisterAccessor<typename Register::minimumUserType>(
121 "APP/0/DAQ0_BUF0", this->nElementsPerChannel(), 0, {});
122 auto buffer1 = backdoor->getRegisterAccessor<typename Register::minimumUserType>(
123 "APP/0/DAQ0_BUF1", this->nElementsPerChannel(), 0, {});
124
125 bool deviceWasOpened = false;
126 if(!backdoor->isOpen()) {
127 backdoor->open();
128 deviceWasOpened = true;
129 }
130
131 boost::shared_ptr<NDRegisterAccessor<typename Register::minimumUserType>> currentBuffer;
132
133 currentBufferNumber->read();
134
135 if(currentBufferNumber->accessData(0) == 1) {
136 currentBuffer = buffer0;
137 }
138 else {
139 currentBuffer = buffer1;
140 }
141 currentBuffer->read();
142 std::vector<std::vector<UserType>> v;
143 for(size_t i = 0; i < this->nChannels(); ++i) {
144 v.push_back(std::vector<UserType>());
145 for(size_t j = 0; j < this->nElementsPerChannel(); ++j) {
146 v[i].push_back(currentBuffer->accessData(j));
147 }
148 }
149
150 if(deviceWasOpened) {
151 backdoor->close();
152 }
153
154 return v;
155 }
156
158 auto currentBufferNumber = backdoor->getRegisterAccessor<uint32_t>("APP.0.WORD_DUB_BUF_CURR", 0, 0, {});
159 auto buffer0 = backdoor->getRegisterAccessor<typename Register::minimumUserType>(
160 "APP/0/DAQ0_BUF0", this->nElementsPerChannel(), 0, {});
161 auto buffer1 = backdoor->getRegisterAccessor<typename Register::minimumUserType>(
162 "APP/0/DAQ0_BUF1", this->nElementsPerChannel(), 0, {});
163 boost::shared_ptr<NDRegisterAccessor<typename Register::minimumUserType>> currentBuffer;
164
165 bool deviceWasOpened = false;
166 if(!backdoor->isOpen()) {
167 backdoor->open();
168 deviceWasOpened = true;
169 }
170
171 currentBufferNumber->accessData(0) = _currentBufferNumber;
172 currentBufferNumber->write();
173 _currentBufferNumber = _currentBufferNumber ? 0 : 1; // change current buffer no. 0->1 or 1->0
174
175 auto values = this->generateValue<typename Register::minimumUserType>();
176
177 if(currentBufferNumber->accessData(0) == 1) {
178 currentBuffer = buffer0;
179 }
180 else {
181 currentBuffer = buffer1;
182 }
183 for(size_t i = 0; i < this->nChannels(); ++i) {
184 for(size_t j = 0; j < this->nElementsPerChannel(); ++j) {
185 currentBuffer->accessData(i, j) = values[i][j];
186 }
187 }
188 currentBuffer->write();
189
190 if(deviceWasOpened) {
191 backdoor->close();
192 }
193 }
194
195 void setForceRuntimeError(bool enable, size_t caseNumber) {
196 if(caseNumber == 0) {
197 backdoor->throwExceptionRead = enable;
198 backdoor->throwExceptionOpen = enable;
199 }
200 }
201};
202
203/**********************************************************************************************************************/
204
205struct MyArea1 {
206 std::string path() { return "/doubleBuffer"; }
207 size_t nElementsPerChannel() { return 10; }
208 size_t address() { return 20; }
209 int32_t increment = 3;
210
211 typedef uint32_t minimumUserType;
212 typedef int32_t rawUserType;
213};
214
215/**********************************************************************************************************************/
216
217template<typename Register>
219
223
226 boost::shared_ptr<NDRegisterAccessor<uint32_t>> doubleBufferingEnabled;
227 // we call the backend doubleBufDummy when we modify the behavior of the thread which reads via double buffering mechanism
228 boost::shared_ptr<DummyForDoubleBuffering> doubleBufDummy;
230 // before any access, also via backdoor, must open
231 d.open();
232 doubleBufDummy = boost::dynamic_pointer_cast<DummyForDoubleBuffering>(backdoor);
233 assert(doubleBufDummy);
234 doubleBufferingEnabled = backdoor->getRegisterAccessor<uint32_t>("APP/0/WORD_DUB_BUF_ENA", 0, 0, {});
235 doubleBufferingEnabled->accessData(0) = 1;
236 doubleBufferingEnabled->write();
237 }
238};
239
241 /*
242 * Test race condition: slow reader, which blocks the Firmware from buffer switching.
243 */
244 auto accessor = d.getOneDRegisterAccessor<uint32_t>("/doubleBuffer");
245
246 // make double buffer operation block after write to ctrl register, at read of buffer number
247 std::thread s([&] {
248 // this thread reads from double-buffered region
249 doubleBufDummy->blockNextRead[0] = true;
250 accessor.read();
251 });
252
253 // wait that thread s is in blocked double-buffer read
254 doubleBufDummy->blockedInRead[0].wait();
255
256 // simplification: instead of writing fw simulation which would overwrite data now,
257 // just check that buffer switching was disabled
258 doubleBufferingEnabled->readLatest();
259 BOOST_CHECK(!doubleBufferingEnabled->accessData(0));
260
261 doubleBufDummy->unblockRead[0].wait();
262 s.join();
263
264 // check that buffer switching enabled, by finalization of double-buffered read
265 doubleBufferingEnabled->readLatest();
266 BOOST_CHECK(doubleBufferingEnabled->accessData(0));
267}
268
270 /*
271 * A test which exposes the dangerous race condition of two readers
272 * - reader A deactivates buffer switching, starts reading buffer0
273 * - reader B (again) deactivates buffer switching, starts reading buffer0
274 * - reader A finished with reading, activates buffer switching already, which is too early
275 * here the correct double buffering implementation would need to wait on reader B
276 * - firmware writes into buffer1 and when done, switches buffers
277 * the writing may have started earlier (e.g. before reader A started reading), important here is
278 * only buffer switch at end
279 * - firmware writes into buffer0 and corrupts data
280 * - reader B finishes reading, and gets corrupt data, enables buffer switching.
281 */
282
283 // wait on "reader B has started" and then wait on "reader A has finished" inside reader B
284
285 std::thread readerA([&] {
286 auto accessor = d.getOneDRegisterAccessor<uint32_t>("/doubleBuffer");
287 // begin read
288 doubleBufDummy->blockNextRead[0] = true;
289 accessor.read();
290 });
291 std::thread readerB([&] {
292 auto accessor = d.getOneDRegisterAccessor<uint32_t>("/doubleBuffer");
293 // wait that readerA is in blocked double-buffer read
294 doubleBufDummy->blockedInRead[0].wait();
295 // begin read
296 doubleBufDummy->blockNextRead[1] = true;
297 accessor.read();
298 });
299 doubleBufDummy->blockedInRead[1].wait(); // wait that reader B also in blocked read
300 doubleBufDummy->unblockRead[0].wait(); // this is for reader A
301 readerA.join();
302
303 // check that after reader A returned, buffer switching is still disabled
304 doubleBufferingEnabled->readLatest();
305 BOOST_CHECK(!doubleBufferingEnabled->accessData(0));
306
307 doubleBufDummy->unblockRead[1].wait(); // this is for reader B
308 // check that after reader B returned, buffer switching is enabled
309 readerB.join();
310 doubleBufferingEnabled->readLatest();
311 BOOST_CHECK(doubleBufferingEnabled->accessData(0));
312}
313
318template<class Derived>
320 const std::string rawDeviceCdd = "(dummy?map=doubleBuffer.map)";
321 const std::string lmap = "(logicalNameMap?map=doubleBuffer.xlmap&target=" + this->rawDeviceCdd + ")";
323 boost::shared_ptr<DeviceBackend> backdoor;
324 boost::shared_ptr<NDRegisterAccessor<uint32_t>> doubleBufferingEnabled, writingBufferNum;
325 boost::shared_ptr<NDRegisterAccessor<float>> buf0, buf1;
330
331 DeviceFixture2D() : d(this->lmap) {
332 ConfigParams c = static_cast<Derived*>(this)->getCf();
333 // before any access, also via backdoor, must open
334 d.open();
335 backdoor = BackendFactory::getInstance().createBackend(this->rawDeviceCdd);
336 doubleBufferingEnabled = backdoor->getRegisterAccessor<uint32_t>(c.enableDoubleBufferingReg, 1, c.daqNumber, {});
337 doubleBufferingEnabled->accessData(0) = 1;
338 doubleBufferingEnabled->write();
339
340 writingBufferNum = backdoor->getRegisterAccessor<uint32_t>(c.currentBufferNumberReg, 1, c.daqNumber, {});
341 buf0 = backdoor->getRegisterAccessor<float>(c.firstBufferReg, 0, 0, {});
342 buf1 = backdoor->getRegisterAccessor<float>(c.secondBufferReg, 0, 0, {});
343 }
344
345 void simpleCheckExtractedChannels(std::string readerAReg) {
346 /*
347 * simple test for access to extracted channels of multiplexed 2D region
348 */
349 writingBufferNum->accessData(0) = 1;
350 writingBufferNum->write();
351
352 float modulation = 4.2; // example data
353 unsigned channel = 3; // must match with xlmap
354 buf0->accessData(channel, 0) = modulation;
355 buf1->accessData(channel, 0) = 2 * modulation;
356 buf0->write();
357 buf1->write();
358
359 boost::barrier waitForBufferSwapStart{2}, waitForBufferSwapDone{2};
360 std::thread readerA([&] {
361 auto accessorA = d.getOneDRegisterAccessor<float>(readerAReg);
362 accessorA.readLatest();
363 // since writingBufferNum = 1, we expect buf0 contents to be read
364 BOOST_CHECK_CLOSE(accessorA[0], modulation, 1e-4);
365 waitForBufferSwapStart.wait();
366 waitForBufferSwapDone.wait();
367 accessorA.readLatest();
368 BOOST_CHECK_CLOSE(accessorA[0], 2 * modulation, 1e-4);
369 });
370
371 waitForBufferSwapStart.wait();
372 writingBufferNum->accessData(0) = 0;
373 writingBufferNum->write();
374 waitForBufferSwapDone.wait();
375
376 readerA.join();
377 }
378
379 void checkExtractedChannels(std::string readerAReg, std::string readerBReg) {
380 /*
381 * test access to extracted channels of multiplexed 2D region
382 * this is an application of concurrent readers
383 */
384
385 writingBufferNum->accessData(0) = 1;
386 writingBufferNum->write();
387
388 float modulation = 4.2; // example data series 1
389 float correction = 10.1; // example data series 2
390 buf0->accessData(3, 0) = modulation;
391 buf1->accessData(3, 0) = 2 * modulation;
392 buf0->accessData(1, 0) = correction;
393 buf1->accessData(1, 0) = 2 * correction;
394 buf0->write();
395 buf1->write();
396
397 boost::barrier waitForBufferSwap{3};
398
399 // since BOOST.Test is not thread-safe, need to buffer check results in main thread
400 bool readerA_ok1, readerA_ok2, readerB_ok1, readerB_ok2;
401
402 std::thread readerA([&] {
403 auto accessorA = d.getOneDRegisterAccessor<float>(readerAReg);
404 accessorA.readLatest();
405 readerA_ok1 = abs(accessorA[0] - modulation) < 1e-4;
406 waitForBufferSwap.wait();
407 waitForBufferSwap.wait();
408 accessorA.readLatest();
409 readerA_ok2 = abs(accessorA[0] - 2 * modulation) < 1e-4;
410 });
411 std::thread readerB([&] {
412 auto accessorB = d.getOneDRegisterAccessor<float>(readerBReg);
413 accessorB.read();
414 readerB_ok1 = abs(accessorB[0] - correction) < 1e-4;
415 waitForBufferSwap.wait();
416 waitForBufferSwap.wait();
417 accessorB.read();
418 readerB_ok2 = abs(accessorB[0] - 2 * correction) < 1e-4;
419 });
420
421 waitForBufferSwap.wait();
422 writingBufferNum->accessData(0) = 0;
423 writingBufferNum->write();
424 waitForBufferSwap.wait();
425
426 readerA.join();
427 readerB.join();
428 BOOST_CHECK(readerA_ok1);
429 BOOST_CHECK(readerA_ok2);
430 BOOST_CHECK(readerB_ok1);
431 BOOST_CHECK(readerB_ok2);
432
433 // Check that also reading from a TransferGroup works
434 TransferGroup tg;
435 auto accessorA = d.getOneDRegisterAccessor<float>(readerAReg);
436 auto accessorB = d.getOneDRegisterAccessor<float>(readerBReg);
437 tg.addAccessor(accessorA);
438 tg.addAccessor(accessorB);
439 tg.read();
440 BOOST_CHECK_CLOSE(accessorA[0], 2 * modulation, 1e-4);
441 BOOST_CHECK_CLOSE(accessorB[0], 2 * correction, 1e-4);
442 // swap back to first value set
443 writingBufferNum->accessData(0) = 1;
444 writingBufferNum->write();
445 tg.read();
446 BOOST_CHECK_CLOSE(accessorA[0], modulation, 1e-4);
447 BOOST_CHECK_CLOSE(accessorB[0], correction, 1e-4);
448 }
449};
450
451struct DeviceFixture2D_DAQ0 : public DeviceFixture2D<DeviceFixture2D_DAQ0> {
452 ConfigParams getCf() {
453 ConfigParams c;
454 c.enableDoubleBufferingReg = "DAQ0/WORD_DUB_BUF_ENA";
455 c.currentBufferNumberReg = "DAQ0/WORD_DUB_BUF_CURR/DUMMY_WRITEABLE";
456 c.firstBufferReg = "APP0/DAQ0_BUF0";
457 c.secondBufferReg = "APP0/DAQ0_BUF1";
458 c.daqNumber = 0;
459 return c;
460 }
461};
462struct DeviceFixture2D_DAQ2 : public DeviceFixture2D<DeviceFixture2D_DAQ2> {
463 ConfigParams getCf() {
464 ConfigParams c;
465 c.enableDoubleBufferingReg = "DAQ2/WORD_DUB_BUF_ENA";
466 c.currentBufferNumberReg = "DAQ2/WORD_DUB_BUF_CURR/DUMMY_WRITEABLE";
467 c.firstBufferReg = "APP2/DAQ2_BUF0";
468 c.secondBufferReg = "APP2/DAQ2_BUF1";
469 c.daqNumber = 2;
470 return c;
471 }
472};
473
475 // config variant: double buffering on lowest level
476 simpleCheckExtractedChannels("modulationA");
477 checkExtractedChannels("modulationA", "correctionA");
478}
479
481 // config variant: double buffering applied to logical registers
482 simpleCheckExtractedChannels("modulationC");
483 checkExtractedChannels("modulationC", "correctionC");
484}
485
486/**********************************************************************************************************************/
487
488BOOST_AUTO_TEST_SUITE_END()
Set of AccessMode flags with additional functionality for an easier handling.
Definition AccessMode.h:48
static BackendFactory & getInstance()
Static function to get an instance of factory.
void registerBackendType(const std::string &backendType, boost::shared_ptr< DeviceBackend >(*creatorFunction)(std::string address, std::map< std::string, std::string > parameters), const std::vector< std::string > &sdmParameterNames={}, const std::string &deviceAccessVersion=CHIMERATK_DEVICEACCESS_VERSION)
Register a backend by the name backendType with the given creatorFunction.
boost::shared_ptr< DeviceBackend > createBackend(const std::string &aliasOrUri)
Create a new backend and return the instance as a shared pointer.
Class allows to read/write registers from device.
Definition Device.h:39
OneDRegisterAccessor< UserType > getOneDRegisterAccessor(const RegisterPath &registerPathName, size_t numberOfWords=0, size_t wordOffsetInRegister=0, const AccessModeFlags &flags=AccessModeFlags({})) const
Get a OneDRegisterAccessor object for the given register.
Definition Device.h:276
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
void read(uint64_t bar, uint64_t address, int32_t *data, size_t sizeInBytes) override
Read function to be implemented by backends.
ExceptionDummy(std::string const &mapFileName, const std::string &dataConsistencyKeyDescriptor="")
Group multiple data accessors to efficiently trigger data transfers on the whole group.
void addAccessor(TransferElementAbstractor &accessor)
Add a register accessor to the group.
void read()
Trigger read transfer for all accessors in the group.
Class to test any backend for correct behaviour.
UnifiedBackendTest< typename boost::mpl::push_back< VECTOR_OF_REGISTERS_T, REG_T >::type > addRegister()
Add a register to be used by the test.
size_t writeQueueLength()
size_t nRuntimeErrorCases()
static constexpr auto capabilities
std::vector< std::vector< UserType > > generateValue()
void setForceRuntimeError(bool enable, size_t caseNumber)
ChimeraTK::AccessModeFlags supportedFlags()
std::vector< std::vector< UserType > > getRemoteValue(bool=false)
static uint32_t _currentBufferNumber
Descriptor for the test capabilities for each register.
constexpr TestCapabilities< _syncRead, TestCapability::disabled, _asyncReadInconsistency, _switchReadOnly, _switchWriteOnly, _writeNeverLosesData, _testWriteOnly, _testReadOnly, _testRawTransfer, _testCatalogue, _setRemoteValueIncrementsVersion, _testPartialAccessor > disableForceDataLossWrite() const
DeviceFixture used for the 2D access tests here no overwriting of ExceptionBackend.
boost::shared_ptr< NDRegisterAccessor< uint32_t > > writingBufferNum
boost::shared_ptr< NDRegisterAccessor< float > > buf1
void simpleCheckExtractedChannels(std::string readerAReg)
boost::shared_ptr< DeviceBackend > backdoor
void checkExtractedChannels(std::string readerAReg, std::string readerBReg)
const std::string rawDeviceCdd
boost::shared_ptr< NDRegisterAccessor< float > > buf0
boost::shared_ptr< NDRegisterAccessor< uint32_t > > doubleBufferingEnabled
const std::string lmap
boost::shared_ptr< NDRegisterAccessor< uint32_t > > doubleBufferingEnabled
boost::shared_ptr< DummyForDoubleBuffering > doubleBufDummy
dummy backend used for testing the double buffering handshake.
void read(uint64_t bar, uint64_t address, int32_t *data, size_t sizeInBytes) override
Read function to be implemented by backends.
static thread_local bool blockNextRead[2]
static boost::shared_ptr< DeviceBackend > createInstance(std::string, std::map< std::string, std::string > parameters)
std::string path()
uint32_t minimumUserType
size_t nElementsPerChannel()
BOOST_FIXTURE_TEST_CASE(testSlowReader, DeviceFixture)
BOOST_AUTO_TEST_CASE(testUnified)
std::string lmap