std::move-able MemoryMapping
[folly.git] / folly / MemoryMapping.cpp
1 /*
2  * Copyright 2014 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "folly/MemoryMapping.h"
18 #include "folly/Format.h"
19
20 #include <fcntl.h>
21 #include <sys/mman.h>
22 #include <sys/types.h>
23 #include <system_error>
24 #include <gflags/gflags.h>
25
26 DEFINE_int64(mlock_chunk_size, 1 << 20,  // 1MB
27              "Maximum bytes to mlock/munlock/munmap at once "
28              "(will be rounded up to PAGESIZE)");
29
30 namespace folly {
31
32 /* protected constructor */
33 MemoryMapping::MemoryMapping()
34   : mapStart_(nullptr)
35   , mapLength_(0)
36   , pageSize_(0)
37   , locked_(false) {
38 }
39
40 MemoryMapping::MemoryMapping(MemoryMapping&& other)
41   : mapStart_(nullptr)
42   , mapLength_(0)
43   , pageSize_(0)
44   , locked_(false) {
45   swap(other);
46 }
47
48 MemoryMapping::MemoryMapping(File file, off_t offset, off_t length,
49                              off_t pageSize)
50   : mapStart_(nullptr)
51   , mapLength_(0)
52   , pageSize_(0)
53   , locked_(false) {
54
55   init(std::move(file), offset, length, pageSize, PROT_READ, false);
56 }
57
58 MemoryMapping::MemoryMapping(const char* name, off_t offset, off_t length,
59                              off_t pageSize)
60   : MemoryMapping(File(name), offset, length, pageSize) { }
61
62 MemoryMapping::MemoryMapping(int fd, off_t offset, off_t length,
63                              off_t pageSize)
64   : MemoryMapping(File(fd), offset, length, pageSize) { }
65
66 void MemoryMapping::init(File file,
67                          off_t offset, off_t length,
68                          off_t pageSize,
69                          int prot,
70                          bool grow) {
71   if (pageSize == 0) {
72     pageSize = sysconf(_SC_PAGESIZE);
73   }
74   CHECK_GT(pageSize, 0);
75   CHECK_EQ(pageSize & (pageSize - 1), 0);  // power of two
76   CHECK_GE(offset, 0);
77   pageSize_ = pageSize;
78
79   // Round down the start of the mapped region
80   size_t skipStart = offset % pageSize;
81   offset -= skipStart;
82
83   file_ = std::move(file);
84   mapLength_ = length;
85   if (mapLength_ != -1) {
86     mapLength_ += skipStart;
87
88     // Round up the end of the mapped region
89     mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
90   }
91
92   // stat the file
93   struct stat st;
94   CHECK_ERR(fstat(file_.fd(), &st));
95   off_t remaining = st.st_size - offset;
96   if (mapLength_ == -1) {
97     length = mapLength_ = remaining;
98   } else {
99     if (length > remaining) {
100       if (grow) {
101         PCHECK(0 == ftruncate(file_.fd(), offset + length))
102           << "ftructate() failed, couldn't grow file";
103         remaining = length;
104       } else {
105         length = remaining;
106       }
107     }
108     if (mapLength_ > remaining) mapLength_ = remaining;
109   }
110
111   if (length == 0) {
112     mapLength_ = 0;
113     mapStart_ = nullptr;
114   } else {
115     unsigned char* start = static_cast<unsigned char*>(
116       mmap(nullptr, mapLength_, prot, MAP_SHARED, file_.fd(), offset));
117     PCHECK(start != MAP_FAILED)
118       << " offset=" << offset
119       << " length=" << mapLength_;
120     mapStart_ = start;
121     data_.reset(start + skipStart, length);
122   }
123 }
124
125 namespace {
126
127 off_t memOpChunkSize(off_t length, off_t pageSize) {
128   off_t chunkSize = length;
129   if (FLAGS_mlock_chunk_size <= 0) {
130     return chunkSize;
131   }
132
133   chunkSize = FLAGS_mlock_chunk_size;
134   off_t r = chunkSize % pageSize;
135   if (r) {
136     chunkSize += (pageSize - r);
137   }
138   return chunkSize;
139 }
140
141 /**
142  * Run @op in chunks over the buffer @mem of @bufSize length.
143  *
144  * Return:
145  * - success: true + amountSucceeded == bufSize (op success on whole buffer)
146  * - failure: false + amountSucceeded == nr bytes on which op succeeded.
147  */
148 bool memOpInChunks(std::function<int(void*, size_t)> op,
149                    void* mem, size_t bufSize, off_t pageSize,
150                    size_t& amountSucceeded) {
151   // unmap/mlock/munlock take a kernel semaphore and block other threads from
152   // doing other memory operations. If the size of the buffer is big the
153   // semaphore can be down for seconds (for benchmarks see
154   // http://kostja-osipov.livejournal.com/42963.html).  Doing the operations in
155   // chunks breaks the locking into intervals and lets other threads do memory
156   // operations of their own.
157
158   size_t chunkSize = memOpChunkSize(bufSize, pageSize);
159
160   char* addr = static_cast<char*>(mem);
161   amountSucceeded = 0;
162
163   while (amountSucceeded < bufSize) {
164     size_t size = std::min(chunkSize, bufSize - amountSucceeded);
165     if (op(addr + amountSucceeded, size) != 0) {
166       return false;
167     }
168     amountSucceeded += size;
169   }
170
171   return true;
172 }
173
174 }  // anonymous namespace
175
176 bool MemoryMapping::mlock(LockMode lock) {
177   size_t amountSucceeded = 0;
178   locked_ = memOpInChunks(::mlock, mapStart_, mapLength_, pageSize_,
179                           amountSucceeded);
180   if (locked_) {
181     return true;
182   }
183
184   auto msg(folly::format(
185     "mlock({}) failed at {}",
186     mapLength_, amountSucceeded).str());
187
188   if (lock == LockMode::TRY_LOCK && (errno == EPERM || errno == ENOMEM)) {
189     PLOG(WARNING) << msg;
190   } else {
191     PLOG(FATAL) << msg;
192   }
193
194   // only part of the buffer was mlocked, unlock it back
195   if (!memOpInChunks(::munlock, mapStart_, amountSucceeded, pageSize_,
196                      amountSucceeded)) {
197     PLOG(WARNING) << "munlock()";
198   }
199
200   return false;
201 }
202
203 void MemoryMapping::munlock(bool dontneed) {
204   if (!locked_) return;
205
206   size_t amountSucceeded = 0;
207   if (!memOpInChunks(::munlock, mapStart_, mapLength_, pageSize_,
208                      amountSucceeded)) {
209     PLOG(WARNING) << "munlock()";
210   }
211   if (mapLength_ && dontneed &&
212       ::madvise(mapStart_, mapLength_, MADV_DONTNEED)) {
213     PLOG(WARNING) << "madvise()";
214   }
215   locked_ = false;
216 }
217
218 void MemoryMapping::hintLinearScan() {
219   advise(MADV_SEQUENTIAL);
220 }
221
222 MemoryMapping::~MemoryMapping() {
223   if (mapLength_) {
224     size_t amountSucceeded = 0;
225     if (!memOpInChunks(::munmap, mapStart_, mapLength_, pageSize_,
226                        amountSucceeded)) {
227       PLOG(FATAL) << folly::format(
228         "munmap({}) failed at {}",
229         mapLength_, amountSucceeded).str();
230     }
231   }
232 }
233
234 void MemoryMapping::advise(int advice) const {
235   if (mapLength_ && ::madvise(mapStart_, mapLength_, advice)) {
236     PLOG(WARNING) << "madvise()";
237   }
238 }
239
240 MemoryMapping& MemoryMapping::operator=(MemoryMapping other) {
241   swap(other);
242   return *this;
243 }
244
245 void MemoryMapping::swap(MemoryMapping& other) {
246   using std::swap;
247   swap(this->file_, other.file_);
248   swap(this->mapStart_, other.mapStart_);
249   swap(this->mapLength_, other.mapLength_);
250   swap(this->pageSize_, other.pageSize_);
251   swap(this->locked_, other.locked_);
252   swap(this->data_, other.data_);
253 }
254
255 WritableMemoryMapping::WritableMemoryMapping(
256     File file, off_t offset, off_t length, off_t pageSize) {
257   init(std::move(file), offset, length, pageSize, PROT_READ | PROT_WRITE, true);
258 }
259
260 void swap(MemoryMapping& a, MemoryMapping& b) { a.swap(b); }
261
262 }  // namespace folly