APCHandle* APCArray::MakeShared(ArrayData* arr, size_t& size, bool inner, bool unserializeObj) { if (!inner) { // only need to call traverseData() on the toplevel array DataWalker walker(DataWalker::LookupFeature::HasObjectOrResource); DataWalker::DataFeature features = walker.traverseData(arr); if (features.isCircular() || features.hasCollection()) { String s = apc_serialize(arr); APCHandle* handle = APCString::MakeShared(KindOfArray, s.get(), size); handle->setSerializedArray(); return handle; } if (apcExtension::UseUncounted && !features.hasObjectOrResource() && !arr->empty()) { size = getMemSize(arr) + sizeof(APCTypedValue); return APCTypedValue::MakeSharedArray(arr); } } if (arr->isVectorData()) { return APCArray::MakePackedShared(arr, size, unserializeObj); } return APCArray::MakeShared(arr, size, unserializeObj); }
APCHandle* APCArray::MakePackedShared(ArrayData* arr, bool unserializeObj) { size_t num_elems = arr->size(); void* p = malloc(sizeof(APCArray) + sizeof(APCHandle*) * num_elems); auto ret = new (p) APCArray(static_cast<size_t>(num_elems)); try { size_t i = 0; for (ArrayIter it(arr); !it.end(); it.next()) { APCHandle* val = APCHandle::Create(it.secondRef(), false, true, unserializeObj); if (val->shouldCache()) { ret->mustCache(); } ret->vals()[i++] = val; } assert(i == num_elems); } catch (...) { delete ret; throw; } return ret->getHandle(); }
bool ConcurrentTableSharedStore::store(const String& key, const Variant& value, int64_t ttl, bool overwrite /* = true */, bool limit_ttl /* = true */) { StoreValue *sval; APCHandle* svar = construct(value); ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); const char *kcp = strdup(key.data()); bool present; time_t expiry = 0; bool overwritePrime = false; { Map::accessor acc; present = !m_vars.insert(acc, kcp); sval = &acc->second; if (present) { free((void *)kcp); if (overwrite || sval->expired()) { // if ApcTTLLimit is set, then only primed keys can have expiry == 0 overwritePrime = (sval->expiry == 0); if (sval->inMem()) { sval->var->unreferenceRoot(); } else { // mark the inFile copy invalid since we are updating the key sval->sAddr = nullptr; sval->sSize = 0; } } else { svar->unreferenceRoot(); return false; } } int64_t adjustedTtl = adjust_ttl(ttl, overwritePrime || !limit_ttl); if (check_noTTL(key.data(), key.size())) { adjustedTtl = 0; } sval->set(svar, adjustedTtl); expiry = sval->expiry; } if (expiry) { addToExpirationQueue(key.data(), expiry); } if (apcExtension::ExpireOnSets) { purgeExpired(); } return true; }
APCHandle* APCObject::MakeAPCObject(APCHandle* obj, CVarRef value) { if (!value.is(KindOfObject) || obj->getObjAttempted()) { return nullptr; } obj->setObjAttempted(); ObjectData *o = value.getObjectData(); DataWalker walker(DataWalker::LookupFeature::DetectSerializable); DataWalker::DataFeature features = walker.traverseData(o); if (features.isCircular() || features.hasCollection() || features.hasSerializableReference()) { return nullptr; } APCHandle* tmp = APCHandle::Create(value, false, true, true); tmp->setObjAttempted(); return tmp; }
APCHandle* APCObject::MakeAPCObject(APCHandle* obj, CVarRef value) { if (!value.is(KindOfObject) || obj->getObjAttempted()) { return nullptr; } obj->setObjAttempted(); ObjectData *o = value.getObjectData(); if (o->instanceof(SystemLib::s_SerializableClass)) { // should also check the object itself return nullptr; } PointerSet seen; if (o->hasInternalReference(seen, true)) { return nullptr; } APCHandle* tmp = APCHandle::Create(value, false, true, true); tmp->setObjAttempted(); return tmp; }
APCHandle* APCObject::MakeAPCObject( APCHandle* obj, size_t& size, const Variant& value) { if (!value.is(KindOfObject) || obj->objAttempted()) { return nullptr; } obj->setObjAttempted(); ObjectData *o = value.getObjectData(); if (apcExtension::OptimizeSerialization) { return MakeShared(o, size, false, false); } DataWalker walker(DataWalker::LookupFeature::DetectSerializable); DataWalker::DataFeature features = walker.traverseData(o); if (features.isCircular() || features.hasCollection() || features.hasSerializableReference()) { return nullptr; } APCHandle* tmp = APCHandle::Create(value, size, false, true, true); tmp->setObjAttempted(); return tmp; }
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; }
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; }
CVarRef APCLocalArray::getValueRef(ssize_t pos) const { APCHandle *sv = m_arr->getValue(pos); DataType t = sv->getType(); if (!IS_REFCOUNTED_TYPE(t)) { return APCTypedValue::fromHandle(sv)->asCVarRef(); } if (LIKELY(m_localCache != nullptr)) { assert(unsigned(pos) < m_arr->capacity()); TypedValue* tv = &m_localCache[pos]; if (tv->m_type != KindOfUninit) { return tvAsCVarRef(tv); } } else { static_assert(KindOfUninit == 0, "must be 0 since we use smart_calloc"); unsigned cap = m_arr->capacity(); m_localCache = (TypedValue*) smart_calloc(cap, sizeof(TypedValue)); } TypedValue* tv = &m_localCache[pos]; tvAsVariant(tv) = sv->toLocal(); assert(tv->m_type != KindOfUninit); return tvAsCVarRef(tv); }
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::storeImpl(const String& key, const Variant& value, int64_t ttl, bool overwrite, bool limit_ttl) { StoreValue *sval; auto svar = APCHandle::Create(value, false, APCHandleLevel::Outer, false); auto keyLen = key.size(); ReadLock l(m_lock); char* const kcp = strdup(key.data()); bool present; time_t expiry = 0; bool overwritePrime = false; { Map::accessor acc; APCHandle* current = nullptr; present = !m_vars.insert(acc, kcp); sval = &acc->second; if (present) { free(kcp); if (!overwrite && !sval->expired()) { svar.handle->unreferenceRoot(svar.size); return false; } sval->data.match( [&] (APCHandle* handle) { current = handle; // If ApcTTLLimit is set, then only primed keys can have // expire == 0. overwritePrime = sval->expire == 0; }, [&] (char*) { // Was inFile, but won't be anymore. sval->data = nullptr; sval->dataSize = 0; overwritePrime = true; } ); } else { APCStats::getAPCStats().addKey(keyLen); } int64_t adjustedTtl = adjust_ttl(ttl, overwritePrime || !limit_ttl); if (check_noTTL(key.data(), key.size())) { adjustedTtl = 0; } if (current) { if (sval->expire == 0 && adjustedTtl != 0) { APCStats::getAPCStats().removeAPCValue( sval->dataSize, current, true, sval->expired()); APCStats::getAPCStats().addAPCValue(svar.handle, svar.size, false); } else { APCStats::getAPCStats().updateAPCValue( svar.handle, svar.size, current, sval->dataSize, sval->expire == 0, sval->expired()); } current->unreferenceRoot(sval->dataSize); } else { APCStats::getAPCStats().addAPCValue(svar.handle, svar.size, present); } sval->set(svar.handle, adjustedTtl); sval->dataSize = svar.size; expiry = sval->expire; if (expiry) { auto ikey = intptr_t(acc->first); if (m_expMap.insert({ ikey, 0 })) { m_expQueue.push({ ikey, expiry }); } } } if (apcExtension::ExpireOnSets) { purgeExpired(); } return true; }
bool ConcurrentTableSharedStore::store(const String& key, CVarRef value, int64_t ttl, bool overwrite /* = true */, bool limit_ttl /* = true */) { StoreValue *sval; APCHandle* svar = construct(value); ConditionalReadLock l(m_lock, !apcExtension::ConcurrentTableLockFree || m_lockingFlag); const char *kcp = strdup(key.data()); bool present; time_t expiry = 0; bool overwritePrime = false; { Map::accessor acc; present = !m_vars.insert(acc, kcp); sval = &acc->second; bool update = false; if (present) { free((void *)kcp); if (overwrite || sval->expired()) { // if ApcTTLLimit is set, then only primed keys can have expiry == 0 overwritePrime = (sval->expiry == 0); if (sval->inMem()) { stats_on_update(key.get(), sval, svar, adjust_ttl(ttl, overwritePrime || !limit_ttl)); sval->var->decRef(); update = true; } else { // mark the inFile copy invalid since we are updating the key sval->sAddr = nullptr; sval->sSize = 0; } } else { svar->decRef(); return false; } } int64_t adjustedTtl = adjust_ttl(ttl, overwritePrime || !limit_ttl); if (check_noTTL(key.data(), key.size())) { adjustedTtl = 0; } sval->set(svar, adjustedTtl); expiry = sval->expiry; if (!update) { stats_on_add(key.get(), sval, adjustedTtl, false, false); } } if (expiry) { addToExpirationQueue(key.data(), expiry); } if (apcExtension::ExpireOnSets) { purgeExpired(); } if (present) { log_apc(std_apc_update); } else { log_apc(std_apc_new); if (RuntimeOption::EnableStats && RuntimeOption::EnableAPCKeyStats) { string prefix = "apc.new." + GetSkeleton(key); ServerStats::Log(prefix, 1); } } return true; }