2 +----------------------------------------------------------------------+
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
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;
47 m_chunkSize
= chunkSize
;
48 assertx(m_chunkSize
< ~uint32_t(0)); // Must fit in low 32 bits of m_current.
49 if (m_state
!= StorageState::Invalid
) {
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
) {
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 assertx(!m_chunks
.empty() || current
== ~0ull);
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
77 "Failed to open additional files for file-backed APC storage, "
78 "falling back to in-memory mode");
79 m_state
= StorageState::Full
;
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
)) {
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 assertx(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()) {
107 assertx(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 %u, 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
;
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
)) {
151 strhash_t h
= *(strhash_t
*)current
;
155 current
+= sizeof(h
);
156 int32_t len
= *(int32_t*)current
;
157 current
+= sizeof(len
);
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
]);
164 strhash_t h_data
= hash_string_cs(current
, len
);
166 Logger::Error("invalid hash at chunk %d offset %" PRId64
, i
,
167 (int64_t)current
- (int64_t)m_chunks
[i
]);
171 if (*current
!= '\0') {
172 Logger::Error("missing \\0 at chunk %d offset %" PRId64
, i
,
173 (int64_t)current
- (int64_t)m_chunks
[i
]);
182 void APCFileStorage::cleanup() {
183 std::lock_guard
<std::mutex
> lock(m_lock
);
184 for (auto& fileName
: m_fileNames
) {
185 unlink(fileName
.c_str());
188 for (auto fd
: m_fds
) {
189 fadvise_dontneed(fd
, m_chunkSize
);
196 bool APCFileStorage::addFile() {
198 snprintf(name
, sizeof(name
), "%s.XXXXXX", m_prefix
.c_str());
199 int fd
= mkstemp(name
);
201 Logger::Error("Failed to open temp file");
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__)
209 { F_ALLOCATECONTIG
, F_PEOFPOSMODE
, 0, static_cast<off_t
>(m_chunkSize
) };
210 int ret
= fcntl(fd
, F_PREALLOCATE
, &store
);
212 store
.fst_flags
= F_ALLOCATEALL
;
213 ret
= fcntl(fd
, F_PREALLOCATE
, &store
);
215 couldAllocate
= false;
218 couldAllocate
= ftruncate(fd
, m_chunkSize
) == 0;
220 #error "No implementation for posix_fallocate on your platform."
222 if (!couldAllocate
) {
223 Logger::Error("Failed to posix_fallocate of size %zu", m_chunkSize
);
227 if (apcExtension::FileStorageKeepFileLinked
) {
228 m_fileNames
.push_back(std::string(name
));
232 char* addr
= (char*)mmap(nullptr, m_chunkSize
, PROT_READ
| PROT_WRITE
,
234 if (addr
== (char*)-1) {
235 Logger::Error("Failed to mmap %s of size %zu", name
, m_chunkSize
);
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
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 assertx(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
;
261 m_chunks
.push_back(addr
);
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
);
271 ///////////////////////////////////////////////////////////////////////////////