Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / apc-file-storage.cpp
blob43042280f9c80ce915dc98541962376dd9196908
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/apc-file-storage.h"
18 #include "hphp/util/alloc.h"
19 #include "hphp/util/compatibility.h"
20 #include "hphp/util/logger.h"
21 #include "hphp/util/numa.h"
22 #include "hphp/util/timer.h"
24 #include "hphp/runtime/base/apc-stats.h"
25 #include "hphp/runtime/base/builtin-functions.h"
26 #include "hphp/runtime/base/runtime-option.h"
27 #include "hphp/runtime/ext/apc/ext_apc.h"
28 #include "hphp/runtime/server/server-stats.h"
30 #include <folly/portability/SysMman.h>
32 #if !defined(HAVE_POSIX_FALLOCATE) && \
33 (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L)
34 # define HAVE_POSIX_FALLOCATE 1
35 #endif
37 namespace HPHP {
39 //////////////////////////////////////////////////////////////////////
41 APCFileStorage s_apc_file_storage;
43 void APCFileStorage::enable(const std::string& prefix, size_t chunkSize) {
44 std::lock_guard<std::mutex> lock(m_lock);
45 if (chunkSize <= PaddingSize) return;
46 m_prefix = prefix;
47 m_chunkSize = chunkSize;
48 assert(m_chunkSize < ~uint32_t(0)); // Must fit in low 32 bits of m_current.
49 if (m_state != StorageState::Invalid) {
50 return;
52 m_state = StorageState::Open;
55 char* APCFileStorage::put(const char* data, uint32_t len) {
56 const uint32_t totalLen = len + PaddingSize;
57 if (m_state != StorageState::Open || totalLen > m_chunkSize) {
58 return nullptr;
61 auto maxOffset = m_chunkSize - totalLen;
62 auto current = m_current.load(std::memory_order_relaxed);
63 // m_current is intialized to -1 to postpone allocation to first 'put'.
64 assert(!m_chunks.empty() || current == ~0ull);
65 do {
66 if (UNLIKELY(static_cast<uint32_t>(current) > maxOffset)) {
67 std::lock_guard<std::mutex> lock(m_lock);
68 // Check again after we have the lock, other threads may have already
69 // created a new chunk.
70 current = m_current.load(std::memory_order::memory_order_relaxed);
71 if (static_cast<uint32_t>(current) > maxOffset) {
72 // It is our duty to add a chunk. Other threads can continue to use
73 // the current chunk, or block on the lock if they also find the need
74 // for a new chunk.
75 if (!addFile()) {
76 Logger::Error(
77 "Failed to open additional files for file-backed APC storage, "
78 "falling back to in-memory mode");
79 m_state = StorageState::Full;
80 return nullptr;
84 // Try grabbing the memory
85 if (m_current.compare_exchange_weak(current, current + totalLen,
86 std::memory_order_relaxed,
87 std::memory_order_relaxed)) {
88 break;
90 } while(true);
92 auto const chunkIndex = current >> 32;
93 char* base = m_chunks[chunkIndex] + static_cast<uint32_t>(current);
94 uint64_t h = hash_string_cs_unsafe(data, len);
95 *(uint64_t*)base = h | (static_cast<uint64_t>(len) << 32);
96 base += sizeof(uint64_t);
97 assert(base[len] == '\0'); // Should already be 0 after mmap.
98 // Return the starting address of the string.
99 return static_cast<char*>(memcpy(base, data, len));
102 void APCFileStorage::seal() {
103 std::lock_guard<std::mutex> lock(m_lock);
104 if (m_state == StorageState::Sealed || m_chunks.empty()) {
105 return;
107 assert(m_state == StorageState::Open || m_state == StorageState::Full);
108 m_state = StorageState::Sealed;
110 auto const current = m_current.load(std::memory_order_acquire);
111 auto const offset = static_cast<uint32_t>(current);
112 if (offset < m_chunkSize - PaddingSize) {
113 *reinterpret_cast<strhash_t*>(m_chunks.back() + offset) = TombHash;
115 m_current = m_chunkSize;
117 for (int i = 0; i < (int)m_chunks.size(); i++) {
118 if (mprotect(m_chunks[i], m_chunkSize, PROT_READ) < 0) {
119 Logger::Error("Failed to mprotect chunk %d", i);
124 void APCFileStorage::adviseOut() {
125 std::lock_guard<std::mutex> lock(m_lock);
126 Timer timer(Timer::WallTime, "advising out apc prime");
127 Logger::FInfo("Advising out {} APCFileStorage chunks\n", m_chunks.size());
128 for (int i = 0; i < (int)m_chunks.size(); i++) {
129 if (madvise(m_chunks[i], m_chunkSize, MADV_DONTNEED) < 0) {
130 Logger::Error("Failed to madvise chunk %d", i);
133 for (auto i = 0u; i < m_fds.size(); i++) {
134 if (fadvise_dontneed(m_fds[i], m_chunkSize) < 0) {
135 Logger::Error("Failed to fadvise chunk file %d, fd = %d", i, m_fds[i]);
140 bool APCFileStorage::hashCheck() {
141 std::lock_guard<std::mutex> lock(m_lock);
142 for (int i = 0; i < (int)m_chunks.size(); i++) {
143 char* current = (char*)m_chunks[i];
144 char* boundary = (char*)m_chunks[i] + m_chunkSize;
145 while (1) {
146 // We may not have TombHash at the end if the chunk is used up.
147 if (reinterpret_cast<uintptr_t>(current) + PaddingSize >=
148 reinterpret_cast<uintptr_t>(boundary)) {
149 break;
151 strhash_t h = *(strhash_t*)current;
152 if (h == TombHash) {
153 break;
155 current += sizeof(h);
156 int32_t len = *(int32_t*)current;
157 current += sizeof(len);
158 if (len < 0 ||
159 len + sizeof(char) >= (int64_t)boundary - (int64_t)current) {
160 Logger::Error("invalid len %d at chunk %d offset %" PRId64, len, i,
161 (int64_t)current - (int64_t)m_chunks[i]);
162 return false;
164 strhash_t h_data = hash_string_cs(current, len);
165 if (h_data != h) {
166 Logger::Error("invalid hash at chunk %d offset %" PRId64, i,
167 (int64_t)current - (int64_t)m_chunks[i]);
168 return false;
170 current += len;
171 if (*current != '\0') {
172 Logger::Error("missing \\0 at chunk %d offset %" PRId64, i,
173 (int64_t)current - (int64_t)m_chunks[i]);
174 return false;
176 current++;
179 return true;
182 void APCFileStorage::cleanup() {
183 std::lock_guard<std::mutex> lock(m_lock);
184 for (auto& fileName : m_fileNames) {
185 unlink(fileName.c_str());
187 m_fileNames.clear();
188 for (auto fd : m_fds) {
189 fadvise_dontneed(fd, m_chunkSize);
190 close(fd);
192 m_fds.clear();
193 m_chunks.clear();
196 bool APCFileStorage::addFile() {
197 char name[PATH_MAX];
198 snprintf(name, sizeof(name), "%s.XXXXXX", m_prefix.c_str());
199 int fd = mkstemp(name);
200 if (fd < 0) {
201 Logger::Error("Failed to open temp file");
202 return false;
204 bool couldAllocate = false;
205 #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE
206 couldAllocate = posix_fallocate(fd, 0, m_chunkSize) == 0;
207 #elif defined(__APPLE__)
208 fstore_t store =
209 { F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, static_cast<off_t>(m_chunkSize) };
210 int ret = fcntl(fd, F_PREALLOCATE, &store);
211 if (ret == -1) {
212 store.fst_flags = F_ALLOCATEALL;
213 ret = fcntl(fd, F_PREALLOCATE, &store);
214 if (ret == -1) {
215 couldAllocate = false;
218 couldAllocate = ftruncate(fd, m_chunkSize) == 0;
219 #else
220 #error "No implementation for posix_fallocate on your platform."
221 #endif
222 if (!couldAllocate) {
223 Logger::Error("Failed to posix_fallocate of size %" PRId64, m_chunkSize);
224 close(fd);
225 return false;
227 if (apcExtension::FileStorageKeepFileLinked) {
228 m_fileNames.push_back(std::string(name));
229 } else {
230 unlink(name);
232 char* addr = (char*)mmap(nullptr, m_chunkSize, PROT_READ | PROT_WRITE,
233 MAP_SHARED, fd, 0);
234 if (addr == (char*)-1) {
235 Logger::Error("Failed to mmap %s of size %" PRId64, name, m_chunkSize);
236 close(fd);
237 return false;
239 numa_interleave(addr, m_chunkSize);
241 if (!m_chunks.empty()) {
242 // We need to finish the previous chunk by writing a TombHash at the end,
243 // if there is enough space. If the usable space in the chunk is smaller
244 // than PaddingSize, no TombHash is needed, because we can tell that the
245 // chunk is used up.
246 auto maxOffset = m_chunkSize - PaddingSize;
247 auto current = m_current.load(std::memory_order_relaxed);
248 while (static_cast<uint32_t>(current) < maxOffset) {
249 if (m_current.compare_exchange_weak(current, current + PaddingSize,
250 std::memory_order_acquire,
251 std::memory_order_relaxed)) {
252 // Guaranteed by the memory_order_acquire
253 assert(current >> 32 == m_chunks.size() - 1);
254 auto const p = m_chunks.back() + static_cast<uint32_t>(current);
255 *reinterpret_cast<strhash_t*>(p) = TombHash;
256 break;
261 m_chunks.push_back(addr);
262 m_fds.push_back(fd);
263 // memory_order_release guarantees that the new chunk is already present in
264 // m_chunks when we reset m_current. It is OK if other threads still try to
265 // use the previous chunk before this point.
266 m_current.store(static_cast<int64_t>(m_chunks.size() - 1) << 32,
267 std::memory_order_release);
268 return true;
271 ///////////////////////////////////////////////////////////////////////////////