Check for empty keys in APC
[hiphop-php.git] / hphp / runtime / ext / apc / ext_apc.cpp
blob264e74df3e084f13a880b017e7ea3b7eb901c929
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/apc/ext_apc.h"
19 #include <fstream>
21 #ifndef _MSC_VER
22 #include <dlfcn.h>
23 #endif
24 #include <memory>
25 #include <set>
26 #include <vector>
27 #include <stdexcept>
28 #include <type_traits>
30 #include <folly/portability/SysTime.h>
32 #include "hphp/util/alloc.h"
33 #include "hphp/util/async-job.h"
34 #include "hphp/util/boot-stats.h"
35 #include "hphp/util/hdf.h"
36 #include "hphp/util/logger.h"
38 #include "hphp/runtime/base/apc-file-storage.h"
39 #include "hphp/runtime/base/array-init.h"
40 #include "hphp/runtime/base/array-iterator.h"
41 #include "hphp/runtime/base/builtin-functions.h"
42 #include "hphp/runtime/base/comparisons.h"
43 #include "hphp/runtime/base/concurrent-shared-store.h"
44 #include "hphp/runtime/base/config.h"
45 #include "hphp/runtime/base/execution-context.h"
46 #include "hphp/runtime/base/ini-setting.h"
47 #include "hphp/runtime/base/program-functions.h"
48 #include "hphp/runtime/base/runtime-option.h"
49 #include "hphp/runtime/base/variable-serializer.h"
50 #include "hphp/runtime/ext/apc/snapshot-builder.h"
51 #include "hphp/runtime/ext/fb/ext_fb.h"
52 #include "hphp/runtime/server/cli-server.h"
53 #include "hphp/runtime/server/upload.h"
55 using HPHP::ScopedMem;
57 namespace HPHP {
58 ///////////////////////////////////////////////////////////////////////////////
60 namespace {
62 std::aligned_storage<
63 sizeof(ConcurrentTableSharedStore),
64 alignof(ConcurrentTableSharedStore)
65 >::type s_apc_storage;
67 using UserAPCCache = folly::AtomicHashMap<uid_t, ConcurrentTableSharedStore*>;
69 std::aligned_storage<
70 sizeof(UserAPCCache),
71 alignof(UserAPCCache)
72 >::type s_user_apc_storage;
74 UserAPCCache& apc_store_local() {
75 void* vpUserStore = &s_user_apc_storage;
76 return *static_cast<UserAPCCache*>(vpUserStore);
79 ConcurrentTableSharedStore& apc_store_local(uid_t uid) {
80 auto& cache = apc_store_local();
81 auto iter = cache.find(uid);
82 if (iter != cache.end()) return *(iter->second);
83 auto table = new ConcurrentTableSharedStore;
84 auto res = cache.insert(uid, table);
85 if (!res.second) delete table;
86 return *res.first->second;
89 ConcurrentTableSharedStore& apc_store() {
90 if (UNLIKELY(!RuntimeOption::RepoAuthoritative &&
91 RuntimeOption::EvalUnixServerQuarantineApc)) {
92 if (auto uc = get_cli_ucred()) {
93 return apc_store_local(uc->uid);
96 void* vpStore = &s_apc_storage;
97 return *static_cast<ConcurrentTableSharedStore*>(vpStore);
100 bool isKeyInvalid(const String &key) {
101 // T39154441 - check if invalid chars exist
102 return key.find('\0') != -1;
107 //////////////////////////////////////////////////////////////////////
109 void initialize_apc() {
110 APCStats::Create();
111 // Note: we never destruct APC, currently.
112 void* vpStore = &s_apc_storage;
113 new (vpStore) ConcurrentTableSharedStore;
115 if (UNLIKELY(!RuntimeOption::RepoAuthoritative &&
116 RuntimeOption::EvalUnixServerQuarantineApc)) {
117 new (&s_user_apc_storage) UserAPCCache(10);
121 //////////////////////////////////////////////////////////////////////
123 const StaticString s_delete("delete");
124 const StaticString s_internal_preload("__apc_internal_preload");
126 extern void const_load();
128 typedef ConcurrentTableSharedStore::KeyValuePair KeyValuePair;
129 typedef ConcurrentTableSharedStore::DumpMode DumpMode;
131 static void* keep_alive;
133 void apcExtension::moduleLoad(const IniSetting::Map& ini, Hdf config) {
134 if (!keep_alive && ini.isString()) {
135 // this is a hack to preserve some dynamic entry points
136 switch (ini.toString().size()) {
137 case 0: keep_alive = (void*)const_load; break;
138 case 2: keep_alive = (void*)const_load_impl_compressed; break;
139 case 4: keep_alive = (void*)apc_load_impl_compressed; break;
143 Config::Bind(Enable, ini, config, "Server.APC.EnableApc", true);
144 Config::Bind(EnableConstLoad, ini, config, "Server.APC.EnableConstLoad",
145 false);
146 Config::Bind(ForceConstLoadToAPC, ini, config,
147 "Server.APC.ForceConstLoadToAPC", true);
148 Config::Bind(PrimeLibrary, ini, config, "Server.APC.PrimeLibrary");
149 Config::Bind(LoadThread, ini, config, "Server.APC.LoadThread", 15);
150 Config::Bind(CompletionKeys, ini, config, "Server.APC.CompletionKeys");
151 Config::Bind(EnableApcSerialize, ini, config, "Server.APC.EnableApcSerialize",
152 true);
153 Config::Bind(ExpireOnSets, ini, config, "Server.APC.ExpireOnSets");
154 Config::Bind(PurgeInterval, ini, config, "Server.APC.PurgeIntervalSeconds",
155 PurgeInterval);
156 Config::Bind(AllowObj, ini, config, "Server.APC.AllowObject");
157 Config::Bind(TTLLimit, ini, config, "Server.APC.TTLLimit", -1);
158 Config::Bind(DeferredExpiration, ini, config,
159 "Server.APC.DeferredExpiration", DeferredExpiration);
160 // Any TTL > TTLMaxFinite will be made infinite. NB: Applied *after* TTLLimit.
161 Config::Bind(TTLMaxFinite, ini, config, "Server.APC.TTLMaxFinite",
162 std::numeric_limits<int64_t>::max());
163 Config::Bind(HotPrefix, ini, config, "Server.APC.HotPrefix");
164 Config::Bind(SerializePrefix, ini, config, "Server.APC.SerializePrefix");
165 Config::Bind(HotSize, ini, config, "Server.APC.HotSize", 30000);
166 Config::Bind(HotLoadFactor, ini, config, "Server.APC.HotLoadFactor", 0.5);
167 Config::Bind(HotKeyAllocLow, ini, config, "Server.APC.HotKeyAllocLow", false);
168 Config::Bind(HotMapAllocLow, ini, config, "Server.APC.HotMapAllocLow", false);
170 // Loads .so PrimeLibrary, writes snapshot output to this file, then exits.
171 Config::Bind(PrimeLibraryUpgradeDest, ini, config,
172 "Server.APC.PrimeLibraryUpgradeDest");
174 // FileStorage
175 Config::Bind(UseFileStorage, ini, config, "Server.APC.FileStorage.Enable");
176 FileStorageChunkSize = Config::GetInt64(ini, config,
177 "Server.APC.FileStorage.ChunkSize",
178 1LL << 29);
179 Config::Bind(FileStoragePrefix, ini, config, "Server.APC.FileStorage.Prefix",
180 "/tmp/apc_store");
181 Config::Bind(FileStorageFlagKey, ini, config,
182 "Server.APC.FileStorage.FlagKey", "_madvise_out");
183 Config::Bind(FileStorageAdviseOutPeriod, ini, config,
184 "Server.APC.FileStorage.AdviseOutPeriod", 1800);
185 Config::Bind(FileStorageKeepFileLinked, ini, config,
186 "Server.APC.FileStorage.KeepFileLinked");
188 #ifdef NO_M_DATA
189 Config::Bind(UseUncounted, ini, config, "Server.APC.MemModelTreadmill", true);
190 #else
191 Config::Bind(UseUncounted, ini, config, "Server.APC.MemModelTreadmill",
192 RuntimeOption::ServerExecutionMode());
193 #endif
194 Config::Bind(ShareUncounted, ini, config, "Server.APC.ShareUncounted", true);
195 if (!UseUncounted && ShareUncounted) ShareUncounted = false;
197 Config::Bind(SizedSampleBytes, ini, config, "Server.APC.SizedSampleBytes", 0);
199 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.enabled", &Enable);
200 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.stat",
201 RuntimeOption::RepoAuthoritative ? "0" : "1", &Stat);
202 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.enable_cli",
203 &EnableCLI);
206 void apcExtension::moduleInit() {
207 #ifdef NO_M_DATA
208 if (!UseUncounted) {
209 Logger::Error("Server.APC.MemModelTreadmill=false ignored in lowptr build");
210 UseUncounted = true;
212 #endif // NO_M_DATA
213 if (UseFileStorage) {
214 // We use 32 bits to represent offset into a chunk, so don't make it too
215 // large.
216 constexpr int64_t MaxChunkSize = 1LL << 31;
217 if (FileStorageChunkSize > MaxChunkSize) {
218 Logger::Warning("Server.APC.FileStorage.ChunkSize too large, "
219 "resetting to %" PRId64, MaxChunkSize);
220 FileStorageChunkSize = MaxChunkSize;
222 s_apc_file_storage.enable(FileStoragePrefix, FileStorageChunkSize);
225 HHVM_RC_INT(APC_ITER_TYPE, 0x1);
226 HHVM_RC_INT(APC_ITER_KEY, 0x2);
227 HHVM_RC_INT(APC_ITER_FILENAME, 0x4);
228 HHVM_RC_INT(APC_ITER_DEVICE, 0x8);
229 HHVM_RC_INT(APC_ITER_INODE, 0x10);
230 HHVM_RC_INT(APC_ITER_VALUE, 0x20);
231 HHVM_RC_INT(APC_ITER_MD5, 0x40);
232 HHVM_RC_INT(APC_ITER_NUM_HITS, 0x80);
233 HHVM_RC_INT(APC_ITER_MTIME, 0x100);
234 HHVM_RC_INT(APC_ITER_CTIME, 0x200);
235 HHVM_RC_INT(APC_ITER_DTIME, 0x400);
236 HHVM_RC_INT(APC_ITER_ATIME, 0x800);
237 HHVM_RC_INT(APC_ITER_REFCOUNT, 0x1000);
238 HHVM_RC_INT(APC_ITER_MEM_SIZE, 0x2000);
239 HHVM_RC_INT(APC_ITER_TTL, 0x4000);
240 HHVM_RC_INT(APC_ITER_NONE, 0x0);
241 HHVM_RC_INT(APC_ITER_ALL, 0xFFFFFFFFFF);
242 HHVM_RC_INT(APC_LIST_ACTIVE, 1);
243 HHVM_RC_INT(APC_LIST_DELETED, 2);
245 HHVM_FE(apc_add);
246 HHVM_FE(apc_store);
247 HHVM_FE(apc_store_as_primed_do_not_use);
248 HHVM_FE(apc_fetch);
249 HHVM_FE(apc_delete);
250 HHVM_FE(apc_clear_cache);
251 HHVM_FE(apc_inc);
252 HHVM_FE(apc_dec);
253 HHVM_FE(apc_cas);
254 HHVM_FE(apc_exists);
255 HHVM_FE(apc_size);
256 HHVM_FE(apc_extend_ttl);
257 HHVM_FE(apc_cache_info);
258 loadSystemlib();
261 void apcExtension::moduleShutdown() {
262 if (UseFileStorage) {
263 s_apc_file_storage.cleanup();
267 void apcExtension::requestShutdown() {
268 apc_store().purgeExpired();
271 std::string apcExtension::serialize() {
272 std::ostringstream oss;
273 apc_store().dumpKeysWithPrefixes(oss, SerializePrefix);
274 return oss.str();
277 void apcExtension::deserialize(std::string data) {
278 auto sd = StringData::MakeUncounted(data);
279 data.clear();
280 apc_store().set(s_internal_preload, Variant{sd}, 0);
281 StringData::ReleaseUncounted(sd); // a copy was made in APC
284 void apcExtension::purgeDeferred(req::vector<StringData*>&& keys) {
285 apc_store().purgeDeferred(std::move(keys));
288 bool apcExtension::Enable = true;
289 bool apcExtension::EnableConstLoad = false;
290 bool apcExtension::ForceConstLoadToAPC = true;
291 std::string apcExtension::PrimeLibrary;
292 int apcExtension::LoadThread = 15;
293 std::set<std::string> apcExtension::CompletionKeys;
294 bool apcExtension::EnableApcSerialize = true;
295 bool apcExtension::AllowObj = false;
296 bool apcExtension::ExpireOnSets = false;
297 int apcExtension::PurgeInterval = 1;
298 int apcExtension::TTLLimit = -1;
299 int64_t apcExtension::TTLMaxFinite = std::numeric_limits<int64_t>::max();
300 int apcExtension::HotSize = 30000;
301 double apcExtension::HotLoadFactor = 0.5;
302 std::vector<std::string> apcExtension::HotPrefix;
303 std::vector<std::string> apcExtension::SerializePrefix;
304 std::string apcExtension::PrimeLibraryUpgradeDest;
305 bool apcExtension::HotKeyAllocLow = false;
306 bool apcExtension::HotMapAllocLow = false;
307 bool apcExtension::UseFileStorage = false;
308 int64_t apcExtension::FileStorageChunkSize = int64_t(1LL << 29);
309 std::string apcExtension::FileStoragePrefix = "/tmp/apc_store";
310 int apcExtension::FileStorageAdviseOutPeriod = 1800;
311 std::string apcExtension::FileStorageFlagKey = "_madvise_out";
312 bool apcExtension::FileStorageKeepFileLinked = false;
313 #ifdef NO_M_DATA
314 bool apcExtension::UseUncounted = true;
315 #else
316 bool apcExtension::UseUncounted = false;
317 #endif
318 bool apcExtension::ShareUncounted = true;
319 bool apcExtension::Stat = true;
320 // Different from zend default but matches what we've been returning for years
321 bool apcExtension::EnableCLI = true;
322 bool apcExtension::DeferredExpiration = true;
323 uint32_t apcExtension::SizedSampleBytes = 0;
325 static apcExtension s_apc_extension;
327 Variant HHVM_FUNCTION(apc_store,
328 const Variant& key_or_array,
329 const Variant& var /* = null */,
330 int64_t ttl /* = 0 */) {
331 if (!apcExtension::Enable) return Variant(false);
333 ARRPROV_USE_RUNTIME_LOCATION();
335 if (key_or_array.isArray()) {
336 Array valuesArr = key_or_array.toArray();
338 for (ArrayIter iter(valuesArr); iter; ++iter) {
339 Variant key = iter.first();
340 if (!key.isString()) {
341 raise_invalid_argument_warning("apc key: (not a string)");
342 return Variant(false);
344 Variant v = iter.second();
346 auto const& strKey = key.asCStrRef();
347 if (strKey.empty()) {
348 raise_invalid_argument_warning("apc key: (empty string)");
349 return false;
351 if (isKeyInvalid(strKey)) {
352 raise_invalid_argument_warning("apc key: (contains invalid characters)");
353 return Variant(false);
355 apc_store().set(strKey, v, ttl);
356 if (RuntimeOption::EnableAPCStats) {
357 ServerStats::Log("apc.write", 1);
360 return Variant(ArrayData::Create());
363 if (!key_or_array.isString()) {
364 raise_invalid_argument_warning("apc key: (not a string)");
365 return Variant(false);
367 String strKey = key_or_array.toString();
369 if (strKey.empty()) {
370 raise_invalid_argument_warning("apc key: (empty string)");
371 return false;
373 if (isKeyInvalid(strKey)) {
374 raise_invalid_argument_warning("apc key: (contains invalid characters)");
375 return Variant(false);
377 apc_store().set(strKey, var, ttl);
378 if (RuntimeOption::EnableAPCStats) {
379 ServerStats::Log("apc.write", 1);
381 return Variant(true);
385 * Stores the key in a similar fashion as "priming" would do (no TTL limit).
386 * Using this function is equivalent to adding your key to apc_prime.so.
388 bool HHVM_FUNCTION(apc_store_as_primed_do_not_use,
389 const String& key,
390 const Variant& var) {
391 if (!apcExtension::Enable) return false;
392 if (key.empty()) {
393 raise_invalid_argument_warning("apc key: (empty string)");
394 return false;
396 if (isKeyInvalid(key)) {
397 raise_invalid_argument_warning("apc key: (contains invalid characters)");
398 return false;
400 apc_store().setWithoutTTL(key, var);
401 if (RuntimeOption::EnableAPCStats) {
402 ServerStats::Log("apc.write", 1);
404 return true;
407 Variant HHVM_FUNCTION(apc_add,
408 const Variant& key_or_array,
409 const Variant& var /* = null */,
410 int64_t ttl /* = 0 */) {
411 if (!apcExtension::Enable) return false;
413 ARRPROV_USE_RUNTIME_LOCATION();
415 if (key_or_array.isArray()) {
416 auto valuesArr = key_or_array.asCArrRef();
418 // errors stores all keys corresponding to entries that could not be cached
419 DArrayInit errors(valuesArr.size());
421 for (ArrayIter iter(valuesArr); iter; ++iter) {
422 Variant key = iter.first();
423 if (!key.isString()) {
424 raise_invalid_argument_warning("apc key: (not a string)");
425 return false;
427 Variant v = iter.second();
429 auto const& strKey = key.asCStrRef();
430 if (strKey.empty()) {
431 raise_invalid_argument_warning("apc key: (empty string)");
432 return false;
434 if (isKeyInvalid(strKey)) {
435 raise_invalid_argument_warning("apc key: (contains invalid characters)");
436 return false;
439 if (!apc_store().add(strKey, v, ttl)) {
440 errors.add(strKey, -1);
443 return errors.toVariant();
446 if (!key_or_array.isString()) {
447 raise_invalid_argument_warning("apc key: (not a string)");
448 return false;
450 auto strKey = key_or_array.asCStrRef();
451 if (strKey.empty()) {
452 raise_invalid_argument_warning("apc key: (empty string)");
453 return false;
455 if (isKeyInvalid(strKey)) {
456 raise_invalid_argument_warning("apc key: (contains invalid characters)");
457 return false;
459 if (RuntimeOption::EnableAPCStats) {
460 ServerStats::Log("apc.write", 1);
462 return apc_store().add(strKey, var, ttl);
465 bool HHVM_FUNCTION(apc_extend_ttl, const String& key, int64_t new_ttl) {
466 if (!apcExtension::Enable) return false;
468 if (new_ttl < 0) return false;
469 return apc_store().bumpTTL(key, new_ttl);
472 TypedValue HHVM_FUNCTION(apc_fetch, const Variant& key, bool& success) {
473 if (!apcExtension::Enable) return make_tv<KindOfBoolean>(false);
475 Variant v;
477 if (key.isArray()) {
478 bool tmp = false;
479 auto keys = key.asCArrRef();
480 DArrayInit init(keys.size());
481 for (ArrayIter iter(keys); iter; ++iter) {
482 Variant k = iter.second();
483 if (!k.isString()) {
484 raise_invalid_argument_warning("apc key: (not a string)");
485 return make_tv<KindOfBoolean>(false);
487 auto strKey = k.asCStrRef();
488 if (apc_store().get(strKey, v)) {
489 if (RuntimeOption::EnableAPCStats) {
490 ServerStats::Log("apc.hit", 1);
492 tmp = true;
493 init.set(strKey, v);
496 success = tmp;
497 return tvReturn(init.toVariant());
500 if (apc_store().get(key.toString(), v)) {
501 success = true;
502 if (RuntimeOption::EnableAPCStats) {
503 ServerStats::Log("apc.hit", 1);
505 } else {
506 success = false;
507 v = false;
508 if (RuntimeOption::EnableAPCStats) {
509 ServerStats::Log("apc.miss", 1);
512 return tvReturn(std::move(v));
515 Variant HHVM_FUNCTION(apc_delete,
516 const Variant& key) {
517 if (!apcExtension::Enable) return false;
519 if (key.isArray()) {
520 auto keys = key.asCArrRef();
521 VArrayInit init(keys.size());
522 for (ArrayIter iter(keys); iter; ++iter) {
523 Variant k = iter.second();
524 if (!k.isString()) {
525 raise_warning("apc key is not a string");
526 init.append(k);
527 } else if (!apc_store().eraseKey(k.asCStrRef())) {
528 init.append(k);
531 return init.toVariant();
532 } else if (key.is(KindOfObject)) {
533 if (!key.getObjectData()->getVMClass()->
534 classof(SystemLib::s_APCIteratorClass)) {
535 raise_error(
536 "apc_delete(): apc_delete object argument must be instance"
537 " of APCIterator"
539 return false;
541 const Func* method =
542 SystemLib::s_APCIteratorClass->lookupMethod(s_delete.get());
543 return Variant::attach(
544 g_context->invokeFuncFew(method, key.getObjectData())
548 return apc_store().eraseKey(key.toString());
551 bool HHVM_FUNCTION(apc_clear_cache, const String& /*cache_type*/ /* = "" */) {
552 if (!apcExtension::Enable) return false;
553 return apc_store().clear();
556 Variant HHVM_FUNCTION(apc_inc,
557 const String& key,
558 int64_t step,
559 bool& success) {
560 if (!apcExtension::Enable) return false;
562 bool found = false;
563 int64_t newValue = apc_store().inc(key, step, found);
564 success = found;
565 if (!found) return false;
566 return newValue;
569 Variant HHVM_FUNCTION(apc_dec,
570 const String& key,
571 int64_t step,
572 bool& success) {
573 if (!apcExtension::Enable) return false;
575 bool found = false;
576 int64_t newValue = apc_store().inc(key, -step, found);
577 success = found;
578 if (!found) return false;
579 return newValue;
582 bool HHVM_FUNCTION(apc_cas,
583 const String& key,
584 int64_t old_cas,
585 int64_t new_cas) {
586 if (!apcExtension::Enable) return false;
587 return apc_store().cas(key, old_cas, new_cas);
590 Variant HHVM_FUNCTION(apc_exists,
591 const Variant& key) {
592 if (!apcExtension::Enable) return false;
594 if (!key.isArray()) return apc_store().exists(key.toString());
595 bool failed = false;
596 auto keys = key.toArray();
597 VArrayInit init(keys.size());
598 IterateV(keys.get(), [&](TypedValue k) {
599 if (!isStringType(type(k))) {
600 raise_invalid_argument_warning("apc key: (not a string)");
601 failed = true;
602 return true;
604 auto strKey = String{val(k).pstr};
605 if (apc_store().exists(strKey)) {
606 init.append(strKey);
608 return false;
610 if (failed) return false;
611 return init.toVariant();
614 TypedValue HHVM_FUNCTION(apc_size, const String& key) {
615 if (!apcExtension::Enable) return make_tv<KindOfNull>();
617 bool found = false;
618 int64_t size = apc_store().size(key, found);
620 return found ? make_tv<KindOfInt64>(size) : make_tv<KindOfNull>();
623 const StaticString s_user("user");
624 const StaticString s_start_time("start_time");
625 const StaticString s_ttl("ttl");
626 const StaticString s_cache_list("cache_list");
627 const StaticString s_info("info");
628 const StaticString s_in_memory("in_memory");
629 const StaticString s_mem_size("mem_size");
630 const StaticString s_type("type");
631 const StaticString s_c_time("creation_time");
632 const StaticString s_mtime("mtime");
634 // This is a guess to the size of the info array. It is significantly
635 // bigger than what we need but hard to control all the info that we
636 // may want to add here.
637 // Try to keep it such that we do not have to resize the array
638 const uint32_t kCacheInfoSize = 40;
639 // Number of elements in the entry array
640 const int32_t kEntryInfoSize = 7;
642 Array HHVM_FUNCTION(
643 apc_cache_info,
644 const String& cache_type,
645 bool limited /* = false */) {
647 DArrayInit info(kCacheInfoSize);
648 info.add(s_start_time, start_time());
649 if (cache_type.size() != 0 && !cache_type.same(s_user)) {
650 return info.toArray();
653 info.add(s_ttl, apcExtension::TTLLimit);
655 std::map<const StringData*, int64_t> stats;
656 APCStats::getAPCStats().collectStats(stats);
657 for (auto const& stat : stats) {
658 info.add(StrNR{stat.first}, make_tv<KindOfInt64>(stat.second));
660 if (!limited) {
661 auto const entries = apc_store().getEntriesInfo();
662 VArrayInit ents(entries.size());
663 for (auto& entry : entries) {
664 DArrayInit ent(kEntryInfoSize);
665 ent.add(s_info,
666 Variant::attach(StringData::Make(entry.key.c_str())));
667 ent.add(s_in_memory, entry.inMem);
668 ent.add(s_ttl, entry.ttl);
669 ent.add(s_mem_size, entry.size);
670 ent.add(s_type, static_cast<int64_t>(entry.type));
671 ent.add(s_c_time, entry.c_time);
672 ent.add(s_mtime, entry.mtime);
673 ents.append(ent.toArray());
675 info.add(s_cache_list, ents.toArray(), false);
677 return info.toArray();
680 ///////////////////////////////////////////////////////////////////////////////
681 // loading APC from archive files
683 typedef void(*PFUNC_APC_LOAD)();
685 // Structure to hold cache meta data
686 // Same definition in ext_apc.cpp
687 struct cache_info {
688 char *a_name;
689 bool use_const;
692 static Mutex dl_mutex;
693 static PFUNC_APC_LOAD apc_load_func(void *handle, const char *name) {
694 #ifdef _MSC_VER
695 throw Exception("apc_load_func is not currently supported under MSVC!");
696 #else
697 PFUNC_APC_LOAD p = (PFUNC_APC_LOAD)dlsym(handle, name);
698 if (p == nullptr) {
699 throw Exception("Unable to find %s in %s", name,
700 apcExtension::PrimeLibrary.c_str());
702 return p;
703 #endif
706 struct ApcLoadJob {
707 ApcLoadJob(void *handle, int index) : m_handle(handle), m_index(index) {}
708 void *m_handle; int m_index;
711 struct ApcLoadWorker {
712 void onThreadEnter() {
713 g_context.getCheck();
715 void doJob(std::shared_ptr<ApcLoadJob> job) {
716 char func_name[128];
717 MemoryManager::SuppressOOM so(*tl_heap);
718 snprintf(func_name, sizeof(func_name), "_apc_load_%d", job->m_index);
719 apc_load_func(job->m_handle, func_name)();
721 void onThreadExit() {
722 hphp_memory_cleanup();
726 static size_t s_const_map_size = 0;
728 static SnapshotBuilder s_snapshotBuilder;
730 void apc_load(int thread) {
731 #ifndef _MSC_VER
732 static void *handle = nullptr;
733 if (handle ||
734 apcExtension::PrimeLibrary.empty() ||
735 !apcExtension::Enable) {
736 return;
738 BootStats::Block timer("loading APC data",
739 RuntimeOption::ServerExecutionMode());
740 if (apc_store().primeFromSnapshot(apcExtension::PrimeLibrary.c_str())) {
741 return;
743 Logger::Info("Fall back to shared object format");
744 handle = dlopen(apcExtension::PrimeLibrary.c_str(), RTLD_LAZY);
745 if (!handle) {
746 throw Exception("Unable to open apc prime library %s: %s",
747 apcExtension::PrimeLibrary.c_str(), dlerror());
750 auto upgradeDest = apcExtension::PrimeLibraryUpgradeDest;
751 if (!upgradeDest.empty()) {
752 thread = 1; // SnapshotBuilder is not (yet) thread-safe.
753 // TODO(9755792): Ensure APCFileStorage is enabled.
756 if (thread <= 1) {
757 apc_load_func(handle, "_apc_load_all")();
758 } else {
759 int count = ((int(*)())apc_load_func(handle, "_apc_load_count"))();
761 std::vector<std::shared_ptr<ApcLoadJob>> jobs;
762 jobs.reserve(count);
763 for (int i = 0; i < count; i++) {
764 jobs.push_back(std::make_shared<ApcLoadJob>(handle, i));
766 JobDispatcher<ApcLoadJob, ApcLoadWorker>(std::move(jobs), thread).run();
769 apc_store().primeDone();
770 if (!upgradeDest.empty()) {
771 s_snapshotBuilder.writeToFile(upgradeDest);
774 // We've copied all the data out, so close it out.
775 dlclose(handle);
776 #endif
779 void apc_advise_out() {
780 apc_store().adviseOut();
783 size_t get_const_map_size() {
784 return s_const_map_size;
787 ///////////////////////////////////////////////////////////////////////////////
788 // Constant and APC priming (always with compressed data).
790 EXTERNALLY_VISIBLE
791 void const_load_impl_compressed(
792 struct cache_info* /*info*/, int* /*int_lens*/, const char* /*int_keys*/,
793 long long* /*int_values*/, int* /*char_lens*/, const char* /*char_keys*/,
794 char* /*char_values*/, int* /*string_lens*/, const char* /*strings*/,
795 int* /*object_lens*/, const char* /*objects*/, int* /*thrift_lens*/,
796 const char* /*thrifts*/, int* /*other_lens*/, const char* /*others*/) {
797 // TODO(8117903): Unused; remove after updating www side.
800 EXTERNALLY_VISIBLE
801 void apc_load_impl_compressed
802 (struct cache_info *info,
803 int *int_lens, const char *int_keys, long long *int_values,
804 int *char_lens, const char *char_keys, char *char_values,
805 int *string_lens, const char *strings,
806 int *object_lens, const char *objects,
807 int *thrift_lens, const char *thrifts,
808 int *other_lens, const char *others) {
809 ARRPROV_USE_RUNTIME_LOCATION();
810 bool readOnly = apcExtension::EnableConstLoad && info && info->use_const;
811 if (readOnly && info->a_name) Logger::FInfo("const archive {}", info->a_name);
812 auto& s = apc_store();
813 SnapshotBuilder* snap = apcExtension::PrimeLibraryUpgradeDest.empty() ?
814 nullptr : &s_snapshotBuilder;
816 int count = int_lens[0];
817 int len = int_lens[1];
818 if (count) {
819 std::vector<KeyValuePair> vars(count);
820 char *keys = gzdecode(int_keys, len);
821 if (keys == nullptr) throw Exception("bad compressed apc archive.");
822 ScopedMem holder(keys);
823 const char *k = keys;
824 long long* v = int_values;
825 for (int i = 0; i < count; i++) {
826 auto& item = vars[i];
827 item.key = k;
828 item.readOnly = readOnly;
829 s.constructPrime(*v++, item);
830 if (UNLIKELY(snap != nullptr)) snap->addInt(v[-1], item);
831 k += int_lens[i + 2] + 1; // skip \0
833 s.prime(std::move(vars));
834 assertx((k - keys) == len);
838 int count = char_lens[0];
839 int len = char_lens[1];
840 if (count) {
841 std::vector<KeyValuePair> vars(count);
842 char *keys = gzdecode(char_keys, len);
843 if (keys == nullptr) throw Exception("bad compressed apc archive.");
844 ScopedMem holder(keys);
845 const char *k = keys;
846 char *v = char_values;
847 for (int i = 0; i < count; i++) {
848 auto& item = vars[i];
849 item.key = k;
850 item.readOnly = readOnly;
851 switch (*v++) {
852 case 0:
853 s.constructPrime(false, item);
854 if (UNLIKELY(snap != nullptr)) snap->addFalse(item);
855 break;
856 case 1:
857 s.constructPrime(true, item);
858 if (UNLIKELY(snap != nullptr)) snap->addTrue(item);
859 break;
860 case 2:
861 s.constructPrime(uninit_null(), item);
862 if (UNLIKELY(snap != nullptr)) snap->addNull(item);
863 break;
864 default:
865 throw Exception("bad apc archive, unknown char type");
867 k += char_lens[i + 2] + 1; // skip \0
869 s.prime(std::move(vars));
870 assertx((k - keys) == len);
874 int count = string_lens[0] / 2;
875 int len = string_lens[1];
876 if (count) {
877 std::vector<KeyValuePair> vars(count);
878 char *decoded = gzdecode(strings, len);
879 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
880 ScopedMem holder(decoded);
881 const char *p = decoded;
882 for (int i = 0; i < count; i++) {
883 auto& item = vars[i];
884 item.key = p;
885 item.readOnly = readOnly;
886 p += string_lens[i + i + 2] + 1; // skip \0
887 // Strings would be copied into APC anyway.
888 String value(p, string_lens[i + i + 3], CopyString);
889 // todo: t2539893: check if value is already a static string
890 s.constructPrime(value, item, false);
891 if (UNLIKELY(snap != nullptr)) snap->addString(value, item);
892 p += string_lens[i + i + 3] + 1; // skip \0
894 s.prime(std::move(vars));
895 assertx((p - decoded) == len);
899 int count = object_lens[0] / 2;
900 int len = object_lens[1];
901 if (count) {
902 std::vector<KeyValuePair> vars(count);
903 char *decoded = gzdecode(objects, len);
904 if (decoded == nullptr) throw Exception("bad compressed APC archive.");
905 ScopedMem holder(decoded);
906 const char *p = decoded;
907 for (int i = 0; i < count; i++) {
908 auto& item = vars[i];
909 item.key = p;
910 item.readOnly = readOnly;
911 p += object_lens[i + i + 2] + 1; // skip \0
912 String value(p, object_lens[i + i + 3], CopyString);
913 s.constructPrime(value, item, true);
914 if (UNLIKELY(snap != nullptr)) snap->addObject(value, item);
915 p += object_lens[i + i + 3] + 1; // skip \0
917 s.prime(std::move(vars));
918 assertx((p - decoded) == len);
922 int count = thrift_lens[0] / 2;
923 int len = thrift_lens[1];
924 if (count) {
925 std::vector<KeyValuePair> vars(count);
926 char *decoded = gzdecode(thrifts, len);
927 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
928 ScopedMem holder(decoded);
929 const char *p = decoded;
930 for (int i = 0; i < count; i++) {
931 auto& item = vars[i];
932 item.key = p;
933 item.readOnly = readOnly;
934 p += thrift_lens[i + i + 2] + 1; // skip \0
935 String value(p, thrift_lens[i + i + 3], CopyString);
936 bool success;
937 Variant v = HHVM_FN(fb_unserialize)(value, success, k_FB_SERIALIZE_VARRAY_DARRAY);
938 if (success == false) {
939 throw Exception("bad apc archive, fb_unserialize failed");
941 s.constructPrime(v, item);
942 if (UNLIKELY(snap != nullptr)) snap->addThrift(value, item);
943 p += thrift_lens[i + i + 3] + 1; // skip \0
945 s.prime(std::move(vars));
946 assertx((p - decoded) == len);
950 int count = other_lens[0] / 2;
951 int len = other_lens[1];
952 if (count) {
953 std::vector<KeyValuePair> vars(count);
954 char *decoded = gzdecode(others, len);
955 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
956 ScopedMem holder(decoded);
957 const char *p = decoded;
958 for (int i = 0; i < count; i++) {
959 auto& item = vars[i];
960 item.key = p;
961 item.readOnly = readOnly;
962 p += other_lens[i + i + 2] + 1; // skip \0
963 String value(p, other_lens[i + i + 3], CopyString);
964 Variant v =
965 unserialize_from_string(value, VariableUnserializer::Type::Internal);
966 if (same(v, false)) {
967 // we can't possibly get here if it was a boolean "false" that's
968 // supposed to be serialized as a char
969 throw Exception("bad apc archive, unserialize_from_string failed");
971 s.constructPrime(v, item);
972 if (UNLIKELY(snap != nullptr)) snap->addOther(value, item);
973 p += other_lens[i + i + 3] + 1; // skip \0
975 s.prime(std::move(vars));
976 assertx((p - decoded) == len);
981 ///////////////////////////////////////////////////////////////////////////////
983 static double my_time() {
984 struct timeval a;
985 double t;
986 gettimeofday(&a, nullptr);
987 t = a.tv_sec + (a.tv_usec/1000000.00);
988 return t;
991 const StaticString
992 s_total("total"),
993 s_current("current"),
994 s_filename("filename"),
995 s_name("name"),
996 s_done("done"),
997 s_temp_filename("temp_filename"),
998 s_cancel_upload("cancel_upload"),
999 s_rate("rate");
1001 #define RFC1867_TRACKING_KEY_MAXLEN 63
1002 #define RFC1867_NAME_MAXLEN 63
1003 #define RFC1867_FILENAME_MAXLEN 127
1005 int apc_rfc1867_progress(apc_rfc1867_data* rfc1867ApcData, unsigned int event,
1006 void* event_data, void** /*extra*/) {
1007 switch (event) {
1008 case MULTIPART_EVENT_START: {
1009 multipart_event_start *data = (multipart_event_start *) event_data;
1010 rfc1867ApcData->content_length = data->content_length;
1011 rfc1867ApcData->tracking_key.clear();
1012 rfc1867ApcData->name.clear();
1013 rfc1867ApcData->cancel_upload = 0;
1014 rfc1867ApcData->temp_filename = "";
1015 rfc1867ApcData->start_time = my_time();
1016 rfc1867ApcData->bytes_processed = 0;
1017 rfc1867ApcData->prev_bytes_processed = 0;
1018 rfc1867ApcData->rate = 0;
1019 rfc1867ApcData->update_freq = RuntimeOption::Rfc1867Freq;
1021 if (rfc1867ApcData->update_freq < 0) {
1022 assertx(false); // TODO: support percentage
1023 // frequency is a percentage, not bytes
1024 rfc1867ApcData->update_freq =
1025 rfc1867ApcData->content_length * RuntimeOption::Rfc1867Freq / 100;
1027 break;
1030 case MULTIPART_EVENT_FORMDATA: {
1031 multipart_event_formdata *data = (multipart_event_formdata *)event_data;
1032 if (data->name &&
1033 !strncasecmp(data->name, RuntimeOption::Rfc1867Name.c_str(),
1034 RuntimeOption::Rfc1867Name.size()) &&
1035 data->value && data->length &&
1036 data->length < RFC1867_TRACKING_KEY_MAXLEN -
1037 RuntimeOption::Rfc1867Prefix.size()) {
1038 int len = RuntimeOption::Rfc1867Prefix.size();
1039 if (len > RFC1867_TRACKING_KEY_MAXLEN) {
1040 len = RFC1867_TRACKING_KEY_MAXLEN;
1042 rfc1867ApcData->tracking_key =
1043 std::string(RuntimeOption::Rfc1867Prefix.c_str(), len);
1044 len = strlen(*data->value);
1045 int rem = RFC1867_TRACKING_KEY_MAXLEN -
1046 rfc1867ApcData->tracking_key.size();
1047 if (len > rem) len = rem;
1048 rfc1867ApcData->tracking_key +=
1049 std::string(*data->value, len);
1050 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1052 /* Facebook: Temporary fix for a bug in PHP's rfc1867 code,
1053 fixed here for convenience:
1054 http://cvs.php.net/viewvc.cgi/php-src/main/
1055 rfc1867.c?r1=1.173.2.1.2.11&r2=1.173.2.1.2.12 */
1056 (*data->newlength) = data->length;
1057 break;
1060 case MULTIPART_EVENT_FILE_START:
1061 if (!rfc1867ApcData->tracking_key.empty()) {
1062 multipart_event_file_start *data =
1063 (multipart_event_file_start *)event_data;
1065 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1066 int len = strlen(*data->filename);
1067 if (len > RFC1867_FILENAME_MAXLEN) len = RFC1867_FILENAME_MAXLEN;
1068 rfc1867ApcData->filename = std::string(*data->filename, len);
1069 rfc1867ApcData->temp_filename = "";
1070 len = strlen(data->name);
1071 if (len > RFC1867_NAME_MAXLEN) len = RFC1867_NAME_MAXLEN;
1072 rfc1867ApcData->name = std::string(data->name, len);
1073 DArrayInit track(6);
1074 track.set(s_total, rfc1867ApcData->content_length);
1075 track.set(s_current, rfc1867ApcData->bytes_processed);
1076 track.set(s_filename, rfc1867ApcData->filename);
1077 track.set(s_name, rfc1867ApcData->name);
1078 track.set(s_done, 0);
1079 track.set(s_start_time, rfc1867ApcData->start_time);
1080 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(), 3600);
1082 break;
1084 case MULTIPART_EVENT_FILE_DATA:
1085 if (!rfc1867ApcData->tracking_key.empty()) {
1086 multipart_event_file_data *data =
1087 (multipart_event_file_data *) event_data;
1088 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1089 if (rfc1867ApcData->bytes_processed -
1090 rfc1867ApcData->prev_bytes_processed >
1091 rfc1867ApcData->update_freq) {
1092 Variant v;
1093 if (apc_store().get(rfc1867ApcData->tracking_key, v)) {
1094 if (v.isArray()) {
1095 DArrayInit track(6);
1096 track.set(s_total, rfc1867ApcData->content_length);
1097 track.set(s_current, rfc1867ApcData->bytes_processed);
1098 track.set(s_filename, rfc1867ApcData->filename);
1099 track.set(s_name, rfc1867ApcData->name);
1100 track.set(s_done, 0);
1101 track.set(s_start_time, rfc1867ApcData->start_time);
1102 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(),
1103 3600);
1105 rfc1867ApcData->prev_bytes_processed =
1106 rfc1867ApcData->bytes_processed;
1110 break;
1112 case MULTIPART_EVENT_FILE_END:
1113 if (!rfc1867ApcData->tracking_key.empty()) {
1114 multipart_event_file_end *data =
1115 (multipart_event_file_end *)event_data;
1116 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1117 rfc1867ApcData->cancel_upload = data->cancel_upload;
1118 rfc1867ApcData->temp_filename = data->temp_filename;
1119 DArrayInit track(8);
1120 track.set(s_total, rfc1867ApcData->content_length);
1121 track.set(s_current, rfc1867ApcData->bytes_processed);
1122 track.set(s_filename, rfc1867ApcData->filename);
1123 track.set(s_name, rfc1867ApcData->name);
1124 track.set(s_temp_filename, rfc1867ApcData->temp_filename);
1125 track.set(s_cancel_upload, rfc1867ApcData->cancel_upload);
1126 track.set(s_done, 0);
1127 track.set(s_start_time, rfc1867ApcData->start_time);
1128 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(), 3600);
1130 break;
1132 case MULTIPART_EVENT_END:
1133 if (!rfc1867ApcData->tracking_key.empty()) {
1134 double now = my_time();
1135 multipart_event_end *data = (multipart_event_end *)event_data;
1136 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1137 if (now>rfc1867ApcData->start_time) {
1138 rfc1867ApcData->rate =
1139 8.0*rfc1867ApcData->bytes_processed/(now-rfc1867ApcData->start_time);
1140 } else {
1141 rfc1867ApcData->rate =
1142 8.0*rfc1867ApcData->bytes_processed; /* Too quick */
1143 DArrayInit track(8);
1144 track.set(s_total, rfc1867ApcData->content_length);
1145 track.set(s_current, rfc1867ApcData->bytes_processed);
1146 track.set(s_rate, rfc1867ApcData->rate);
1147 track.set(s_filename, rfc1867ApcData->filename);
1148 track.set(s_name, rfc1867ApcData->name);
1149 track.set(s_cancel_upload, rfc1867ApcData->cancel_upload);
1150 track.set(s_done, 1);
1151 track.set(s_start_time, rfc1867ApcData->start_time);
1152 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(),
1153 3600);
1156 break;
1158 return 0;
1161 ///////////////////////////////////////////////////////////////////////////////
1162 // apc serialization
1164 String apc_serialize(const_variant_ref value,
1165 APCSerializeMode mode /* = Normal */) {
1166 auto const enableApcSerialize = apcExtension::EnableApcSerialize;
1167 VariableSerializer::Type sType =
1168 enableApcSerialize ?
1169 VariableSerializer::Type::APCSerialize :
1170 VariableSerializer::Type::Internal;
1171 auto const options = enableApcSerialize && mode == APCSerializeMode::Prime
1172 ? VariableSerializer::kAPC_PRIME_SERIALIZE
1173 : 0;
1174 VariableSerializer vs(sType, options);
1175 return vs.serialize(value, true);
1178 Variant apc_unserialize(const char* data, int len) {
1179 VariableUnserializer::Type sType =
1180 apcExtension::EnableApcSerialize ?
1181 VariableUnserializer::Type::APCSerialize :
1182 VariableUnserializer::Type::Internal;
1183 return unserialize_ex(data, len, sType);
1186 String apc_reserialize(const String& str) {
1187 if (str.empty() ||
1188 !apcExtension::EnableApcSerialize) return str;
1190 VariableUnserializer uns(str.data(), str.size(),
1191 VariableUnserializer::Type::APCSerialize);
1192 StringBuffer buf;
1193 uns.reserialize(buf);
1195 return buf.detach();
1198 ///////////////////////////////////////////////////////////////////////////////
1199 // debugging support
1201 bool apc_dump(const char *filename, bool keyOnly, bool metaDump) {
1202 DumpMode mode;
1203 std::ofstream out(filename);
1205 // only one of these should ever be specified
1206 if (keyOnly && metaDump) {
1207 return false;
1210 if (out.fail()) {
1211 return false;
1214 if (keyOnly) {
1215 mode = DumpMode::KeyOnly;
1216 } else if (metaDump) {
1217 mode = DumpMode::KeyAndMeta;
1218 } else {
1219 mode = DumpMode::KeyAndValue;
1222 apc_store().dump(out, mode);
1223 out.close();
1224 return true;
1227 bool apc_dump_prefix(const char *filename,
1228 const std::string &prefix,
1229 uint32_t count) {
1230 std::ofstream out(filename);
1231 if (out.fail()) {
1232 return false;
1234 SCOPE_EXIT { out.close(); };
1236 apc_store().dumpPrefix(out, prefix, count);
1237 return true;
1240 bool apc_get_random_entries(std::ostream &out, uint32_t count) {
1241 apc_store().dumpRandomKeys(out, count);
1242 return true;
1245 // skewed sampling, so that bigger ones are more likely to be sampled.
1246 void apc_sample_by_size() {
1247 if (apcExtension::SizedSampleBytes == 0) return;
1248 if (!StructuredLog::enabled()) return;
1249 auto entries =
1250 apc_store().sampleEntriesInfoBySize(apcExtension::SizedSampleBytes);
1251 StructuredLogEntry sample;
1252 for (auto& entry : entries) {
1253 sample.setStr("key", entry.key);
1254 sample.setInt("in_mem", static_cast<int64_t>(entry.inMem));
1255 sample.setInt("ttl", entry.ttl);
1256 sample.setInt("size", entry.size);
1257 StructuredLog::log("apc_samples", sample);
1261 ///////////////////////////////////////////////////////////////////////////////