ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
SharedDummyBackend.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
5
6#include "BackendFactory.h"
7#include "Exception.h"
8#include "MapFileParser.h"
9#include "parserUtilities.h"
10#include "Utilities.h"
11
12#include <boost/filesystem.hpp>
13#include <boost/lambda/lambda.hpp>
14
15#include <algorithm>
16#include <cstring>
17#include <functional>
18#include <regex>
19#include <sstream>
20
21namespace ChimeraTK {
22
24 size_t instanceIdHash, const std::string& mapFileName, const std::string& dataConsistencyKeyDescriptor)
25 : DummyBackendBase(mapFileName, dataConsistencyKeyDescriptor), _mapFile(mapFileName),
26 _barSizesInBytes(getBarSizesInBytesFromRegisterMapping()) {
27 retry:
28 try {
29 sharedMemoryManager = std::make_unique<SharedMemoryManager>(*this, instanceIdHash, mapFileName);
30 }
31 catch(boost::interprocess::lock_exception&) {
32 std::cerr << "SharedDummyBackend: boost::interprocess error, clearing shared memory segment." << std::endl;
33 // remove shared memory and mutex
34 std::string name = Utilities::createShmName(instanceIdHash, mapFileName, getUserName());
35 boost::interprocess::shared_memory_object::remove(name.c_str());
36 boost::interprocess::named_mutex::remove(name.c_str());
37 goto retry;
38 }
39
40 setupBarContents();
41 }
42
44 // Destroy the InterruptDispatcherInterface first because it keeps a reference to this backend.
45 sharedMemoryManager->intDispatcherIf.reset();
46 // all other objects clean up for themselves when they go out of scope.
47 }
48
49 // Construct a segment for each bar and set required size
50 void SharedDummyBackend::setupBarContents() {
51 for(auto& _barSizesInByte : _barSizesInBytes) {
52 std::string barName = SHARED_MEMORY_BAR_PREFIX + std::to_string(_barSizesInByte.first);
53
54 size_t barSizeInWords = (_barSizesInByte.second + sizeof(int32_t) - 1) / sizeof(int32_t);
55
56 try {
57 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
58 _barContents[_barSizesInByte.first] = sharedMemoryManager->findOrConstructVector(barName, barSizeInWords);
59 }
60 catch(boost::interprocess::bad_alloc&) {
61 // Clean up
62 sharedMemoryManager.reset();
63
64 std::string errMsg{"Could not allocate shared memory while constructing registers. "
65 "Please file a bug report at "
66 "https://github.com/ChimeraTK/DeviceAccess."};
67 throw ChimeraTK::logic_error(errMsg);
68 }
69 } /* for(barSizesInBytesIter) */
70 }
71
75
77 _opened = false;
78 }
79
80 void SharedDummyBackend::read(uint64_t bar, uint64_t address, int32_t* data, size_t sizeInBytes) {
81 if(!_opened) {
82 throw ChimeraTK::logic_error("Device is closed.");
83 }
85 checkSizeIsMultipleOfWordSize(sizeInBytes);
86 uint64_t wordBaseIndex = address / sizeof(int32_t);
87
88 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
89 for(uint64_t wordIndex = 0; wordIndex < sizeInBytes / sizeof(int32_t); ++wordIndex) {
90 TRY_REGISTER_ACCESS(data[wordIndex] = _barContents[bar]->at(wordBaseIndex + wordIndex););
91 }
92 }
93
94 void SharedDummyBackend::write(uint64_t bar, uint64_t address, int32_t const* data, size_t sizeInBytes) {
95 if(!_opened) {
96 throw ChimeraTK::logic_error("Device is closed.");
97 }
99 checkSizeIsMultipleOfWordSize(sizeInBytes);
100 uint64_t wordBaseIndex = address / sizeof(int32_t);
101
102 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
103
104 for(uint64_t wordIndex = 0; wordIndex < sizeInBytes / sizeof(int32_t); ++wordIndex) {
105 TRY_REGISTER_ACCESS(_barContents[bar]->at(wordBaseIndex + wordIndex) = data[wordIndex];);
106 }
107 }
108
110 std::stringstream info;
111 info << "SharedDummyBackend"; // TODO add map file name again
112 return info.str();
113 }
114
115 size_t SharedDummyBackend::getTotalRegisterSizeInBytes() const {
116 size_t totalRegSize = 0;
117 for(const auto& pair : _barSizesInBytes) {
118 totalRegSize += pair.second;
119 }
120 return totalRegSize;
121 }
122
123 void SharedDummyBackend::checkSizeIsMultipleOfWordSize(size_t sizeInBytes) {
124 if(sizeInBytes % sizeof(int32_t)) {
125 throw ChimeraTK::logic_error("Read/write size has to be a multiple of 4");
126 }
127 }
128
129 boost::shared_ptr<DeviceBackend> SharedDummyBackend::createInstance(
130 std::string address, std::map<std::string, std::string> parameters) {
131 // create instanceId from address and parameters.
132 // note, this approach is not perfect: in case two different device URIs are created by reordering their parameters
133 // and URIs are used from same process, the BackendFactory will create two SharedDummyBackend instances (since it
134 // uses the URI string as a key). The order of parameters is lost in the parameters map, so we will here assume the
135 // same instanceIdHash. That will lead further down to some error, since we will try to access the same shared
136 // memory segment twice in the same process.
137 size_t instanceIdHash = Utilities::shmDummyInstanceIdHash(address, parameters);
138 std::string mapFileName = parameters["map"];
139 if(mapFileName.empty()) {
140 throw ChimeraTK::logic_error("No map file name given.");
141 }
142
143 // when the factory is used to create the dummy device, mapfile path in the
144 // dmap file is relative to the dmap file location. Converting the relative
145 // mapFile path to an absolute path avoids issues when the dmap file is not
146 // in the working directory of the application.
147 return returnInstance<SharedDummyBackend>(
148 address, instanceIdHash, convertPathRelativeToDmapToAbs(mapFileName), parameters["DataConsistencyKeys"]);
149 }
150
151 std::string SharedDummyBackend::convertPathRelativeToDmapToAbs(const std::string& mapfileName) {
153 std::string absPathToDmapDir = parserUtilities::convertToAbsolutePath(dmapDir);
154 // the map file is relative to the dmap file location. Convert the relative
155 // mapfilename to an absolute path
156 boost::filesystem::path absPathToMapFile{parserUtilities::concatenatePaths(absPathToDmapDir, mapfileName)};
157 // Possible ./, ../ elements are removed, as the path may be constructed
158 // differently in different client applications
159 return boost::filesystem::canonical(absPathToMapFile).string();
160 }
161
163 this->sharedMemoryManager->intDispatcherIf->triggerInterrupt(interruptNumber);
164
165 // Since VersionNumber consistency is defined only per process, we generate a new one here
166 // and also in the triggered process
167 return {};
168 }
169
170 SharedDummyBackend::InterruptDispatcherInterface::InterruptDispatcherInterface(SharedDummyBackend& backend,
171 boost::interprocess::managed_shared_memory& shm, boost::interprocess::named_mutex& shmMutex)
172 : _shmMutex(shmMutex), _backend(backend) {
173 // locking not needed, already defined as atomic
174 _semBuf = shm.find_or_construct<ShmForSems>(boost::interprocess::unique_instance)();
175 _semId = getOwnPID();
176
177 _dispatcherThread = boost::movelib::unique_ptr<InterruptDispatcherThread>(new InterruptDispatcherThread(this));
178 }
179
180 SharedDummyBackend::InterruptDispatcherInterface::~InterruptDispatcherInterface() {
181 // stop thread and remove semaphore on destruction
182 _dispatcherThread.reset(); // stops and deletes thread which uses semaphore
183 try {
184 // The scope of the try-block is the scope of the lock_guard, which can throw when locking.
185 // All the lines in the try-block have to be executed under the lock, although not everything
186 // might be throwing.
187
188 std::lock_guard<boost::interprocess::named_mutex> lock(_shmMutex);
189 _semBuf->removeSem(_semId);
190 }
191 catch(boost::interprocess::interprocess_exception&) {
192 // interprocess_exception is only thrown if something seriously went wrong.
193 // In this case we don't want anyone to catch it but terminate.
194 std::terminate();
195 }
196 }
197
198 void SharedDummyBackend::InterruptDispatcherInterface::cleanupShm(boost::interprocess::managed_shared_memory& shm) {
199 shm.destroy<SharedMemoryVector>(boost::interprocess::unique_instance);
200 }
201 void SharedDummyBackend::InterruptDispatcherInterface::cleanupShm(
202 boost::interprocess::managed_shared_memory& shm, PidSet* pidSet) {
203 ShmForSems* semBuf = shm.find_or_construct<ShmForSems>(boost::interprocess::unique_instance)();
204 semBuf->cleanup(pidSet);
205 }
206
207 void SharedDummyBackend::InterruptDispatcherInterface::triggerInterrupt(uint32_t intNumber) {
208 std::list<boost::interprocess::interprocess_semaphore*> semList;
209 {
210 std::lock_guard<boost::interprocess::named_mutex> lock(_shmMutex);
211 // find list of processes and their semaphores
212 // update interrupt info.
213 semList = _semBuf->findSems(intNumber, true);
214 }
215 // trigger the interrupts
216 for(auto* sem : semList) {
217#ifdef _DEBUG
218 std::cout << " InterruptDispatcherInterface::triggerInterrupt: post sem for interrupt: " << intNumber
219 << std::endl;
220 _semBuf->print();
221#endif
222 sem->post();
223 }
224 }
225
226 SharedDummyBackend::InterruptDispatcherThread::InterruptDispatcherThread(
227 InterruptDispatcherInterface* dispatcherInterf)
228 : _dispatcherInterf(dispatcherInterf), _semId(dispatcherInterf->_semId), _semShm(dispatcherInterf->_semBuf) {
229 _thr = boost::thread(&InterruptDispatcherThread::run, this);
230 }
231
232 SharedDummyBackend::InterruptDispatcherThread::~InterruptDispatcherThread() {
233 stop();
234 try {
235 _thr.join();
236 }
237
238 catch(boost::system::system_error&) {
239 std::terminate();
240 }
241 }
242
243 void SharedDummyBackend::InterruptDispatcherThread::run() {
244 // copy interrupt counts at the beginning, and then
245 // only look for different values. count up all values till they match
246 // map (controller,intNumber) -> count
247 // use map instead of vector because search is more efficient
248 std::map<std::pair<int, int>, std::uint32_t> lastInterruptState;
249 {
250 std::lock_guard<boost::interprocess::named_mutex> lock(_dispatcherInterf->_shmMutex);
251 for(auto& entry : _semShm->interruptEntries) {
252 assert(entry._controllerId == 0);
253 if(!entry.used) continue;
254 lastInterruptState[std::make_pair(entry._controllerId, entry._intNumber)] = entry._counter;
255 }
256 // we register a semaphore only after being ready
257 _sem = _semShm->addSem(_semId);
258 _started = true;
259 }
260
261 // local copy of shm contents, used to reduce lock time
262 InterruptEntry interruptEntries[maxInterruptEntries];
263
264 while(!_stop) {
265 _sem->wait();
266 {
267 std::lock_guard<boost::interprocess::named_mutex> lock(_dispatcherInterf->_shmMutex);
268 std::memcpy(interruptEntries, _semShm->interruptEntries, sizeof(interruptEntries));
269 }
270 for(auto& entry : interruptEntries) {
271 assert(entry._controllerId == 0);
272 if(!entry.used) continue;
273
274 // find match with controllerId and intNumber
275 auto key = std::make_pair(entry._controllerId, entry._intNumber);
276 auto it = lastInterruptState.find(key);
277 if(it != lastInterruptState.end()) {
278 while(it->second != entry._counter) {
279 // call trigger/dispatch
280#ifdef _DEBUG
281 std::cout << "existing interrupt event for x,y = " << entry._controllerId << ", " << entry._intNumber
282 << std::endl;
283#endif
284 handleInterrupt(entry._intNumber);
285 it->second++;
286 }
287 }
288 else {
289 // new interrupt number
290 // call trigger/dispatch count times
291#ifdef _DEBUG
292 std::cout << "count = " << entry._counter << " interrupt events for x,y = " << entry._controllerId << ", "
293 << entry._intNumber << std::endl;
294#endif
295 handleInterrupt(entry._intNumber);
296 lastInterruptState[key] = entry._counter;
297 }
298 }
299 }
300 }
301
302 void SharedDummyBackend::InterruptDispatcherThread::stop() noexcept {
303 _stop = true;
304 // we must wait until the semaphore is registered
305 try {
306 while(!_started) {
307 boost::this_thread::sleep_for(boost::chrono::milliseconds{10});
308 }
309 }
310 catch(const boost::thread_interrupted&) {
311 // Simply suppress the thread_interrupted here. This function is only called
312 // within a destructor, which anyway would have terminated the program when it sees the exception.
313 // There are two possible scenarios what can happen now.
314 // 1. _started is set and the destruction can continue normally.
315 // 2. _started is not set yet an we don't know if the semaphore is in the correct state.
316 // Again there are two possibilities:
317 // 2a_ The semaphore was set correctly and the destructor continues normally.
318 // 2b_ sem->post() throws and terminate() is called (which otherwise would have been called from the escaping
319 // thread_interrupted
320 }
321 catch(const boost::system::system_error&) {
322 // if something went really wrong we terminate here
323 std::terminate();
324 }
325
326 try {
327 _sem->post();
328 }
329 catch(const boost::interprocess::interprocess_exception&) {
330 std::terminate();
331 }
332 }
333
334 void SharedDummyBackend::InterruptDispatcherThread::handleInterrupt(uint32_t interruptNumber) {
335 SharedDummyBackend& backend = _dispatcherInterf->_backend;
336 auto asyncDomain = boost::dynamic_pointer_cast<async::DomainImpl<std::nullptr_t>>(
337 backend._asyncDomainsContainer.getDomain(interruptNumber));
338
339 if(!asyncDomain) {
340 // If the asyncDomain is not there, the pointer in the _asyncDomainsContainer must be nullptr as well.
341 // Otherwise the dynamic cast failed, which should never happen.
342 assert(!backend._asyncDomainsContainer.getDomain(interruptNumber));
343 return;
344 }
345
346 asyncDomain->distribute(nullptr);
347 }
348
349 SharedDummyBackend::ShmForSems::Sem* SharedDummyBackend::ShmForSems::addSem(SemId semId) {
350 // look up whether semaphore for id already exists and return error
351 for(auto& entry : semEntries) {
352 if(entry.used && entry.semId == semId) {
353 throw logic_error("error: semId already exists - check assumption about identifiers!");
354 }
355 }
356
357 for(auto& entry : semEntries) {
358 if(!entry.used) {
359 entry.semId = semId;
360 entry.used = true;
361 // It would be nice to also reset semaphores state, but if
362 // interrupt dispatcher thread which last used it terminated property its not necessary
363 // (since it calls post in destructor)
364 return &entry.s;
365 }
366 }
367 // increasing size not implemented
368 throw runtime_error("error: semaphore array full - increase maxSems!");
369 }
370
371 bool SharedDummyBackend::ShmForSems::removeSem(SemId semId) {
372 bool found = false;
373 for(auto& entry : semEntries) {
374 if(entry.used && entry.semId == semId) {
375 entry.used = false;
376 found = true;
377 break;
378 }
379 }
380 return found;
381 }
382 void SharedDummyBackend::ShmForSems::cleanup(PidSet* pidSet) {
383 for(auto& entry : semEntries) {
384 if(entry.used) {
385 if(std::find(std::begin(*pidSet), std::end(*pidSet), (int32_t)entry.semId) == std::end(*pidSet)) {
386 entry.used = false;
387 }
388 }
389 }
390 }
391
392 void SharedDummyBackend::ShmForSems::addInterrupt(uint32_t interruptNumber) {
393 bool found = false;
394 for(auto& entry : interruptEntries) {
395 if(entry.used && entry._controllerId == 0 && static_cast<uint32_t>(entry._intNumber) == interruptNumber) {
396 entry._counter++;
397 found = true;
398 break;
399 }
400 }
401 if(!found) {
402 bool added = false;
403 for(auto& entry : interruptEntries) {
404 if(!entry.used) {
405 entry.used = true;
406 entry._controllerId = 0;
407 entry._intNumber = static_cast<int>(interruptNumber);
408 entry._counter = 1;
409 added = true;
410 break;
411 }
412 }
413 if(!added) {
414 throw runtime_error("no place left in interruptEntries!");
415 }
416 }
417 }
418
419 std::list<SharedDummyBackend::ShmForSems::Sem*> SharedDummyBackend::ShmForSems::findSems(
420 uint32_t interruptNumber, bool update) {
421 std::list<Sem*> ret;
422 for(auto& entry : semEntries) {
423 if(entry.used) {
424 // we simply return all semaphores
425 ret.push_back(&entry.s);
426 }
427 }
428 if(update) addInterrupt(interruptNumber);
429 return ret;
430 }
431
432 void SharedDummyBackend::ShmForSems::print() {
433 std::cout << "shmem contents: " << std::endl;
434 for(auto& entry : semEntries) {
435 if(entry.used) std::cout << "sem : " << entry.semId << std::endl;
436 }
437 for(auto& entry : interruptEntries) {
438 if(entry.used) {
439 std::cout << "interrupt : " << entry._controllerId << "," << entry._intNumber << " count = " << entry._counter
440 << std::endl;
441 }
442 }
443
444 std::cout << std::endl;
445 }
446
447} // Namespace ChimeraTK
#define TRY_REGISTER_ACCESS(COMMAND)
std::string getUserName()
unsigned getOwnPID()
boost::interprocess::vector< int32_t, ShmemAllocator > PidSet
boost::interprocess::vector< int32_t, ShmemAllocator > SharedMemoryVector
static BackendFactory & getInstance()
Static function to get an instance of factory.
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
void checkActiveException() final
Function to be called by backends when needing to check for an active exception.
std::atomic< bool > _opened
flag if backend is opened
Base class for DummyBackends, provides common functionality.
The shared dummy device opens a mapping file defining the registers and implements them in shared mem...
void read(uint64_t bar, uint64_t address, int32_t *data, size_t sizeInBytes) override
Read function to be implemented by backends.
SharedDummyBackend(size_t instanceIdHash, const std::string &mapFileName, const std::string &dataConsistencyKeyDescriptor="")
void open() override
Open the device.
std::string readDeviceInfo() override
Return a device information string containing hardware details like the firmware version number or th...
void closeImpl() override
All backends derrived from NumericAddressedBackend must implement closeImpl() instead of close.
VersionNumber triggerInterrupt(uint32_t interruptNumber) override
Simulate the arrival of an interrupt.
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
void write(uint64_t bar, uint64_t address, int32_t const *data, size_t sizeInBytes) override
Write function to be implemented by backends.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
std::size_t shmDummyInstanceIdHash(const std::string &address, const std::map< std::string, std::string > &parameters)
Generates shm dummy instanceId hash from address and parameter map, Intended for use with parseDevice...
std::string createShmName(std::size_t instanceIdHash, const std::string &mapFileName, const std::string &userName)
Generates shm dummy name from parameter hashes.
std::string extractDirectory(std::string const &path)
Returns the path to the directory containing the file provided as the input parameter.
std::string concatenatePaths(const std::string &path1, const std::string &path2)
Concatenates two given paths using custom rules.
std::string convertToAbsolutePath(std::string const &relativePath)
Converts a relative path to its absolute path.
std::string getDMapFilePath()
Returns the dmap file name which the library currently uses for looking up device(alias) names.
std::string to_string(const std::string &v)