Add support for HHBC ops with 5 immediates
[hiphop-php.git] / hphp / runtime / ext / apc / ext_apc.cpp
blob81688dc845daf09a26c4300287c34d23f42a00bb
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/ext/apc/snapshot-builder.h"
39 #include "hphp/runtime/ext/fb/ext_fb.h"
40 #include "hphp/runtime/base/array-init.h"
41 #include "hphp/runtime/base/comparisons.h"
42 #include "hphp/runtime/base/execution-context.h"
43 #include "hphp/runtime/base/runtime-option.h"
44 #include "hphp/runtime/base/program-functions.h"
45 #include "hphp/runtime/base/builtin-functions.h"
46 #include "hphp/runtime/base/variable-serializer.h"
47 #include "hphp/runtime/base/concurrent-shared-store.h"
48 #include "hphp/runtime/base/ini-setting.h"
49 #include "hphp/runtime/base/config.h"
50 #include "hphp/runtime/base/apc-file-storage.h"
51 #include "hphp/runtime/server/cli-server.h"
53 using HPHP::ScopedMem;
55 namespace HPHP {
56 ///////////////////////////////////////////////////////////////////////////////
58 namespace {
60 std::aligned_storage<
61 sizeof(ConcurrentTableSharedStore),
62 alignof(ConcurrentTableSharedStore)
63 >::type s_apc_storage;
65 using UserAPCCache = folly::AtomicHashMap<uid_t, ConcurrentTableSharedStore*>;
67 std::aligned_storage<
68 sizeof(UserAPCCache),
69 alignof(UserAPCCache)
70 >::type s_user_apc_storage;
72 UserAPCCache& apc_store_local() {
73 void* vpUserStore = &s_user_apc_storage;
74 return *static_cast<UserAPCCache*>(vpUserStore);
77 ConcurrentTableSharedStore& apc_store_local(uid_t uid) {
78 auto& cache = apc_store_local();
79 auto iter = cache.find(uid);
80 if (iter != cache.end()) return *(iter->second);
81 auto table = new ConcurrentTableSharedStore;
82 auto res = cache.insert(uid, table);
83 if (!res.second) delete table;
84 return *res.first->second;
87 ConcurrentTableSharedStore& apc_store() {
88 if (UNLIKELY(!RuntimeOption::RepoAuthoritative &&
89 RuntimeOption::EvalUnixServerQuarantineApc)) {
90 if (auto uc = get_cli_ucred()) {
91 return apc_store_local(uc->uid);
94 void* vpStore = &s_apc_storage;
95 return *static_cast<ConcurrentTableSharedStore*>(vpStore);
100 //////////////////////////////////////////////////////////////////////
102 void initialize_apc() {
103 APCStats::Create();
104 // Note: we never destruct APC, currently.
105 void* vpStore = &s_apc_storage;
106 new (vpStore) ConcurrentTableSharedStore;
108 if (UNLIKELY(!RuntimeOption::RepoAuthoritative &&
109 RuntimeOption::EvalUnixServerQuarantineApc)) {
110 new (&s_user_apc_storage) UserAPCCache(10);
114 //////////////////////////////////////////////////////////////////////
116 const StaticString
117 s_delete("delete");
119 extern void const_load();
121 typedef ConcurrentTableSharedStore::KeyValuePair KeyValuePair;
122 typedef ConcurrentTableSharedStore::DumpMode DumpMode;
124 static void* keep_alive;
126 void apcExtension::moduleLoad(const IniSetting::Map& ini, Hdf config) {
127 if (!keep_alive && ini.isString()) {
128 // this is a hack to preserve some dynamic entry points
129 switch (ini.toString().size()) {
130 case 0: keep_alive = (void*)const_load; break;
131 case 2: keep_alive = (void*)const_load_impl_compressed; break;
132 case 4: keep_alive = (void*)apc_load_impl_compressed; break;
136 Config::Bind(Enable, ini, config, "Server.APC.EnableApc", true);
137 Config::Bind(EnableConstLoad, ini, config, "Server.APC.EnableConstLoad",
138 false);
139 Config::Bind(ForceConstLoadToAPC, ini, config,
140 "Server.APC.ForceConstLoadToAPC", true);
141 Config::Bind(PrimeLibrary, ini, config, "Server.APC.PrimeLibrary");
142 Config::Bind(LoadThread, ini, config, "Server.APC.LoadThread", 15);
143 Config::Bind(CompletionKeys, ini, config, "Server.APC.CompletionKeys");
144 std::string tblType = Config::GetString(ini, config, "Server.APC.TableType",
145 "concurrent");
146 if (strcasecmp(tblType.c_str(), "concurrent") == 0) {
147 TableType = TableTypes::ConcurrentTable;
148 } else {
149 throw std::runtime_error("invalid apc table type");
151 Config::Bind(EnableApcSerialize, ini, config, "Server.APC.EnableApcSerialize",
152 true);
153 Config::Bind(ExpireOnSets, ini, config, "Server.APC.ExpireOnSets");
154 Config::Bind(PurgeFrequency, ini, config, "Server.APC.PurgeFrequency", 4096);
155 Config::Bind(PurgeRate, ini, config, "Server.APC.PurgeRate", -1);
157 Config::Bind(AllowObj, ini, config, "Server.APC.AllowObject");
158 Config::Bind(TTLLimit, ini, config, "Server.APC.TTLLimit", -1);
159 // Any TTL > TTLMaxFinite will be made infinite. NB: Applied *after* TTLLimit.
160 Config::Bind(TTLMaxFinite, ini, config, "Server.APC.TTLMaxFinite",
161 std::numeric_limits<int64_t>::max());
162 Config::Bind(HotPrefix, ini, config, "Server.APC.HotPrefix");
163 Config::Bind(HotSize, ini, config, "Server.APC.HotSize", 30000);
164 Config::Bind(HotLoadFactor, ini, config, "Server.APC.HotLoadFactor", 0.5);
165 Config::Bind(HotKeyAllocLow, ini, config, "Server.APC.HotKeyAllocLow", false);
166 Config::Bind(HotMapAllocLow, ini, config, "Server.APC.HotMapAllocLow", false);
168 // Loads .so PrimeLibrary, writes snapshot output to this file, then exits.
169 Config::Bind(PrimeLibraryUpgradeDest, ini, config,
170 "Server.APC.PrimeLibraryUpgradeDest");
172 // FileStorage
173 Config::Bind(UseFileStorage, ini, config, "Server.APC.FileStorage.Enable");
174 FileStorageChunkSize = Config::GetInt64(ini, config,
175 "Server.APC.FileStorage.ChunkSize",
176 1LL << 29);
177 Config::Bind(FileStoragePrefix, ini, config, "Server.APC.FileStorage.Prefix",
178 "/tmp/apc_store");
179 Config::Bind(FileStorageFlagKey, ini, config,
180 "Server.APC.FileStorage.FlagKey", "_madvise_out");
181 Config::Bind(FileStorageAdviseOutPeriod, ini, config,
182 "Server.APC.FileStorage.AdviseOutPeriod", 1800);
183 Config::Bind(FileStorageKeepFileLinked, ini, config,
184 "Server.APC.FileStorage.KeepFileLinked");
186 Config::Bind(KeyMaturityThreshold, ini, config,
187 "Server.APC.KeyMaturityThreshold", 20);
188 Config::Bind(MaximumCapacity, ini, config, "Server.APC.MaximumCapacity", 0);
189 Config::Bind(KeyFrequencyUpdatePeriod, ini, config,
190 "Server.APC.KeyFrequencyUpdatePeriod", 1000);
192 Config::Bind(NoTTLPrefix, ini, config, "Server.APC.NoTTLPrefix");
194 #ifdef NO_M_DATA
195 Config::Bind(UseUncounted, ini, config, "Server.APC.MemModelTreadmill", true);
196 #else
197 Config::Bind(UseUncounted, ini, config, "Server.APC.MemModelTreadmill",
198 RuntimeOption::ServerExecutionMode());
199 #endif
200 Config::Bind(ShareUncounted, ini, config, "Server.APC.ShareUncounted", true);
201 if (!UseUncounted && ShareUncounted) ShareUncounted = false;
203 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.enabled", &Enable);
204 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.stat",
205 RuntimeOption::RepoAuthoritative ? "0" : "1", &Stat);
206 IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM, "apc.enable_cli",
207 &EnableCLI);
210 void apcExtension::moduleInit() {
211 #ifdef NO_M_DATA
212 if (!UseUncounted) {
213 Logger::Error("Server.APC.MemModelTreadmill=false ignored in lowptr build");
214 UseUncounted = true;
216 #endif // NO_M_DATA
217 if (UseFileStorage) {
218 // We use 32 bits to represent offset into a chunk, so don't make it too
219 // large.
220 constexpr int64_t MaxChunkSize = 1LL << 31;
221 if (FileStorageChunkSize > MaxChunkSize) {
222 Logger::Warning("Server.APC.FileStorage.ChunkSize too large, "
223 "resetting to %" PRId64, MaxChunkSize);
224 FileStorageChunkSize = MaxChunkSize;
226 s_apc_file_storage.enable(FileStoragePrefix, FileStorageChunkSize);
229 HHVM_RC_INT(APC_ITER_TYPE, 0x1);
230 HHVM_RC_INT(APC_ITER_KEY, 0x2);
231 HHVM_RC_INT(APC_ITER_FILENAME, 0x4);
232 HHVM_RC_INT(APC_ITER_DEVICE, 0x8);
233 HHVM_RC_INT(APC_ITER_INODE, 0x10);
234 HHVM_RC_INT(APC_ITER_VALUE, 0x20);
235 HHVM_RC_INT(APC_ITER_MD5, 0x40);
236 HHVM_RC_INT(APC_ITER_NUM_HITS, 0x80);
237 HHVM_RC_INT(APC_ITER_MTIME, 0x100);
238 HHVM_RC_INT(APC_ITER_CTIME, 0x200);
239 HHVM_RC_INT(APC_ITER_DTIME, 0x400);
240 HHVM_RC_INT(APC_ITER_ATIME, 0x800);
241 HHVM_RC_INT(APC_ITER_REFCOUNT, 0x1000);
242 HHVM_RC_INT(APC_ITER_MEM_SIZE, 0x2000);
243 HHVM_RC_INT(APC_ITER_TTL, 0x4000);
244 HHVM_RC_INT(APC_ITER_NONE, 0x0);
245 HHVM_RC_INT(APC_ITER_ALL, 0xFFFFFFFFFF);
246 HHVM_RC_INT(APC_LIST_ACTIVE, 1);
247 HHVM_RC_INT(APC_LIST_DELETED, 2);
249 HHVM_FE(apc_add);
250 HHVM_FE(apc_store);
251 HHVM_FE(apc_store_as_primed_do_not_use);
252 HHVM_FE(apc_fetch);
253 HHVM_FE(apc_delete);
254 HHVM_FE(apc_clear_cache);
255 HHVM_FE(apc_inc);
256 HHVM_FE(apc_dec);
257 HHVM_FE(apc_cas);
258 HHVM_FE(apc_exists);
259 HHVM_FE(apc_cache_info);
260 HHVM_FE(apc_sma_info);
261 loadSystemlib();
264 void apcExtension::moduleShutdown() {
265 if (UseFileStorage) {
266 s_apc_file_storage.cleanup();
271 bool apcExtension::Enable = true;
272 bool apcExtension::EnableConstLoad = false;
273 bool apcExtension::ForceConstLoadToAPC = true;
274 std::string apcExtension::PrimeLibrary;
275 int apcExtension::LoadThread = 15;
276 std::set<std::string> apcExtension::CompletionKeys;
277 apcExtension::TableTypes apcExtension::TableType =
278 TableTypes::ConcurrentTable;
279 bool apcExtension::EnableApcSerialize = true;
280 int64_t apcExtension::KeyMaturityThreshold = 20;
281 int64_t apcExtension::MaximumCapacity = 0;
282 int apcExtension::KeyFrequencyUpdatePeriod = 1000;
283 bool apcExtension::ExpireOnSets = false;
284 int apcExtension::PurgeFrequency = 4096;
285 int apcExtension::PurgeRate = -1;
286 bool apcExtension::AllowObj = false;
287 int apcExtension::TTLLimit = -1;
288 int64_t apcExtension::TTLMaxFinite = std::numeric_limits<int64_t>::max();
289 int apcExtension::HotSize = 30000;
290 double apcExtension::HotLoadFactor = 0.5;
291 std::vector<std::string> apcExtension::HotPrefix;
292 bool apcExtension::HotKeyAllocLow = false;
293 bool apcExtension::HotMapAllocLow = false;
294 std::string apcExtension::PrimeLibraryUpgradeDest;
295 bool apcExtension::UseFileStorage = false;
296 int64_t apcExtension::FileStorageChunkSize = int64_t(1LL << 29);
297 std::string apcExtension::FileStoragePrefix = "/tmp/apc_store";
298 int apcExtension::FileStorageAdviseOutPeriod = 1800;
299 std::string apcExtension::FileStorageFlagKey = "_madvise_out";
300 bool apcExtension::FileStorageKeepFileLinked = false;
301 std::vector<std::string> apcExtension::NoTTLPrefix;
302 #ifdef NO_M_DATA
303 bool apcExtension::UseUncounted = true;
304 #else
305 bool apcExtension::UseUncounted = false;
306 #endif
307 bool apcExtension::ShareUncounted = true;
308 bool apcExtension::Stat = true;
309 // Different from zend default but matches what we've been returning for years
310 bool apcExtension::EnableCLI = true;
312 static apcExtension s_apc_extension;
314 Variant HHVM_FUNCTION(apc_store,
315 const Variant& key_or_array,
316 const Variant& var /* = null */,
317 int64_t ttl /* = 0 */) {
318 if (!apcExtension::Enable) return Variant(false);
320 if (key_or_array.isArray()) {
321 Array valuesArr = key_or_array.toArray();
323 for (ArrayIter iter(valuesArr); iter; ++iter) {
324 Variant key = iter.first();
325 if (!key.isString()) {
326 throw_invalid_argument("apc key: (not a string)");
327 return Variant(false);
329 Variant v = iter.second();
330 apc_store().set(key.toString(), v, ttl);
333 return Variant(staticEmptyArray());
336 if (!key_or_array.isString()) {
337 throw_invalid_argument("apc key: (not a string)");
338 return Variant(false);
340 String strKey = key_or_array.toString();
341 apc_store().set(strKey, var, ttl);
342 return Variant(true);
346 * Stores the key in a similar fashion as "priming" would do (no TTL limit).
347 * Using this function is equivalent to adding your key to apc_prime.so.
349 bool HHVM_FUNCTION(apc_store_as_primed_do_not_use,
350 const String& key,
351 const Variant& var) {
352 if (!apcExtension::Enable) return false;
353 apc_store().setWithoutTTL(key, var);
354 return true;
357 Variant HHVM_FUNCTION(apc_add,
358 const Variant& key_or_array,
359 const Variant& var /* = null */,
360 int64_t ttl /* = 0 */) {
361 if (!apcExtension::Enable) return false;
363 if (key_or_array.isArray()) {
364 Array valuesArr = key_or_array.toArray();
366 // errors stores all keys corresponding to entries that could not be cached
367 ArrayInit errors(valuesArr.size(), ArrayInit::Map{});
369 for (ArrayIter iter(valuesArr); iter; ++iter) {
370 Variant key = iter.first();
371 if (!key.isString()) {
372 throw_invalid_argument("apc key: (not a string)");
373 return false;
375 Variant v = iter.second();
376 if (!apc_store().add(key.toString(), v, ttl)) {
377 errors.add(key, -1);
380 return errors.toVariant();
383 if (!key_or_array.isString()) {
384 throw_invalid_argument("apc key: (not a string)");
385 return false;
387 String strKey = key_or_array.toString();
388 return apc_store().add(strKey, var, ttl);
391 TypedValue HHVM_FUNCTION(apc_fetch, const Variant& key, VRefParam success) {
392 if (!apcExtension::Enable) return make_tv<KindOfBoolean>(false);
394 Variant v;
396 if (key.isArray()) {
397 bool tmp = false;
398 Array keys = key.toArray();
399 ArrayInit init(keys.size(), ArrayInit::Map{});
400 for (ArrayIter iter(keys); iter; ++iter) {
401 Variant k = iter.second();
402 if (!k.isString()) {
403 throw_invalid_argument("apc key: (not a string)");
404 return make_tv<KindOfBoolean>(false);
406 String strKey = k.toString();
407 if (apc_store().get(strKey, v)) {
408 tmp = true;
409 init.set(strKey, v);
412 success.assignIfRef(tmp);
413 return tvReturn(init.toVariant());
416 if (apc_store().get(key.toString(), v)) {
417 success.assignIfRef(true);
418 } else {
419 success.assignIfRef(false);
420 v = false;
422 return tvReturn(std::move(v));
425 Variant HHVM_FUNCTION(apc_delete,
426 const Variant& key) {
427 if (!apcExtension::Enable) return false;
429 if (key.isArray()) {
430 Array keys = key.toArray();
431 PackedArrayInit init(keys.size());
432 for (ArrayIter iter(keys); iter; ++iter) {
433 Variant k = iter.second();
434 if (!k.isString()) {
435 raise_warning("apc key is not a string");
436 init.append(k);
437 } else if (!apc_store().eraseKey(k.toCStrRef())) {
438 init.append(k);
441 return init.toVariant();
442 } else if(key.is(KindOfObject)) {
443 if (!key.getObjectData()->getVMClass()->
444 classof(SystemLib::s_APCIteratorClass)) {
445 raise_error(
446 "apc_delete(): apc_delete object argument must be instance"
447 " of APCIterator"
449 return false;
451 const Func* method =
452 SystemLib::s_APCIteratorClass->lookupMethod(s_delete.get());
453 return Variant::attach(
454 g_context->invokeFuncFew(method, key.getObjectData())
458 return apc_store().eraseKey(key.toString());
461 bool HHVM_FUNCTION(apc_clear_cache, const String& /*cache_type*/ /* = "" */) {
462 if (!apcExtension::Enable) return false;
463 return apc_store().clear();
466 Variant HHVM_FUNCTION(apc_inc,
467 const String& key,
468 int64_t step /* = 1 */,
469 VRefParam success /* = null */) {
470 if (!apcExtension::Enable) return false;
472 bool found = false;
473 int64_t newValue = apc_store().inc(key, step, found);
474 success.assignIfRef(found);
475 if (!found) return false;
476 return newValue;
479 Variant HHVM_FUNCTION(apc_dec,
480 const String& key,
481 int64_t step /* = 1 */,
482 VRefParam success /* = null */) {
483 if (!apcExtension::Enable) return false;
485 bool found = false;
486 int64_t newValue = apc_store().inc(key, -step, found);
487 success.assignIfRef(found);
488 if (!found) return false;
489 return newValue;
492 bool HHVM_FUNCTION(apc_cas,
493 const String& key,
494 int64_t old_cas,
495 int64_t new_cas) {
496 if (!apcExtension::Enable) return false;
497 return apc_store().cas(key, old_cas, new_cas);
500 Variant HHVM_FUNCTION(apc_exists,
501 const Variant& key) {
502 if (!apcExtension::Enable) return false;
504 if (key.isArray()) {
505 Array keys = key.toArray();
506 PackedArrayInit init(keys.size());
507 for (ArrayIter iter(keys); iter; ++iter) {
508 Variant k = iter.second();
509 if (!k.isString()) {
510 throw_invalid_argument("apc key: (not a string)");
511 return false;
513 String strKey = k.toString();
514 if (apc_store().exists(strKey)) {
515 init.append(strKey);
518 return init.toVariant();
521 return apc_store().exists(key.toString());
525 const StaticString s_user("user");
526 const StaticString s_start_time("start_time");
527 const StaticString s_ttl("ttl");
528 const StaticString s_cache_list("cache_list");
529 const StaticString s_info("info");
530 const StaticString s_in_memory("in_memory");
531 const StaticString s_mem_size("mem_size");
532 const StaticString s_type("type");
533 const StaticString s_c_time("creation_time");
534 const StaticString s_mtime("mtime");
536 // This is a guess to the size of the info array. It is significantly
537 // bigger than what we need but hard to control all the info that we
538 // may want to add here.
539 // Try to keep it such that we do not have to resize the array
540 const uint32_t kCacheInfoSize = 40;
541 // Number of elements in the entry array
542 const int32_t kEntryInfoSize = 7;
544 Variant HHVM_FUNCTION(apc_cache_info,
545 const String& cache_type,
546 bool limited /* = false */) {
547 ArrayInit info(kCacheInfoSize, ArrayInit::Map{});
548 info.add(s_start_time, start_time());
549 if (cache_type.size() != 0 && !cache_type.same(s_user)) {
550 return info.toArray();
553 info.add(s_ttl, apcExtension::TTLLimit);
555 std::map<const StringData*, int64_t> stats;
556 APCStats::getAPCStats().collectStats(stats);
557 for (auto it = stats.begin(); it != stats.end(); it++) {
558 info.add(Variant(it->first, Variant::PersistentStrInit{}), it->second);
560 if (!limited) {
561 auto const entries = apc_store().getEntriesInfo();
562 PackedArrayInit ents(entries.size());
563 for (auto& entry : entries) {
564 ArrayInit ent(kEntryInfoSize, ArrayInit::Map{});
565 ent.add(s_info,
566 Variant::attach(StringData::Make(entry.key.c_str())));
567 ent.add(s_in_memory, entry.inMem);
568 ent.add(s_ttl, entry.ttl);
569 ent.add(s_mem_size, entry.size);
570 ent.add(s_type, static_cast<int64_t>(entry.type));
571 ent.add(s_c_time, entry.c_time);
572 ent.add(s_mtime, entry.mtime);
573 ents.append(ent.toArray());
575 info.add(s_cache_list, ents.toArray(), false);
577 return info.toArray();
580 Array HHVM_FUNCTION(apc_sma_info, bool /*limited*/ /* = false */) {
581 return empty_array();
584 ///////////////////////////////////////////////////////////////////////////////
585 // loading APC from archive files
587 typedef void(*PFUNC_APC_LOAD)();
589 // Structure to hold cache meta data
590 // Same definition in ext_apc.cpp
591 struct cache_info {
592 char *a_name;
593 bool use_const;
596 static Mutex dl_mutex;
597 static PFUNC_APC_LOAD apc_load_func(void *handle, const char *name) {
598 #ifdef _MSC_VER
599 throw Exception("apc_load_func is not currently supported under MSVC!");
600 #else
601 PFUNC_APC_LOAD p = (PFUNC_APC_LOAD)dlsym(handle, name);
602 if (p == nullptr) {
603 throw Exception("Unable to find %s in %s", name,
604 apcExtension::PrimeLibrary.c_str());
606 return p;
607 #endif
610 struct ApcLoadJob {
611 ApcLoadJob(void *handle, int index) : m_handle(handle), m_index(index) {}
612 void *m_handle; int m_index;
615 struct ApcLoadWorker {
616 void onThreadEnter() {
617 g_context.getCheck();
619 void doJob(std::shared_ptr<ApcLoadJob> job) {
620 char func_name[128];
621 MemoryManager::SuppressOOM so(*tl_heap);
622 snprintf(func_name, sizeof(func_name), "_apc_load_%d", job->m_index);
623 apc_load_func(job->m_handle, func_name)();
625 void onThreadExit() {
626 hphp_memory_cleanup();
630 static size_t s_const_map_size = 0;
632 static SnapshotBuilder s_snapshotBuilder;
634 void apc_load(int thread) {
635 #ifndef _MSC_VER
636 static void *handle = nullptr;
637 if (handle ||
638 apcExtension::PrimeLibrary.empty() ||
639 !apcExtension::Enable) {
640 return;
642 BootStats::Block timer("loading APC data");
643 if (apc_store().primeFromSnapshot(apcExtension::PrimeLibrary.c_str())) {
644 return;
646 Logger::Info("Fall back to shared object format");
647 handle = dlopen(apcExtension::PrimeLibrary.c_str(), RTLD_LAZY);
648 if (!handle) {
649 throw Exception("Unable to open apc prime library %s: %s",
650 apcExtension::PrimeLibrary.c_str(), dlerror());
653 auto upgradeDest = apcExtension::PrimeLibraryUpgradeDest;
654 if (!upgradeDest.empty()) {
655 thread = 1; // SnapshotBuilder is not (yet) thread-safe.
656 // TODO(9755792): Ensure APCFileStorage is enabled.
659 if (thread <= 1) {
660 apc_load_func(handle, "_apc_load_all")();
661 } else {
662 int count = ((int(*)())apc_load_func(handle, "_apc_load_count"))();
664 std::vector<std::shared_ptr<ApcLoadJob>> jobs;
665 jobs.reserve(count);
666 for (int i = 0; i < count; i++) {
667 jobs.push_back(std::make_shared<ApcLoadJob>(handle, i));
669 JobDispatcher<ApcLoadJob, ApcLoadWorker>(std::move(jobs), thread).run();
672 apc_store().primeDone();
673 if (!upgradeDest.empty()) {
674 BootStats::Block block("SnapshotBuilder::writeToFile");
675 s_snapshotBuilder.writeToFile(upgradeDest);
678 // We've copied all the data out, so close it out.
679 dlclose(handle);
680 #endif
683 void apc_advise_out() {
684 apc_store().adviseOut();
687 size_t get_const_map_size() {
688 return s_const_map_size;
691 ///////////////////////////////////////////////////////////////////////////////
692 // Constant and APC priming (always with compressed data).
694 EXTERNALLY_VISIBLE
695 void const_load_impl_compressed(
696 struct cache_info* /*info*/, int* /*int_lens*/, const char* /*int_keys*/,
697 long long* /*int_values*/, int* /*char_lens*/, const char* /*char_keys*/,
698 char* /*char_values*/, int* /*string_lens*/, const char* /*strings*/,
699 int* /*object_lens*/, const char* /*objects*/, int* /*thrift_lens*/,
700 const char* /*thrifts*/, int* /*other_lens*/, const char* /*others*/) {
701 // TODO(8117903): Unused; remove after updating www side.
704 EXTERNALLY_VISIBLE
705 void apc_load_impl_compressed
706 (struct cache_info *info,
707 int *int_lens, const char *int_keys, long long *int_values,
708 int *char_lens, const char *char_keys, char *char_values,
709 int *string_lens, const char *strings,
710 int *object_lens, const char *objects,
711 int *thrift_lens, const char *thrifts,
712 int *other_lens, const char *others) {
713 bool readOnly = apcExtension::EnableConstLoad && info && info->use_const;
714 if (readOnly && info->a_name) Logger::FInfo("const archive {}", info->a_name);
715 auto& s = apc_store();
716 SnapshotBuilder* snap = apcExtension::PrimeLibraryUpgradeDest.empty() ?
717 nullptr : &s_snapshotBuilder;
719 int count = int_lens[0];
720 int len = int_lens[1];
721 if (count) {
722 std::vector<KeyValuePair> vars(count);
723 char *keys = gzdecode(int_keys, len);
724 if (keys == nullptr) throw Exception("bad compressed apc archive.");
725 ScopedMem holder(keys);
726 const char *k = keys;
727 long long* v = int_values;
728 for (int i = 0; i < count; i++) {
729 auto& item = vars[i];
730 item.key = k;
731 item.readOnly = readOnly;
732 s.constructPrime(*v++, item);
733 if (UNLIKELY(snap != nullptr)) snap->addInt(v[-1], item);
734 k += int_lens[i + 2] + 1; // skip \0
736 s.prime(std::move(vars));
737 assertx((k - keys) == len);
741 int count = char_lens[0];
742 int len = char_lens[1];
743 if (count) {
744 std::vector<KeyValuePair> vars(count);
745 char *keys = gzdecode(char_keys, len);
746 if (keys == nullptr) throw Exception("bad compressed apc archive.");
747 ScopedMem holder(keys);
748 const char *k = keys;
749 char *v = char_values;
750 for (int i = 0; i < count; i++) {
751 auto& item = vars[i];
752 item.key = k;
753 item.readOnly = readOnly;
754 switch (*v++) {
755 case 0:
756 s.constructPrime(false, item);
757 if (UNLIKELY(snap != nullptr)) snap->addFalse(item);
758 break;
759 case 1:
760 s.constructPrime(true, item);
761 if (UNLIKELY(snap != nullptr)) snap->addTrue(item);
762 break;
763 case 2:
764 s.constructPrime(uninit_null(), item);
765 if (UNLIKELY(snap != nullptr)) snap->addNull(item);
766 break;
767 default:
768 throw Exception("bad apc archive, unknown char type");
770 k += char_lens[i + 2] + 1; // skip \0
772 s.prime(std::move(vars));
773 assertx((k - keys) == len);
777 int count = string_lens[0] / 2;
778 int len = string_lens[1];
779 if (count) {
780 std::vector<KeyValuePair> vars(count);
781 char *decoded = gzdecode(strings, len);
782 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
783 ScopedMem holder(decoded);
784 const char *p = decoded;
785 for (int i = 0; i < count; i++) {
786 auto& item = vars[i];
787 item.key = p;
788 item.readOnly = readOnly;
789 p += string_lens[i + i + 2] + 1; // skip \0
790 // Strings would be copied into APC anyway.
791 String value(p, string_lens[i + i + 3], CopyString);
792 // todo: t2539893: check if value is already a static string
793 s.constructPrime(value, item, false);
794 if (UNLIKELY(snap != nullptr)) snap->addString(value, item);
795 p += string_lens[i + i + 3] + 1; // skip \0
797 s.prime(std::move(vars));
798 assertx((p - decoded) == len);
802 int count = object_lens[0] / 2;
803 int len = object_lens[1];
804 if (count) {
805 std::vector<KeyValuePair> vars(count);
806 char *decoded = gzdecode(objects, len);
807 if (decoded == nullptr) throw Exception("bad compressed APC archive.");
808 ScopedMem holder(decoded);
809 const char *p = decoded;
810 for (int i = 0; i < count; i++) {
811 auto& item = vars[i];
812 item.key = p;
813 item.readOnly = readOnly;
814 p += object_lens[i + i + 2] + 1; // skip \0
815 String value(p, object_lens[i + i + 3], CopyString);
816 s.constructPrime(value, item, true);
817 if (UNLIKELY(snap != nullptr)) snap->addObject(value, item);
818 p += object_lens[i + i + 3] + 1; // skip \0
820 s.prime(std::move(vars));
821 assertx((p - decoded) == len);
825 int count = thrift_lens[0] / 2;
826 int len = thrift_lens[1];
827 if (count) {
828 std::vector<KeyValuePair> vars(count);
829 char *decoded = gzdecode(thrifts, len);
830 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
831 ScopedMem holder(decoded);
832 const char *p = decoded;
833 for (int i = 0; i < count; i++) {
834 auto& item = vars[i];
835 item.key = p;
836 item.readOnly = readOnly;
837 p += thrift_lens[i + i + 2] + 1; // skip \0
838 String value(p, thrift_lens[i + i + 3], CopyString);
839 Variant success;
840 Variant v = HHVM_FN(fb_unserialize)(value, ref(success));
841 if (same(success, false)) {
842 throw Exception("bad apc archive, fb_unserialize failed");
844 s.constructPrime(v, item);
845 if (UNLIKELY(snap != nullptr)) snap->addThrift(value, item);
846 p += thrift_lens[i + i + 3] + 1; // skip \0
848 s.prime(std::move(vars));
849 assertx((p - decoded) == len);
853 int count = other_lens[0] / 2;
854 int len = other_lens[1];
855 if (count) {
856 std::vector<KeyValuePair> vars(count);
857 char *decoded = gzdecode(others, len);
858 if (decoded == nullptr) throw Exception("bad compressed apc archive.");
859 ScopedMem holder(decoded);
860 const char *p = decoded;
861 for (int i = 0; i < count; i++) {
862 auto& item = vars[i];
863 item.key = p;
864 item.readOnly = readOnly;
865 p += other_lens[i + i + 2] + 1; // skip \0
866 String value(p, other_lens[i + i + 3], CopyString);
867 Variant v =
868 unserialize_from_string(value, VariableUnserializer::Type::Internal);
869 if (same(v, false)) {
870 // we can't possibly get here if it was a boolean "false" that's
871 // supposed to be serialized as a char
872 throw Exception("bad apc archive, unserialize_from_string failed");
874 s.constructPrime(v, item);
875 if (UNLIKELY(snap != nullptr)) snap->addOther(value, item);
876 p += other_lens[i + i + 3] + 1; // skip \0
878 s.prime(std::move(vars));
879 assertx((p - decoded) == len);
884 ///////////////////////////////////////////////////////////////////////////////
886 static double my_time() {
887 struct timeval a;
888 double t;
889 gettimeofday(&a, nullptr);
890 t = a.tv_sec + (a.tv_usec/1000000.00);
891 return t;
894 const StaticString
895 s_total("total"),
896 s_current("current"),
897 s_filename("filename"),
898 s_name("name"),
899 s_done("done"),
900 s_temp_filename("temp_filename"),
901 s_cancel_upload("cancel_upload"),
902 s_rate("rate");
904 #define RFC1867_TRACKING_KEY_MAXLEN 63
905 #define RFC1867_NAME_MAXLEN 63
906 #define RFC1867_FILENAME_MAXLEN 127
908 int apc_rfc1867_progress(apc_rfc1867_data* rfc1867ApcData, unsigned int event,
909 void* event_data, void** /*extra*/) {
910 switch (event) {
911 case MULTIPART_EVENT_START: {
912 multipart_event_start *data = (multipart_event_start *) event_data;
913 rfc1867ApcData->content_length = data->content_length;
914 rfc1867ApcData->tracking_key.clear();
915 rfc1867ApcData->name.clear();
916 rfc1867ApcData->cancel_upload = 0;
917 rfc1867ApcData->temp_filename = "";
918 rfc1867ApcData->start_time = my_time();
919 rfc1867ApcData->bytes_processed = 0;
920 rfc1867ApcData->prev_bytes_processed = 0;
921 rfc1867ApcData->rate = 0;
922 rfc1867ApcData->update_freq = RuntimeOption::Rfc1867Freq;
924 if (rfc1867ApcData->update_freq < 0) {
925 assertx(false); // TODO: support percentage
926 // frequency is a percentage, not bytes
927 rfc1867ApcData->update_freq =
928 rfc1867ApcData->content_length * RuntimeOption::Rfc1867Freq / 100;
930 break;
933 case MULTIPART_EVENT_FORMDATA: {
934 multipart_event_formdata *data = (multipart_event_formdata *)event_data;
935 if (data->name &&
936 !strncasecmp(data->name, RuntimeOption::Rfc1867Name.c_str(),
937 RuntimeOption::Rfc1867Name.size()) &&
938 data->value && data->length &&
939 data->length < RFC1867_TRACKING_KEY_MAXLEN -
940 RuntimeOption::Rfc1867Prefix.size()) {
941 int len = RuntimeOption::Rfc1867Prefix.size();
942 if (len > RFC1867_TRACKING_KEY_MAXLEN) {
943 len = RFC1867_TRACKING_KEY_MAXLEN;
945 rfc1867ApcData->tracking_key =
946 std::string(RuntimeOption::Rfc1867Prefix.c_str(), len);
947 len = strlen(*data->value);
948 int rem = RFC1867_TRACKING_KEY_MAXLEN -
949 rfc1867ApcData->tracking_key.size();
950 if (len > rem) len = rem;
951 rfc1867ApcData->tracking_key +=
952 std::string(*data->value, len);
953 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
955 /* Facebook: Temporary fix for a bug in PHP's rfc1867 code,
956 fixed here for convenience:
957 http://cvs.php.net/viewvc.cgi/php-src/main/
958 rfc1867.c?r1=1.173.2.1.2.11&r2=1.173.2.1.2.12 */
959 (*data->newlength) = data->length;
960 break;
963 case MULTIPART_EVENT_FILE_START:
964 if (!rfc1867ApcData->tracking_key.empty()) {
965 multipart_event_file_start *data =
966 (multipart_event_file_start *)event_data;
968 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
969 int len = strlen(*data->filename);
970 if (len > RFC1867_FILENAME_MAXLEN) len = RFC1867_FILENAME_MAXLEN;
971 rfc1867ApcData->filename = std::string(*data->filename, len);
972 rfc1867ApcData->temp_filename = "";
973 len = strlen(data->name);
974 if (len > RFC1867_NAME_MAXLEN) len = RFC1867_NAME_MAXLEN;
975 rfc1867ApcData->name = std::string(data->name, len);
976 ArrayInit track(6, ArrayInit::Map{});
977 track.set(s_total, rfc1867ApcData->content_length);
978 track.set(s_current, rfc1867ApcData->bytes_processed);
979 track.set(s_filename, rfc1867ApcData->filename);
980 track.set(s_name, rfc1867ApcData->name);
981 track.set(s_done, 0);
982 track.set(s_start_time, rfc1867ApcData->start_time);
983 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(), 3600);
985 break;
987 case MULTIPART_EVENT_FILE_DATA:
988 if (!rfc1867ApcData->tracking_key.empty()) {
989 multipart_event_file_data *data =
990 (multipart_event_file_data *) event_data;
991 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
992 if (rfc1867ApcData->bytes_processed -
993 rfc1867ApcData->prev_bytes_processed >
994 rfc1867ApcData->update_freq) {
995 Variant v;
996 if (apc_store().get(rfc1867ApcData->tracking_key, v)) {
997 if (v.isArray()) {
998 ArrayInit track(6, ArrayInit::Map{});
999 track.set(s_total, rfc1867ApcData->content_length);
1000 track.set(s_current, rfc1867ApcData->bytes_processed);
1001 track.set(s_filename, rfc1867ApcData->filename);
1002 track.set(s_name, rfc1867ApcData->name);
1003 track.set(s_done, 0);
1004 track.set(s_start_time, rfc1867ApcData->start_time);
1005 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(),
1006 3600);
1008 rfc1867ApcData->prev_bytes_processed =
1009 rfc1867ApcData->bytes_processed;
1013 break;
1015 case MULTIPART_EVENT_FILE_END:
1016 if (!rfc1867ApcData->tracking_key.empty()) {
1017 multipart_event_file_end *data =
1018 (multipart_event_file_end *)event_data;
1019 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1020 rfc1867ApcData->cancel_upload = data->cancel_upload;
1021 rfc1867ApcData->temp_filename = data->temp_filename;
1022 ArrayInit track(8, ArrayInit::Map{});
1023 track.set(s_total, rfc1867ApcData->content_length);
1024 track.set(s_current, rfc1867ApcData->bytes_processed);
1025 track.set(s_filename, rfc1867ApcData->filename);
1026 track.set(s_name, rfc1867ApcData->name);
1027 track.set(s_temp_filename, rfc1867ApcData->temp_filename);
1028 track.set(s_cancel_upload, rfc1867ApcData->cancel_upload);
1029 track.set(s_done, 0);
1030 track.set(s_start_time, rfc1867ApcData->start_time);
1031 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(), 3600);
1033 break;
1035 case MULTIPART_EVENT_END:
1036 if (!rfc1867ApcData->tracking_key.empty()) {
1037 double now = my_time();
1038 multipart_event_end *data = (multipart_event_end *)event_data;
1039 rfc1867ApcData->bytes_processed = data->post_bytes_processed;
1040 if (now>rfc1867ApcData->start_time) {
1041 rfc1867ApcData->rate =
1042 8.0*rfc1867ApcData->bytes_processed/(now-rfc1867ApcData->start_time);
1043 } else {
1044 rfc1867ApcData->rate =
1045 8.0*rfc1867ApcData->bytes_processed; /* Too quick */
1046 ArrayInit track(8, ArrayInit::Map{});
1047 track.set(s_total, rfc1867ApcData->content_length);
1048 track.set(s_current, rfc1867ApcData->bytes_processed);
1049 track.set(s_rate, rfc1867ApcData->rate);
1050 track.set(s_filename, rfc1867ApcData->filename);
1051 track.set(s_name, rfc1867ApcData->name);
1052 track.set(s_cancel_upload, rfc1867ApcData->cancel_upload);
1053 track.set(s_done, 1);
1054 track.set(s_start_time, rfc1867ApcData->start_time);
1055 HHVM_FN(apc_store)(rfc1867ApcData->tracking_key, track.toVariant(),
1056 3600);
1059 break;
1061 return 0;
1064 ///////////////////////////////////////////////////////////////////////////////
1065 // apc serialization
1067 String apc_serialize(const Variant& value) {
1068 VariableSerializer::Type sType =
1069 apcExtension::EnableApcSerialize ?
1070 VariableSerializer::Type::APCSerialize :
1071 VariableSerializer::Type::Internal;
1072 VariableSerializer vs(sType);
1073 return vs.serialize(value, true);
1076 Variant apc_unserialize(const char* data, int len) {
1077 VariableUnserializer::Type sType =
1078 apcExtension::EnableApcSerialize ?
1079 VariableUnserializer::Type::APCSerialize :
1080 VariableUnserializer::Type::Internal;
1081 return unserialize_ex(data, len, sType);
1084 String apc_reserialize(const String& str) {
1085 if (str.empty() ||
1086 !apcExtension::EnableApcSerialize) return str;
1088 VariableUnserializer uns(str.data(), str.size(),
1089 VariableUnserializer::Type::APCSerialize);
1090 StringBuffer buf;
1091 uns.reserialize(buf);
1093 return buf.detach();
1096 ///////////////////////////////////////////////////////////////////////////////
1097 // debugging support
1099 bool apc_dump(const char *filename, bool keyOnly, bool metaDump) {
1100 DumpMode mode;
1101 std::ofstream out(filename);
1103 // only one of these should ever be specified
1104 if (keyOnly && metaDump) {
1105 return false;
1108 if (out.fail()) {
1109 return false;
1112 if (keyOnly) {
1113 mode = DumpMode::KeyOnly;
1114 } else if (metaDump) {
1115 mode = DumpMode::KeyAndMeta;
1116 } else {
1117 mode = DumpMode::KeyAndValue;
1120 apc_store().dump(out, mode);
1121 out.close();
1122 return true;
1125 bool apc_get_random_entries(std::ostream &out, uint32_t count) {
1126 apc_store().dumpRandomKeys(out, count);
1127 return true;
1130 ///////////////////////////////////////////////////////////////////////////////