ChimeraTK-DeviceAccess  03.18.00
SharedMemoryManager.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 namespace ChimeraTK {
7 
8  namespace {
9  // Proxy class to add try_lock_for() to boost::interprocess::named_mutex::named_mutex, which is not present on older
10  // BOOST versions like on Ubuntu 20.04. This can be removed and replaced with boost::interprocess::named_mutex once
11  // we are fully on Ubuntu 24.04 or newer.
12  class IpcNamedMutex {
13  public:
14  explicit IpcNamedMutex(boost::interprocess::named_mutex& mx) : _mx(mx) {}
15 
16  template<typename Duration>
17  // NOLINTNEXTLINE(readability-identifier-naming)
18  bool try_lock_for(const Duration& dur) {
19  auto abs_time = boost::posix_time::microsec_clock::universal_time();
20  auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
21  auto bmillis = boost::posix_time::milliseconds(millis);
22  abs_time += bmillis;
23  return _mx.timed_lock(abs_time);
24  }
25 
26  template<typename Duration>
27  // NOLINTNEXTLINE(readability-identifier-naming)
28  bool try_lock_until(const Duration& abs_time) {
29  return _mx.timed_lock(abs_time);
30  }
31 
32  void lock() { _mx.lock(); }
33 
34  void unlock() { _mx.unlock(); }
35 
36  // NOLINTNEXTLINE(readability-identifier-naming)
37  bool try_lock() { return _mx.try_lock(); }
38 
39  private:
40  boost::interprocess::named_mutex& _mx;
41  };
42  } // namespace
43 
44  // Construct/deconstruct
45  SharedDummyBackend::SharedMemoryManager::SharedMemoryManager(
46  SharedDummyBackend& sharedDummyBackend_, const std::string& instanceId, const std::string& mapFileName)
47  : sharedDummyBackend(sharedDummyBackend_), userHash(std::to_string(std::hash<std::string>{}(getUserName()))),
48  mapFileHash(std::to_string(std::hash<std::string>{}(mapFileName))),
49  instanceIdHash(std::to_string(std::hash<std::string>{}(instanceId))),
50  name("ChimeraTK_SharedDummy_" + instanceIdHash + "_" + mapFileHash + "_" + userHash),
51  segment(boost::interprocess::open_or_create, name.c_str(), getRequiredMemoryWithOverhead()),
52  sharedMemoryIntAllocator(segment.get_segment_manager()),
53  interprocessMutex(boost::interprocess::open_or_create, name.c_str()) {
54  retry:
55  // scope for lock guard of the interprocess mutex
56  {
57  IpcNamedMutex proxy(interprocessMutex);
58  std::unique_lock<IpcNamedMutex> lock(proxy, std::defer_lock);
59  auto ok = lock.try_lock_for(std::chrono::milliseconds(2000));
60  if(!ok) {
61  std::cerr << "SharedDummyBackend: stale lock detected, removing mutex... " << std::endl;
62 
63  // named_mutex has no (move) assignment operator, so we need to work around here (placement delete and new)
64  boost::interprocess::named_mutex::remove(name.c_str());
65  interprocessMutex.~named_mutex();
66  new(&interprocessMutex) boost::interprocess::named_mutex(boost::interprocess::open_or_create, name.c_str());
67 
68  goto retry;
69  }
70 
71  pidSet = findOrConstructVector(SHARED_MEMORY_PID_SET_NAME, 0);
72 
73  // Clean up pidSet, if needed
74  bool reInitRequired = checkPidSetConsistency();
75 
76  // If only "zombie" processes were found in PidSet,
77  // reset data entries in shared memory.
78  if(reInitRequired) {
79  reInitMemory();
80  }
81 
82  // Get memory item for version number
83  requiredVersion = segment.find_or_construct<unsigned>(SHARED_MEMORY_REQUIRED_VERSION_NAME)(0);
84 
85  // Protect against too many accessing processes to prevent
86  // overflow of pidSet in shared memory.
87  if(pidSet->size() >= SHARED_MEMORY_N_MAX_MEMBER) {
88  std::string errMsg{"Maximum number of accessing members reached."};
89  throw ChimeraTK::runtime_error(errMsg);
90  }
91  InterruptDispatcherInterface::cleanupShm(segment, pidSet);
92 
93  pidSet->emplace_back(static_cast<int32_t>(getOwnPID()));
94  } // releases the lock
95  this->intDispatcherIf = boost::movelib::unique_ptr<InterruptDispatcherInterface>(
96  new InterruptDispatcherInterface(sharedDummyBackend, segment, interprocessMutex));
97  }
98 
99  SharedDummyBackend::SharedMemoryManager::~SharedMemoryManager() {
100  // stop and delete dispatcher thread first since it uses shm and mutex
101  intDispatcherIf.reset();
102  size_t pidSetSize;
103  try {
104  // The scope of the try-block is the scope of the lock_guard, which can throw when locking.
105  // All the lines in the try-block have to be executed under the lock, although not everything
106  // might be throwing.
107 
108  // lock guard with the interprocess mutex
109  std::lock_guard<boost::interprocess::named_mutex> lock(interprocessMutex);
110 
111  // Clean up
112  checkPidSetConsistency();
113 
114  auto ownPid = static_cast<int32_t>(getOwnPID());
115  for(auto it = pidSet->begin(); it != pidSet->end();) {
116  if(*it == ownPid) {
117  it = pidSet->erase(it);
118  }
119  else {
120  ++it;
121  }
122  }
123  pidSetSize = pidSet->size();
124  }
125  catch(boost::interprocess::interprocess_exception&) {
126  // interprocess_exception is only thrown if something seriously went wrong.
127  // In this case we don't want anyone to catch it but terminate.
128  std::terminate();
129  }
130  // If size of pidSet is 0 (i.e, the instance belongs to the last accessing
131  // process), destroy shared memory and the interprocess mutex
132  if(pidSetSize == 0) {
133  boost::interprocess::shared_memory_object::remove(name.c_str());
134  boost::interprocess::named_mutex::remove(name.c_str());
135  }
136  }
137 
138  // Member functions
139  SharedMemoryVector* SharedDummyBackend::SharedMemoryManager::findOrConstructVector(
140  const std::string& objName, const size_t size) {
141  SharedMemoryVector* vector =
142  segment.find_or_construct<SharedMemoryVector>(objName.c_str())(size, 0, sharedMemoryIntAllocator);
143 
144  return vector;
145  }
146 
147  size_t SharedDummyBackend::SharedMemoryManager::getRequiredMemoryWithOverhead() {
148  // Note: This uses _barSizeInBytes to determine number of vectors used,
149  // as it is initialized when this method gets called in the init list.
150  return SHARED_MEMORY_OVERHEAD_PER_VECTOR * sharedDummyBackend._barSizesInBytes.size() +
151  SHARED_MEMORY_CONST_OVERHEAD + sharedDummyBackend.getTotalRegisterSizeInBytes() + sizeof(ShmForSems);
152  }
153 
154  std::pair<size_t, size_t> SharedDummyBackend::SharedMemoryManager::getInfoOnMemory() {
155  return std::make_pair(segment.get_size(), segment.get_free_memory());
156  }
157 
158  bool SharedDummyBackend::SharedMemoryManager::checkPidSetConsistency() {
159  unsigned pidSetSizeBeforeCleanup = pidSet->size();
160 
161  for(auto it = pidSet->begin(); it != pidSet->end();) {
162  if(!processExists(*it)) {
163  // std::cout << "Nonexistent PID " << *it << " found. " <<std::endl;
164  it = pidSet->erase(it);
165  }
166  else {
167  it++;
168  }
169  }
170 
171  return pidSetSizeBeforeCleanup != 0 && pidSet->empty();
172  }
173 
174  void SharedDummyBackend::SharedMemoryManager::reInitMemory() {
175  std::vector<std::string> nameList = listNamedElements();
176 
177  for(auto& item : nameList) {
178  if(item == SHARED_MEMORY_REQUIRED_VERSION_NAME) {
179  segment.destroy<unsigned>(item.c_str());
180  }
181  // reset the BAR vectors in shm.
182  // Note, InterruptDispatcherInterface uses unique_instance mechanism so it is not affected here
183  else if(item != SHARED_MEMORY_PID_SET_NAME) {
184  segment.destroy<SharedMemoryVector>(item.c_str());
185  }
186  }
187  InterruptDispatcherInterface::cleanupShm(segment);
188  }
189 
190  std::vector<std::string> SharedDummyBackend::SharedMemoryManager::listNamedElements() {
191  std::vector<std::string> list(segment.get_num_named_objects());
192 
193  for(auto seg = segment.named_begin(); seg != segment.named_end(); ++seg) {
194  list.emplace_back(seg->name());
195  }
196  return list;
197  }
198 
199 } /* namespace ChimeraTK */
SharedDummyBackend.h
ChimeraTK::runtime_error
Exception thrown when a runtime error has occured.
Definition: Exception.h:18
ChimeraTK::DataValidity::ok
@ ok
processExists
bool processExists(unsigned pid)
Definition: ProcessManagement.cpp:16
ChimeraTK::SHARED_MEMORY_N_MAX_MEMBER
const int SHARED_MEMORY_N_MAX_MEMBER
Definition: SharedDummyBackend.h:36
getOwnPID
unsigned getOwnPID()
Definition: ProcessManagement.cpp:20
SharedMemoryVector
boost::interprocess::vector< int32_t, ShmemAllocator > SharedMemoryVector
Definition: SharedDummyBackend.h:30
ChimeraTK
Definition: DummyBackend.h:16
ChimeraTK::to_string
std::string to_string(Boolean &value)
Definition: SupportedUserTypes.h:59
getUserName
std::string getUserName()
Definition: ProcessManagement.cpp:24