Housekeeping: rename ConcurrentTableSharedStore::ExpMap -> ExpSet
[hiphop-php.git] / hphp / runtime / base / concurrent-shared-store.h
blobb23d4ce28679f5e769f73fe42626b6568317dc36
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #pragma once
19 #include <atomic>
20 #include <utility>
21 #include <vector>
22 #include <string>
23 #include <iosfwd>
25 #include <folly/SharedMutex.h>
27 #include <tbb/concurrent_hash_map.h>
28 #include <tbb/concurrent_priority_queue.h>
30 #include "hphp/util/either.h"
31 #include "hphp/util/smalllocks.h"
33 #include "hphp/runtime/base/apc-handle.h"
34 #include "hphp/runtime/base/apc-stats.h"
35 #include "hphp/runtime/server/server-stats.h"
36 #include "hphp/runtime/vm/treadmill.h"
38 namespace HPHP {
40 //////////////////////////////////////////////////////////////////////
43 * This is the in-APC representation of a value, in ConcurrentTableSharedStore.
45 struct StoreValue {
47 * Index into cache layer. Valid indices are >= 0 and never invalidated.
49 using HotCacheIdx = int32_t;
51 StoreValue() = default;
52 StoreValue(const StoreValue& o)
53 : m_data{o.m_data.load(std::memory_order_acquire)}
54 , expireTime{o.expireTime.load(std::memory_order_relaxed)}
55 , dataSize{o.dataSize}
56 , kind(o.kind)
57 , bumpTTL{o.bumpTTL.load(std::memory_order_relaxed)}
58 , c_time{o.c_time}
59 , maxExpireTime{o.maxExpireTime.load(std::memory_order_relaxed)}
61 hotIndex.store(o.hotIndex.load(std::memory_order_relaxed),
62 std::memory_order_relaxed);
65 APCHandle* data() const {
66 auto data = m_data.load(std::memory_order_acquire);
67 assertx(data != nullptr);
68 return data;
70 void setHandle(APCHandle* v) {
71 kind = v->kind();
72 m_data.store(v, std::memory_order_release);
74 APCKind getKind() const {
75 assertx(data());
76 assertx(data()->kind() == kind);
77 return kind;
79 Variant toLocal() const {
80 return data()->toLocal();
82 void set(APCHandle* v, int64_t expireTTL, int64_t maxTTL, int64_t bumpTTL);
83 bool expired() const;
84 uint32_t rawExpire() const {
85 return expireTime.load(std::memory_order_acquire);
87 uint32_t queueExpire() const;
89 /* Special invalid indices; used to classify cache lookup misses. */
90 static constexpr HotCacheIdx kHotCacheUnknown = -1;
91 static constexpr HotCacheIdx kHotCacheKnownIneligible = -2;
93 // Mutable fields here are so that we can deserialize the object from disk
94 // while holding a const pointer to the StoreValue, or remember a cache entry.
97 * Each entry in APC is an APCHandle*
99 * Note also that 'expire' may not be safe to read even if data is
100 * valid, due to non-atomicity of updates; use 'expired()'.
102 * Note: expiration, creation, and modification times are stored unsigned
103 * in 32-bits as seconds since the Epoch to save cache-line space.
104 * HHVM might get confused after 2106 :)
106 mutable std::atomic<APCHandle*> m_data;
107 mutable std::atomic<uint32_t> expireTime{};
108 int32_t dataSize{0}; // For file storage, negative means serialized object
109 // Reference to any HotCache entry to be cleared if the value is treadmilled.
110 mutable std::atomic<HotCacheIdx> hotIndex{kHotCacheUnknown};
111 APCKind kind;
112 mutable std::atomic<uint16_t> bumpTTL{0};
113 mutable std::atomic<int64_t> expireRequestIdx{Treadmill::kIdleGenCount};
114 uint32_t c_time{0}; // Creation time; 0 for primed values
115 mutable std::atomic<uint32_t> maxExpireTime{};
118 //////////////////////////////////////////////////////////////////////
121 * Hold info about an entry in APC. Used as a temporary holder to expose
122 * information about APC entries.
124 struct EntryInfo {
125 enum class Type {
126 Unknown,
127 Uncounted,
128 UncountedString,
129 APCString,
130 APCArray,
131 APCObject,
132 SerializedObject,
133 UncountedVec,
134 UncountedDict,
135 UncountedKeyset,
136 SerializedVec,
137 SerializedDict,
138 SerializedKeyset,
139 APCVec,
140 APCDict,
141 APCKeyset,
142 APCRFunc,
143 APCRClsMeth,
146 EntryInfo(const char* apckey,
147 int32_t size,
148 int64_t ttl,
149 int64_t maxTTL,
150 uint16_t bumpTTL,
151 Type type,
152 int64_t c_time,
153 bool inHotCache)
154 : key(apckey)
155 , size(size)
156 , ttl(ttl)
157 , maxTTL(maxTTL)
158 , bumpTTL(bumpTTL)
159 , type(type)
160 , c_time(c_time)
161 , inHotCache(inHotCache)
164 static Type getAPCType(const APCHandle* handle);
166 std::string key;
167 int32_t size;
168 int64_t ttl;
169 int64_t maxTTL;
170 uint16_t bumpTTL;
171 Type type;
172 int64_t c_time;
173 bool inHotCache;
176 //////////////////////////////////////////////////////////////////////
179 * This is the backing store for APC. Maintains a key to value mapping, where
180 * each value optionally has a time-to-live.
182 * After a value reaches its TTL, it's considered "expired", and most
183 * operations on the table will act like it's not present (exceptions to this
184 * should be documented below). TTL function arguments to this module are
185 * specified in seconds.
187 struct ConcurrentTableSharedStore {
188 ConcurrentTableSharedStore() = default;
189 ConcurrentTableSharedStore(const ConcurrentTableSharedStore&) = delete;
190 ConcurrentTableSharedStore&
191 operator=(const ConcurrentTableSharedStore&) = delete;
194 * Retrieve a value from the store. Returns false if the value wasn't
195 * contained in the table (or was expired).
197 bool get(const String& key, Variant& value);
200 * Add a value to the store if no (unexpired) value with this key is already
201 * present.
203 * The requested ttl is limited by the ApcTTLLimit.
205 * Returns: true if the value was added, including if we've replaced an
206 * expired value.
208 bool add(const String& key, const Variant& val, int64_t max_ttl, int64_t bump_ttl);
211 * Set the value for `key' to `val'. If there was an existing value, it is
212 * overwritten.
214 * The requested ttl is limited by the ApcTTLLimit, unless we're overwriting
215 * a primed key.
217 void set(const String& key, const Variant& val, int64_t max_ttl, int64_t bump_ttl);
220 * Set the value for `key' to `val', without any TTL, even if it wasn't
221 * a primed key.
223 void setWithoutTTL(const String& key, const Variant& val);
226 * Increment the value for the key `key' by step, iff it is present,
227 * non-expired, and a numeric (KindOfInt64 or KindOfDouble) value. Sets
228 * `found' to true if the increment is performed, false otherwise.
230 * Returns: the new value for the key, or zero if the key was not found.
232 int64_t inc(const String& key, int64_t step, bool& found);
235 * Attempt to atomically compare and swap the value for the key `key' from
236 * `oldVal' to `newVal'. If the key is present, non-expired, and has the
237 * same value as `oldVal' (after conversions), set it to `newVal' and return
238 * true. Otherwise returns false.
240 bool cas(const String& key, int64_t oldVal, int64_t newVal);
243 * Returns: true if this key exists in the store, and is not expired.
245 bool exists(const String& key);
248 * Extend the expiration time to now + new_ttl if that is longer than
249 * the current TTL (or to infinity if new_ttl is zero). Returns true if
250 * it succeeds (the key exists in apc, is unexpired, and the expiration
251 * was actually adjusted), false otherwise.
253 bool bumpTTL(const String& key, int64_t new_ttl);
256 * Returns the size of an entry if it exists. Sets `found` to true if it
257 * exists and false if not.
259 int64_t size(const String& key, bool& found);
262 * Remove the specified key, if it exists in the table.
264 * Returns: false if the key was not in the table, true if the key was in the
265 * table **even if it was expired**.
267 bool eraseKey(const String& key);
270 * Schedule deletion of expired entries.
272 void purgeExpired();
273 void purgeDeferred(req::vector<StringData*>&&);
276 * Clear the entire APC table.
278 bool clear();
281 * Init
283 void init();
286 * Debugging. Dump information about the table to an output stream.
288 * This is extremely expensive and not recommended for use outside of
289 * development scenarios.
291 enum class DumpMode {
292 KeyOnly,
293 KeyAndValue,
294 KeyAndMeta
296 void dump(std::ostream& out, DumpMode dumpMode);
298 * Dump up to count keys that begin with the given prefix. This is a subset
299 * of what the dump `KeyAndValue` command would do.
301 void dumpPrefix(std::ostream& out, const std::string &prefix, uint32_t count);
303 * Dump all non-primed keys that begin with one of the prefixes. Different
304 * keys are separated by \n in the output stream. Keys containing \r or \n
305 * will not be included.
307 void dumpKeysWithPrefixes(std::ostream& out,
308 const std::vector<std::string>& prefixes);
310 * Dump random key and entry size to output stream
312 void dumpRandomKeys(std::ostream& out, uint32_t count);
315 * Return 'count' randomly chosen entries, possibly with duplicates. If the
316 * store is empty or this operation is not supported, returns an empty vector.
318 std::vector<EntryInfo> sampleEntriesInfo(uint32_t count);
320 * Return a list of entries with consideration of memory usage. Roughly one
321 * sample every 'bytes' of memory is used.
323 std::vector<EntryInfo> sampleEntriesInfoBySize(uint32_t bytes);
326 * Debugging. Access information about all the entries in this table.
328 * This is extremely expensive and not recommended for use outside of
329 * development scenarios.
331 std::vector<EntryInfo> getEntriesInfo();
333 private:
334 // Fake a StringData as a char* with the high bit set. charHashCompare below
335 // will properly handle the value and reuse the hash value of the StringData.
337 static char* tagStringData(StringData* s) {
338 return reinterpret_cast<char*>(-reinterpret_cast<intptr_t>(s));
341 static StringData* getStringData(const char* s) {
342 assertx(reinterpret_cast<intptr_t>(s) < 0);
343 return reinterpret_cast<StringData*>(-reinterpret_cast<intptr_t>(s));
346 inline static bool isTaggedStringData(const char* s) {
347 return reinterpret_cast<intptr_t>(s) < 0;
350 private:
351 struct CharHashCompare {
352 bool equal(const char* s1, const char* s2) const {
353 assertx(s1 && s2);
354 // tbb implementation call equal with the second pointer being the
355 // value in the table and thus not a StringData*. We are asserting
356 // to make sure that is the case
357 assertx(!isTaggedStringData(s2));
358 if (isTaggedStringData(s1)) {
359 s1 = getStringData(s1)->data();
361 return strcmp(s1, s2) == 0;
363 size_t hash(const char* s) const {
364 assertx(s);
365 return isTaggedStringData(s) ? getStringData(s)->hash() :
366 StringData::hash(s, strlen(s));
370 private:
371 template<typename Key, typename T, typename HashCompare>
372 struct APCMap :
373 tbb::concurrent_hash_map<Key,T,HashCompare,APCAllocator<char>> {
374 // Append a random entry to 'entries'. The map must be non-empty and not
375 // concurrently accessed. Returns false if this operation is not supported.
376 bool getRandomAPCEntry(std::vector<EntryInfo>& entries);
378 using node = typename tbb::concurrent_hash_map<Key,T,HashCompare,
379 APCAllocator<char>>::node;
380 static_assert(sizeof(node) == 64, "Node should be cache-line sized");
383 using Map = APCMap<const char*,StoreValue,CharHashCompare>;
384 using ExpirationPair = std::pair<intptr_t,time_t>;
385 enum class ExpNil {};
386 using ExpSet = tbb::concurrent_hash_map<intptr_t,ExpNil>;
388 struct ExpirationCompare {
389 bool operator()(const ExpirationPair& p1, const ExpirationPair& p2) const {
390 return p1.second > p2.second;
394 private:
395 bool checkExpire(const String& keyStr, Map::const_accessor& acc);
396 bool eraseImpl(const char*, bool, int64_t, ExpSet::accessor* expAcc);
397 bool storeImpl(const String&, const Variant&, int64_t, int64_t, bool, bool);
398 bool handlePromoteObj(const String&, APCHandle*, const Variant&);
399 void dumpKeyAndValue(std::ostream&);
400 static EntryInfo makeEntryInfo(const char*, StoreValue*, int64_t curr_time);
402 private:
403 Map m_vars;
404 folly::SharedMutex m_lock;
406 * m_expQueue is a queue of keys to be expired. We purge items from
407 * it every n (configurable) apc_stores.
409 * We can't (easily) remove items from m_expQueue, so if we add a
410 * new entry every time an item is updated we could end up with a
411 * lot of copies of the same key in the queue. To avoid that, we use
412 * m_expSet, and only add an entry to the queue if there isn't one
413 * already.
415 * In the current implementation, that means that if an element is
416 * updated before it expires, when its entry in m_expQueue is
417 * processed, it does nothing; except being put back into the queue
418 * again with the new expiry time.
420 * This implementation uses the apc key's address as the key into
421 * m_expSet, and as the identifier in ExpirationPair. We ensure that
422 * the m_expSet entry is removed before the apc key is freed, and
423 * guarantee that the key is valid as a char* if it exists in
424 * m_expSet. If the entry subsequently pops off m_expQueue, we check
425 * to see if its in m_expSet, and only try to purge it from apc if
426 * its found.
428 * Note that its possible that the apc key was freed and
429 * reallocated, and the entry in m_expQueue doesn't correspond to
430 * the new key; but thats fine - if the key really has expired, it
431 * will be purged, and if not, nothing will happen.
433 tbb::concurrent_priority_queue<ExpirationPair,
434 ExpirationCompare> m_expQueue;
435 ExpSet m_expSet;
436 std::atomic<time_t> m_lastPurgeTime{0};
439 //////////////////////////////////////////////////////////////////////