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