bool ConcurrentTableSharedStore::exists(const String& keyStr) { const StoreValue *sval; ReadLock l(m_lock); bool expired = false; auto tag = tagStringData(keyStr.get()); { Map::const_accessor acc; if (!m_vars.find(acc, tag)) { return false; } else { sval = &acc->second; if (sval->expired()) { // Because it only has a read lock on the data, deletion from // expiration has to happen after the lock is released expired = true; } } } if (expired) { eraseImpl(tag, true, apcExtension::UseUncounted ? HPHP::Treadmill::getOldestStartTime() : 0, nullptr); return false; } return true; }
bool ConcurrentTableSharedStore::handlePromoteObj(const String& key, APCHandle* svar, const Variant& value) { auto const pair = APCObject::MakeAPCObject(svar, value); if (!pair.handle) return false; auto const converted = pair.handle; auto const size = pair.size; Map::accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { // There is a chance another thread deletes the key when this thread is // converting the object. In that case, we just bail converted->unreferenceRoot(size); return false; } // Our handle may not be same as `svar' here because some other thread may // have updated it already, check before updating. auto& sval = acc->second; auto const handle = sval.data.left(); if (handle == svar && handle->kind() == APCKind::SerializedObject) { sval.data = converted; APCStats::getAPCStats().updateAPCValue( converted, size, handle, sval.dataSize, sval.expire == 0, false); handle->unreferenceRoot(sval.dataSize); sval.dataSize = size; return true; } converted->unreferenceRoot(size); return false; }
bool ConcurrentTableSharedStore::cas(const String& key, int64_t old, int64_t val) { ReadLock l(m_lock); Map::accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { return false; } auto& sval = acc->second; if (sval.expired()) return false; auto const oldHandle = sval.data.match( [&] (APCHandle* h) { return h; }, [&] (char* file) { return unserialize(key, &sval); } ); if (!oldHandle || oldHandle->toLocal().toInt64() != old) { return false; } auto const pair = APCHandle::Create(Variant(val), false, APCHandleLevel::Outer, false); APCStats::getAPCStats().updateAPCValue(pair.handle, pair.size, oldHandle, sval.dataSize, sval.expire == 0, false); oldHandle->unreferenceRoot(sval.dataSize); sval.data = pair.handle; sval.dataSize = pair.size; return true; }
int64_t ConcurrentTableSharedStore::inc(const String& key, int64_t step, bool& found) { found = false; ReadLock l(m_lock); Map::accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { return 0; } auto& sval = acc->second; if (sval.expired()) return 0; /* * Inc only works on KindOfDouble or KindOfInt64, which are never kept in * file-backed storage from priming. So we don't need to try to deserialize * anything or handle the case that sval.data is file-backed. */ auto const oldHandle = sval.data.left(); if (oldHandle == nullptr) return 0; if (oldHandle->type() != KindOfInt64 && oldHandle->type() != KindOfDouble) { return 0; } auto const ret = oldHandle->toLocal().toInt64() + step; auto const pair = APCHandle::Create(Variant(ret), false); APCStats::getAPCStats().updateAPCValue(pair.handle, pair.size, oldHandle, sval.dataSize, sval.expire == 0, false); oldHandle->unreferenceRoot(sval.dataSize); sval.data = pair.handle; sval.dataSize = pair.size; found = true; return ret; }
bool ConcurrentTableSharedStore::handlePromoteObj(const String& key, APCHandle* svar, const Variant& value) { APCHandle *converted = APCObject::MakeAPCObject(svar, value); if (converted) { Map::accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { // There is a chance another thread deletes the key when this thread is // converting the object. In that case, we just bail converted->unreferenceRoot(); return false; } // A write lock was acquired during find StoreValue *sval = &acc->second; APCHandle *sv = sval->var; // sv may not be same as svar here because some other thread may have // updated it already, check before updating if (sv == svar && !sv->getIsObj()) { sval->var = converted; sv->unreferenceRoot(); return true; } converted->unreferenceRoot(); } return false; }
/** * The Map::accessor here establishes a write lock, which means that other * threads, protected by read locks through Map::const_accessor, will not * read erased values from APC. * The ReadLock here is to sync with clear(), which only has a WriteLock, * not a specific accessor. */ bool ConcurrentTableSharedStore::eraseImpl(const String& key, bool expired, int64_t oldestLive) { if (key.isNull()) return false; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); Map::accessor acc; if (m_vars.find(acc, tagStringData(key.get()))) { if (expired && !acc->second.expired()) { return false; } if (acc->second.inMem()) { if (expired && acc->second.expiry < oldestLive && acc->second.var->getUncounted()) { APCTypedValue::fromHandle(acc->second.var)->deleteUncounted(); } else { acc->second.var->unreferenceRoot(); } } else { assert(acc->second.inFile()); assert(acc->second.expiry == 0); } if (expired && acc->second.inFile()) { // a primed key expired, do not erase the table entry acc->second.var = nullptr; acc->second.size = 0; acc->second.expiry = 0; } else { eraseAcc(acc); } return true; } return false; }
bool ConcurrentTableSharedStore::get(const String& key, Variant& value) { const StoreValue *sval; APCHandle *svar = nullptr; ReadLock l(m_lock); bool expired = false; bool promoteObj = false; { Map::const_accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { return false; } else { sval = &acc->second; if (sval->expired()) { // Because it only has a read lock on the data, deletion from // expiration has to happen after the lock is released expired = true; } else { if (auto const handle = sval->data.left()) { svar = handle; } else { std::lock_guard<SmallLock> sval_lock(sval->lock); if (auto const handle = sval->data.left()) { svar = handle; } else { /* * Note that unserialize can run arbitrary php code via a __wakeup * routine, which could try to access this same key, and we're * holding various locks here. This is only for promoting primed * values to in-memory values, so it's basically not a real * problem, but ... :) */ svar = unserialize(key, const_cast<StoreValue*>(sval)); if (!svar) return false; } } if (apcExtension::AllowObj && svar->type() == KindOfObject && !svar->objAttempted()) { // Hold ref here for later promoting the object svar->reference(); promoteObj = true; } value = svar->toLocal(); } } } if (expired) { eraseImpl(key, true, apcExtension::UseUncounted ? HPHP::Treadmill::getOldestStartTime() : 0); return false; } if (promoteObj) { handlePromoteObj(key, svar, value); // release the extra ref svar->unreference(); } return true; }
bool ConcurrentTableSharedStore::exists(const String& key) { const StoreValue *sval; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); bool expired = false; { Map::const_accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { log_apc(std_apc_miss); return false; } else { sval = &acc->second; if (sval->expired()) { // Because it only has a read lock on the data, deletion from // expiration has to happen after the lock is released expired = true; } else { // No need toLocal() here, avoiding the copy if (sval->inMem()) { stats_on_get(key.get(), sval->var); } } } } if (expired) { log_apc(std_apc_miss); eraseImpl(key, true); return false; } log_apc(std_apc_hit); return true; }
/** * The Map::accessor here establishes a write lock, which means that other * threads, protected by read locks through Map::const_accessor, will not * read erased values from APC. * The ReadLock here is to sync with clear(), which only has a WriteLock, * not a specific accessor. */ bool ConcurrentTableSharedStore::eraseImpl(const String& key, bool expired) { if (key.isNull()) return false; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); Map::accessor acc; if (m_vars.find(acc, tagStringData(key.get()))) { if (expired && !acc->second.expired()) { return false; } if (acc->second.inMem()) { stats_on_delete(key.get(), &acc->second, expired); acc->second.var->decRef(); } else { assert(acc->second.inFile()); assert(acc->second.expiry == 0); } if (expired && acc->second.inFile()) { // a primed key expired, do not erase the table entry acc->second.var = nullptr; acc->second.size = 0; acc->second.expiry = 0; } else { eraseAcc(acc); } return true; } return false; }
bool ConcurrentTableSharedStore::get(const String& key, Variant &value) { const StoreValue *sval; APCHandle *svar = nullptr; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); bool expired = false; bool promoteObj = false; { Map::const_accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { log_apc(std_apc_miss); return false; } else { sval = &acc->second; if (sval->expired()) { // Because it only has a read lock on the data, deletion from // expiration has to happen after the lock is released expired = true; } else { if (!sval->inMem()) { std::lock_guard<SmallLock> sval_lock(sval->lock); if (!sval->inMem()) { svar = unserialize(key, sval); if (!svar) return false; } else { svar = sval->var; } } else { svar = sval->var; } if (apcExtension::AllowObj && svar->is(KindOfObject) && !svar->getObjAttempted()) { // Hold ref here for later promoting the object svar->incRef(); promoteObj = true; } value = svar->toLocal(); stats_on_get(key.get(), svar); } } } if (expired) { log_apc(std_apc_miss); eraseImpl(key, true); return false; } log_apc(std_apc_hit); if (promoteObj) { handlePromoteObj(key, svar, value); // release the extra ref svar->decRef(); } return true; }
bool ConcurrentTableSharedStore::cas(const String& key, int64_t old, int64_t val) { bool success = false; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); StoreValue *sval; { Map::accessor acc; if (m_vars.find(acc, tagStringData(key.get()))) { sval = &acc->second; if (!sval->expired() && get_int64_value(sval) == old) { APCHandle *var = construct(Variant(val)); sval->var->unreferenceRoot(); sval->var = var; success = true; } } } return success; }
int64_t ConcurrentTableSharedStore::inc(const String& key, int64_t step, bool &found) { found = false; int64_t ret = 0; ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); StoreValue *sval; { Map::accessor acc; if (m_vars.find(acc, tagStringData(key.get()))) { sval = &acc->second; if (!sval->expired()) { ret = get_int64_value(sval) + step; APCHandle *svar = construct(Variant(ret)); sval->var->unreferenceRoot(); sval->var = svar; found = true; } } } return ret; }
/* * The Map::accessor here establishes a write lock, which means that other * threads, protected by read locks through Map::const_accessor, will not * read erased values from APC. * * The ReadLock here is to sync with clear(), which only has a WriteLock, * not a specific accessor. */ bool ConcurrentTableSharedStore::eraseImpl(const String& key, bool expired, int64_t oldestLive) { if (key.isNull()) return false; ReadLock l(m_lock); Map::accessor acc; if (!m_vars.find(acc, tagStringData(key.get()))) { return false; } if (expired && !acc->second.expired()) { return false; } auto& storeVal = acc->second; storeVal.data.match( [&] (APCHandle* var) { APCStats::getAPCStats().removeAPCValue(storeVal.dataSize, var, storeVal.expire == 0, expired); if (expired && storeVal.expire < oldestLive && var->isUncounted()) { APCTypedValue::fromHandle(var)->deleteUncounted(); } else { var->unreferenceRoot(storeVal.dataSize); } eraseAcc(acc); }, [&] (char* file) { assert(!expired); // primed keys never say true to expired() eraseAcc(acc); } ); return true; }
bool ConcurrentTableSharedStore::eraseKey(const String& key) { assert(!key.isNull()); return eraseImpl(tagStringData(key.get()), false, 0, nullptr); }