2 +----------------------------------------------------------------------+
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"
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
;
58 ///////////////////////////////////////////////////////////////////////////////
63 sizeof(ConcurrentTableSharedStore
),
64 alignof(ConcurrentTableSharedStore
)
65 >::type s_apc_storage
;
67 using UserAPCCache
= folly::AtomicHashMap
<uid_t
, ConcurrentTableSharedStore
*>;
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() {
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",
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",
153 Config::Bind(ExpireOnSets
, ini
, config
, "Server.APC.ExpireOnSets");
154 Config::Bind(PurgeInterval
, ini
, config
, "Server.APC.PurgeIntervalSeconds",
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");
175 Config::Bind(UseFileStorage
, ini
, config
, "Server.APC.FileStorage.Enable");
176 FileStorageChunkSize
= Config::GetInt64(ini
, config
,
177 "Server.APC.FileStorage.ChunkSize",
179 Config::Bind(FileStoragePrefix
, ini
, config
, "Server.APC.FileStorage.Prefix",
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");
189 Config::Bind(UseUncounted
, ini
, config
, "Server.APC.MemModelTreadmill", true);
191 Config::Bind(UseUncounted
, ini
, config
, "Server.APC.MemModelTreadmill",
192 RuntimeOption::ServerExecutionMode());
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",
206 void apcExtension::moduleInit() {
209 Logger::Error("Server.APC.MemModelTreadmill=false ignored in lowptr build");
213 if (UseFileStorage
) {
214 // We use 32 bits to represent offset into a chunk, so don't make it too
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);
247 HHVM_FE(apc_store_as_primed_do_not_use
);
250 HHVM_FE(apc_clear_cache
);
256 HHVM_FE(apc_extend_ttl
);
257 HHVM_FE(apc_cache_info
);
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
);
277 void apcExtension::deserialize(std::string data
) {
278 auto sd
= StringData::MakeUncounted(data
);
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;
314 bool apcExtension::UseUncounted
= true;
316 bool apcExtension::UseUncounted
= false;
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)");
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)");
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
,
390 const Variant
& var
) {
391 if (!apcExtension::Enable
) return false;
393 raise_invalid_argument_warning("apc key: (empty string)");
396 if (isKeyInvalid(key
)) {
397 raise_invalid_argument_warning("apc key: (contains invalid characters)");
400 apc_store().setWithoutTTL(key
, var
);
401 if (RuntimeOption::EnableAPCStats
) {
402 ServerStats::Log("apc.write", 1);
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)");
427 Variant v
= iter
.second();
429 auto const& strKey
= key
.asCStrRef();
430 if (strKey
.empty()) {
431 raise_invalid_argument_warning("apc key: (empty string)");
434 if (isKeyInvalid(strKey
)) {
435 raise_invalid_argument_warning("apc key: (contains invalid characters)");
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)");
450 auto strKey
= key_or_array
.asCStrRef();
451 if (strKey
.empty()) {
452 raise_invalid_argument_warning("apc key: (empty string)");
455 if (isKeyInvalid(strKey
)) {
456 raise_invalid_argument_warning("apc key: (contains invalid characters)");
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);
479 auto keys
= key
.asCArrRef();
480 DArrayInit
init(keys
.size());
481 for (ArrayIter
iter(keys
); iter
; ++iter
) {
482 Variant k
= iter
.second();
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);
497 return tvReturn(init
.toVariant());
500 if (apc_store().get(key
.toString(), v
)) {
502 if (RuntimeOption::EnableAPCStats
) {
503 ServerStats::Log("apc.hit", 1);
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;
520 auto keys
= key
.asCArrRef();
521 VArrayInit
init(keys
.size());
522 for (ArrayIter
iter(keys
); iter
; ++iter
) {
523 Variant k
= iter
.second();
525 raise_warning("apc key is not a string");
527 } else if (!apc_store().eraseKey(k
.asCStrRef())) {
531 return init
.toVariant();
532 } else if (key
.is(KindOfObject
)) {
533 if (!key
.getObjectData()->getVMClass()->
534 classof(SystemLib::s_APCIteratorClass
)) {
536 "apc_delete(): apc_delete object argument must be instance"
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
,
560 if (!apcExtension::Enable
) return false;
563 int64_t newValue
= apc_store().inc(key
, step
, found
);
565 if (!found
) return false;
569 Variant
HHVM_FUNCTION(apc_dec
,
573 if (!apcExtension::Enable
) return false;
576 int64_t newValue
= apc_store().inc(key
, -step
, found
);
578 if (!found
) return false;
582 bool HHVM_FUNCTION(apc_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());
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)");
604 auto strKey
= String
{val(k
).pstr
};
605 if (apc_store().exists(strKey
)) {
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
>();
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;
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
));
661 auto const entries
= apc_store().getEntriesInfo();
662 VArrayInit
ents(entries
.size());
663 for (auto& entry
: entries
) {
664 DArrayInit
ent(kEntryInfoSize
);
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
692 static Mutex dl_mutex
;
693 static PFUNC_APC_LOAD
apc_load_func(void *handle
, const char *name
) {
695 throw Exception("apc_load_func is not currently supported under MSVC!");
697 PFUNC_APC_LOAD p
= (PFUNC_APC_LOAD
)dlsym(handle
, name
);
699 throw Exception("Unable to find %s in %s", name
,
700 apcExtension::PrimeLibrary
.c_str());
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
) {
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
) {
732 static void *handle
= nullptr;
734 apcExtension::PrimeLibrary
.empty() ||
735 !apcExtension::Enable
) {
738 BootStats::Block
timer("loading APC data",
739 RuntimeOption::ServerExecutionMode());
740 if (apc_store().primeFromSnapshot(apcExtension::PrimeLibrary
.c_str())) {
743 Logger::Info("Fall back to shared object format");
744 handle
= dlopen(apcExtension::PrimeLibrary
.c_str(), RTLD_LAZY
);
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.
757 apc_load_func(handle
, "_apc_load_all")();
759 int count
= ((int(*)())apc_load_func(handle
, "_apc_load_count"))();
761 std::vector
<std::shared_ptr
<ApcLoadJob
>> jobs
;
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.
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).
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.
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];
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
];
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];
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
];
850 item
.readOnly
= readOnly
;
853 s
.constructPrime(false, item
);
854 if (UNLIKELY(snap
!= nullptr)) snap
->addFalse(item
);
857 s
.constructPrime(true, item
);
858 if (UNLIKELY(snap
!= nullptr)) snap
->addTrue(item
);
861 s
.constructPrime(uninit_null(), item
);
862 if (UNLIKELY(snap
!= nullptr)) snap
->addNull(item
);
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];
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
];
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];
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
];
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];
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
];
933 item
.readOnly
= readOnly
;
934 p
+= thrift_lens
[i
+ i
+ 2] + 1; // skip \0
935 String
value(p
, thrift_lens
[i
+ i
+ 3], CopyString
);
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];
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
];
961 item
.readOnly
= readOnly
;
962 p
+= other_lens
[i
+ i
+ 2] + 1; // skip \0
963 String
value(p
, other_lens
[i
+ i
+ 3], CopyString
);
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() {
986 gettimeofday(&a
, nullptr);
987 t
= a
.tv_sec
+ (a
.tv_usec
/1000000.00);
993 s_current("current"),
994 s_filename("filename"),
997 s_temp_filename("temp_filename"),
998 s_cancel_upload("cancel_upload"),
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*/) {
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;
1030 case MULTIPART_EVENT_FORMDATA
: {
1031 multipart_event_formdata
*data
= (multipart_event_formdata
*)event_data
;
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
;
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);
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
) {
1093 if (apc_store().get(rfc1867ApcData
->tracking_key
, v
)) {
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(),
1105 rfc1867ApcData
->prev_bytes_processed
=
1106 rfc1867ApcData
->bytes_processed
;
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);
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
);
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(),
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
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
) {
1188 !apcExtension::EnableApcSerialize
) return str
;
1190 VariableUnserializer
uns(str
.data(), str
.size(),
1191 VariableUnserializer::Type::APCSerialize
);
1193 uns
.reserialize(buf
);
1195 return buf
.detach();
1198 ///////////////////////////////////////////////////////////////////////////////
1199 // debugging support
1201 bool apc_dump(const char *filename
, bool keyOnly
, bool metaDump
) {
1203 std::ofstream
out(filename
);
1205 // only one of these should ever be specified
1206 if (keyOnly
&& metaDump
) {
1215 mode
= DumpMode::KeyOnly
;
1216 } else if (metaDump
) {
1217 mode
= DumpMode::KeyAndMeta
;
1219 mode
= DumpMode::KeyAndValue
;
1222 apc_store().dump(out
, mode
);
1227 bool apc_dump_prefix(const char *filename
,
1228 const std::string
&prefix
,
1230 std::ofstream
out(filename
);
1234 SCOPE_EXIT
{ out
.close(); };
1236 apc_store().dumpPrefix(out
, prefix
, count
);
1240 bool apc_get_random_entries(std::ostream
&out
, uint32_t count
) {
1241 apc_store().dumpRandomKeys(out
, count
);
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;
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 ///////////////////////////////////////////////////////////////////////////////