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