/Users/deen/code/yugabyte-db/src/yb/util/shared_mem.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include "yb/util/shared_mem.h" |
15 | | |
16 | | #if defined(__linux__) |
17 | | #include <mntent.h> |
18 | | #include <sys/utsname.h> |
19 | | #include <sys/syscall.h> |
20 | | #endif |
21 | | #include <fcntl.h> |
22 | | #include <string> |
23 | | #include <glog/logging.h> |
24 | | |
25 | | #include "yb/gutil/casts.h" |
26 | | |
27 | | #include "yb/util/errno.h" |
28 | | #include "yb/util/format.h" |
29 | | #include "yb/util/random_util.h" |
30 | | #include "yb/util/scope_exit.h" |
31 | | #include "yb/util/status_format.h" |
32 | | |
33 | | namespace yb { |
34 | | |
35 | | namespace { |
36 | | |
37 | | // File name prefix for anonymous shared memory files. |
38 | | const char* const kAnonShmFilenamePrefix = "yb_shm_anon"; |
39 | | |
40 | | // Maximum number of temporary file name collisions to tolerate before giving up. |
41 | | // If this limit is being reached, either the files are not being removed |
42 | | // properly, or kAnonShmFilenameRandomnessSize is too small. |
43 | | constexpr int kMaxAnonShmFileCollisionRetries = 20; |
44 | | |
45 | | // The length (in characters) of the random suffix generated for temporary |
46 | | // anonymous shared memory files. |
47 | | constexpr int kAnonShmFilenameRandomnessSize = 16; |
48 | | |
49 | | // Helper method to memory map a shared memory file using its file descriptor. |
50 | 23.4k | Result<void*> MMap(int fd, SharedMemorySegment::AccessMode access_mode, size_t segment_size) { |
51 | 23.4k | void* segment_address = mmap( |
52 | 23.4k | NULL /* addr */, |
53 | 23.4k | segment_size, |
54 | 23.4k | access_mode, |
55 | 23.4k | MAP_SHARED, |
56 | 23.4k | fd, |
57 | 23.4k | 0 /* offset */); |
58 | 23.4k | if (segment_address == MAP_FAILED) { |
59 | 4 | return STATUS_FORMAT( |
60 | 4 | IOError, |
61 | 4 | "Error mapping shared memory segment: errno=$0: $1", |
62 | 4 | errno, |
63 | 4 | ErrnoToString(errno)); |
64 | 4 | } |
65 | 23.4k | return segment_address; |
66 | 23.4k | } |
67 | | |
68 | | // Returns the directory in which all shared memory files should be created. |
69 | 17.3k | std::string GetSharedMemoryDirectory() { |
70 | 17.3k | std::string directory = "/tmp"; |
71 | | |
72 | | #if defined(__linux__) |
73 | | auto* mount_file = fopen("/proc/mounts", "r"); |
74 | | auto se = ScopeExit([&mount_file] { |
75 | | if (mount_file) { |
76 | | fclose(mount_file); |
77 | | } |
78 | | }); |
79 | | |
80 | | if (mount_file) { |
81 | | while (struct mntent* mount_info = getmntent(mount_file)) { |
82 | | // We want a read-write tmpfs mount. |
83 | | if (strcmp(mount_info->mnt_type, "tmpfs") == 0 |
84 | | && hasmntopt(mount_info, MNTOPT_RW) != nullptr) { |
85 | | directory = std::string(mount_info->mnt_dir); |
86 | | |
87 | | // Give preference to /dev/shm. |
88 | | if (directory == "/dev/shm") { |
89 | | break; |
90 | | } |
91 | | } |
92 | | } |
93 | | } else if (errno != ENOENT) { |
94 | | LOG(ERROR) << "Unexpected error when reading /proc/mounts: errno=" << errno |
95 | | << ": " << ErrnoToString(errno); |
96 | | } |
97 | | #endif |
98 | | |
99 | 17.3k | return directory; |
100 | 17.3k | } |
101 | | |
102 | | #if defined(__linux__) |
103 | | int memfd_create() { |
104 | | // This name doesn't really matter, it is only useful for debugging purposes. |
105 | | // See http://man7.org/linux/man-pages/man2/memfd_create.2.html. |
106 | | return narrow_cast<int>(syscall(__NR_memfd_create, kAnonShmFilenamePrefix, 0 /* flags */)); |
107 | | } |
108 | | |
109 | | // Attempts to create a shared memory file using memfd_create. |
110 | | // Returns the file descriptor if successful, otherwise -1 is returned. |
111 | | int TryMemfdCreate() { |
112 | | int fd = -1; |
113 | | |
114 | | struct utsname uts_name; |
115 | | if (uname(&uts_name) == -1) { |
116 | | LOG(ERROR) << "Failed to get kernel name information: errno=" << errno |
117 | | << ": " << ErrnoToString(errno); |
118 | | return fd; |
119 | | } |
120 | | |
121 | | char* kernel_version = uts_name.release; |
122 | | int major_version = 0; |
123 | | int minor_version = 0; |
124 | | char garbage; |
125 | | |
126 | | std::stringstream version_stream; |
127 | | version_stream << kernel_version; |
128 | | version_stream >> major_version >> garbage >> minor_version; |
129 | | |
130 | | // Check that version is > 3.17. Note: memfd_create is available as of 3.17.8, |
131 | | // however we assume that any kernel with version 3.17.x does not have |
132 | | // memfd_create to avoid having to parse the patch version. |
133 | | auto combined_version = major_version * 1000 + minor_version; |
134 | | if (combined_version >= 3017) { |
135 | | LOG(INFO) << "Using memfd_create as a shared memory provider"; |
136 | | |
137 | | fd = memfd_create(); |
138 | | if (fd == -1) { |
139 | | LOG(ERROR) << "Error creating shared memory via memfd_create: errno=" << errno |
140 | | << ": " << ErrnoToString(errno); |
141 | | } |
142 | | } |
143 | | |
144 | | return fd; |
145 | | } |
146 | | #endif |
147 | | |
148 | | // Creates and unlinks a temporary file for use as a shared memory segment. |
149 | | // Returns the file descriptor, or an error if the creation fails. |
150 | 17.3k | Result<int> CreateTempSharedMemoryFile() { |
151 | 17.3k | int fd = -1; |
152 | | |
153 | 17.3k | std::string shared_memory_dir = GetSharedMemoryDirectory(); |
154 | 17.3k | LOG(INFO) << "Using directory " << shared_memory_dir << " to store shared memory objects"; |
155 | | |
156 | 17.3k | for (int attempt = 0; attempt < kMaxAnonShmFileCollisionRetries; ++attempt0 ) { |
157 | 17.3k | std::string temp_file_name = string(kAnonShmFilenamePrefix) + "_" |
158 | 17.3k | + RandomHumanReadableString(kAnonShmFilenameRandomnessSize); |
159 | 17.3k | std::string temp_file_path = shared_memory_dir + "/" + temp_file_name; |
160 | | |
161 | | // We do not use shm_open here, since shm_unlink will automatically close the file |
162 | | // descriptor. We want to unlink with the ability to keep the descriptor open. |
163 | 17.3k | fd = open(temp_file_path.c_str(), O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR); |
164 | 17.3k | if (fd == -1) { |
165 | 0 | if (errno == EEXIST) { |
166 | | // This file already exists, try another random name. |
167 | 0 | continue; |
168 | 0 | } |
169 | | |
170 | 0 | return STATUS_FORMAT( |
171 | 0 | IOError, |
172 | 0 | "Error creating anonymous shared memory file: errno=$0: $1", |
173 | 0 | errno, |
174 | 0 | ErrnoToString(errno)); |
175 | 0 | } |
176 | | |
177 | | // Immediately unlink the file to so it will be removed when all file descriptors close. |
178 | 17.3k | if (unlink(temp_file_path.c_str()) == -1) { |
179 | 0 | LOG(ERROR) << "Leaking shared memory file '" << temp_file_path |
180 | 0 | << "' after failure to unlink: errno=" << errno |
181 | 0 | << ": " << ErrnoToString(errno); |
182 | 0 | } |
183 | 17.3k | break; |
184 | 17.3k | } |
185 | | |
186 | 17.3k | if (fd == -1) { |
187 | 0 | return STATUS_FORMAT( |
188 | 0 | InternalError, |
189 | 0 | "Giving up creating anonymous shared memory segment after $0 failed attempts", |
190 | 0 | kMaxAnonShmFileCollisionRetries); |
191 | 0 | } |
192 | | |
193 | 17.3k | return fd; |
194 | 17.3k | } |
195 | | |
196 | | } // namespace |
197 | | |
198 | 17.3k | Result<SharedMemorySegment> SharedMemorySegment::Create(size_t segment_size) { |
199 | 17.3k | int fd = -1; |
200 | 17.3k | bool auto_close_fd = true; |
201 | 17.3k | auto se = ScopeExit([&fd, &auto_close_fd] { |
202 | 17.3k | if (fd != -1 && auto_close_fd) { |
203 | 1 | close(fd); |
204 | 1 | } |
205 | 17.3k | }); |
206 | | |
207 | | #if defined(__linux__) |
208 | | // Prefer memfd_create over creating temporary files, if available. |
209 | | fd = TryMemfdCreate(); |
210 | | #endif |
211 | | |
212 | 17.3k | if (fd == -1) { |
213 | 17.3k | fd = VERIFY_RESULT(CreateTempSharedMemoryFile()); |
214 | 17.3k | } |
215 | | |
216 | | // If we have made it here, we should have a valid shared memory file. |
217 | 17.3k | DCHECK_NE(fd, -1); |
218 | | |
219 | 17.3k | if (ftruncate(fd, segment_size) == -1) { |
220 | 1 | return STATUS_FORMAT( |
221 | 1 | IOError, |
222 | 1 | "Error truncating shared memory segment: errno=$0: $1", |
223 | 1 | errno, |
224 | 1 | ErrnoToString(errno)); |
225 | 1 | } |
226 | | |
227 | 17.3k | void* segment_address = VERIFY_RESULT(MMap(fd, AccessMode::kReadWrite, segment_size)); |
228 | | |
229 | 0 | auto_close_fd = false; |
230 | 17.3k | return SharedMemorySegment(segment_address, fd, segment_size); |
231 | 17.3k | } |
232 | | |
233 | | Result<SharedMemorySegment> SharedMemorySegment::Open( |
234 | | int fd, |
235 | | AccessMode access_mode, |
236 | 6.11k | size_t segment_size) { |
237 | 6.11k | void* segment_address = VERIFY_RESULT6.10k (MMap(fd, access_mode, segment_size));6.10k |
238 | 0 | return SharedMemorySegment(DCHECK_NOTNULL(segment_address), fd, segment_size); |
239 | 6.11k | } |
240 | | |
241 | | SharedMemorySegment::SharedMemorySegment(void* base_address, int fd, size_t segment_size) |
242 | | : base_address_(base_address), |
243 | | fd_(fd), |
244 | 23.4k | segment_size_(segment_size) { |
245 | 23.4k | } |
246 | | |
247 | | SharedMemorySegment::SharedMemorySegment(SharedMemorySegment&& other) |
248 | | : base_address_(other.base_address_), |
249 | | fd_(other.fd_), |
250 | 123k | segment_size_(other.segment_size_) { |
251 | 123k | other.base_address_ = nullptr; |
252 | 123k | other.fd_ = -1; |
253 | 123k | } |
254 | | |
255 | 129k | SharedMemorySegment::~SharedMemorySegment() { |
256 | 129k | if (base_address_ && munmap(base_address_, segment_size_) == -16.36k ) { |
257 | 0 | LOG(ERROR) << "Failed to unmap shared memory segment: errno=" << errno |
258 | 0 | << ": " << ErrnoToString(errno); |
259 | 0 | } |
260 | | |
261 | 129k | if (fd_ != -1) { |
262 | 6.36k | close(fd_); |
263 | 6.36k | } |
264 | 129k | } |
265 | | |
266 | 515k | void* SharedMemorySegment::GetAddress() const { |
267 | 515k | return base_address_; |
268 | 515k | } |
269 | | |
270 | 2.04k | int SharedMemorySegment::GetFd() const { |
271 | 2.04k | return fd_; |
272 | 2.04k | } |
273 | | |
274 | | } // namespace yb |